1use std::io::{self, Write};
10
11use crate::record::{LoopEvent, NameId, Origin, Record, Session, Span, Track};
12
13pub const MAGIC: &[u8; 8] = b"KUITRACE";
15pub const VERSION: u16 = 1;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum Error {
21 NotATrace,
23 Version(u16),
25}
26
27impl std::fmt::Display for Error {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Error::NotATrace => write!(f, "not a kui recording (no KUITRACE header)"),
31 Error::Version(v) => write!(f, "a version {v} recording; this reads {VERSION}"),
32 }
33 }
34}
35
36impl std::error::Error for Error {}
37
38mod tag {
39 pub const SESSION: u8 = 1;
40 pub const NAME: u8 = 2;
41 pub const TRACK: u8 = 3;
42 pub const FRAME_BEGIN: u8 = 4;
43 pub const HOLDER: u8 = 5;
44 pub const FRAME_END: u8 = 6;
45 pub const PRESENT: u8 = 7;
46 pub const SPAN: u8 = 8;
47 pub const INPUT: u8 = 9;
48 pub const EVENT: u8 = 10;
49 pub const WAKE: u8 = 11;
50 pub const REQUEST: u8 = 12;
51 pub const LOOP: u8 = 13;
52 pub const COUNTER: u8 = 14;
53 pub const GAUGE: u8 = 15;
54 pub const MARK: u8 = 16;
55 pub const NOTE: u8 = 17;
56 pub const PLACEMENT: u8 = 18;
57}
58
59fn put_u(out: &mut Vec<u8>, mut v: u64) {
60 loop {
61 let b = (v & 0x7f) as u8;
62 v >>= 7;
63 if v == 0 {
64 out.push(b);
65 return;
66 }
67 out.push(b | 0x80);
68 }
69}
70
71fn put_i(out: &mut Vec<u8>, v: i64) {
72 put_u(out, ((v << 1) ^ (v >> 63)) as u64);
73}
74
75fn put_str(out: &mut Vec<u8>, s: &str) {
76 put_u(out, s.len() as u64);
77 out.extend_from_slice(s.as_bytes());
78}
79
80fn put_opt(out: &mut Vec<u8>, v: Option<u64>) {
81 put_u(out, v.map_or(0, |v| v + 1));
82}
83
84#[derive(Default)]
86pub struct Encoder {
87 prev_t: u64,
88 body: Vec<u8>,
89}
90
91impl Encoder {
92 pub fn new() -> Self {
93 Self::default()
94 }
95
96 fn t(&mut self, t: u64) {
97 put_i(&mut self.body, t as i64 - self.prev_t as i64);
98 self.prev_t = t;
99 }
100
101 pub fn encode(&mut self, r: &Record, out: &mut Vec<u8>) {
103 self.body.clear();
104 let tag = match r {
105 Record::Session(s) => {
106 put_str(&mut self.body, &s.kui);
107 put_str(&mut self.body, &s.app);
108 put_str(&mut self.body, &s.platform);
109 self.body.extend_from_slice(&s.refresh_hz.to_le_bytes());
110 self.body.extend_from_slice(&s.budget_ms.to_le_bytes());
111 self.body.extend_from_slice(&s.scale.to_le_bytes());
112 put_u(&mut self.body, s.wall_ms);
113 tag::SESSION
114 }
115 Record::Name { id, text } => {
116 put_u(&mut self.body, *id as u64);
117 put_str(&mut self.body, text);
118 tag::NAME
119 }
120 Record::Track { id, name } => {
121 put_u(&mut self.body, id.0 as u64);
122 put_u(&mut self.body, *name as u64);
123 tag::TRACK
124 }
125 Record::FrameBegin { t, frame, causes } => {
126 self.t(*t);
127 put_u(&mut self.body, *frame);
128 put_u(&mut self.body, causes.len() as u64);
129 for c in causes {
130 put_u(&mut self.body, *c as u64);
131 }
132 tag::FRAME_BEGIN
133 }
134 Record::Holder {
135 frame,
136 what,
137 name,
138 node,
139 } => {
140 put_u(&mut self.body, *frame);
141 put_u(&mut self.body, *what as u64);
142 put_u(&mut self.body, *name as u64);
143 put_u(&mut self.body, *node);
144 tag::HOLDER
145 }
146 Record::FrameEnd {
147 t,
148 frame,
149 unchanged,
150 nodes,
151 } => {
152 self.t(*t);
153 put_u(&mut self.body, *frame);
154 put_u(
155 &mut self.body,
156 match unchanged {
157 None => 0,
158 Some(false) => 1,
159 Some(true) => 2,
160 },
161 );
162 put_u(&mut self.body, *nodes as u64);
163 tag::FRAME_END
164 }
165 Record::Present {
166 t,
167 frame,
168 render,
169 wait,
170 } => {
171 self.t(*t);
172 put_u(&mut self.body, *frame);
173 put_u(&mut self.body, *render);
174 put_u(&mut self.body, *wait);
175 tag::PRESENT
176 }
177 Record::Span(s) => {
178 self.t(s.start);
179 put_u(&mut self.body, s.track.0 as u64);
180 put_u(&mut self.body, s.id as u64);
181 put_u(&mut self.body, s.parent as u64);
182 put_u(&mut self.body, s.name as u64);
183 match s.origin {
184 Origin::Kui => put_u(&mut self.body, 0),
185 Origin::Host => put_u(&mut self.body, 1),
186 Origin::Extension(ns) => put_u(&mut self.body, 2 + ns as u64),
187 }
188 put_u(&mut self.body, s.dur);
189 put_opt(&mut self.body, s.cpu);
190 put_u(&mut self.body, s.node);
191 put_u(&mut self.body, s.cut as u64);
192 tag::SPAN
193 }
194 Record::Input {
195 t,
196 kind,
197 os_t,
198 count,
199 until,
200 } => {
201 self.t(*t);
202 put_u(&mut self.body, *kind as u64);
203 put_opt(&mut self.body, *os_t);
204 put_u(&mut self.body, *count as u64);
205 put_u(&mut self.body, until.saturating_sub(*t));
206 tag::INPUT
207 }
208 Record::Event { t, kind, node } => {
209 self.t(*t);
210 put_u(&mut self.body, *kind as u64);
211 put_u(&mut self.body, *node);
212 tag::EVENT
213 }
214 Record::Wake { t, name } => {
215 self.t(*t);
216 put_u(&mut self.body, *name as u64);
217 tag::WAKE
218 }
219 Record::Request { t, why, at } => {
220 self.t(*t);
221 put_u(&mut self.body, *why as u64);
222 put_u(&mut self.body, *at as u64);
223 tag::REQUEST
224 }
225 Record::Loop { t, event } => {
226 self.t(*t);
227 match event {
228 LoopEvent::Sleep { until } => {
229 put_u(&mut self.body, 0);
230 put_opt(&mut self.body, *until);
231 }
232 LoopEvent::Woke { by } => {
233 put_u(&mut self.body, 1);
234 put_u(&mut self.body, *by as u64);
235 }
236 LoopEvent::Os { kind } => {
237 put_u(&mut self.body, 2);
238 put_u(&mut self.body, *kind as u64);
239 }
240 }
241 tag::LOOP
242 }
243 Record::Counter {
244 t,
245 frame,
246 name,
247 value,
248 } => {
249 self.t(*t);
250 put_u(&mut self.body, *frame);
251 put_u(&mut self.body, *name as u64);
252 put_i(&mut self.body, *value);
253 tag::COUNTER
254 }
255 Record::Gauge { t, name, value } => {
256 self.t(*t);
257 put_u(&mut self.body, *name as u64);
258 self.body.extend_from_slice(&value.to_le_bytes());
259 tag::GAUGE
260 }
261 Record::Mark { t, track, name } => {
262 self.t(*t);
263 put_u(&mut self.body, track.0 as u64);
264 put_u(&mut self.body, *name as u64);
265 tag::MARK
266 }
267 Record::Note { t, text } => {
268 self.t(*t);
269 put_str(&mut self.body, text);
270 tag::NOTE
271 }
272 Record::Placement {
273 t,
274 node,
275 component,
276 answer,
277 why,
278 } => {
279 self.t(*t);
280 put_u(&mut self.body, *node);
281 put_u(&mut self.body, *component as u64);
282 put_u(&mut self.body, *answer as u64);
283 put_u(&mut self.body, *why as u64);
284 tag::PLACEMENT
285 }
286 };
287 out.push(tag);
288 put_u(out, self.body.len() as u64);
289 out.extend_from_slice(&self.body);
290 }
291}
292
293pub fn header() -> Vec<u8> {
295 let mut h = MAGIC.to_vec();
296 h.extend_from_slice(&VERSION.to_le_bytes());
297 h
298}
299
300pub struct Writer<W: Write> {
302 out: W,
303 enc: Encoder,
304 buf: Vec<u8>,
305}
306
307impl<W: Write> Writer<W> {
308 pub fn new(mut out: W) -> io::Result<Self> {
310 out.write_all(&header())?;
311 Ok(Writer {
312 out,
313 enc: Encoder::new(),
314 buf: Vec::new(),
315 })
316 }
317
318 pub fn write(&mut self, r: &Record) -> io::Result<()> {
319 self.buf.clear();
320 self.enc.encode(r, &mut self.buf);
321 self.out.write_all(&self.buf)
322 }
323
324 pub fn flush(&mut self) -> io::Result<()> {
325 self.out.flush()
326 }
327
328 pub fn into_inner(self) -> W {
329 self.out
330 }
331}
332
333struct Cursor<'a> {
334 b: &'a [u8],
335 at: usize,
336}
337
338impl Cursor<'_> {
339 fn u(&mut self) -> Option<u64> {
340 let mut v = 0u64;
341 let mut shift = 0;
342 loop {
343 let byte = *self.b.get(self.at)?;
344 self.at += 1;
345 if shift < 64 {
346 v |= ((byte & 0x7f) as u64) << shift;
347 }
348 if byte & 0x80 == 0 {
349 return Some(v);
350 }
351 shift += 7;
352 }
353 }
354
355 fn i(&mut self) -> Option<i64> {
356 let u = self.u()?;
357 Some(((u >> 1) as i64) ^ -((u & 1) as i64))
358 }
359
360 fn u32(&mut self) -> Option<u32> {
361 self.u().map(|v| v as u32)
362 }
363
364 fn opt(&mut self) -> Option<Option<u64>> {
365 self.u().map(|v| v.checked_sub(1))
366 }
367
368 fn bytes(&mut self, n: usize) -> Option<&[u8]> {
369 let end = self.at.checked_add(n)?;
370 let s = self.b.get(self.at..end)?;
371 self.at = end;
372 Some(s)
373 }
374
375 fn str(&mut self) -> Option<String> {
376 let n = self.u()? as usize;
377 self.bytes(n)
378 .map(|b| String::from_utf8_lossy(b).into_owned())
379 }
380
381 fn f32(&mut self) -> Option<f32> {
382 self.bytes(4)
383 .map(|b| f32::from_le_bytes(b.try_into().unwrap()))
384 }
385
386 fn f64(&mut self) -> Option<f64> {
387 self.bytes(8)
388 .map(|b| f64::from_le_bytes(b.try_into().unwrap()))
389 }
390}
391
392pub struct Reader<'a> {
394 b: &'a [u8],
395 at: usize,
396 dec: Decoder,
397}
398
399#[derive(Clone, Copy, Debug, Default)]
402pub(crate) struct Decoder {
403 prev_t: u64,
404}
405
406pub(crate) fn framed(b: &[u8], at: usize) -> Option<(u8, std::ops::Range<usize>)> {
409 let tag = *b.get(at)?;
410 let mut c = Cursor { b, at: at + 1 };
411 let len = c.u()? as usize;
412 let end = c.at.checked_add(len)?;
413 (end <= b.len()).then_some((tag, c.at..end))
414}
415
416pub(crate) fn check_header(b: &[u8]) -> Result<Option<usize>, Error> {
419 if b.len() < 10 {
420 return if MAGIC.starts_with(&b[..b.len().min(8)]) {
421 Ok(None)
422 } else {
423 Err(Error::NotATrace)
424 };
425 }
426 if &b[..8] != MAGIC {
427 return Err(Error::NotATrace);
428 }
429 let v = u16::from_le_bytes([b[8], b[9]]);
430 if v != VERSION {
431 return Err(Error::Version(v));
432 }
433 Ok(Some(10))
434}
435
436impl<'a> Reader<'a> {
437 pub fn new(b: &'a [u8]) -> Result<Self, Error> {
439 let at = check_header(b)?.ok_or(Error::NotATrace)?;
440 Ok(Reader {
441 b,
442 at,
443 dec: Decoder::default(),
444 })
445 }
446}
447
448impl Decoder {
449 fn t(&mut self, c: &mut Cursor<'_>) -> Option<u64> {
450 let d = c.i()?;
451 self.prev_t = (self.prev_t as i64).saturating_add(d).max(0) as u64;
452 Some(self.prev_t)
453 }
454
455 pub(crate) fn decode(&mut self, tag: u8, body: &[u8]) -> Option<Option<Record>> {
458 let mut c = Cursor { b: body, at: 0 };
459 let r = match tag {
460 tag::SESSION => Record::Session(Session {
461 kui: c.str()?,
462 app: c.str()?,
463 platform: c.str()?,
464 refresh_hz: c.f32()?,
465 budget_ms: c.f32()?,
466 scale: c.f32()?,
467 wall_ms: c.u()?,
468 }),
469 tag::NAME => Record::Name {
470 id: c.u32()?,
471 text: c.str()?,
472 },
473 tag::TRACK => Record::Track {
474 id: Track(c.u32()?),
475 name: c.u32()?,
476 },
477 tag::FRAME_BEGIN => {
478 let t = self.t(&mut c)?;
479 let frame = c.u()?;
480 let n = c.u()? as usize;
481 let mut causes = Vec::with_capacity(n.min(64));
482 for _ in 0..n {
483 causes.push(c.u32()?);
484 }
485 Record::FrameBegin { t, frame, causes }
486 }
487 tag::HOLDER => Record::Holder {
488 frame: c.u()?,
489 what: c.u32()?,
490 name: c.u32()?,
491 node: c.u()?,
492 },
493 tag::FRAME_END => Record::FrameEnd {
494 t: self.t(&mut c)?,
495 frame: c.u()?,
496 unchanged: match c.u()? {
497 1 => Some(false),
498 2 => Some(true),
499 _ => None,
500 },
501 nodes: c.u32()?,
502 },
503 tag::PRESENT => Record::Present {
504 t: self.t(&mut c)?,
505 frame: c.u()?,
506 render: c.u()?,
507 wait: c.u()?,
508 },
509 tag::SPAN => {
510 let start = self.t(&mut c)?;
511 Record::Span(Span {
512 start,
513 track: Track(c.u32()?),
514 id: c.u32()?,
515 parent: c.u32()?,
516 name: c.u32()?,
517 origin: match c.u()? {
518 0 => Origin::Kui,
519 1 => Origin::Host,
520 n => Origin::Extension((n - 2) as NameId),
521 },
522 dur: c.u()?,
523 cpu: c.opt()?,
524 node: c.u()?,
525 cut: c.u()? != 0,
526 })
527 }
528 tag::INPUT => {
529 let t = self.t(&mut c)?;
530 Record::Input {
531 t,
532 kind: c.u32()?,
533 os_t: c.opt()?,
534 count: c.u32()?,
535 until: t.saturating_add(c.u()?),
536 }
537 }
538 tag::EVENT => Record::Event {
539 t: self.t(&mut c)?,
540 kind: c.u32()?,
541 node: c.u()?,
542 },
543 tag::WAKE => Record::Wake {
544 t: self.t(&mut c)?,
545 name: c.u32()?,
546 },
547 tag::REQUEST => Record::Request {
548 t: self.t(&mut c)?,
549 why: c.u32()?,
550 at: c.u32()?,
551 },
552 tag::LOOP => {
553 let t = self.t(&mut c)?;
554 let event = match c.u()? {
555 0 => LoopEvent::Sleep { until: c.opt()? },
556 1 => LoopEvent::Woke { by: c.u32()? },
557 _ => LoopEvent::Os { kind: c.u32()? },
558 };
559 Record::Loop { t, event }
560 }
561 tag::COUNTER => Record::Counter {
562 t: self.t(&mut c)?,
563 frame: c.u()?,
564 name: c.u32()?,
565 value: c.i()?,
566 },
567 tag::GAUGE => Record::Gauge {
568 t: self.t(&mut c)?,
569 name: c.u32()?,
570 value: c.f64()?,
571 },
572 tag::MARK => Record::Mark {
573 t: self.t(&mut c)?,
574 track: Track(c.u32()?),
575 name: c.u32()?,
576 },
577 tag::NOTE => Record::Note {
578 t: self.t(&mut c)?,
579 text: c.str()?,
580 },
581 tag::PLACEMENT => Record::Placement {
582 t: self.t(&mut c)?,
583 node: c.u()?,
584 component: c.u32()?,
585 answer: c.u32()?,
586 why: c.u32()?,
587 },
588 _ => return Some(None),
590 };
591 Some(Some(r))
592 }
593}
594
595impl Iterator for Reader<'_> {
596 type Item = Record;
597
598 fn next(&mut self) -> Option<Record> {
601 loop {
602 let (tag, body) = framed(self.b, self.at)?;
603 self.at = body.end;
604 match self.dec.decode(tag, &self.b[body]) {
605 Some(Some(r)) => return Some(r),
606 Some(None) | None => continue,
608 }
609 }
610 }
611}