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 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 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 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 new_path.pop();
214 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 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 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}