1use std::collections::{BTreeMap, VecDeque};
9
10use nucleus_itm::Packet;
11use serde::Serialize;
12
13const DWT_PC_SAMPLE: u8 = 2;
15
16const CPU_WINDOW: usize = 32;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum VarType {
22 F32,
23 U16,
24 U32,
25 I32,
26}
27
28impl VarType {
29 pub fn parse(s: &str) -> Option<VarType> {
31 match s {
32 "f32" => Some(VarType::F32),
33 "u16" => Some(VarType::U16),
34 "u32" => Some(VarType::U32),
35 "i32" => Some(VarType::I32),
36 _ => None,
37 }
38 }
39
40 fn name(self) -> &'static str {
41 match self {
42 VarType::F32 => "f32",
43 VarType::U16 => "u16",
44 VarType::U32 => "u32",
45 VarType::I32 => "i32",
46 }
47 }
48
49 fn decode(self, data: &[u8]) -> serde_json::Value {
52 let mut buf4 = [0u8; 4];
53 let n = data.len().min(4);
54 buf4[..n].copy_from_slice(&data[..n]);
55 match self {
56 VarType::F32 => json_f64(f64::from(f32::from_le_bytes(buf4))),
57 VarType::U32 => serde_json::Value::from(u32::from_le_bytes(buf4)),
58 VarType::I32 => serde_json::Value::from(i32::from_le_bytes(buf4)),
59 VarType::U16 => {
60 let mut buf2 = [0u8; 2];
61 let m = data.len().min(2);
62 buf2[..m].copy_from_slice(&data[..m]);
63 serde_json::Value::from(u16::from_le_bytes(buf2))
64 }
65 }
66 }
67}
68
69fn json_f64(v: f64) -> serde_json::Value {
70 serde_json::Number::from_f64(v)
71 .map(serde_json::Value::Number)
72 .unwrap_or(serde_json::Value::Null)
73}
74
75#[derive(Debug, Clone)]
77pub struct Variable {
78 pub name: String,
79 pub ty: VarType,
80}
81
82#[derive(Debug, Clone, Default)]
84pub struct VariableMap(BTreeMap<u8, Variable>);
85
86impl VariableMap {
87 pub fn new() -> VariableMap {
88 VariableMap(BTreeMap::new())
89 }
90
91 pub fn insert(&mut self, port: u8, name: impl Into<String>, ty: VarType) {
93 self.0.insert(
94 port,
95 Variable {
96 name: name.into(),
97 ty,
98 },
99 );
100 }
101
102 pub fn from_config(trace: &nucleus_compiler::config::Trace) -> VariableMap {
105 let mut map = VariableMap::new();
106 for v in &trace.variables {
107 if let Some(ty) = VarType::parse(&v.ty) {
108 map.insert(v.port, v.name.clone(), ty);
109 }
110 }
111 map
112 }
113
114 fn get(&self, port: u8) -> Option<&Variable> {
115 self.0.get(&port)
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Serialize)]
121#[serde(tag = "kind", rename_all = "lowercase")]
122pub enum TraceEvent {
123 Log { message: String },
125 Variable {
127 port: u8,
128 name: String,
129 #[serde(rename = "type")]
130 ty: &'static str,
131 value: serde_json::Value,
132 },
133 Overflow,
135 CpuLoad { load: f64 },
137}
138
139#[derive(Debug)]
142pub struct Translator {
143 vars: VariableMap,
144 log_line: Vec<u8>,
146 cpu_samples: VecDeque<bool>,
148}
149
150impl Translator {
151 pub fn new(vars: VariableMap) -> Translator {
152 Translator {
153 vars,
154 log_line: Vec::new(),
155 cpu_samples: VecDeque::with_capacity(CPU_WINDOW),
156 }
157 }
158
159 pub fn translate(&mut self, packet: &Packet) -> Vec<TraceEvent> {
161 match packet {
162 Packet::Instrumentation { port: 0, data } => self.push_log_bytes(data),
163 Packet::Instrumentation { port, data } => match self.vars.get(*port) {
164 Some(var) => vec![TraceEvent::Variable {
165 port: *port,
166 name: var.name.clone(),
167 ty: var.ty.name(),
168 value: var.ty.decode(data),
169 }],
170 None => Vec::new(), },
172 Packet::Overflow => vec![TraceEvent::Overflow],
173 Packet::Hardware {
175 discriminator: DWT_PC_SAMPLE,
176 data,
177 } => self.push_pc_sample(data),
178 _ => Vec::new(),
180 }
181 }
182
183 fn push_pc_sample(&mut self, data: &[u8]) -> Vec<TraceEvent> {
190 let running = !(data.len() == 1 && data[0] == 0x00);
191 if self.cpu_samples.len() == CPU_WINDOW {
192 self.cpu_samples.pop_front();
193 }
194 self.cpu_samples.push_back(running);
195 let active = self.cpu_samples.iter().filter(|&&r| r).count();
196 let load = active as f64 / self.cpu_samples.len() as f64;
197 vec![TraceEvent::CpuLoad { load }]
198 }
199
200 pub fn flush(&mut self) -> Vec<TraceEvent> {
202 if self.log_line.is_empty() {
203 return Vec::new();
204 }
205 let message = take_utf8(&mut self.log_line);
206 vec![TraceEvent::Log { message }]
207 }
208
209 fn push_log_bytes(&mut self, data: &[u8]) -> Vec<TraceEvent> {
210 let mut events = Vec::new();
211 for &b in data {
212 if b == b'\n' {
213 let message = take_utf8(&mut self.log_line);
214 events.push(TraceEvent::Log { message });
215 } else if b != b'\r' {
216 self.log_line.push(b);
217 }
218 }
219 events
220 }
221}
222
223fn take_utf8(buf: &mut Vec<u8>) -> String {
225 let s = String::from_utf8_lossy(buf).into_owned();
226 buf.clear();
227 s
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 fn map() -> VariableMap {
235 let mut m = VariableMap::new();
236 m.insert(1, "temperature", VarType::F32);
237 m.insert(2, "duty", VarType::U16);
238 m.insert(3, "loop_us", VarType::U32);
239 m
240 }
241
242 #[test]
243 fn port0_bytes_become_log_lines() {
244 let mut t = Translator::new(map());
245 let mut events = Vec::new();
246 for ch in b"hi\nbye\n" {
247 events.extend(t.translate(&Packet::Instrumentation {
248 port: 0,
249 data: vec![*ch],
250 }));
251 }
252 assert_eq!(
253 events,
254 vec![
255 TraceEvent::Log {
256 message: "hi".into()
257 },
258 TraceEvent::Log {
259 message: "bye".into()
260 },
261 ]
262 );
263 }
264
265 #[test]
266 fn partial_log_line_waits_for_newline() {
267 let mut t = Translator::new(map());
268 assert!(t
269 .translate(&Packet::Instrumentation {
270 port: 0,
271 data: b"par".to_vec()
272 })
273 .is_empty());
274 let done = t.translate(&Packet::Instrumentation {
275 port: 0,
276 data: b"tial\n".to_vec(),
277 });
278 assert_eq!(
279 done,
280 vec![TraceEvent::Log {
281 message: "partial".into()
282 }]
283 );
284 }
285
286 #[test]
287 fn f32_variable_decodes_to_a_number() {
288 let mut t = Translator::new(map());
289 let events = t.translate(&Packet::Instrumentation {
290 port: 1,
291 data: 3.5f32.to_le_bytes().to_vec(),
292 });
293 assert_eq!(
294 events,
295 vec![TraceEvent::Variable {
296 port: 1,
297 name: "temperature".into(),
298 ty: "f32",
299 value: serde_json::json!(3.5),
300 }]
301 );
302 }
303
304 #[test]
305 fn u16_and_u32_variables_decode() {
306 let mut t = Translator::new(map());
307 let duty = t.translate(&Packet::Instrumentation {
308 port: 2,
309 data: 1000u16.to_le_bytes().to_vec(),
310 });
311 assert_eq!(
312 duty[0],
313 TraceEvent::Variable {
314 port: 2,
315 name: "duty".into(),
316 ty: "u16",
317 value: serde_json::json!(1000),
318 }
319 );
320 let lt = t.translate(&Packet::Instrumentation {
321 port: 3,
322 data: 70_000u32.to_le_bytes().to_vec(),
323 });
324 assert_eq!(
325 lt[0],
326 TraceEvent::Variable {
327 port: 3,
328 name: "loop_us".into(),
329 ty: "u32",
330 value: serde_json::json!(70_000),
331 }
332 );
333 }
334
335 #[test]
336 fn unmapped_port_is_ignored() {
337 let mut t = Translator::new(map());
338 assert!(t
339 .translate(&Packet::Instrumentation {
340 port: 5,
341 data: vec![1, 2, 3, 4]
342 })
343 .is_empty());
344 }
345
346 #[test]
347 fn dwt_pc_samples_estimate_cpu_load() {
348 let mut t = Translator::new(map());
349 let sleep = Packet::Hardware {
352 discriminator: 2,
353 data: vec![0x00],
354 };
355 let run = Packet::Hardware {
356 discriminator: 2,
357 data: vec![0x00, 0x10, 0x00, 0x08],
358 };
359
360 let e = t.translate(&run);
362 assert_eq!(e, vec![TraceEvent::CpuLoad { load: 1.0 }]);
363 let e = t.translate(&sleep);
365 assert_eq!(e, vec![TraceEvent::CpuLoad { load: 0.5 }]);
366 }
367
368 #[test]
369 fn cpu_load_event_json_shape() {
370 let json = serde_json::to_value(TraceEvent::CpuLoad { load: 0.25 }).unwrap();
371 assert_eq!(json, serde_json::json!({"kind": "cpuload", "load": 0.25}));
372 }
373
374 #[test]
375 fn non_pc_hardware_packets_are_ignored() {
376 let mut t = Translator::new(map());
377 assert!(t
379 .translate(&Packet::Hardware {
380 discriminator: 1,
381 data: vec![0x01, 0x02]
382 })
383 .is_empty());
384 }
385
386 #[test]
387 fn overflow_is_reported_and_serializes() {
388 let mut t = Translator::new(map());
389 let events = t.translate(&Packet::Overflow);
390 assert_eq!(events, vec![TraceEvent::Overflow]);
391 let json = serde_json::to_string(&events[0]).unwrap();
392 assert_eq!(json, r#"{"kind":"overflow"}"#);
393 }
394
395 #[test]
396 fn variable_event_json_shape() {
397 let ev = TraceEvent::Variable {
398 port: 1,
399 name: "temperature".into(),
400 ty: "f32",
401 value: serde_json::json!(3.5),
402 };
403 let json = serde_json::to_value(&ev).unwrap();
404 assert_eq!(
405 json,
406 serde_json::json!({
407 "kind": "variable",
408 "port": 1,
409 "name": "temperature",
410 "type": "f32",
411 "value": 3.5
412 })
413 );
414 }
415}