Coverage Report

Created: 2026-11-13 23:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-sim/src/trace.rs
Line
Count
Source
1
// Copyright 2026 Trace Machina, Inc. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
//! Everything that crosses a boundary the harness owns, in the order it
16
//! happened: the client streams, the worker connections, the simulated
17
//! executions, the faults and the clock. Names are the scenario's own
18
//! (`op`, `action`, `worker`), never the ids the servers mint, so two runs
19
//! of one scenario can be compared line by line.
20
21
use core::fmt;
22
use std::sync::Arc;
23
24
use parking_lot::Mutex;
25
26
use crate::clock::SimClock;
27
28
/// The stage a client saw on its `Execute` or `WaitExecution` stream.
29
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
30
pub enum Stage {
31
    Unknown,
32
    CacheCheck,
33
    Queued,
34
    Executing,
35
    Completed,
36
}
37
38
impl Stage {
39
40.9k
    pub(crate) const fn from_proto(value: i32) -> Self {
40
40.9k
        match value {
41
0
            1 => Self::CacheCheck,
42
18.6k
            2 => Self::Queued,
43
12.2k
            3 => Self::Executing,
44
9.96k
            4 => Self::Completed,
45
0
            _ => Self::Unknown,
46
        }
47
40.9k
    }
48
}
49
50
/// How a client's operation ended.
51
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
52
pub enum Outcome {
53
    /// The action ran and exited with this code.
54
    Exited(i32),
55
    /// The operation failed with this gRPC code.
56
    Failed(i32),
57
}
58
59
#[derive(Clone, Debug, PartialEq, Eq)]
60
pub enum Event {
61
    /// A client called `Execute` for `action`.
62
    Submit {
63
        op: String,
64
        action: String,
65
        priority: i32,
66
    },
67
    ClientStage {
68
        op: String,
69
        stage: Stage,
70
    },
71
    ClientDone {
72
        op: String,
73
        outcome: Outcome,
74
    },
75
    /// The client dropped its stream for good.
76
    ClientGone {
77
        op: String,
78
    },
79
    /// The stream ended or failed before the operation was done; the
80
    /// client re-subscribes with `WaitExecution`, as Bazel does.
81
    ClientReconnect {
82
        op: String,
83
        code: i32,
84
    },
85
    /// `WaitExecution` did not know the operation; the client calls
86
    /// `Execute` again.
87
    ClientResubmit {
88
        op: String,
89
    },
90
    /// The scheduler sent `StartExecute`. `key` is the scheduler's
91
    /// operation id, which a requeue keeps and a new execution does not.
92
    Dispatch {
93
        worker: String,
94
        action: String,
95
        key: String,
96
    },
97
    Accept {
98
        worker: String,
99
        action: String,
100
    },
101
    Decline {
102
        worker: String,
103
        action: String,
104
    },
105
    /// The worker released the slot (`ExecuteComplete`).
106
    Complete {
107
        worker: String,
108
        action: String,
109
    },
110
    /// The worker reported a result: `ok` is a completed stage, otherwise
111
    /// an internal error.
112
    Result {
113
        worker: String,
114
        action: String,
115
        ok: bool,
116
    },
117
    /// The scheduler asked the worker to kill the operation.
118
    Kill {
119
        worker: String,
120
        action: String,
121
    },
122
    ExecStart {
123
        worker: String,
124
        action: String,
125
        key: String,
126
    },
127
    ExecEnd {
128
        worker: String,
129
        action: String,
130
        key: String,
131
        killed: bool,
132
    },
133
    /// The worker's `kill_all`, after it lost its connection.
134
    KillAll {
135
        worker: String,
136
    },
137
    WorkerStarted {
138
        worker: String,
139
    },
140
    /// The scheduler answered the connection; `dispatch_ack` says the worker
141
    /// acknowledges dispatches.
142
    WorkerConnected {
143
        worker: String,
144
        dispatch_ack: bool,
145
    },
146
    /// The scheduler's stream to the worker ended (eviction, restart,
147
    /// a cut link).
148
    WorkerDisconnected {
149
        worker: String,
150
    },
151
    /// The worker process is gone.
152
    WorkerStopped {
153
        worker: String,
154
    },
155
    Fault {
156
        what: String,
157
    },
158
    SchedulerUp,
159
    SchedulerDown,
160
    /// What the clock reads jumped forward; timers did not move.
161
    ClockJump {
162
        ms: u64,
163
    },
164
    /// What the scheduler and the workers held at the end: operations the
165
    /// scheduler has executing, and executions the workers are running.
166
    Ledger {
167
        scheduler_executing: usize,
168
        workers_running: usize,
169
    },
170
    Settled {
171
        quiet: bool,
172
    },
173
}
174
175
impl fmt::Display for Event {
176
2.35k
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177
2.35k
        match self {
178
            Self::Submit {
179
141
                op,
180
141
                action,
181
141
                priority,
182
141
            } => write!(f, "submit {op} {action} p{priority}"),
183
583
            Self::ClientStage { op, stage } => write!(f, "stage {op} {stage:?}"),
184
141
            Self::ClientDone { op, outcome } => write!(f, "done {op} {outcome:?}"),
185
0
            Self::ClientGone { op } => write!(f, "gone {op}"),
186
0
            Self::ClientReconnect { op, code } => write!(f, "reconnect {op} code {code}"),
187
0
            Self::ClientResubmit { op } => write!(f, "resubmit {op}"),
188
            Self::Dispatch {
189
221
                worker,
190
221
                action,
191
221
                key,
192
221
            } => write!(f, "dispatch {worker} {action} {key}"),
193
221
            Self::Accept { worker, action } => write!(f, "accept {worker} {action}"),
194
0
            Self::Decline { worker, action } => write!(f, "decline {worker} {action}"),
195
141
            Self::Complete { worker, action } => write!(f, "complete {worker} {action}"),
196
141
            Self::Result { worker, action, ok } => write!(f, "result {worker} {action} ok={ok}"),
197
0
            Self::Kill { worker, action } => write!(f, "kill {worker} {action}"),
198
            Self::ExecStart {
199
221
                worker,
200
221
                action,
201
221
                key,
202
221
            } => write!(f, "exec-start {worker} {action} {key}"),
203
            Self::ExecEnd {
204
221
                worker,
205
221
                action,
206
221
                key,
207
221
                killed,
208
221
            } => write!(f, "exec-end {worker} {action} {key} killed={killed}"),
209
20
            Self::KillAll { worker } => write!(f, "kill-all {worker}"),
210
61
            Self::WorkerStarted { worker } => write!(f, "worker-started {worker}"),
211
            Self::WorkerConnected {
212
81
                worker,
213
81
                dispatch_ack,
214
81
            } => write!(f, "worker-connected {worker} ack={dispatch_ack}"),
215
40
            Self::WorkerDisconnected { worker } => write!(f, "worker-disconnected {worker}"),
216
20
            Self::WorkerStopped { worker } => write!(f, "worker-stopped {worker}"),
217
20
            Self::Fault { what } => write!(f, "fault {what}"),
218
21
            Self::SchedulerUp => write!(f, "scheduler-up"),
219
21
            Self::SchedulerDown => write!(f, "scheduler-down"),
220
0
            Self::ClockJump { ms } => write!(f, "clock-jump +{ms}ms"),
221
            Self::Ledger {
222
21
                scheduler_executing,
223
21
                workers_running,
224
21
            } => write!(
225
21
                f,
226
                "ledger scheduler={scheduler_executing} workers={workers_running}"
227
            ),
228
21
            Self::Settled { quiet } => write!(f, "settled quiet={quiet}"),
229
        }
230
2.35k
    }
