1use std::fmt;
12use std::time::Duration;
13
14pub use reverie::DispatchStats;
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::pid::DetTid;
21use crate::time::LogicalTime;
22
23#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31#[serde(deny_unknown_fields)]
32pub struct PathEvidence {
33 pub schema: u8,
34 pub guest_rpc_observed: bool,
35 pub ptrace_fallback_sites: usize,
36 pub trusted_shared_object_sites: usize,
37 pub trusted_shared_objects: Vec<String>,
38}
39
40impl PathEvidence {
41 pub const SCHEMA: u8 = 1;
42}
43
44#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
50pub struct TimesliceStats {
51 pub count: u64,
53 pub sum_ns: u64,
55 pub min_ns: u64,
57 pub max_ns: u64,
59}
60
61impl TimesliceStats {
62 pub fn record(&mut self, ns: u64) {
64 if self.count == 0 {
65 self.min_ns = ns;
66 self.max_ns = ns;
67 } else {
68 self.min_ns = self.min_ns.min(ns);
69 self.max_ns = self.max_ns.max(ns);
70 }
71 self.sum_ns += ns;
72 self.count += 1;
73 }
74
75 pub fn merge(&mut self, other: &TimesliceStats) {
77 if other.count == 0 {
78 return;
79 }
80 if self.count == 0 {
81 *self = *other;
82 return;
83 }
84 self.min_ns = self.min_ns.min(other.min_ns);
85 self.max_ns = self.max_ns.max(other.max_ns);
86 self.sum_ns += other.sum_ns;
87 self.count += other.count;
88 }
89
90 pub fn mean_ns(&self) -> u64 {
92 self.sum_ns.checked_div(self.count).unwrap_or(0)
93 }
94
95 pub fn is_empty(&self) -> bool {
97 self.count == 0
98 }
99}
100
101#[derive(Debug, Serialize, Deserialize, Clone, Default)]
103pub struct RunSummary {
104 pub sched_turns: u64,
106
107 pub schedevent_replayed: u64,
109 pub schedevent_recorded: u64,
111 pub schedevent_desynced: u64,
113
114 pub desync_descrip: Option<String>,
116
117 pub reprio_descrip: Option<String>,
119
120 pub threads_descrip: String,
122
123 pub num_processes: u64,
125 pub num_threads: u64,
127
128 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub syscalls: Option<u64>,
131
132 pub virttime_elapsed: u64,
134 pub virttime_final: u64,
136
137 pub realtime_elapsed: Option<Duration>,
139
140 pub timeslice_stats: TimesliceStats,
143
144 pub per_thread_timeslice: Vec<(DetTid, TimesliceStats)>,
147
148 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub dispatch_stats: Option<DispatchStats>,
155}
156
157impl fmt::Display for RunSummary {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 self.fmt_summary(f, true, self.reprio_descrip.as_deref())
161 }
162}
163
164pub struct RunSummaryInfo<'a> {
167 summary: &'a RunSummary,
168 reprio_description: Option<&'a str>,
169}
170
171impl fmt::Display for RunSummaryInfo<'_> {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 self.summary.fmt_summary(f, false, self.reprio_description)
174 }
175}
176
177impl RunSummary {
178 pub fn info<'a>(&'a self, reprio_description: Option<&'a str>) -> RunSummaryInfo<'a> {
179 RunSummaryInfo {
180 summary: self,
181 reprio_description,
182 }
183 }
184
185 fn fmt_summary(
186 &self,
187 f: &mut fmt::Formatter<'_>,
188 include_replay_bookkeeping: bool,
189 reprio_description: Option<&str>,
190 ) -> fmt::Result {
191 let RunSummary {
192 sched_turns,
193 schedevent_replayed,
194 schedevent_recorded,
195 schedevent_desynced,
196 desync_descrip,
197 reprio_descrip: _,
198 num_processes,
199 num_threads,
200 syscalls,
201 virttime_elapsed,
202 virttime_final,
203 realtime_elapsed,
204 threads_descrip,
205 timeslice_stats,
206 per_thread_timeslice,
207 dispatch_stats: _,
208 } = self;
209 writeln!(f, "Final thread-tree was: {}", threads_descrip)?;
210 writeln!(
211 f,
212 "There were {} group leaders of {} thread(s) total.",
213 num_processes, num_threads
214 )?;
215 if let Some(syscalls) = syscalls {
216 writeln!(f, "Guest threads completed {syscalls} syscalls.")?;
217 }
218 if include_replay_bookkeeping {
219 writeln!(
220 f,
221 "Internally, the hermit scheduler ran {} turns, recorded {} events, replayed {} events ({} desynced)",
222 sched_turns, schedevent_recorded, schedevent_replayed, schedevent_desynced,
223 )?;
224 } else {
225 writeln!(
226 f,
227 "Internally, the hermit scheduler ran {} turns, recorded {} events ({} desynced)",
228 sched_turns, schedevent_recorded, schedevent_desynced,
229 )?;
230 }
231
232 if let Some(txt) = desync_descrip {
233 write!(f, "{}", txt)?;
234 }
235 if let Some(txt) = reprio_description {
236 write!(f, "{}", txt)?;
237 }
238
239 writeln!(
240 f,
241 "Final virtual global (cpu) time: {}",
242 LogicalTime::from_nanos(*virttime_final)
243 )?;
244 writeln!(
245 f,
246 "Elapsed virtual global (cpu) time: {}",
247 LogicalTime::from_nanos(*virttime_elapsed)
248 )?;
249
250 if timeslice_stats.is_empty() {
251 writeln!(f, "Timeslice stats: none recorded")?;
252 } else {
253 writeln!(
254 f,
255 "Timeslice stats: min={}ns max={}ns mean={}ns count={}",
256 timeslice_stats.min_ns,
257 timeslice_stats.max_ns,
258 timeslice_stats.mean_ns(),
259 timeslice_stats.count,
260 )?;
261 if per_thread_timeslice.len() > 1 {
264 for (dettid, st) in per_thread_timeslice {
265 if st.is_empty() {
266 continue;
267 }
268 writeln!(
269 f,
270 " timeslice thread {}: min={}ns max={}ns mean={}ns count={}",
271 dettid,
272 st.min_ns,
273 st.max_ns,
274 st.mean_ns(),
275 st.count,
276 )?;
277 }
278 }
279 }
280
281 if let Some(rt) = realtime_elapsed {
282 writeln!(f, "Nondeterministic realtime elapsed: {:?}", rt)?
283 };
284
285 Ok(())
286 }
287}
288
289#[cfg(test)]
301mod tests {
302 use super::PathEvidence;
303 use super::RunSummary;
304 use super::TimesliceStats;
305
306 #[test]
307 fn path_evidence_json_is_exact() {
308 let evidence = PathEvidence {
309 schema: PathEvidence::SCHEMA,
310 guest_rpc_observed: true,
311 ptrace_fallback_sites: 0,
312 trusted_shared_object_sites: 1,
313 trusted_shared_objects: vec!["/usr/lib/libc.so.6".into()],
314 };
315 let json = serde_json::to_string(&evidence).unwrap();
316 assert_eq!(
317 json,
318 r#"{"schema":1,"guest_rpc_observed":true,"ptrace_fallback_sites":0,"trusted_shared_object_sites":1,"trusted_shared_objects":["/usr/lib/libc.so.6"]}"#
319 );
320 assert_eq!(
321 serde_json::from_str::<PathEvidence>(&json).unwrap(),
322 evidence
323 );
324
325 let unknown = json.replace(
326 r#""trusted_shared_objects""#,
327 r#""unexpected":0,"trusted_shared_objects""#,
328 );
329 let error = serde_json::from_str::<PathEvidence>(&unknown).unwrap_err();
330 assert!(error.to_string().contains("unknown field `unexpected`"));
331
332 let missing = json.replace(r#""guest_rpc_observed":true,"#, "");
333 let error = serde_json::from_str::<PathEvidence>(&missing).unwrap_err();
334 assert!(
335 error
336 .to_string()
337 .contains("missing field `guest_rpc_observed`")
338 );
339 }
340
341 #[test]
342 fn timeslice_stats_empty() {
343 let s = TimesliceStats::default();
344 assert!(s.is_empty());
345 assert_eq!(s.count, 0);
346 assert_eq!(s.mean_ns(), 0); }
348
349 #[test]
350 fn timeslice_stats_record() {
351 let mut s = TimesliceStats::default();
352 s.record(10);
353 s.record(30);
354 s.record(20);
355 assert!(!s.is_empty());
356 assert_eq!(s.count, 3);
357 assert_eq!(s.min_ns, 10);
358 assert_eq!(s.max_ns, 30);
359 assert_eq!(s.sum_ns, 60);
360 assert_eq!(s.mean_ns(), 20);
361 }
362
363 #[test]
364 fn timeslice_stats_merge() {
365 let mut a = TimesliceStats::default();
366 a.record(10);
367 a.record(40);
368 let mut b = TimesliceStats::default();
369 b.record(5);
370 b.record(100);
371 a.merge(&b);
372 assert_eq!(a.count, 4);
373 assert_eq!(a.min_ns, 5);
374 assert_eq!(a.max_ns, 100);
375 assert_eq!(a.sum_ns, 155);
376
377 let before = a;
379 a.merge(&TimesliceStats::default());
380 assert_eq!(a, before);
381
382 let mut empty = TimesliceStats::default();
384 empty.merge(&b);
385 assert_eq!(empty, b);
386 }
387
388 #[test]
389 fn older_summary_json_keeps_an_unrecorded_syscall_total_absent() {
390 let value = serde_json::to_value(RunSummary::default()).unwrap();
391 let mut object = value.as_object().unwrap().clone();
392 object.remove("syscalls");
393 let parsed: RunSummary = serde_json::from_value(object.into()).unwrap();
394 assert_eq!(parsed.syscalls, None);
395
396 let measured = RunSummary {
397 syscalls: Some(0),
398 ..Default::default()
399 };
400 let value = serde_json::to_value(&measured).unwrap();
401 assert_eq!(value["syscalls"], 0);
402 let parsed: RunSummary = serde_json::from_value(value).unwrap();
403 assert_eq!(parsed.syscalls, Some(0));
404 }
405}