
# 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  comparing two traces  Co-authored-by: Robert Swain <robert.swain@gmail.com>
163 lines
4.6 KiB
Rust
163 lines
4.6 KiB
Rust
//! helper to extract span stats from a chrome trace file
|
|
//! spec: <https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.puwqg050lyuy>
|
|
|
|
use std::ops::Div;
|
|
|
|
use clap::Parser;
|
|
use parse::read_trace;
|
|
use regex::Regex;
|
|
use termcolor::{ColorChoice, StandardStream};
|
|
|
|
use crate::pretty::{print_spanstats, set_bold, simplify_name};
|
|
|
|
mod parse;
|
|
mod pretty;
|
|
|
|
#[derive(Parser, Debug)]
|
|
struct Args {
|
|
#[clap(short, long, default_value_t = 0.0)]
|
|
/// Filter spans that have an average shorther than the threshold
|
|
threshold: f32,
|
|
|
|
#[clap(short, long)]
|
|
/// Filter spans by name matching the pattern
|
|
pattern: Option<Regex>,
|
|
|
|
#[clap(short, long)]
|
|
/// Simplify system names
|
|
short: bool,
|
|
|
|
trace: String,
|
|
/// Optional, second trace to compare
|
|
second_trace: Option<String>,
|
|
}
|
|
|
|
fn main() {
|
|
let cli = Args::parse();
|
|
|
|
// Setup stdout to support colors
|
|
let mut stdout = StandardStream::stdout(ColorChoice::Auto);
|
|
|
|
// Read the first trace file
|
|
let reference = read_trace(cli.trace);
|
|
if let Some(comparison) = cli.second_trace {
|
|
// Read the second trace file
|
|
let mut comparison = read_trace(comparison);
|
|
|
|
reference
|
|
.iter()
|
|
.filter(|(_, stats)| filter_by_threshold(stats, cli.threshold))
|
|
.filter(|(name, _)| filter_by_pattern(name, cli.pattern.as_ref()))
|
|
.for_each(|(span, reference)| {
|
|
// for each span in the first trace
|
|
set_bold(&mut stdout, true);
|
|
if cli.short {
|
|
println!("{}", simplify_name(span));
|
|
} else {
|
|
println!("{}", span);
|
|
}
|
|
set_bold(&mut stdout, false);
|
|
print!(" ");
|
|
let comparison = comparison.remove(span);
|
|
print_spanstats(&mut stdout, Some(reference), comparison.as_ref(), false);
|
|
});
|
|
comparison
|
|
.iter()
|
|
.filter(|(_, stats)| filter_by_threshold(stats, cli.threshold))
|
|
.filter(|(name, _)| filter_by_pattern(name, cli.pattern.as_ref()))
|
|
.for_each(|(span, comparison)| {
|
|
// print the spans only present in the second trace
|
|
set_bold(&mut stdout, true);
|
|
if cli.short {
|
|
println!("{}", simplify_name(span));
|
|
} else {
|
|
println!("{}", span);
|
|
}
|
|
set_bold(&mut stdout, false);
|
|
print!(" ");
|
|
print_spanstats(&mut stdout, None, Some(comparison), false);
|
|
});
|
|
} else {
|
|
// just print stats from the first trace
|
|
reference
|
|
.iter()
|
|
.filter(|(_, stats)| filter_by_threshold(stats, cli.threshold))
|
|
.filter(|(name, _)| filter_by_pattern(name, cli.pattern.as_ref()))
|
|
.for_each(|(span, reference)| {
|
|
set_bold(&mut stdout, true);
|
|
if cli.short {
|
|
println!("{}", simplify_name(span));
|
|
} else {
|
|
println!("{}", span);
|
|
}
|
|
set_bold(&mut stdout, false);
|
|
print!(" ");
|
|
print_spanstats(&mut stdout, Some(reference), None, true);
|
|
});
|
|
}
|
|
}
|
|
|
|
fn filter_by_threshold(span_stats: &SpanStats, threshold: f32) -> bool {
|
|
span_stats.avg > threshold
|
|
}
|
|
|
|
fn filter_by_pattern(name: &str, pattern: Option<&Regex>) -> bool {
|
|
if let Some(pattern) = pattern {
|
|
pattern.is_match(name)
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SpanStats {
|
|
pub count: usize,
|
|
pub avg: f32,
|
|
pub min: f32,
|
|
pub max: f32,
|
|
}
|
|
|
|
impl Default for SpanStats {
|
|
fn default() -> Self {
|
|
Self {
|
|
count: 0,
|
|
avg: 0.0,
|
|
min: f32::MAX,
|
|
max: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SpanStats {
|
|
fn add_span(&mut self, duration: f32) {
|
|
if duration < self.min {
|
|
self.min = duration;
|
|
}
|
|
if duration > self.max {
|
|
self.max = duration;
|
|
}
|
|
self.avg = (self.avg * self.count as f32 + duration) / (self.count as f32 + 1.0);
|
|
self.count += 1;
|
|
}
|
|
}
|
|
|
|
pub struct SpanRelative {
|
|
count: f32,
|
|
avg: f32,
|
|
min: f32,
|
|
max: f32,
|
|
}
|
|
|
|
impl Div for &SpanStats {
|
|
type Output = SpanRelative;
|
|
|
|
fn div(self, rhs: Self) -> Self::Output {
|
|
Self::Output {
|
|
count: self.count as f32 / rhs.count as f32,
|
|
avg: self.avg / rhs.avg,
|
|
min: self.min / rhs.min,
|
|
max: self.max / rhs.max,
|
|
}
|
|
}
|
|
}
|