231
}
232
233
#[derive(Clone, Debug, PartialEq, Eq)]
234
pub struct Record {
235
    pub at_ms: u64,
236
    pub event: Event,
237
}
238
239
impl fmt::Display for Record {
240
1.18k
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241
1.18k
        write!(f, "{:>9} {}", self.at_ms, self.event)
242
1.18k
    }
243
}
244
245
/// The shared trace. Cheap to clone; every component holds one.
246
#[derive(Clone, Debug, Default)]
247
pub struct Trace(Arc<Mutex<Vec<Record>>>);
248
249
impl Trace {
250
134k
    pub fn record(&self, event: Event) {
251
134k
        self.0.lock().push(Record {
252
134k
            at_ms: SimClock::now_ms(),
253
134k
            event,
254
134k
        });
255
134k
    }
256
257
364
    pub fn records(&self) -> Vec<Record> {
258
364
        self.0.lock().clone()
259
364
    }
260
261
30.7k
    pub fn len(&self) -> usize {
262
30.7k
        self.0.lock().len()
263
30.7k
    }
264
265
0
    pub fn is_empty(&self) -> bool {
266
0
        self.0.lock().is_empty()
267
0
    }
268
}
269
270
/// The trace as text, one record per line.
271
11
pub fn render(records: &[Record]) -> String {
272
11
    let mut out = String::new();
273
1.18k
    for record in 
records11
{
274
1.18k
        out.push_str(&record.to_string());
275
1.18k
        out.push('\n');
276
1.18k
    }
277
11
    out
278
11
}