1use std::collections::HashMap;
10use std::fmt;
11use std::ops::Add;
12use std::ops::Sub;
13use std::time::Duration;
14
15use chrono::DateTime;
16use chrono::Utc;
17use reverie_syscalls::Timespec;
18use reverie_syscalls::Timeval;
19use serde::Deserialize;
20use serde::Serialize;
21use tracing::trace;
22
23use crate::config::Config;
24use crate::pid::DetTid;
25
26const NANOS_PER_SEC: u64 = 1_000_000_000;
28const NANOS_PER_MILLI: u64 = 1_000_000;
29const NANOS_PER_MICRO: u64 = 1_000;
30const MILLIS_PER_SEC: u64 = 1_000;
31const MICROS_PER_SEC: u64 = 1_000_000;
32
33pub const NANOS_PER_SYSCALL: f64 = 10000.0;
37
38pub const NANOS_PER_RCB: f64 = 10.0;
40
41const RCB_TIME_MULTIPLIER_SCALE: u64 = 1_u64 << 32;
46
47#[derive(
51 Debug,
52 Clone,
53 Copy,
54 Serialize,
55 Deserialize,
56 Ord,
57 PartialOrd,
58 Eq,
59 PartialEq,
60 Hash
61)]
62pub struct RcbTimeMultiplier(u64);
63
64impl RcbTimeMultiplier {
65 pub const ONE: Self = Self(RCB_TIME_MULTIPLIER_SCALE);
67
68 pub const MAX: f64 = u64::MAX as f64 / RCB_TIME_MULTIPLIER_SCALE as f64;
70
71 pub fn from_f64(value: f64) -> Self {
73 assert!(value.is_finite() && value > 0.0 && value <= Self::MAX);
74 let scaled = (value * RCB_TIME_MULTIPLIER_SCALE as f64).round() as u64;
75 Self(scaled.max(1))
76 }
77
78 pub fn as_f64(self) -> f64 {
80 self.0 as f64 / RCB_TIME_MULTIPLIER_SCALE as f64
81 }
82
83 fn units(self) -> u64 {
84 self.0
85 }
86}
87
88impl Default for RcbTimeMultiplier {
89 fn default() -> Self {
90 Self::ONE
91 }
92}
93
94pub const NANOS_PER_NONDET_INSTR: f64 = 25.0;
96
97pub const NANOS_PER_SCHED: f64 = 500_000.0;
99
100#[derive(
109 Default,
110 Debug,
111 Clone,
112 Copy,
113 Serialize,
114 Deserialize,
115 Ord,
116 PartialOrd,
117 Eq,
118 PartialEq,
119 Hash
120)]
121pub struct LogicalTime(u64);
122
123pub type LogicalDuration = LogicalTime;
126
127impl LogicalTime {
128 pub const ZERO: LogicalTime = LogicalTime(0);
130 pub const MAX: LogicalTime = LogicalTime(u64::MAX);
132
133 pub const INDEFINITE: LogicalTime = LogicalTime::MAX;
146
147 pub fn is_indefinite(&self) -> bool {
149 *self == LogicalTime::INDEFINITE
150 }
151
152 pub fn as_micros(&self) -> u64 {
154 self.0 / NANOS_PER_MICRO
155 }
156
157 pub fn as_millis(&self) -> u64 {
159 self.0 / NANOS_PER_MILLI
160 }
161
162 pub fn as_nanos(&self) -> u64 {
164 self.0
165 }
166
167 pub fn as_secs(&self) -> u64 {
171 self.0 / NANOS_PER_SEC
172 }
173
174 pub fn from_micros(micros: u64) -> Self {
176 LogicalTime(micros * NANOS_PER_MICRO)
177 }
178
179 pub fn from_millis(millis: u64) -> Self {
181 LogicalTime(millis * NANOS_PER_MILLI)
182 }
183
184 pub fn from_nanos(nanos: u64) -> Self {
186 LogicalTime(nanos)
187 }
188
189 pub fn from_big_nanos(nanos: u128) -> Self {
191 LogicalTime(nanos as u64)
193 }
194
195 pub fn from_secs(secs: u64) -> Self {
197 LogicalTime(secs * NANOS_PER_SEC)
198 }
199
200 pub fn subsec_micros(&self) -> u32 {
204 ((self.0 / NANOS_PER_MICRO) % MICROS_PER_SEC) as u32
205 }
206
207 pub fn subsec_millis(&self) -> u32 {
211 ((self.0 / NANOS_PER_MILLI) % MILLIS_PER_SEC) as u32
212 }
213
214 pub fn subsec_nanos(&self) -> u32 {
218 (self.0 % NANOS_PER_SEC) as u32
219 }
220
221 pub fn from_rcbs(n: u64) -> Self {
223 LogicalTime((n as f64 * NANOS_PER_RCB) as u64)
224 }
225
226 pub fn into_rcbs(self) -> u64 {
229 (self.0 as f64 / NANOS_PER_RCB) as u64
230 }
231
232 pub fn into_rcbs_with_multiplier(self, multiplier: f64) -> u64 {
234 debug_assert!(multiplier > 0.0);
235 (self.0 as f64 / (NANOS_PER_RCB * multiplier)).floor() as u64
236 }
237
238 pub fn is_zero(&self) -> bool {
240 self.0 == 0
241 }
242
243 pub fn duration_since(&self, from: LogicalTime) -> Duration {
245 if from.0 > self.0 {
246 panic!(
247 "LogicalTime::duration_since cannot take duration since a time in the *future* ({}), relative to {}",
248 from, self
249 );
250 }
251 Duration::from_nanos(self.0 - from.0)
252 }
253}
254
255impl std::fmt::Display for LogicalTime {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 let chars = format!("{}", self.0);
259 let mut remain = chars.len();
260 let mut first_char = true;
261 for ch in chars.chars() {
262 if !first_char && remain % 3 == 0 {
263 if remain == 9 {
264 write!(f, ".")?;
265 } else {
266 write!(f, "_")?;
269 }
270 }
271 first_char = false;
272 remain -= 1;
273 write!(f, "{}", ch)?;
274 }
275 if chars.len() <= 9 {
276 write!(f, "ns")
277 } else {
278 write!(f, "s")
279 }
280 }
281}
282
283impl Add for LogicalTime {
284 type Output = Self;
285 fn add(self, rhs: Self) -> Self {
286 LogicalTime(self.0.saturating_add(rhs.0))
289 }
290}
291
292impl Add<Duration> for LogicalTime {
293 type Output = Self;
294 fn add(self, rhs: Duration) -> Self {
295 let nanos = u64::try_from(rhs.as_nanos()).unwrap_or(u64::MAX);
300 LogicalTime(self.0.saturating_add(nanos))
301 }
302}
303
304impl Add<u128> for LogicalTime {
305 type Output = Self;
306 fn add(self, rhs: u128) -> Self {
307 LogicalTime(self.0.saturating_add(rhs.min(u64::MAX as u128) as u64))
309 }
310}
311
312impl Sub for LogicalTime {
313 type Output = Self;
314 fn sub(self, rhs: Self) -> Self {
315 LogicalTime(self.0 - rhs.0)
316 }
317}
318
319impl From<LogicalTime> for Timespec {
320 fn from(logical_time: LogicalTime) -> Timespec {
321 Timespec {
322 tv_sec: logical_time.as_secs() as i64,
323 tv_nsec: logical_time.subsec_nanos() as i64,
324 }
325 }
326}
327
328impl From<LogicalTime> for Timeval {
329 fn from(logical_time: LogicalTime) -> Timeval {
330 Timeval {
331 tv_sec: logical_time.as_secs() as i64,
332 tv_usec: logical_time.subsec_micros() as i64,
333 }
334 }
335}
336
337#[test]
338fn print_nanoseconds() {
339 let ns1 = LogicalTime(946_684_799_000_000_000);
340 let ns2 = LogicalTime(729_860_000);
341 assert_eq!(format!("{}", ns1), "946_684_799.000_000_000s");
342 assert_eq!(format!("{}", ns1 + ns2), "946_684_799.729_860_000s");
343 assert_eq!(format!("{}", ns2), "729_860_000ns");
344}
345
346#[test]
347fn subsecond_units_are_converted_from_nanoseconds() {
348 let time = LogicalTime::from_secs(2) + LogicalTime::from_nanos(345_678_901);
349
350 assert_eq!(time.subsec_millis(), 345);
351 assert_eq!(time.subsec_micros(), 345_678);
352 assert_eq!(time.subsec_nanos(), 345_678_901);
353
354 let timeval: Timeval = time.into();
355 assert_eq!(timeval.tv_sec, 2);
356 assert_eq!(timeval.tv_usec, 345_678);
357}
358
359#[test]
360fn indefinite_is_recognized_and_nothing_else_is() {
361 assert!(LogicalTime::INDEFINITE.is_indefinite());
362 assert!(!LogicalTime::ZERO.is_indefinite());
363 assert!(!LogicalTime::from_secs(1_767_225_600).is_indefinite());
364 assert!(!LogicalTime(u64::MAX - 1).is_indefinite());
365
366 assert!((LogicalTime(u64::MAX - 10) + LogicalTime::from_secs(1)).is_indefinite());
369}
370
371#[test]
372fn add_saturates_instead_of_overflowing() {
373 let near_max = LogicalTime(u64::MAX - 10);
377
378 assert_eq!(near_max + LogicalTime::from_secs(1), LogicalTime::MAX);
380
381 assert_eq!(near_max + Duration::from_secs(1), LogicalTime::MAX);
383 assert_eq!(
384 LogicalTime::ZERO + Duration::from_secs(u64::MAX / 1_000_000_000 + 1),
385 LogicalTime::MAX
386 );
387
388 assert_eq!(near_max + 100u128, LogicalTime::MAX);
390 assert_eq!(
391 LogicalTime::ZERO + (u128::from(u64::MAX) + 5),
392 LogicalTime::MAX
393 );
394
395 assert_eq!(
397 LogicalTime::from_secs(2) + Duration::from_secs(3),
398 LogicalTime::from_secs(5)
399 );
400}
401
402pub type Microseconds = u64;
404
405#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct DetTime {
432 syscalls: u64,
434
435 #[serde(default)]
440 syscall_nanos: Option<u64>,
441
442 rcbs: u64,
445
446 #[serde(default)]
454 weighted_rcbs: Option<u128>,
455
456 nondet_instrs: u64,
458
459 #[serde(default)]
461 extra_nanos: u64,
462
463 starting_micros: Microseconds,
465
466 multiplier: f64,
468
469 #[serde(default)]
473 inherited_nanos: LogicalDuration,
474}
475
476impl Default for DetTime {
478 fn default() -> Self {
479 DetTime {
480 syscalls: 0,
481 syscall_nanos: Some(0),
482 rcbs: 0,
483 weighted_rcbs: None,
484 nondet_instrs: 0,
485 extra_nanos: 0,
486 starting_micros: 0,
487 multiplier: 1.0,
488 inherited_nanos: LogicalTime::ZERO,
489 }
490 }
491}
492
493impl Eq for DetTime {}
494
495impl Ord for DetTime {
497 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
498 let nanos = other.as_nanos();
499 self.as_nanos().cmp(&nanos)
500 }
501}
502
503#[allow(clippy::non_canonical_partial_ord_impl)]
504impl PartialOrd for DetTime {
505 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
506 let nanos = other.as_nanos();
507 self.as_nanos().partial_cmp(&nanos)
508 }
509}
510
511impl PartialEq for DetTime {
512 fn eq(&self, other: &Self) -> bool {
513 self.as_nanos() == other.as_nanos()
514 }
515}
516
517impl From<&DateTime<Utc>> for DetTime {
518 fn from(dt: &DateTime<Utc>) -> Self {
519 DetTime {
520 syscalls: 0,
521 syscall_nanos: Some(0),
522 rcbs: 0,
523 weighted_rcbs: None,
524 nondet_instrs: 0,
525 extra_nanos: 0,
526 starting_micros: micros_from_utc(dt),
527 multiplier: 1.0,
528 inherited_nanos: LogicalTime::ZERO,
529 }
530 }
531}
532
533fn micros_from_utc(dt: &DateTime<Utc>) -> Microseconds {
534 dt.timestamp() as Microseconds * 1_000_000 + dt.timestamp_subsec_micros() as Microseconds
535}
536
537#[allow(clippy::from_over_into)]
539impl Into<Timespec> for DetTime {
540 fn into(self) -> Timespec {
541 self.as_nanos().into()
542 }
543}
544
545impl DetTime {
546 pub fn new(cfg: &Config) -> Self {
548 let additional_multiplier = if cfg.sequentialize_threads && !cfg.use_rcb_time() {
553 500.0
554 } else {
555 1.0
558 };
559 match cfg.clock_multiplier {
560 Some(m) => DetTime::from(&cfg.epoch).with_multiplier(m * additional_multiplier),
561 None => DetTime::from(&cfg.epoch).with_multiplier(additional_multiplier),
562 }
563 }
564
565 pub fn zero() -> Self {
567 DetTime {
568 syscalls: 0,
569 syscall_nanos: Some(0),
570 rcbs: 0,
571 weighted_rcbs: None,
572 nondet_instrs: 0,
573 extra_nanos: 0,
574 starting_micros: 0,
575 multiplier: 1.0,
576 inherited_nanos: LogicalTime::ZERO,
577 }
578 }
579
580 pub fn clone_for_child(&self) -> Self {
584 let mut child = self.clone();
585 child.inherited_nanos = self.without_starting();
586 child
587 }
588
589 pub fn inherited_nanos(&self) -> LogicalDuration {
591 self.inherited_nanos
592 }
593
594 pub fn add_syscall(&mut self) {
596 self.add_syscall_with_cost(NANOS_PER_SYSCALL as u64);
597 }
598
599 pub fn add_syscall_with_cost(&mut self, nanos: u64) {
601 let previous_uniform_nanos = (self.syscalls as f64 * NANOS_PER_SYSCALL) as u64;
602 self.syscalls += 1;
603 match &mut self.syscall_nanos {
604 Some(syscall_nanos) => *syscall_nanos += nanos,
605 None => self.syscall_nanos = Some(previous_uniform_nanos + nanos),
606 }
607 trace!(
608 "[detcore] added syscall cost of {}ns to logical time, yielding: {:?}",
609 nanos, self
610 );
611 }
612
613 pub fn add_rdtsc(&mut self) {
615 self.nondet_instrs += 1;
616 }
617
618 pub fn add_cpuid(&mut self) {
620 self.nondet_instrs += 1;
621 }
622
623 pub fn add_rcbs(&mut self, count: u64) {
625 if let Some(weighted_rcbs) = &mut self.weighted_rcbs {
626 *weighted_rcbs += u128::from(count) * u128::from(RCB_TIME_MULTIPLIER_SCALE);
627 }
628 self.rcbs += count;
629 }
630
631 pub fn add_rcbs_with_multiplier(&mut self, count: u64, factor: RcbTimeMultiplier) {
638 let weighted_rcbs = self
639 .weighted_rcbs
640 .get_or_insert_with(|| u128::from(self.rcbs) * u128::from(RCB_TIME_MULTIPLIER_SCALE));
641 *weighted_rcbs += u128::from(count) * u128::from(factor.units());
642 self.rcbs += count;
643 }
644
645 pub fn advance_to(&mut self, deadline: LogicalTime) {
647 let current = self.as_nanos();
648 assert!(deadline >= current);
649 self.extra_nanos += (deadline - current).as_nanos();
650 }
651
652 pub fn rcbs(&self) -> u64 {
654 self.rcbs
655 }
656
657 pub fn as_nanos(&self) -> LogicalTime {
659 let syscall_nanos = self
662 .syscall_nanos
663 .unwrap_or((self.syscalls as f64 * NANOS_PER_SYSCALL) as u64);
664 let rcb_nanos = self.weighted_rcbs.map_or_else(
665 || self.rcbs as f64 * NANOS_PER_RCB,
666 |weighted| weighted as f64 * NANOS_PER_RCB / RCB_TIME_MULTIPLIER_SCALE as f64,
667 );
668 LogicalTime(
669 (self.starting_micros * 1000)
670 + self.extra_nanos
671 + ((syscall_nanos as f64 * self.multiplier) as u64)
672 + ((rcb_nanos * self.multiplier) as u64)
673 + ((self.nondet_instrs as f64 * NANOS_PER_NONDET_INSTR * self.multiplier) as u64),
674 )
675 }
676
677 pub fn without_starting(&self) -> LogicalDuration {
679 let LogicalTime(t1) = self.as_nanos();
680 LogicalTime(t1 - (self.starting_micros * 1000))
681 }
682
683 pub fn user_cpu_time(&self) -> LogicalDuration {
686 let rcb_nanos = self.weighted_rcbs.map_or_else(
687 || self.rcbs as f64 * NANOS_PER_RCB,
688 |weighted| weighted as f64 * NANOS_PER_RCB / RCB_TIME_MULTIPLIER_SCALE as f64,
689 );
690 LogicalTime(
691 ((rcb_nanos + (self.nondet_instrs as f64 * NANOS_PER_NONDET_INSTR)) * self.multiplier)
692 as u64,
693 )
694 }
695
696 pub fn system_cpu_time(&self) -> LogicalDuration {
699 let syscall_nanos = self
700 .syscall_nanos
701 .unwrap_or((self.syscalls as f64 * NANOS_PER_SYSCALL) as u64);
702 LogicalTime((syscall_nanos as f64 * self.multiplier) as u64)
703 }
704
705 pub fn as_micros(&self) -> Microseconds {
707 self.as_nanos().0 / 1000
708 }
709
710 pub fn with_multiplier(mut self, m: f64) -> Self {
712 self.multiplier = m;
713 self
714 }
715
716 pub fn as_duration(&self) -> std::time::Duration {
718 std::time::Duration::from_nanos(self.as_nanos().0 - self.starting_micros * 1000)
719 }
720}
721
722#[cfg(test)]
723mod rcb_multiplier_tests {
724 use super::*;
725
726 #[test]
729 fn weighted_rcb_time_is_batching_independent() {
730 let factor = RcbTimeMultiplier::from_f64(2.5);
731 let mut one_batch = DetTime::zero();
732 one_batch.add_rcbs_with_multiplier(10, factor);
733
734 let mut split_batches = DetTime::zero();
735 split_batches.add_rcbs_with_multiplier(4, factor);
736 split_batches.add_rcbs_with_multiplier(6, factor);
737
738 assert_eq!(one_batch.rcbs(), 10);
739 assert_eq!(split_batches.rcbs(), 10);
740 assert_eq!(one_batch.as_nanos(), LogicalTime::from_nanos(250));
741 assert_eq!(one_batch.as_nanos(), split_batches.as_nanos());
742 assert_eq!(one_batch.user_cpu_time(), split_batches.user_cpu_time());
743 }
744
745 #[test]
748 fn uniform_rcbs_after_weighted_rcbs_keep_continuity() {
749 let mut time = DetTime::zero();
750 time.add_rcbs_with_multiplier(10, RcbTimeMultiplier::from_f64(0.5));
751 assert_eq!(time.as_nanos(), LogicalTime::from_nanos(50));
752
753 time.add_rcbs(5);
754 assert_eq!(time.rcbs(), 15);
755 assert_eq!(time.as_nanos(), LogicalTime::from_nanos(100));
756 }
757}
758
759#[derive(Default, Debug, Clone, Serialize, Deserialize)]
761pub struct GlobalTime {
762 starting_nanos: LogicalTime,
764
765 time_vector: HashMap<DetTid, LogicalTime>,
768
769 #[serde(default)]
772 inherited_time: HashMap<DetTid, LogicalDuration>,
773
774 extra_time: LogicalTime,
776
777 total: LogicalTime,
780
781 multiplier: f64,
783
784 #[serde(default)]
789 scheduler_turn_charge: Option<u64>,
790}
791
792#[derive(Debug, Clone, Copy, PartialEq, Eq)]
795pub struct ClockExhausted {
796 pub charge: u64,
798 pub total: LogicalTime,
800}
801
802impl std::fmt::Display for ClockExhausted {
803 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
804 write!(
805 f,
806 "HERMIT_VIRTUAL_CLOCK_EXHAUSTED: virtual clock exhausted: a scheduler turn charging {} \
807 ns would pass the end of representable time at {} ns; lower --scheduler-turn-cost or \
808 --clock-multiplier",
809 self.charge, self.total.0
810 )
811 }
812}
813
814impl GlobalTime {
815 pub fn new(cfg: &Config) -> Self {
817 let base = DetTime::new(cfg);
818 GlobalTime {
819 starting_nanos: LogicalTime::from_micros(micros_from_utc(&cfg.epoch)),
820 time_vector: HashMap::new(),
821 inherited_time: HashMap::new(),
822 extra_time: LogicalTime::from_nanos(0),
823 total: base.as_nanos(),
824 multiplier: cfg.clock_multiplier.unwrap_or(1.0),
825 scheduler_turn_charge: cfg.scheduler_turn_cost.map(|_| {
830 cfg.scheduler_turn_charge()
831 .unwrap_or_else(|error| panic!("invalid scheduler turn cost: {error}"))
832 }),
833 }
834 }
835
836 pub fn update_global_time(
838 &mut self,
839 tid: DetTid,
840 newtime: LogicalTime,
841 inherited: LogicalDuration,
842 ) {
843 if newtime < self.starting_nanos {
844 panic!(
845 "update_global_time: Cannot set thread {} time to {}, which is before start of container execution {}",
846 tid, newtime, self.starting_nanos
847 );
848 }
849
850 let newtime = newtime - self.starting_nanos;
852 trace!(
853 "[tid {}] ticked its global time component to {}",
854 tid, newtime,
855 );
856 if let Some(old) = self.time_vector.get_mut(&tid) {
857 if *old > newtime {
858 panic!(
859 "Attempted to update tid {} time to {}, but was already {}",
860 tid, newtime, old
861 );
862 }
863 let LogicalTime(diff) = newtime - *old;
865 *old = newtime;
866 self.bump_total(Duration::from_nanos(diff));
870 } else {
871 assert!(
872 inherited <= newtime,
873 "thread {tid} inherited time {inherited} beyond its local duration {newtime}"
874 );
875 self.time_vector.insert(tid, newtime);
876 self.inherited_time.insert(tid, inherited);
877 self.bump_total(Duration::from_nanos((newtime - inherited).0));
881 }
882 }
883
884 fn sanity(&self) {
885 debug_assert_eq!(self.sum_up(), self.total);
886 }
887
888 fn bump_total(&mut self, delta: Duration) {
889 self.total = self.total + delta;
890 self.sanity();
891 }
892
893 fn sum_up(&self) -> LogicalTime {
895 let mut sum = self.starting_nanos;
896 for (tid, tm) in &self.time_vector {
897 sum = sum + (*tm - self.inherited_duration(*tid));
898 }
899 sum + self.extra_time
900 }
901
902 pub fn add_scheduler_time(&mut self) -> Result<LogicalTime, ClockExhausted> {
913 self.add_capped_scheduler_time(u64::MAX)
914 }
915
916 pub fn add_capped_scheduler_time(&mut self, cap: u64) -> Result<LogicalTime, ClockExhausted> {
926 let full = self
927 .scheduler_turn_charge
928 .unwrap_or((NANOS_PER_SCHED * self.multiplier) as u64);
929 let charge = full.min(cap.max(1));
930 if self.scheduler_turn_charge.is_some() && self.total.0.checked_add(charge).is_none() {
931 return Err(ClockExhausted {
932 charge,
933 total: self.total,
934 });
935 }
936 Ok(self.add_extra_time(Duration::from_nanos(charge)))
937 }
938
939 pub fn add_extra_time(&mut self, delta: Duration) -> LogicalTime {
946 self.extra_time = self.extra_time + delta;
947 self.bump_total(delta);
949 self.as_nanos()
950 }
951
952 pub fn threads_time(&self, dtid: DetTid) -> LogicalTime {
955 self.starting_nanos + self.threads_duration(dtid)
956 }
957
958 pub fn contains_thread(&self, dtid: DetTid) -> bool {
962 self.time_vector.contains_key(&dtid)
963 }
964
965 pub fn reassign_thread(&mut self, from: DetTid, to: DetTid) {
973 if from == to {
974 return;
975 }
976
977 self.retire_thread(to);
979 let survivor_time = self
980 .time_vector
981 .remove(&from)
982 .unwrap_or_else(|| panic!("cannot reassign missing thread clock {from}"));
983 let survivor_inherited = self.inherited_time.remove(&from).unwrap_or_default();
984 self.time_vector.insert(to, survivor_time);
985 self.inherited_time.insert(to, survivor_inherited);
986 self.sanity();
987 }
988
989 pub fn retire_thread(&mut self, dtid: DetTid) {
994 let inherited = self.inherited_time.remove(&dtid).unwrap_or_default();
995 if let Some(time) = self.time_vector.remove(&dtid) {
996 self.extra_time = self.extra_time + (time - inherited);
997 }
998 self.sanity();
999 }
1000
1001 fn inherited_duration(&self, dtid: DetTid) -> LogicalDuration {
1002 self.inherited_time.get(&dtid).copied().unwrap_or_default()
1003 }
1004
1005 pub fn threads_inherited_time(&self, dtid: DetTid) -> LogicalTime {
1009 self.starting_nanos + self.inherited_duration(dtid)
1010 }
1011
1012 pub fn threads_duration(&self, dtid: DetTid) -> LogicalDuration {
1016 *self.time_vector.get(&dtid).unwrap_or_else(|| {
1017 panic!(
1018 "Trying to extract time for thread {}, but no entry found!",
1019 dtid
1020 )
1021 })
1022 }
1023
1024 pub fn as_nanos(&self) -> LogicalTime {
1029 self.total
1030 }
1031
1032 pub fn elapsed_nanos(&self) -> Option<LogicalDuration> {
1040 self.total
1041 .as_nanos()
1042 .checked_sub(self.starting_nanos.as_nanos())
1043 .map(LogicalTime::from_nanos)
1044 }
1045}
1046
1047#[cfg(test)]
1048mod global_time_tests {
1049 use super::*;
1050
1051 fn publish(time: &mut GlobalTime, tid: DetTid, clock: &DetTime) {
1052 time.update_global_time(tid, clock.as_nanos(), clock.inherited_nanos());
1053 }
1054
1055 #[test]
1056 fn submicrosecond_epoch_starts_with_zero_elapsed_time() {
1057 let config = Config {
1058 epoch: "2026-09-24T23:52:07.760605385Z".parse().unwrap(),
1059 ..Config::default()
1060 };
1061 let mut time = GlobalTime::new(&config);
1062 assert_eq!(time.elapsed_nanos(), Some(LogicalTime::ZERO));
1063 time.add_extra_time(Duration::from_nanos(1));
1064 assert_eq!(time.elapsed_nanos(), Some(LogicalTime::from_nanos(1)));
1065 }
1066
1067 #[test]
1068 fn global_time_behind_its_baseline_is_refused_not_wrapped() {
1069 let mut time = GlobalTime::new(&Config::default());
1070 time.total = LogicalTime::from_nanos(time.starting_nanos.as_nanos() - 1);
1071 assert_eq!(time.elapsed_nanos(), None);
1072 }
1073
1074 #[test]
1075 fn descendants_preserve_absolute_clocks_and_charge_only_their_own_work() {
1076 let config = Config {
1077 clock_multiplier: Some(2.0),
1078 ..Config::default()
1079 };
1080 let root = DetTid::from_raw(3);
1081 let child = DetTid::from_raw(4);
1082 let grandchild = DetTid::from_raw(5);
1083 let mut time = GlobalTime::new(&config);
1084 let start = time.as_nanos();
1085 let mut root_clock = DetTime::new(&config);
1086 root_clock.add_syscall_with_cost(11);
1087 root_clock.add_rcbs(3);
1088 root_clock.add_rdtsc();
1089 root_clock.advance_to(root_clock.as_nanos() + LogicalTime::from_nanos(1));
1090 assert_eq!(root_clock.without_starting(), LogicalTime::from_nanos(133));
1091 publish(&mut time, root, &root_clock);
1092
1093 let mut child_clock = root_clock.clone_for_child();
1094 assert_eq!(child_clock.as_nanos(), root_clock.as_nanos());
1095 publish(&mut time, child, &child_clock);
1096 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(133));
1097 assert_eq!(time.threads_time(child), child_clock.as_nanos());
1098 child_clock.add_syscall_with_cost(7);
1099 let mut split_rcbs = child_clock.clone();
1100 split_rcbs.add_rcbs_with_multiplier(1, RcbTimeMultiplier::from_f64(0.5));
1101 split_rcbs.add_rcbs_with_multiplier(3, RcbTimeMultiplier::from_f64(0.5));
1102 child_clock.add_rcbs_with_multiplier(4, RcbTimeMultiplier::from_f64(0.5));
1103 assert_eq!(split_rcbs.as_nanos(), child_clock.as_nanos());
1104 assert_eq!(split_rcbs.inherited_nanos(), child_clock.inherited_nanos());
1105 child_clock.add_cpuid();
1106 child_clock.advance_to(child_clock.as_nanos() + LogicalTime::from_nanos(1));
1107 assert_eq!(child_clock.without_starting(), LogicalTime::from_nanos(238));
1108 let snapshot = child_clock.clone();
1109 assert_eq!(snapshot.inherited_nanos(), LogicalTime::from_nanos(133));
1110 publish(&mut time, child, &snapshot);
1111 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(238));
1112 publish(&mut time, child, &snapshot);
1113 publish(&mut time, child, &snapshot);
1114 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(238));
1115
1116 let mut grandchild_clock = child_clock.clone_for_child();
1117 assert_eq!(
1118 grandchild_clock.inherited_nanos(),
1119 LogicalTime::from_nanos(238)
1120 );
1121 publish(&mut time, grandchild, &grandchild_clock);
1122 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(238));
1123 grandchild_clock.add_syscall_with_cost(3);
1124 grandchild_clock.advance_to(grandchild_clock.as_nanos() + LogicalTime::from_nanos(1));
1125 publish(&mut time, grandchild, &grandchild_clock);
1126 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(245));
1127 assert_eq!(time.threads_time(child), child_clock.as_nanos());
1128 assert_eq!(time.threads_time(grandchild), grandchild_clock.as_nanos());
1129 assert_eq!(
1130 time.threads_duration(grandchild),
1131 LogicalTime::from_nanos(245)
1132 );
1133
1134 time.add_scheduler_time().unwrap();
1135 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_000_245));
1136 }
1137
1138 #[test]
1141 fn scheduler_turn_cost_sets_the_charge_per_turn() {
1142 let mut default = GlobalTime::new(&Config::default());
1143 let start = default.as_nanos();
1144 default.add_scheduler_time().unwrap();
1145 assert_eq!(default.as_nanos(), start + LogicalTime::from_nanos(500_000));
1146
1147 let config = Config {
1148 scheduler_turn_cost: std::num::NonZeroU64::new(10_000),
1149 clock_multiplier: Some(0.1),
1150 ..Config::default()
1151 };
1152 let mut time = GlobalTime::new(&config);
1153 let start = time.as_nanos();
1154 time.add_scheduler_time().unwrap();
1155 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_000));
1156 time.add_scheduler_time().unwrap();
1157 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(2_000));
1158 }
1159
1160 #[test]
1164 fn the_largest_scheduler_turn_charge_advances_every_turn() {
1165 let config = Config {
1166 scheduler_turn_cost: std::num::NonZeroU64::new(crate::config::MAX_SCHEDULER_TURN_COST),
1167 ..Config::default()
1168 };
1169 let mut time = GlobalTime::new(&config);
1170 let mut previous = time.as_nanos();
1171 for _ in 0..1_000 {
1172 let now = time.add_scheduler_time().unwrap();
1173 assert_eq!(
1174 now,
1175 previous + LogicalTime::from_nanos(crate::config::MAX_SCHEDULER_TURN_COST)
1176 );
1177 previous = now;
1178 }
1179 }
1180
1181 #[test]
1184 fn a_configured_turn_charge_refuses_to_saturate_the_clock() {
1185 let config = Config {
1187 scheduler_turn_cost: std::num::NonZeroU64::new(crate::config::MAX_SCHEDULER_TURN_COST),
1188 epoch: "2554-07-21T23:34:30Z".parse().unwrap(),
1189 ..Config::default()
1190 };
1191 let mut time = GlobalTime::new(&config);
1192 let mut previous = time.as_nanos();
1193 for _ in 0..3 {
1194 let now = time.add_scheduler_time().unwrap();
1195 assert_eq!(
1196 now,
1197 previous + LogicalTime::from_nanos(crate::config::MAX_SCHEDULER_TURN_COST)
1198 );
1199 previous = now;
1200 }
1201 assert_eq!(
1202 time.add_scheduler_time(),
1203 Err(ClockExhausted {
1204 charge: crate::config::MAX_SCHEDULER_TURN_COST,
1205 total: previous,
1206 })
1207 );
1208 assert_eq!(time.as_nanos(), previous, "a refused turn adds nothing");
1209 }
1210
1211 #[test]
1214 fn a_capped_turn_charges_its_cap_bounded_by_the_full_charge_and_one_nanosecond() {
1215 let advance = |config: &Config, cap: u64| {
1216 let mut time = GlobalTime::new(config);
1217 let start = time.as_nanos();
1218 time.add_capped_scheduler_time(cap).unwrap() - start
1219 };
1220 let default = Config::default();
1221 let configured = Config {
1222 scheduler_turn_cost: std::num::NonZeroU64::new(10_000),
1223 ..Config::default()
1224 };
1225 for (config, full) in [(&default, NANOS_PER_SCHED as u64), (&configured, 10_000)] {
1226 assert_eq!(advance(config, 1_000), LogicalTime::from_nanos(1_000));
1227 assert_eq!(advance(config, full - 1), LogicalTime::from_nanos(full - 1));
1228 assert_eq!(advance(config, full), LogicalTime::from_nanos(full));
1229 assert_eq!(advance(config, full + 1), LogicalTime::from_nanos(full));
1230 assert_eq!(advance(config, u64::MAX), LogicalTime::from_nanos(full));
1231 assert_eq!(advance(config, 0), LogicalTime::from_nanos(1));
1232 }
1233 let mut uncapped = GlobalTime::new(&default);
1234 let start = uncapped.as_nanos();
1235 assert_eq!(
1236 uncapped.add_scheduler_time().unwrap() - start,
1237 LogicalTime::from_nanos(NANOS_PER_SCHED as u64)
1238 );
1239 }
1240
1241 #[test]
1244 fn a_capped_turn_refuses_to_saturate_the_clock_with_its_capped_charge() {
1245 let config = Config {
1246 scheduler_turn_cost: std::num::NonZeroU64::new(crate::config::MAX_SCHEDULER_TURN_COST),
1247 epoch: "2554-07-21T23:34:33Z".parse().unwrap(),
1248 ..Config::default()
1249 };
1250 let mut time = GlobalTime::new(&config);
1251 let headroom = u64::MAX - time.as_nanos().as_nanos();
1252 time.add_capped_scheduler_time(headroom).unwrap();
1253 let end = time.as_nanos();
1254 assert_eq!(end, LogicalTime::MAX);
1255 assert_eq!(
1256 time.add_capped_scheduler_time(5),
1257 Err(ClockExhausted {
1258 charge: 5,
1259 total: end,
1260 })
1261 );
1262 assert_eq!(time.as_nanos(), end, "a refused turn adds nothing");
1263 }
1264
1265 #[test]
1268 fn the_default_turn_charge_still_saturates() {
1269 let config = Config {
1270 epoch: "2554-07-21T23:34:33Z".parse().unwrap(),
1271 ..Config::default()
1272 };
1273 let mut time = GlobalTime::new(&config);
1274 for _ in 0..2_000 {
1275 time.add_scheduler_time().unwrap();
1276 }
1277 assert_eq!(time.as_nanos(), LogicalTime::MAX);
1278 }
1279
1280 #[test]
1281 fn exec_preserves_inherited_baselines_and_each_retired_threads_work() {
1282 let config = Config::default();
1283 let ancestor = DetTid::from_raw(3);
1284 let leader = DetTid::from_raw(4);
1285 let worker = DetTid::from_raw(5);
1286 let child_after_exec = DetTid::from_raw(6);
1287 let mut time = GlobalTime::new(&config);
1288 let start = time.as_nanos();
1289 let mut ancestor_clock = DetTime::new(&config);
1290 ancestor_clock.advance_to(start + LogicalTime::from_nanos(1_000));
1291 publish(&mut time, ancestor, &ancestor_clock);
1292 let mut leader_clock = ancestor_clock.clone_for_child();
1293 leader_clock.advance_to(start + LogicalTime::from_nanos(1_100));
1294 publish(&mut time, leader, &leader_clock);
1295 let mut worker_clock = leader_clock.clone_for_child();
1296 worker_clock.advance_to(start + LogicalTime::from_nanos(1_350));
1297 publish(&mut time, worker, &worker_clock);
1298 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_350));
1299
1300 time.reassign_thread(worker, leader);
1301 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_350));
1302 assert_eq!(time.threads_time(leader), worker_clock.as_nanos());
1303 assert!(!time.contains_thread(worker));
1304
1305 let mut reloaded = DetTime::new(&config);
1309 reloaded.advance_to(time.threads_time(leader));
1310 assert_eq!(reloaded.inherited_nanos(), LogicalTime::ZERO);
1311 publish(&mut time, leader, &reloaded);
1312 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_350));
1313 reloaded.advance_to(reloaded.as_nanos() + LogicalTime::from_nanos(1));
1314 publish(&mut time, leader, &reloaded);
1315 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_351));
1316 time.reassign_thread(leader, leader);
1318 let mut leader_reload = DetTime::new(&config);
1319 leader_reload.advance_to(time.threads_time(leader));
1320 publish(&mut time, leader, &leader_reload);
1321 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_351));
1322 let mut after_exec = reloaded.clone_for_child();
1323 publish(&mut time, child_after_exec, &after_exec);
1324 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_351));
1325 after_exec.advance_to(after_exec.as_nanos() + LogicalTime::from_nanos(1));
1326 publish(&mut time, child_after_exec, &after_exec);
1327 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_352));
1328 }
1329
1330 #[test]
1331 fn inherited_clock_survives_rpc_serialization_and_legacy_json_defaults() {
1332 let mut parent = DetTime::zero();
1333 parent.add_syscall_with_cost(13);
1334 let child = parent.clone_for_child();
1335 let wire = bincode::serde::encode_to_vec(
1338 (child.clone(), 0x1234_5678_u64),
1339 bincode::config::legacy(),
1340 )
1341 .unwrap();
1342 let ((restored, following), consumed): ((DetTime, u64), usize) =
1343 bincode::serde::decode_from_slice(&wire, bincode::config::legacy()).unwrap();
1344 assert_eq!(consumed, wire.len());
1345 assert_eq!(following, 0x1234_5678);
1346 assert_eq!(restored.as_nanos(), child.as_nanos());
1347 assert_eq!(restored.inherited_nanos(), LogicalTime::from_nanos(13));
1348
1349 let mut legacy = serde_json::to_value(parent.clone()).unwrap();
1350 legacy.as_object_mut().unwrap().remove("inherited_nanos");
1351 let restored: DetTime = serde_json::from_value(legacy).unwrap();
1352 assert_eq!(restored.as_nanos(), parent.as_nanos());
1353 assert_eq!(restored.inherited_nanos(), LogicalTime::ZERO);
1354 }
1355
1356 #[test]
1357 #[should_panic(expected = "beyond its local duration")]
1358 fn inherited_baseline_cannot_exceed_the_first_local_duration() {
1359 let config = Config::default();
1360 let mut time = GlobalTime::new(&config);
1361 time.update_global_time(
1362 DetTid::from_raw(3),
1363 time.as_nanos(),
1364 LogicalTime::from_nanos(1),
1365 );
1366 }
1367
1368 #[test]
1369 fn exec_reassigns_survivor_clock_without_losing_aggregate_time() {
1370 let config = Config::default();
1371 let mut time = GlobalTime::new(&config);
1372 let leader = DetTid::from_raw(17);
1373 let worker = DetTid::from_raw(18);
1374 let start = DetTime::new(&config).as_nanos();
1375 let leader_time = start + LogicalTime::from_nanos(100);
1376 let worker_time = start + LogicalTime::from_nanos(250);
1377 time.update_global_time(leader, leader_time, LogicalTime::ZERO);
1378 time.update_global_time(worker, worker_time, LogicalTime::ZERO);
1379 let total_before = time.as_nanos();
1380
1381 time.reassign_thread(worker, leader);
1382
1383 assert_eq!(time.as_nanos(), total_before);
1384 assert_eq!(time.threads_time(leader), worker_time);
1385 assert!(time.contains_thread(leader));
1386 assert!(!time.contains_thread(worker));
1387 }
1388
1389 #[test]
1394 fn a_reused_thread_id_starts_its_own_clock_and_keeps_the_old_work() {
1395 let config = Config::default();
1396 let mut time = GlobalTime::new(&config);
1397 let start = time.as_nanos();
1398 let parent = DetTid::from_raw(17);
1399 let reused = DetTid::from_raw(18);
1400 let mut parent_clock = DetTime::new(&config);
1401 parent_clock.add_syscall_with_cost(100);
1402 publish(&mut time, parent, &parent_clock);
1403 let mut first_holder = parent_clock.clone_for_child();
1404 first_holder.add_syscall_with_cost(50);
1405 publish(&mut time, reused, &first_holder);
1406 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(150));
1407
1408 time.retire_thread(reused);
1409 assert!(!time.contains_thread(reused));
1410 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(150));
1411
1412 parent_clock.add_syscall_with_cost(1_000);
1413 publish(&mut time, parent, &parent_clock);
1414 let mut second_holder = parent_clock.clone_for_child();
1415 publish(&mut time, reused, &second_holder);
1416 assert_eq!(time.threads_time(reused), second_holder.as_nanos());
1417 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_150));
1418 second_holder.add_syscall_with_cost(7);
1419 publish(&mut time, reused, &second_holder);
1420 assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_157));
1421 }
1422}