bevy/tools/spancmp/src/parse.rs
François 068e9eaae8 simple tool to compare traces between executions (#4628)
# Objective

- Have an easy way to compare spans between executions

## Solution

- Add a tool to compare spans from chrome traces

```bash
> cargo run --release  -p spancmp -- --help
   Compiling spancmp v0.1.0
    Finished release [optimized] target(s) in 1.10s
     Running `target/release/spancmp --help`
spancmp

USAGE:
    spancmp [OPTIONS] <TRACE> [SECOND_TRACE]

ARGS:
    <TRACE>
    <SECOND_TRACE>

OPTIONS:
    -h, --help                     Print help information
    -p, --pattern <PATTERN>        Filter spans by name matching the pattern
    -t, --threshold <THRESHOLD>    Filter spans that have an average shorther than the threshold
                                   [default: 0]
```

for each span, it will display the count, minimum duration, average duration and max duration. It can be filtered by a pattern on the span name or by a minimum average duration.

just displaying a trace
![Screenshot 2022-04-28 at 21 56 21](https://user-images.githubusercontent.com/8672791/165835310-f465c6f2-9e6b-4808-803e-884b06e49292.png)

comparing two traces
![Screenshot 2022-04-28 at 21 56 55](https://user-images.githubusercontent.com/8672791/165835353-097d266b-a70c-41b8-a8c1-27804011dc97.png)



Co-authored-by: Robert Swain <robert.swain@gmail.com>
2022-05-06 19:29:44 +00:00

97 lines
2.2 KiB
Rust

use std::{
cell::RefCell,
collections::HashMap,
fs::File,
io::{BufReader, Read},
rc::Rc,
};
use serde::Deserialize;
use serde_json::Deserializer;
use crate::SpanStats;
/// A span from the trace
#[derive(Deserialize, Debug)]
struct Span {
/// name
name: String,
/// phase
ph: String,
/// timestamp
ts: f32,
}
/// Ignore entries in the trace that are not a span
#[derive(Deserialize, Debug)]
struct Ignore {}
/// deserialize helper
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum SpanOrIgnore {
/// deserialize as a span
Span(Span),
/// catchall that didn't match a span
Ignore(Ignore),
}
#[derive(Clone)]
struct SkipperWrapper {
reader: Rc<RefCell<BufReader<File>>>,
}
impl SkipperWrapper {
fn from(mut reader: BufReader<File>) -> SkipperWrapper {
let _ = reader.seek_relative(1);
Self {
reader: Rc::new(RefCell::new(reader)),
}
}
fn skip(&self) {
let _ = self.reader.borrow_mut().seek_relative(1);
}
}
impl Read for SkipperWrapper {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
self.reader.borrow_mut().read(buf)
}
}
pub fn read_trace(file: String) -> HashMap<String, SpanStats> {
let file = File::open(file).unwrap();
let reader = BufReader::new(file);
let reader_wrapper = SkipperWrapper::from(reader);
let spans = Deserializer::from_reader(reader_wrapper.clone()).into_iter::<SpanOrIgnore>();
let mut open_spans: HashMap<String, f32> = HashMap::new();
let mut all_spans_stats: HashMap<String, SpanStats> = HashMap::new();
spans
.flat_map(move |s| {
reader_wrapper.skip();
if let Ok(SpanOrIgnore::Span(span)) = s {
Some(span)
} else {
None
}
})
.for_each(|s| {
if s.ph == "B" {
open_spans.insert(s.name.clone(), s.ts);
} else if s.ph == "E" {
let begin = open_spans.remove(&s.name).unwrap();
all_spans_stats
.entry(s.name)
.or_default()
.add_span(s.ts - begin);
}
});
all_spans_stats
}