Skip to main content

scan_scxml/
print_trace.rs

1use crate::parser::{OmgBaseType, OmgType, OmgTypeDef, OmgTypes};
2
3use super::ScxmlModel;
4use scan_core::channel_system::{Event, EventType};
5use scan_core::{RunOutcome, Time, Tracer, Val};
6use std::{
7    env::current_dir,
8    fs::{File, create_dir, create_dir_all, exists, remove_file, rename},
9    path::PathBuf,
10    sync::{Arc, atomic::AtomicU32},
11};
12
13#[derive(Debug)]
14pub struct TracePrinter<'a> {
15    index: Arc<AtomicU32>,
16    path: PathBuf,
17    writer: Option<csv::Writer<flate2::write::GzEncoder<File>>>,
18    model: &'a ScxmlModel,
19}
20
21impl<'a> TracePrinter<'a> {
22    const FOLDER: &'static str = "traces";
23    const TEMP: &'static str = ".temp";
24    const SUCCESSES: &'static str = "successes";
25    const FAILURES: &'static str = "failures";
26    const HEADER: [&'static str; 5] = ["Time", "Origin", "Target", "Event", "Values"];
27
28    pub fn new(model: &'a ScxmlModel) -> Self {
29        let mut path = current_dir().expect("current dir");
30        for i in 0.. {
31            path.push(format!("{}_{i:02}", Self::FOLDER));
32            if std::fs::create_dir(&path).is_ok() {
33                path.push(Self::TEMP);
34                create_dir(&path).expect("create temp dir");
35                assert!(path.pop());
36                path.push(Self::SUCCESSES);
37                create_dir(&path).expect("create temp dir");
38                assert!(path.pop());
39                path.push(Self::FAILURES);
40                create_dir(&path).expect("create temp dir");
41                assert!(path.pop());
42                break;
43            } else {
44                assert!(path.pop());
45            }
46        }
47
48        Self {
49            index: Arc::new(AtomicU32::new(0)),
50            path,
51            writer: None,
52            model,
53        }
54    }
55
56    fn format_state<I: IntoIterator<Item = Val>>(&self, ports: I) -> Vec<String> {
57        let mut iter = ports.into_iter();
58        self.model
59            .ports
60            .iter()
61            .map(move |(_, omg_type, types)| {
62                format_val(
63                    iter.by_ref()
64                        .take(types.len())
65                        .collect::<Vec<_>>()
66                        .as_slice(),
67                    omg_type,
68                    &self.model.omg_types,
69                )
70            })
71            .collect()
72    }
73}
74
75impl<'a> Clone for TracePrinter<'a> {
76    fn clone(&self) -> Self {
77        // Get the temp folder
78        let mut path = self.path.clone();
79        if path.is_file() {
80            path.pop();
81        }
82        Self {
83            index: Arc::clone(&self.index),
84            path,
85            writer: None,
86            model: self.model,
87        }
88    }
89}
90
91impl<'a> Tracer<Event> for TracePrinter<'a> {
92    fn init(&mut self) {
93        let idx = self
94            .index
95            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
96        let filename = PathBuf::new()
97            .with_file_name(format!("{idx:04}"))
98            .with_extension("csv");
99        self.path.push(Self::TEMP);
100        self.path.push(&filename);
101        self.path.add_extension("gz");
102        let file = File::create_new(&self.path).expect("create file");
103        let enc = flate2::GzBuilder::new()
104            .filename(filename.to_str().expect("file name"))
105            .comment("Scan-generated execution trace")
106            .write(file, flate2::Compression::best());
107        let mut writer = csv::WriterBuilder::new().from_writer(enc);
108        writer
109            .write_record(
110                Self::HEADER.into_iter().map(String::from).chain(
111                    self.model.ports.iter().map(|(name, omg_type, _)| {
112                        format!("{name}: {}", format_omg_type(omg_type))
113                    }),
114                ),
115            )
116            .expect("write header");
117        self.writer = Some(writer);
118    }
119
120    fn trace<I: IntoIterator<Item = Val>>(&mut self, event: &Event, time: Time, ports: I) {
121        let mut fields = Vec::new();
122        let time = time.to_string();
123        let origin_name;
124        let target_name;
125        let event_name;
126        let param_types;
127        let mut params = String::new();
128        fields.push(time.as_str());
129
130        if let Some((src, trg, event_idx)) = self.model.parameters.get(&event.channel) {
131            origin_name = self.model.fsm_names.get(&(*src).into()).unwrap().to_owned();
132            target_name = self.model.fsm_names.get(&(*trg).into()).unwrap().to_owned();
133            (event_name, param_types) = self.model.events.get(*event_idx).unwrap().clone();
134            if let EventType::Send(ref vals) = event.event_type {
135                params = format_val_from_def(
136                    vals,
137                    param_types.as_ref().unwrap(),
138                    &self.model.omg_types,
139                    true,
140                );
141            } else {
142                return;
143            }
144        } else if let Some(trg) = self.model.ext_queues.get(&event.channel) {
145            target_name = self.model.fsm_names.get(&(*trg).into()).unwrap().to_owned();
146            if let EventType::Send(ref vals) = event.event_type {
147                if let (Val::Natural(sent_event), Val::Natural(origin)) = (vals[0], vals[1]) {
148                    origin_name = self
149                        .model
150                        .fsm_names
151                        .get(&(origin as u16))
152                        .unwrap()
153                        .to_owned();
154                    (event_name, param_types) = self.model.events[sent_event as usize].clone();
155                    if param_types.is_some() {
156                        // No need to trace this as parameters event already traced
157                        return;
158                    }
159                } else {
160                    panic!("events should be pairs");
161                }
162            } else {
163                return;
164            }
165        } else if self.model.int_queues.contains(&event.channel) {
166            origin_name = self
167                .model
168                .fsm_names
169                .get(&event.pg_id.into())
170                .unwrap()
171                .to_owned();
172            target_name = origin_name.clone();
173            if let EventType::Send(ref vals) = event.event_type {
174                if let Val::Natural(sent_event) = vals[0] {
175                    (event_name, param_types) = self.model.events[sent_event as usize].clone();
176                    if param_types.is_some() {
177                        // No need to trace this as parameters event already traced
178                        return;
179                    }
180                } else {
181                    panic!("events should be indexed by natural");
182                }
183            } else {
184                return;
185            }
186        } else {
187            panic!("Events should all be either internal or external events");
188        }
189
190        let state = self.format_state(ports);
191        self.writer
192            .as_mut()
193            .unwrap()
194            .write_record(
195                [time, origin_name, target_name, event_name, params]
196                    .into_iter()
197                    .chain(state),
198            )
199            .expect("write record");
200    }
201
202    fn finalize(self, outcome: &RunOutcome) {
203        let mut writer = self.writer.unwrap();
204        writer.flush().expect("flush csv content");
205        writer
206            .into_inner()
207            .expect("encoder")
208            .try_finish()
209            .expect("finish");
210
211        let mut new_path = self.path.clone();
212        // pop file name
213        new_path.pop();
214        // pop temp folder
215        new_path.pop();
216        match outcome {
217            RunOutcome::Verified(verified) => {
218                if verified.iter().all(|b| *b) {
219                    new_path.push(Self::SUCCESSES);
220                } else {
221                    new_path.push(Self::FAILURES);
222                    // This path might not exist yet
223                    if !exists(new_path.as_path()).expect("check folder") {
224                        create_dir_all(new_path.clone()).expect("create missing folder");
225                    }
226                }
227            }
228            RunOutcome::Incomplete => {
229                remove_file(&self.path).expect("delete file");
230                return;
231            }
232        }
233
234        new_path.push(self.path.file_name().expect("file name"));
235        rename(&self.path, new_path).expect("renaming");
236    }
237}
238
239fn format_omg_type(omg_type: &OmgType) -> String {
240    match omg_type {
241        OmgType::Base(omg_base_type) => format_omg_base_type(*omg_base_type).to_string(),
242        OmgType::Array(omg_base_type, _) => {
243            format!("[{}]", format_omg_base_type(*omg_base_type))
244        }
245        OmgType::Custom(name) => name.clone(),
246    }
247}
248
249fn format_omg_base_type(omg_base_type: OmgBaseType) -> &'static str {
250    match omg_base_type {
251        OmgBaseType::Boolean => "bool",
252        OmgBaseType::Int64 => "int32",
253        OmgBaseType::F64 => "float64",
254        OmgBaseType::Uri => "uri",
255        OmgBaseType::String => "string",
256        OmgBaseType::Uint64 => "uint64",
257    }
258}
259
260fn format_val(vals: &[Val], omg_type: &OmgType, omg_types: &OmgTypes) -> String {
261    match omg_type {
262        OmgType::Base(_omg_base_type) => format_base_val(vals[0]),
263        OmgType::Array(_omg_base_type, _len) => format!(
264            "{:?}",
265            vals.iter()
266                .map(|val: &Val| format_base_val(*val))
267                .collect::<Vec<String>>()
268        )
269        .replace("\"", ""),
270        OmgType::Custom(omg_name) => format!(
271            "{omg_name}: {}",
272            format_val_from_def(
273                vals,
274                omg_types.type_defs.get(omg_name).expect("type def"),
275                omg_types,
276                false
277            )
278        ),
279    }
280}
281
282fn format_val_from_def(
283    vals: &[Val],
284    omg_type: &OmgTypeDef,
285    omg_types: &OmgTypes,
286    spread_structs: bool,
287) -> String {
288    match omg_type {
289        OmgTypeDef::Enumeration(items) => {
290            if let Val::Natural(int) = vals[0] {
291                items[int as usize].clone()
292            } else {
293                panic!("enumeration is not represented as Natural")
294            }
295        }
296        OmgTypeDef::Structure(btree_map) => {
297            let mut prev_size_acc = 0;
298            let mut size_acc = 0;
299            let fields = btree_map
300                .iter()
301                .map(|(name, omg_type)| {
302                    let size = omg_type.size(omg_types).unwrap();
303                    prev_size_acc = size_acc;
304                    size_acc += size;
305                    let field = format_val(&vals[prev_size_acc..size_acc], omg_type, omg_types);
306                    format!("{name}: {field}")
307                })
308                .collect::<Vec<_>>();
309            if spread_structs {
310                // print on multiple lines
311                format!("{fields:#?}")
312            } else {
313                format!("{fields:?}")
314            }
315            .replace("\"", "")
316        }
317    }
318}
319
320fn format_base_val(val: Val) -> String {
321    match val {
322        Val::Boolean(true) => "true".to_string(),
323        Val::Boolean(false) => "false".to_string(),
324        Val::Integer(i) => i.to_string(),
325        Val::Float(ordered_float) => ordered_float.to_string(),
326        Val::Natural(n) => n.to_string(),
327    }
328}