Skip to main content

detcore_model/
time.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use 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
26// Time conversion constants from https://doc.rust-lang.org/stable/src/core/time.rs.html#26-30
27const 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
33// TODO: make all of these integral types to rule out fractional values.
34
35/// Default virtual nanoseconds elapsed for callers that do not provide a syscall-specific cost.
36pub const NANOS_PER_SYSCALL: f64 = 10000.0;
37
38/// Virtual nanoseconds elapsed per Retired Conditional Branch.
39pub const NANOS_PER_RCB: f64 = 10.0;
40
41// AUTONOMOUS-BOT-IMPLEMENTED
42// TODO-HUMAN-REVIEW(PR-1151)
43/// Fixed-point scale used for deterministic per-thread RCB time multipliers.
44/// Q32 keeps accumulation independent of how a backend batches RCB updates.
45const RCB_TIME_MULTIPLIER_SCALE: u64 = 1_u64 << 32;
46
47// AUTONOMOUS-BOT-IMPLEMENTED
48// TODO-HUMAN-REVIEW(PR-1151)
49/// A positive Q32 multiplier for converting RCB progress into virtual time.
50#[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    /// The identity multiplier.
66    pub const ONE: Self = Self(RCB_TIME_MULTIPLIER_SCALE);
67
68    /// Largest representable multiplier.
69    pub const MAX: f64 = u64::MAX as f64 / RCB_TIME_MULTIPLIER_SCALE as f64;
70
71    /// Quantize a finite positive multiplier to deterministic Q32 units.
72    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    /// Convert the fixed-point value back to a floating-point multiplier.
79    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
94/// Virtual nanoseconds elapsed per nondeterministic instruction other than system calls.
95pub const NANOS_PER_NONDET_INSTR: f64 = 25.0;
96
97/// Virtual nanoseconds elapsed per step of the scheduler.
98pub const NANOS_PER_SCHED: f64 = 500_000.0;
99
100// TODO: should map addresses to physical addresses.
101
102// Deterministic Time:
103//--------------------------------------------------------------------------------
104
105/// Represents an absolute point in time in nanoseconds.
106/// Parts of this API are largely inspired by `std::time::Duration`.
107/// This could go to 128 bits if we need more than ~585 years of nanosecond precision.
108#[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
123// TODO: replace this with a wrapper around Duration, and change places that currently use Duration
124// but should use LogicalDuration.
125pub type LogicalDuration = LogicalTime;
126
127impl LogicalTime {
128    /// 0 integer nanoseconds.
129    pub const ZERO: LogicalTime = LogicalTime(0);
130    /// The maximum representable integer nanoseconds.
131    pub const MAX: LogicalTime = LogicalTime(u64::MAX);
132
133    /// The sentinel deadline for a wait that has *no* deadline.
134    ///
135    /// A `pause(2)` blocks until a signal arrives and can never time out, so it
136    /// registers this value rather than a real deadline. The saturating `Add`
137    /// impls below also land here when a guest arms an absurdly far-future timer
138    /// (issue #219), which is the same situation: a deadline that can never be
139    /// reached while virtual time remains representable.
140    ///
141    /// Callers that fast-forward virtual time to a pending deadline must check
142    /// [`LogicalTime::is_indefinite`] first. Jumping the global clock onto this
143    /// value would both destroy the continuity of virtual time (a ~584-year
144    /// step) and wake a waiter that Linux would have left blocked.
145    pub const INDEFINITE: LogicalTime = LogicalTime::MAX;
146
147    /// Is this the [`LogicalTime::INDEFINITE`] sentinel, i.e. "no deadline"?
148    pub fn is_indefinite(&self) -> bool {
149        *self == LogicalTime::INDEFINITE
150    }
151
152    /// Returns the total number of whole microseconds contained by this `LogicalTime`.
153    pub fn as_micros(&self) -> u64 {
154        self.0 / NANOS_PER_MICRO
155    }
156
157    /// Returns the total number of whole milliseconds contained by this `LogicalTime`.
158    pub fn as_millis(&self) -> u64 {
159        self.0 / NANOS_PER_MILLI
160    }
161
162    /// Returns the total number of nanoseconds contained by this `LogicalTime`.
163    pub fn as_nanos(&self) -> u64 {
164        self.0
165    }
166
167    /// Returns the total number of *whole* seconds contained by this `LogicalTime`.
168    /// The returned value does not include fractional (nanosecond) part of the duration,
169    /// which can be obtained using `subsec_nanos`.
170    pub fn as_secs(&self) -> u64 {
171        self.0 / NANOS_PER_SEC
172    }
173
174    /// Creates a new `LogicalTime` from the specified number of microseconds.
175    pub fn from_micros(micros: u64) -> Self {
176        LogicalTime(micros * NANOS_PER_MICRO)
177    }
178
179    /// Creates a new `LogicalTime` from the specified number of milliseconds.
180    pub fn from_millis(millis: u64) -> Self {
181        LogicalTime(millis * NANOS_PER_MILLI)
182    }
183
184    /// Creates a new `LogicalTime` from the specified number of nanoseconds.
185    pub fn from_nanos(nanos: u64) -> Self {
186        LogicalTime(nanos)
187    }
188
189    /// Creates a new `LogicalTime` from the specified number of nanoseconds.
190    pub fn from_big_nanos(nanos: u128) -> Self {
191        // No good solution for this until we change the internal rep to 128 bit:
192        LogicalTime(nanos as u64)
193    }
194
195    /// Creates a new `LogicalTime` from the specified number of seconds.
196    pub fn from_secs(secs: u64) -> Self {
197        LogicalTime(secs * NANOS_PER_SEC)
198    }
199
200    /// Returns the fractional part of this `LogicalTime`, in microseconds.
201    /// This method does not return the length of the duration when represented by microseconds. The returned number always represents
202    /// a fractional portion of a second (i.e., it is less than one million).
203    pub fn subsec_micros(&self) -> u32 {
204        ((self.0 / NANOS_PER_MICRO) % MICROS_PER_SEC) as u32
205    }
206
207    /// Returns the fractional part of this `LogicalTime`, in milliseconds.
208    /// This method does not return the length of the duration when represented by milliseconds. The returned number always represents
209    /// a fractional portion of a second (i.e., it is less than one thousand).
210    pub fn subsec_millis(&self) -> u32 {
211        ((self.0 / NANOS_PER_MILLI) % MILLIS_PER_SEC) as u32
212    }
213
214    /// Returns the fractional part of this `LogicalTime`, in nanoseconds.
215    /// This method does not return the length of the duration when represented by nanoseconds. The returned number always represents
216    /// a fractional portion of a second (i.e., it is less than one billion).
217    pub fn subsec_nanos(&self) -> u32 {
218        (self.0 % NANOS_PER_SEC) as u32
219    }
220
221    /// Convert a number of Retired Conditional Branches (RCBs) to Nanoseconds
222    pub fn from_rcbs(n: u64) -> Self {
223        LogicalTime((n as f64 * NANOS_PER_RCB) as u64)
224    }
225
226    /// Inverse of from_rcbs.  Non-injective, as it loses information, truncating to a
227    /// coarser grained unit of time.
228    pub fn into_rcbs(self) -> u64 {
229        (self.0 as f64 / NANOS_PER_RCB) as u64
230    }
231
232    /// Convert a virtual duration to RCBs after applying a logical clock multiplier.
233    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    /// Test if the quantity is zero nanoseconds.
239    pub fn is_zero(&self) -> bool {
240        self.0 == 0
241    }
242
243    /// Measure the duration of the time interval since a previous time.
244    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        // Start with the raw characters for printed u64:
258        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                    // Could also consider \u{2009} thin space here, but it prints fixed
267                    // width in most terminals anyway:
268                    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        // Saturate at LogicalTime::MAX rather than panicking on overflow: a
287        // far-future logical time is clamped to "the end of time".
288        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        // A `Duration` can hold more nanoseconds than fit in u64, and the sum
296        // can exceed u64::MAX (e.g. Java arms a far-future timer, issue #219).
297        // Saturate the u128->u64 conversion and the addition instead of
298        // overflowing/truncating.
299        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        // Saturate both the u128->u64 clamp and the addition.
308        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    // A saturated far-future deadline (issue #219) lands on the same sentinel,
367    // and is equally unreachable, so the scheduler must treat it the same way.
368    assert!((LogicalTime(u64::MAX - 10) + LogicalTime::from_secs(1)).is_indefinite());
369}
370
371#[test]
372fn add_saturates_instead_of_overflowing() {
373    // Regression for issue #219: Java arms a far-future timer whose deadline
374    // overflows u64. All three `Add` impls must saturate at LogicalTime::MAX
375    // rather than panic.
376    let near_max = LogicalTime(u64::MAX - 10);
377
378    // Add<LogicalTime>
379    assert_eq!(near_max + LogicalTime::from_secs(1), LogicalTime::MAX);
380
381    // Add<Duration>, including a Duration whose nanos exceed u64::MAX.
382    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    // Add<u128>, including an rhs larger than u64::MAX.
389    assert_eq!(near_max + 100u128, LogicalTime::MAX);
390    assert_eq!(
391        LogicalTime::ZERO + (u128::from(u64::MAX) + 5),
392        LogicalTime::MAX
393    );
394
395    // A non-overflowing add still produces the exact sum.
396    assert_eq!(
397        LogicalTime::from_secs(2) + Duration::from_secs(3),
398        LogicalTime::from_secs(5)
399    );
400}
401
402/// The same basic type alias as nanoseconds. Just for clarity/readability.
403pub type Microseconds = u64;
404
405/// A determinstic notion of time, measuring progress of one thread.
406///
407/// It is based on counting syscalls and conditionals.  Ideally it would count
408/// instructions, but that is not possible deterministically.
409///
410/// `DetTime` is a measure of the LOCAL progress of a thread. The notion of
411/// global time is defined by aggretion of multiple local times (vector
412/// clocks, as in the Kendo algorithm).
413///
414/// Here are a few relevant definitions for deterministic time:
415///
416/// **Granularity**
417///
418/// How rapidly and consistently does the clock tick with thread progress?
419/// A finer notion of deterministic time counts *more* events (ideally instructions).
420/// A coarser notion of deterministic time counts fewer events (like syscalls).
421///
422/// **Productivity**
423///
424/// In corecursion, or coinductive datatypes like streams, productivity means you can get
425/// the next result with finite work. Deterministic time can be viewed a stream of "tick
426/// events". We don't want a guest thread to be able to do an unbounded amount of work,
427/// without a tick occurring. For example, retired branches are a safe bet (any
428/// non-trivial amount of work will execute a branch), but system calls are not: a
429/// spinning thread can burn cycles forever without executing a syscall.
430#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct DetTime {
432    /// The syscalls issued by this thread.
433    syscalls: u64,
434
435    /// Accumulated syscall cost before applying `multiplier`.
436    ///
437    /// `None` preserves the uniform-cost interpretation of serialized `DetTime` values created
438    /// before syscall-specific costs were introduced.
439    #[serde(default)]
440    syscall_nanos: Option<u64>,
441
442    /// Retired conditional branches, as given "opaquely" by the reverie clock.
443    /// Technically, that these are RCBs is an implementation detail of reverie.
444    rcbs: u64,
445
446    // AUTONOMOUS-BOT-IMPLEMENTED
447    // TODO-HUMAN-REVIEW(PR-1151)
448    /// RCB progress weighted by per-thread virtual-time multipliers, in Q32 RCB units.
449    /// `None` preserves the uniform interpretation of older serialized values.
450    // This field participates in Reverie's non-self-describing bincode RPC.
451    // Never skip it during serialization: doing so would shift the following
452    // `GlobalRequest` bytes and corrupt the tuple on decode.
453    #[serde(default)]
454    weighted_rcbs: Option<u128>,
455
456    /// Number of nondeterministic instructions (rdtsc, cpuid)
457    nondet_instrs: u64,
458
459    /// Explicit virtual-time advances, such as a PMU maximum when RCB accounting is disabled.
460    #[serde(default)]
461    extra_nanos: u64,
462
463    /// Baseline amount of time to add.
464    starting_micros: Microseconds,
465
466    /// Multiplier for all time advances.
467    multiplier: f64,
468
469    /// Elapsed local time inherited when this thread was created. It remains
470    /// part of the absolute clock, but is work already charged to its ancestors.
471    /// Like the other clock fields, this must always be present in bincode RPCs.
472    #[serde(default)]
473    inherited_nanos: LogicalDuration,
474}
475
476// Don't derive Default because it would give us a 0.0 multiplier:
477impl 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
495/// `DetTime` behaves as a totally ordered scalar.
496impl 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// implementing From<DetTime> for Timespec is not possible due to dependency graph
538#[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    /// Create an initial `DetTime` respecting a `Config`
547    pub fn new(cfg: &Config) -> Self {
548        // We inflate the amount of time everything consumes to compensate for the fact that we
549        // are ONLY counting certain events sparsely. In theory this should be based on some kind
550        // of expected value for compute-between-syscalls on average applications. (But is that
551        // even a normal distribution?)
552        let additional_multiplier = if cfg.sequentialize_threads && !cfg.use_rcb_time() {
553            500.0
554        } else {
555            // Otherwise, virtual time isn't really used for scheduling, just for
556            // metadata, so it doesn't really matter what the rate of ticking is.
557            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    /// Create a new `Dettime` which is the earliest possible.
566    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    /// Inherit an absolute clock for a newly created thread without attributing
581    /// its ancestors' work to that thread. Ordinary `Clone` still copies an
582    /// existing clock, including its original inheritance, for RPC snapshots.
583    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    /// Local elapsed time already accounted for by this thread's ancestors.
590    pub fn inherited_nanos(&self) -> LogicalDuration {
591        self.inherited_nanos
592    }
593
594    /// Register that another syscall has executed.
595    pub fn add_syscall(&mut self) {
596        self.add_syscall_with_cost(NANOS_PER_SYSCALL as u64);
597    }
598
599    /// Register a syscall with its unscaled virtual-time cost in nanoseconds.
600    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    /// Register that an `rdtsc` intsruction has executed.
614    pub fn add_rdtsc(&mut self) {
615        self.nondet_instrs += 1;
616    }
617
618    /// Register that an `cpuid` intsruction has executed.
619    pub fn add_cpuid(&mut self) {
620        self.nondet_instrs += 1;
621    }
622
623    /// Update internal counts using the reverie clock value.
624    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    // AUTONOMOUS-BOT-IMPLEMENTED
632    // TODO-HUMAN-REVIEW(PR-1151)
633    /// Add RCB progress using a deterministic per-thread virtual-time multiplier.
634    ///
635    /// The Q32 accumulator makes the result independent of whether a backend reports
636    /// the same RCB total in one update or several smaller updates.
637    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    /// Advance this local clock to an explicit virtual deadline.
646    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    /// Return current rcbs
653    pub fn rcbs(&self) -> u64 {
654        self.rcbs
655    }
656
657    /// Project deterministic logical time into a rough number of nanoseconds.
658    pub fn as_nanos(&self) -> LogicalTime {
659        // Note: these counts could be pre-collapsed into scalar within the DetTime
660        // representation.  But currently we leave them separate for debuggability.
661        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    /// Same as as_nanos but without the starting time.
678    pub fn without_starting(&self) -> LogicalDuration {
679        let LogicalTime(t1) = self.as_nanos();
680        LogicalTime(t1 - (self.starting_micros * 1000))
681    }
682
683    // TODO-HUMAN-REVIEW(#797): Review logical user/system CPU-time projections.
684    /// Guest-execution time that corresponds to user-space instructions.
685    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    // TODO-HUMAN-REVIEW(#797): Review logical user/system CPU-time projections.
697    /// Synthetic time charged for intercepted syscall execution.
698    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    /// Project deterministic logical time into a rough number of microseconds.
706    pub fn as_micros(&self) -> Microseconds {
707        self.as_nanos().0 / 1000
708    }
709
710    /// Set the clock multiplier
711    pub fn with_multiplier(mut self, m: f64) -> Self {
712        self.multiplier = m;
713        self
714    }
715
716    /// Project deterministic time duration from imaginary starting point of deterministic time creation
717    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    // AUTONOMOUS-BOT-IMPLEMENTED
727    // TODO-HUMAN-REVIEW(PR-1151)
728    #[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    // AUTONOMOUS-BOT-IMPLEMENTED
746    // TODO-HUMAN-REVIEW(PR-1151)
747    #[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/// Deterministic global time, combining local times.
760#[derive(Default, Debug, Clone, Serialize, Deserialize)]
761pub struct GlobalTime {
762    /// The time when the container began execution.
763    starting_nanos: LogicalTime,
764
765    /// Latest absolute local duration for each thread, excluding the epoch.
766    /// This includes inherited history for scheduler and replay consumers.
767    time_vector: HashMap<DetTid, LogicalTime>,
768
769    /// The inherited part of each local duration. It contributes no new work
770    /// to aggregate time. Missing entries represent zero for older snapshots.
771    #[serde(default)]
772    inherited_time: HashMap<DetTid, LogicalDuration>,
773
774    /// A source of central, logically external, time passage generated by the scheduler.
775    extra_time: LogicalTime,
776
777    /// This won't always be possible, but for now, since local clocks don't tick
778    /// asynchronously, it is quite straightforward to keep a count of cumulative progress.
779    total: LogicalTime,
780
781    /// Immutable. Simply copied from the Config.
782    multiplier: f64,
783}
784
785impl GlobalTime {
786    /// Create a fresh global time, respecting the `Config`.
787    pub fn new(cfg: &Config) -> Self {
788        let base = DetTime::new(cfg);
789        GlobalTime {
790            starting_nanos: LogicalTime::from_micros(micros_from_utc(&cfg.epoch)),
791            time_vector: HashMap::new(),
792            inherited_time: HashMap::new(),
793            extra_time: LogicalTime::from_nanos(0),
794            total: base.as_nanos(),
795            multiplier: cfg.clock_multiplier.unwrap_or(1.0),
796        }
797    }
798
799    /// Tick the time of a particular thread.
800    pub fn update_global_time(
801        &mut self,
802        tid: DetTid,
803        newtime: LogicalTime,
804        inherited: LogicalDuration,
805    ) {
806        if newtime < self.starting_nanos {
807            panic!(
808                "update_global_time: Cannot set thread {} time to {}, which is before start of container execution {}",
809                tid, newtime, self.starting_nanos
810            );
811        }
812
813        // TODO(T136359599): change to duration_since, and store durations in time_vector:
814        let newtime = newtime - self.starting_nanos;
815        trace!(
816            "[tid {}] ticked its global time component to {}",
817            tid, newtime,
818        );
819        if let Some(old) = self.time_vector.get_mut(&tid) {
820            if *old > newtime {
821                panic!(
822                    "Attempted to update tid {} time to {}, but was already {}",
823                    tid, newtime, old
824                );
825            }
826            // Update the cached total for efficiency:
827            let LogicalTime(diff) = newtime - *old;
828            *old = newtime;
829            // Exec may reload fresh local state. Its first response restores
830            // the absolute clock, while this existing component retains the
831            // original inherited baseline rather than charging that work again.
832            self.bump_total(Duration::from_nanos(diff));
833        } else {
834            assert!(
835                inherited <= newtime,
836                "thread {tid} inherited time {inherited} beyond its local duration {newtime}"
837            );
838            self.time_vector.insert(tid, newtime);
839            self.inherited_time.insert(tid, inherited);
840            // A child's first startup RPC reports its inherited clock before
841            // it has executed guest work. Its arrival must contribute zero,
842            // whether it precedes or follows the scheduler's time snapshot.
843            self.bump_total(Duration::from_nanos((newtime - inherited).0));
844        }
845    }
846
847    fn sanity(&self) {
848        debug_assert_eq!(self.sum_up(), self.total);
849    }
850
851    fn bump_total(&mut self, delta: Duration) {
852        self.total = self.total + delta;
853        self.sanity();
854    }
855
856    // The expensive way to get the total (internal)
857    fn sum_up(&self) -> LogicalTime {
858        let mut sum = self.starting_nanos;
859        for (tid, tm) in &self.time_vector {
860            sum = sum + (*tm - self.inherited_duration(*tid));
861        }
862        sum + self.extra_time
863    }
864
865    /// Add time that passage is not driven by the internal events within guest threads.
866    /// This is effectively used to account for "time" consumed by the scheduler, and to
867    /// ensure monotonic increase of global time while scheduling.
868    pub fn add_scheduler_time(&mut self) -> LogicalTime {
869        let delta = Duration::from_nanos((NANOS_PER_SCHED * self.multiplier) as u64);
870        self.add_extra_time(delta)
871    }
872
873    /// Update the global clock to account for time not driven by internal
874    /// within guest threads.  This is a central or external expenditure of
875    /// time, rather than a thread-internal one.
876    ///
877    /// The argument is in nanosecods and should have had any clock multiplier
878    /// applied alreday.
879    pub fn add_extra_time(&mut self, delta: Duration) -> LogicalTime {
880        self.extra_time = self.extra_time + delta;
881        // Update the cached total for efficiency:
882        self.bump_total(delta);
883        self.as_nanos()
884    }
885
886    /// Project a thread's absolute local clock, including its inherited history
887    /// and the epoch. Exec recovery and scheduler replay use this projection.
888    pub fn threads_time(&self, dtid: DetTid) -> LogicalTime {
889        self.starting_nanos + self.threads_duration(dtid)
890    }
891
892    // AUTONOMOUS-BOT-IMPLEMENTED
893    // TODO-HUMAN-REVIEW(PR-845): Review thread-presence detection for backend reconnects.
894    /// Returns whether this clock has observed work from a thread.
895    pub fn contains_thread(&self, dtid: DetTid) -> bool {
896        self.time_vector.contains_key(&dtid)
897    }
898
899    // AUTONOMOUS-BOT-IMPLEMENTED
900    // TODO-HUMAN-REVIEW(PR-1173): Review SaBRe exec clock reassignment.
901    /// Move a surviving thread's clock component to the process-leader TID
902    /// installed by Linux after a non-leader thread successfully execs.
903    ///
904    /// Work previously performed by the old leader remains part of aggregate
905    /// time, but no longer belongs to the new image's local thread clock.
906    pub fn reassign_thread(&mut self, from: DetTid, to: DetTid) {
907        if from == to {
908            return;
909        }
910
911        let survivor_time = self
912            .time_vector
913            .remove(&from)
914            .unwrap_or_else(|| panic!("cannot reassign missing thread clock {from}"));
915        let survivor_inherited = self.inherited_time.remove(&from).unwrap_or_default();
916        let retired_inherited = self.inherited_time.remove(&to).unwrap_or_default();
917        if let Some(retired_leader_time) = self.time_vector.remove(&to) {
918            self.extra_time = self.extra_time + (retired_leader_time - retired_inherited);
919        }
920        self.time_vector.insert(to, survivor_time);
921        self.inherited_time.insert(to, survivor_inherited);
922        self.sanity();
923    }
924
925    fn inherited_duration(&self, dtid: DetTid) -> LogicalDuration {
926        self.inherited_time.get(&dtid).copied().unwrap_or_default()
927    }
928
929    /// Project a thread's local duration, including inherited history but
930    /// excluding the epoch. This is the duration used by scheduler replay;
931    /// aggregate time separately excludes the inherited part.
932    pub fn threads_duration(&self, dtid: DetTid) -> LogicalDuration {
933        *self.time_vector.get(&dtid).unwrap_or_else(|| {
934            panic!(
935                "Trying to extract time for thread {}, but no entry found!",
936                dtid
937            )
938        })
939    }
940
941    /// Deterministic lower bound on the amount of work that has happened across all
942    /// threads, starting with the same epoch time as individual thread clocks.
943    ///
944    /// This roughly models something like real time if all threads were running on one core.
945    pub fn as_nanos(&self) -> LogicalTime {
946        self.total
947    }
948
949    /// Elapsed virtual time in the same precision domain as this clock's
950    /// baseline. `DetTime` stores the epoch at microsecond precision, so
951    /// subtracting the original nanosecond `DateTime` can underflow for an
952    /// otherwise empty run whose epoch has a sub-microsecond component.
953    ///
954    /// Global time never falls behind its own baseline; `None` reports that
955    /// broken invariant in every build profile instead of wrapping in release.
956    pub fn elapsed_nanos(&self) -> Option<LogicalDuration> {
957        self.total
958            .as_nanos()
959            .checked_sub(self.starting_nanos.as_nanos())
960            .map(LogicalTime::from_nanos)
961    }
962}
963
964#[cfg(test)]
965mod global_time_tests {
966    use super::*;
967
968    fn publish(time: &mut GlobalTime, tid: DetTid, clock: &DetTime) {
969        time.update_global_time(tid, clock.as_nanos(), clock.inherited_nanos());
970    }
971
972    #[test]
973    fn submicrosecond_epoch_starts_with_zero_elapsed_time() {
974        let config = Config {
975            epoch: "2026-09-24T23:52:07.760605385Z".parse().unwrap(),
976            ..Config::default()
977        };
978        let mut time = GlobalTime::new(&config);
979        assert_eq!(time.elapsed_nanos(), Some(LogicalTime::ZERO));
980        time.add_extra_time(Duration::from_nanos(1));
981        assert_eq!(time.elapsed_nanos(), Some(LogicalTime::from_nanos(1)));
982    }
983
984    #[test]
985    fn global_time_behind_its_baseline_is_refused_not_wrapped() {
986        let mut time = GlobalTime::new(&Config::default());
987        time.total = LogicalTime::from_nanos(time.starting_nanos.as_nanos() - 1);
988        assert_eq!(time.elapsed_nanos(), None);
989    }
990
991    #[test]
992    fn descendants_preserve_absolute_clocks_and_charge_only_their_own_work() {
993        let config = Config {
994            clock_multiplier: Some(2.0),
995            ..Config::default()
996        };
997        let root = DetTid::from_raw(3);
998        let child = DetTid::from_raw(4);
999        let grandchild = DetTid::from_raw(5);
1000        let mut time = GlobalTime::new(&config);
1001        let start = time.as_nanos();
1002        let mut root_clock = DetTime::new(&config);
1003        root_clock.add_syscall_with_cost(11);
1004        root_clock.add_rcbs(3);
1005        root_clock.add_rdtsc();
1006        root_clock.advance_to(root_clock.as_nanos() + LogicalTime::from_nanos(1));
1007        assert_eq!(root_clock.without_starting(), LogicalTime::from_nanos(133));
1008        publish(&mut time, root, &root_clock);
1009
1010        let mut child_clock = root_clock.clone_for_child();
1011        assert_eq!(child_clock.as_nanos(), root_clock.as_nanos());
1012        publish(&mut time, child, &child_clock);
1013        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(133));
1014        assert_eq!(time.threads_time(child), child_clock.as_nanos());
1015        child_clock.add_syscall_with_cost(7);
1016        let mut split_rcbs = child_clock.clone();
1017        split_rcbs.add_rcbs_with_multiplier(1, RcbTimeMultiplier::from_f64(0.5));
1018        split_rcbs.add_rcbs_with_multiplier(3, RcbTimeMultiplier::from_f64(0.5));
1019        child_clock.add_rcbs_with_multiplier(4, RcbTimeMultiplier::from_f64(0.5));
1020        assert_eq!(split_rcbs.as_nanos(), child_clock.as_nanos());
1021        assert_eq!(split_rcbs.inherited_nanos(), child_clock.inherited_nanos());
1022        child_clock.add_cpuid();
1023        child_clock.advance_to(child_clock.as_nanos() + LogicalTime::from_nanos(1));
1024        assert_eq!(child_clock.without_starting(), LogicalTime::from_nanos(238));
1025        let snapshot = child_clock.clone();
1026        assert_eq!(snapshot.inherited_nanos(), LogicalTime::from_nanos(133));
1027        publish(&mut time, child, &snapshot);
1028        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(238));
1029        publish(&mut time, child, &snapshot);
1030        publish(&mut time, child, &snapshot);
1031        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(238));
1032
1033        let mut grandchild_clock = child_clock.clone_for_child();
1034        assert_eq!(
1035            grandchild_clock.inherited_nanos(),
1036            LogicalTime::from_nanos(238)
1037        );
1038        publish(&mut time, grandchild, &grandchild_clock);
1039        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(238));
1040        grandchild_clock.add_syscall_with_cost(3);
1041        grandchild_clock.advance_to(grandchild_clock.as_nanos() + LogicalTime::from_nanos(1));
1042        publish(&mut time, grandchild, &grandchild_clock);
1043        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(245));
1044        assert_eq!(time.threads_time(child), child_clock.as_nanos());
1045        assert_eq!(time.threads_time(grandchild), grandchild_clock.as_nanos());
1046        assert_eq!(
1047            time.threads_duration(grandchild),
1048            LogicalTime::from_nanos(245)
1049        );
1050
1051        time.add_scheduler_time();
1052        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_000_245));
1053    }
1054
1055    #[test]
1056    fn exec_preserves_inherited_baselines_and_each_retired_threads_work() {
1057        let config = Config::default();
1058        let ancestor = DetTid::from_raw(3);
1059        let leader = DetTid::from_raw(4);
1060        let worker = DetTid::from_raw(5);
1061        let child_after_exec = DetTid::from_raw(6);
1062        let mut time = GlobalTime::new(&config);
1063        let start = time.as_nanos();
1064        let mut ancestor_clock = DetTime::new(&config);
1065        ancestor_clock.advance_to(start + LogicalTime::from_nanos(1_000));
1066        publish(&mut time, ancestor, &ancestor_clock);
1067        let mut leader_clock = ancestor_clock.clone_for_child();
1068        leader_clock.advance_to(start + LogicalTime::from_nanos(1_100));
1069        publish(&mut time, leader, &leader_clock);
1070        let mut worker_clock = leader_clock.clone_for_child();
1071        worker_clock.advance_to(start + LogicalTime::from_nanos(1_350));
1072        publish(&mut time, worker, &worker_clock);
1073        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_350));
1074
1075        time.reassign_thread(worker, leader);
1076        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_350));
1077        assert_eq!(time.threads_time(leader), worker_clock.as_nanos());
1078        assert!(!time.contains_thread(worker));
1079
1080        // An in-process backend reloads DetTime after exec and restores its
1081        // absolute clock from the RPC response. The global component must keep
1082        // the pre-exec baseline even though this fresh local field is zero.
1083        let mut reloaded = DetTime::new(&config);
1084        reloaded.advance_to(time.threads_time(leader));
1085        assert_eq!(reloaded.inherited_nanos(), LogicalTime::ZERO);
1086        publish(&mut time, leader, &reloaded);
1087        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_350));
1088        reloaded.advance_to(reloaded.as_nanos() + LogicalTime::from_nanos(1));
1089        publish(&mut time, leader, &reloaded);
1090        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_351));
1091        // A subsequent leader exec retains the same component and baseline.
1092        time.reassign_thread(leader, leader);
1093        let mut leader_reload = DetTime::new(&config);
1094        leader_reload.advance_to(time.threads_time(leader));
1095        publish(&mut time, leader, &leader_reload);
1096        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_351));
1097        let mut after_exec = reloaded.clone_for_child();
1098        publish(&mut time, child_after_exec, &after_exec);
1099        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_351));
1100        after_exec.advance_to(after_exec.as_nanos() + LogicalTime::from_nanos(1));
1101        publish(&mut time, child_after_exec, &after_exec);
1102        assert_eq!(time.as_nanos(), start + LogicalTime::from_nanos(1_352));
1103    }
1104
1105    #[test]
1106    fn inherited_clock_survives_rpc_serialization_and_legacy_json_defaults() {
1107        let mut parent = DetTime::zero();
1108        parent.add_syscall_with_cost(13);
1109        let child = parent.clone_for_child();
1110        // The following tuple field detects a skipped positional clock field,
1111        // which would otherwise consume bytes from the RPC request.
1112        let wire = bincode::serde::encode_to_vec(
1113            (child.clone(), 0x1234_5678_u64),
1114            bincode::config::legacy(),
1115        )
1116        .unwrap();
1117        let ((restored, following), consumed): ((DetTime, u64), usize) =
1118            bincode::serde::decode_from_slice(&wire, bincode::config::legacy()).unwrap();
1119        assert_eq!(consumed, wire.len());
1120        assert_eq!(following, 0x1234_5678);
1121        assert_eq!(restored.as_nanos(), child.as_nanos());
1122        assert_eq!(restored.inherited_nanos(), LogicalTime::from_nanos(13));
1123
1124        let mut legacy = serde_json::to_value(parent.clone()).unwrap();
1125        legacy.as_object_mut().unwrap().remove("inherited_nanos");
1126        let restored: DetTime = serde_json::from_value(legacy).unwrap();
1127        assert_eq!(restored.as_nanos(), parent.as_nanos());
1128        assert_eq!(restored.inherited_nanos(), LogicalTime::ZERO);
1129    }
1130
1131    #[test]
1132    #[should_panic(expected = "beyond its local duration")]
1133    fn inherited_baseline_cannot_exceed_the_first_local_duration() {
1134        let config = Config::default();
1135        let mut time = GlobalTime::new(&config);
1136        time.update_global_time(
1137            DetTid::from_raw(3),
1138            time.as_nanos(),
1139            LogicalTime::from_nanos(1),
1140        );
1141    }
1142
1143    #[test]
1144    fn exec_reassigns_survivor_clock_without_losing_aggregate_time() {
1145        let config = Config::default();
1146        let mut time = GlobalTime::new(&config);
1147        let leader = DetTid::from_raw(17);
1148        let worker = DetTid::from_raw(18);
1149        let start = DetTime::new(&config).as_nanos();
1150        let leader_time = start + LogicalTime::from_nanos(100);
1151        let worker_time = start + LogicalTime::from_nanos(250);
1152        time.update_global_time(leader, leader_time, LogicalTime::ZERO);
1153        time.update_global_time(worker, worker_time, LogicalTime::ZERO);
1154        let total_before = time.as_nanos();
1155
1156        time.reassign_thread(worker, leader);
1157
1158        assert_eq!(time.as_nanos(), total_before);
1159        assert_eq!(time.threads_time(leader), worker_time);
1160        assert!(time.contains_thread(leader));
1161        assert!(!time.contains_thread(worker));
1162    }
1163}