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(OmgBaseType::String) => {
263            if let Val::Natural(index) = vals
264                .first()
265                .expect("strings should be encoded as exactly one natural number variable")
266            {
267                format!(
268                    "'{}'",
269                    omg_types
270                        .get_string(*index as usize)
271                        .expect("all string codes should correspond to a string")
272                )
273            } else {
274                panic!("string not encoded as a natural number")
275            }
276        }
277        OmgType::Base(_omg_base_type) => format_base_val(vals[0]),
278        OmgType::Array(_omg_base_type, _len) => format!(
279            "{:?}",
280            vals.iter()
281                .map(|val: &Val| format_base_val(*val))
282                .collect::<Vec<String>>()
283        )
284        .replace("\"", ""),
285        OmgType::Custom(omg_name) => format!(
286            "{omg_name}: {}",
287            format_val_from_def(
288                vals,
289                omg_types.type_defs.get(omg_name).expect("type def"),
290                omg_types,
291                false
292            )
293        ),
294    }
295}
296
297fn format_val_from_def(
298    vals: &[Val],
299    omg_type: &OmgTypeDef,
300    omg_types: &OmgTypes,
301    spread_structs: bool,
302) -> String {
303    match omg_type {
304        OmgTypeDef::Enumeration(items) => {
305            if let Val::Natural(int) = vals[0] {
306                items[int as usize].clone()
307            } else {
308                panic!("enumeration is not represented as Natural")
309            }
310        }
311        OmgTypeDef::Structure(btree_map) => {
312            let mut prev_size_acc = 0;
313            let mut size_acc = 0;
314            let fields = btree_map
315                .iter()
316                .map(|(name, omg_type)| {
317                    let size = omg_type.size(omg_types).unwrap();
318                    prev_size_acc = size_acc;
319                    size_acc += size;
320                    let field = format_val(&vals[prev_size_acc..size_acc], omg_type, omg_types);
321                    format!("{name}: {field}")
322                })
323                .collect::<Vec<_>>();
324            if spread_structs {
325                // print on multiple lines
326                format!("{fields:#?}")
327            } else {
328                format!("{fields:?}")
329            }
330            .replace("\"", "")
331        }
332    }
333}
334
335fn format_base_val(val: Val) -> String {
336    match val {
337        Val::Boolean(true) => "true".to_string(),
338        Val::Boolean(false) => "false".to_string(),
339        Val::Integer(i) => i.to_string(),
340        Val::Float(ordered_float) => ordered_float.to_string(),
341        Val::Natural(n) => n.to_string(),
342    }
343}