Skip to main content

kui_trace/
model.rs

1//! A recording read whole: its names, its records, and the timeline they
2//! make — frames as intervals, each with the gap before it, and every
3//! other record sorted by time — which the devtools' Perf tab, `kui-perf`,
4//! the adapters and `diff` all read.
5
6use std::collections::HashMap;
7
8use crate::codec::{Encoder, Error, Reader, header};
9use crate::record::{LoopEvent, NameId, Record, Session, Span, Track};
10
11/// How far past the names read so far a name's id may land: a writer
12/// hands ids out in order, so any gap is a record lost, and one this wide
13/// is a corrupt file.
14const MAX_NAME_GAP: usize = 1 << 16;
15
16/// Names to ids, as a writer hands them out: each string once.
17#[derive(Clone, Debug, Default)]
18pub struct Names {
19    map: HashMap<String, NameId>,
20    list: Vec<String>,
21}
22
23impl Names {
24    pub fn new() -> Self {
25        Names {
26            map: HashMap::new(),
27            list: vec![String::new()],
28        }
29    }
30
31    /// `s`'s id, and whether it is new — a [`Record::Name`] to write.
32    pub fn get(&mut self, s: &str) -> (NameId, bool) {
33        if s.is_empty() {
34            return (0, false);
35        }
36        if let Some(&id) = self.map.get(s) {
37            return (id, false);
38        }
39        if self.list.is_empty() {
40            self.list.push(String::new());
41        }
42        let id = self.list.len() as NameId;
43        self.list.push(s.to_string());
44        self.map.insert(s.to_string(), id);
45        (id, true)
46    }
47
48    /// The string an id stands for; the empty string for one never given.
49    pub fn text(&self, id: NameId) -> &str {
50        self.list.get(id as usize).map_or("", String::as_str)
51    }
52
53    /// Every name, by id; the first is the empty string.
54    pub fn all(&self) -> &[String] {
55        &self.list
56    }
57
58    fn set(&mut self, id: NameId, text: String) {
59        let i = id as usize;
60        // A writer hands ids out one after another, so a name far past the
61        // table is a corrupt file, not a name: kept, it would make room
62        // for every id before it — one 20-byte record asked for 94 GB
63        // (the `trace` fuzz target). Read as a name never given.
64        if i > self.list.len() + MAX_NAME_GAP {
65            return;
66        }
67        if self.list.len() <= i {
68            self.list.resize(i + 1, String::new());
69        }
70        self.map.insert(text.clone(), id);
71        self.list[i] = text;
72    }
73}
74
75/// A recording in memory: what it was made of, its names and tracks, and
76/// the rest of its records in the order written.
77#[derive(Clone, Debug, Default)]
78pub struct Trace {
79    pub session: Option<Session>,
80    pub names: Names,
81    pub tracks: Vec<(Track, NameId)>,
82    pub records: Vec<Record>,
83}
84
85impl Trace {
86    pub fn new() -> Self {
87        Trace {
88            names: Names::new(),
89            ..Default::default()
90        }
91    }
92
93    /// The recording these bytes hold, read up to where they stop.
94    pub fn from_bytes(b: &[u8]) -> Result<Trace, Error> {
95        let mut t = Trace::new();
96        for r in Reader::new(b)? {
97            t.push(r);
98        }
99        Ok(t)
100    }
101
102    /// Takes one record as a reader hands it: names and tracks into their
103    /// tables, the session aside, the rest kept.
104    pub fn push(&mut self, r: Record) {
105        match r {
106            Record::Session(s) => self.session = Some(s),
107            Record::Name { id, text } => self.names.set(id, text),
108            Record::Track { id, name } => {
109                if !self.tracks.iter().any(|(t, _)| *t == id) {
110                    self.tracks.push((id, name));
111                }
112            }
113            other => self.records.push(other),
114        }
115    }
116
117    /// The records of frames `first..=last` of `tl` (this recording's
118    /// timeline) and their gaps, up to the last one's present: what a range
119    /// dragged across the bars reads, the session, names and tracks kept.
120    pub fn slice(&self, tl: &Timeline, first: usize, last: usize) -> Trace {
121        let f0 = &tl.frames[first];
122        let f1 = &tl.frames[last];
123        let from = f0.gap_from;
124        let to = f1
125            .present
126            .map_or(f1.end_or_begin(), |p| p.t.max(f1.end_or_begin()))
127            + 1;
128        let frames: std::collections::HashSet<u64> =
129            tl.frames[first..=last].iter().map(|f| f.frame).collect();
130        let records = self
131            .records
132            .iter()
133            .filter(|r| match (r.t(), r) {
134                (Some(t), _) => t >= from && t < to,
135                (None, Record::Holder { frame, .. }) => frames.contains(frame),
136                (None, _) => true,
137            })
138            .cloned()
139            .collect();
140        Trace {
141            session: self.session.clone(),
142            names: self.names.clone(),
143            tracks: self.tracks.clone(),
144            records,
145        }
146    }
147
148    /// A name's string.
149    pub fn name(&self, id: NameId) -> &str {
150        self.names.text(id)
151    }
152
153    /// A track's name: `main`, `loop`, or the one declared.
154    pub fn track_name(&self, t: Track) -> &str {
155        match t {
156            Track::MAIN => "main",
157            Track::LOOP => "loop",
158            t => self
159                .tracks
160                .iter()
161                .find(|(id, _)| *id == t)
162                .map_or("", |(_, n)| self.name(*n)),
163        }
164    }
165
166    /// The recording as bytes: the header, the session, every name and
167    /// track, then the records.
168    pub fn to_bytes(&self) -> Vec<u8> {
169        let mut out = header();
170        let mut enc = Encoder::new();
171        if let Some(s) = &self.session {
172            enc.encode(&Record::Session(s.clone()), &mut out);
173        }
174        for (id, text) in self.names.all().iter().enumerate().skip(1) {
175            // A gap a reader padded with the empty string is not written.
176            if text.is_empty() {
177                continue;
178            }
179            enc.encode(
180                &Record::Name {
181                    id: id as NameId,
182                    text: text.clone(),
183                },
184                &mut out,
185            );
186        }
187        for (id, name) in &self.tracks {
188            enc.encode(
189                &Record::Track {
190                    id: *id,
191                    name: *name,
192                },
193                &mut out,
194            );
195        }
196        for r in &self.records {
197            enc.encode(r, &mut out);
198        }
199        out
200    }
201
202    /// The timeline the records make.
203    pub fn timeline(&self) -> Timeline {
204        Timeline::of(&self.records)
205    }
206}
207
208/// Who held a frame.
209#[derive(Clone, Debug, PartialEq)]
210pub struct Holder {
211    pub what: NameId,
212    pub name: NameId,
213    pub node: u64,
214}
215
216/// A frame drawn: the driver's render and vsync's wait, µs.
217#[derive(Clone, Copy, Debug, PartialEq)]
218pub struct Present {
219    pub t: u64,
220    pub render: u64,
221    pub wait: u64,
222}
223
224/// One frame of the timeline.
225#[derive(Clone, Debug, PartialEq)]
226pub struct Frame {
227    pub frame: u64,
228    pub begin: u64,
229    /// When it finished; `None` for one the recording stopped inside.
230    pub end: Option<u64>,
231    /// Where its gap starts: the frame before's end, or the recording's
232    /// start.
233    pub gap_from: u64,
234    pub causes: Vec<NameId>,
235    pub holders: Vec<Holder>,
236    pub present: Option<Present>,
237    pub unchanged: Option<bool>,
238    pub nodes: u32,
239    pub counters: Vec<(NameId, i64)>,
240}
241
242impl Frame {
243    /// Its end, or its begin when it never finished.
244    pub fn end_or_begin(&self) -> u64 {
245        self.end.unwrap_or(self.begin)
246    }
247
248    /// The core's part: begin to end, µs.
249    pub fn core_us(&self) -> u64 {
250        self.end_or_begin().saturating_sub(self.begin)
251    }
252
253    /// Its work: the core's part and the driver's render, not vsync's
254    /// wait, µs.
255    pub fn work_us(&self) -> u64 {
256        self.core_us()
257            .saturating_add(self.present.map_or(0, |p| p.render))
258    }
259}
260
261#[derive(Clone, Copy, Debug, PartialEq)]
262pub struct Input {
263    pub t: u64,
264    pub kind: NameId,
265    pub os_t: Option<u64>,
266    pub count: u32,
267    pub until: u64,
268}
269
270#[derive(Clone, Copy, Debug, PartialEq)]
271pub struct Event {
272    pub t: u64,
273    pub kind: NameId,
274    pub node: u64,
275}
276
277#[derive(Clone, Copy, Debug, PartialEq)]
278pub struct Request {
279    pub t: u64,
280    pub why: NameId,
281    pub at: NameId,
282}
283
284#[derive(Clone, Copy, Debug, PartialEq)]
285pub struct Gauge {
286    pub t: u64,
287    pub name: NameId,
288    pub value: f64,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq)]
292pub struct Mark {
293    pub t: u64,
294    pub track: Track,
295    pub name: NameId,
296}
297
298/// A component placed, and how it was filled ([`Record::Placement`]).
299#[derive(Clone, Copy, Debug, PartialEq)]
300pub struct Placement {
301    pub t: u64,
302    pub node: u64,
303    pub component: NameId,
304    pub answer: NameId,
305    pub why: NameId,
306}
307
308/// Every record of a recording, placed: frames in order, everything else
309/// sorted by time.
310#[derive(Clone, Debug, Default, PartialEq)]
311pub struct Timeline {
312    pub frames: Vec<Frame>,
313    /// Sorted by start; a parent before its children at one start.
314    pub spans: Vec<Span>,
315    pub inputs: Vec<Input>,
316    pub events: Vec<Event>,
317    pub wakes: Vec<(u64, NameId)>,
318    pub requests: Vec<Request>,
319    pub loops: Vec<(u64, LoopEvent)>,
320    pub gauges: Vec<Gauge>,
321    pub marks: Vec<Mark>,
322    pub notes: Vec<(u64, String)>,
323    pub placements: Vec<Placement>,
324    /// The first and the last time any record is placed at.
325    pub start: u64,
326    pub end: u64,
327    /// Each open frame's index by its number, for the records after its
328    /// begin; and whether any record has a time yet.
329    open: HashMap<u64, usize>,
330    placed: bool,
331}
332
333/// Sorts `v` from the first of its first `old` items that one after them
334/// goes before, stably, by `key`.
335fn sort_tail<T, K: Ord>(v: &mut [T], old: usize, key: impl Fn(&T) -> K) {
336    let Some(least) = v[old..].iter().map(&key).min() else {
337        return;
338    };
339    let from = v[..old].partition_point(|x| key(x) <= least);
340    v[from..].sort_by_key(key);
341}
342
343impl Timeline {
344    pub fn of(records: &[Record]) -> Timeline {
345        let mut tl = Timeline::default();
346        tl.extend(records);
347        tl
348    }
349
350    /// Places more records, written after those it holds: what a stream
351    /// read as it arrives (backlog F249) grows its timeline by, sorting
352    /// only the end of each list the new records reach into.
353    pub fn extend(&mut self, records: &[Record]) {
354        let tl = self;
355        let was = [
356            tl.spans.len(),
357            tl.inputs.len(),
358            tl.events.len(),
359            tl.wakes.len(),
360            tl.requests.len(),
361            tl.loops.len(),
362            tl.gauges.len(),
363            tl.marks.len(),
364            tl.notes.len(),
365            tl.placements.len(),
366        ];
367        let open = &mut tl.open;
368        let (mut first, mut last) = if tl.placed {
369            (tl.start, tl.end)
370        } else {
371            (u64::MAX, 0)
372        };
373        for r in records {
374            if let Some(t) = r.t() {
375                first = first.min(t);
376                let e = match r {
377                    Record::Span(s) => s.end(),
378                    Record::Input { until, .. } => *until,
379                    _ => t,
380                };
381                last = last.max(e);
382            }
383            match r {
384                Record::FrameBegin { t, frame, causes } => {
385                    let gap_from = tl.frames.last().map_or(0, Frame::end_or_begin);
386                    open.insert(*frame, tl.frames.len());
387                    tl.frames.push(Frame {
388                        frame: *frame,
389                        begin: *t,
390                        end: None,
391                        gap_from,
392                        causes: causes.clone(),
393                        holders: Vec::new(),
394                        present: None,
395                        unchanged: None,
396                        nodes: 0,
397                        counters: Vec::new(),
398                    });
399                }
400                Record::Holder {
401                    frame,
402                    what,
403                    name,
404                    node,
405                } => {
406                    if let Some(&i) = open.get(frame) {
407                        tl.frames[i].holders.push(Holder {
408                            what: *what,
409                            name: *name,
410                            node: *node,
411                        });
412                    }
413                }
414                Record::FrameEnd {
415                    t,
416                    frame,
417                    unchanged,
418                    nodes,
419                } => {
420                    if let Some(&i) = open.get(frame) {
421                        let f = &mut tl.frames[i];
422                        f.end = Some(*t);
423                        f.unchanged = *unchanged;
424                        f.nodes = *nodes;
425                    }
426                }
427                Record::Present {
428                    t,
429                    frame,
430                    render,
431                    wait,
432                } => {
433                    if let Some(&i) = open.get(frame) {
434                        tl.frames[i].present = Some(Present {
435                            t: *t,
436                            render: *render,
437                            wait: *wait,
438                        });
439                    }
440                }
441                Record::Counter {
442                    frame, name, value, ..
443                } => {
444                    if let Some(&i) = open.get(frame) {
445                        tl.frames[i].counters.push((*name, *value));
446                    }
447                }
448                Record::Span(s) => tl.spans.push(s.clone()),
449                Record::Input {
450                    t,
451                    kind,
452                    os_t,
453                    count,
454                    until,
455                } => tl.inputs.push(Input {
456                    t: *t,
457                    kind: *kind,
458                    os_t: *os_t,
459                    count: *count,
460                    until: *until,
461                }),
462                Record::Event { t, kind, node } => tl.events.push(Event {
463                    t: *t,
464                    kind: *kind,
465                    node: *node,
466                }),
467                Record::Wake { t, name } => tl.wakes.push((*t, *name)),
468                Record::Request { t, why, at } => tl.requests.push(Request {
469                    t: *t,
470                    why: *why,
471                    at: *at,
472                }),
473                Record::Loop { t, event } => tl.loops.push((*t, *event)),
474                Record::Gauge { t, name, value } => tl.gauges.push(Gauge {
475                    t: *t,
476                    name: *name,
477                    value: *value,
478                }),
479                Record::Mark { t, track, name } => tl.marks.push(Mark {
480                    t: *t,
481                    track: *track,
482                    name: *name,
483                }),
484                Record::Note { t, text } => tl.notes.push((*t, text.clone())),
485                Record::Placement {
486                    t,
487                    node,
488                    component,
489                    answer,
490                    why,
491                } => tl.placements.push(Placement {
492                    t: *t,
493                    node: *node,
494                    component: *component,
495                    answer: *answer,
496                    why: *why,
497                }),
498                Record::Session(_) | Record::Name { .. } | Record::Track { .. } => {}
499            }
500        }
501        // Each list sorted again from the first of what was there that a
502        // new record goes before: for records written in time, the new
503        // ones alone, and a span closed late reaching back to its start.
504        sort_tail(&mut tl.spans, was[0], |s| {
505            (s.start, std::cmp::Reverse(s.dur))
506        });
507        sort_tail(&mut tl.inputs, was[1], |i| i.t);
508        sort_tail(&mut tl.events, was[2], |e| e.t);
509        sort_tail(&mut tl.wakes, was[3], |w| w.0);
510        sort_tail(&mut tl.requests, was[4], |r| r.t);
511        sort_tail(&mut tl.loops, was[5], |l| l.0);
512        sort_tail(&mut tl.gauges, was[6], |g| g.t);
513        sort_tail(&mut tl.marks, was[7], |m| m.t);
514        sort_tail(&mut tl.notes, was[8], |n| n.0);
515        sort_tail(&mut tl.placements, was[9], |p| p.t);
516        if first != u64::MAX {
517            tl.start = first;
518            tl.end = last;
519            tl.placed = true;
520        }
521    }
522
523    /// The spans that start in `[from, to)`.
524    pub fn spans_in(&self, from: u64, to: u64) -> &[Span] {
525        if from >= to {
526            return &[];
527        }
528        let a = self.spans.partition_point(|s| s.start < from);
529        let b = self.spans.partition_point(|s| s.start < to);
530        &self.spans[a..b]
531    }
532
533    /// The inputs that arrived in `[from, to)`.
534    pub fn inputs_in(&self, from: u64, to: u64) -> &[Input] {
535        if from >= to {
536            return &[];
537        }
538        let a = self.inputs.partition_point(|i| i.t < from);
539        let b = self.inputs.partition_point(|i| i.t < to);
540        &self.inputs[a..b]
541    }
542
543    /// The events handed out in `[from, to)`.
544    pub fn events_in(&self, from: u64, to: u64) -> &[Event] {
545        if from >= to {
546            return &[];
547        }
548        let a = self.events.partition_point(|e| e.t < from);
549        let b = self.events.partition_point(|e| e.t < to);
550        &self.events[a..b]
551    }
552
553    /// The loop's records in `[from, to)`.
554    pub fn loops_in(&self, from: u64, to: u64) -> &[(u64, LoopEvent)] {
555        if from >= to {
556            return &[];
557        }
558        let a = self.loops.partition_point(|l| l.0 < from);
559        let b = self.loops.partition_point(|l| l.0 < to);
560        &self.loops[a..b]
561    }
562
563    /// The wakes in `[from, to)`.
564    pub fn wakes_in(&self, from: u64, to: u64) -> &[(u64, NameId)] {
565        if from >= to {
566            return &[];
567        }
568        let a = self.wakes.partition_point(|w| w.0 < from);
569        let b = self.wakes.partition_point(|w| w.0 < to);
570        &self.wakes[a..b]
571    }
572
573    /// The marks in `[from, to)`.
574    pub fn marks_in(&self, from: u64, to: u64) -> &[Mark] {
575        if from >= to {
576            return &[];
577        }
578        let a = self.marks.partition_point(|m| m.t < from);
579        let b = self.marks.partition_point(|m| m.t < to);
580        &self.marks[a..b]
581    }
582
583    /// The notes written in `[from, to)`.
584    pub fn notes_in(&self, from: u64, to: u64) -> &[(u64, String)] {
585        if from >= to {
586            return &[];
587        }
588        let a = self.notes.partition_point(|n| n.0 < from);
589        let b = self.notes.partition_point(|n| n.0 < to);
590        &self.notes[a..b]
591    }
592
593    /// The components placed in `[from, to)`.
594    pub fn placements_in(&self, from: u64, to: u64) -> &[Placement] {
595        if from >= to {
596            return &[];
597        }
598        let a = self.placements.partition_point(|p| p.t < from);
599        let b = self.placements.partition_point(|p| p.t < to);
600        &self.placements[a..b]
601    }
602
603    /// The requests in `[from, to)`.
604    pub fn requests_in(&self, from: u64, to: u64) -> &[Request] {
605        if from >= to {
606            return &[];
607        }
608        let a = self.requests.partition_point(|r| r.t < from);
609        let b = self.requests.partition_point(|r| r.t < to);
610        &self.requests[a..b]
611    }
612
613    /// Whether a driver recorded its loop at all: what `blocked` needs.
614    pub fn has_loop(&self) -> bool {
615        !self.loops.is_empty()
616    }
617
618    /// A span's own time: its length less its children's.
619    pub fn self_us(&self, s: &Span) -> u64 {
620        // A child counted up to its parent's end: one that runs past it
621        // (cut there, or timed loosely) takes no more than the parent had.
622        let kids: u64 = self
623            .spans_in(s.start, s.end().max(s.start + 1))
624            .iter()
625            .filter(|k| k.parent == s.id && k.id != s.id)
626            .map(|k| k.end().min(s.end()).saturating_sub(k.start))
627            .sum();
628        s.dur.saturating_sub(kids)
629    }
630}