Stopwatch logo

Rust · from scratch · a call tree you can read

See where a program
spent its life.

Stopwatch is a profiler. It measures the time inside every function, separates a function's own work from time spent in its children, and blooms the whole run into a chronomandala. Pick a workload and watch the time unfold.

How to use this playground
Pick a workload and watch Stopwatch profile it, blooming the call tree into a chronomandala.
hottest
 
full run
waits on children (cold) its own work (hot) ring depth = call depth
 

The same profiler, two clocks

Timing is usually the flaky part of a profiler, because wall-clock noise makes small runs unrepeatable. Stopwatch injects the clock. The Rust binary reads the system clock, and the demo above uses a clock that counts units of work, so the bloom is exactly reproducible. Everything else is identical.

1

Enter and exit

Every span records the time on the way in and on the way out. Spans nest, so the profiler always knows which function is running and who called it.

2

Self vs total

Total time is everything between enter and exit. Self time subtracts the time spent inside children, so a function that only calls others shows almost no self time. That difference is the whole game.

3

The bloom

Each function becomes an arc. Its sweep is its share of total time, its ring is its call depth, and its color runs from cold blue (waiting on children) to hot orange (its own work). Hot leaves glow.

// self time is computed per activation, never guessed
fn exit(&mut self) {
    let elapsed = self.clock.now_nanos() - frame.start;
    node.total_nanos += elapsed;
    node.self_nanos  += elapsed - frame.children_nanos;
    parent.children_nanos += elapsed;
}

Read the source, not just the numbers

The CLI runs real workloads under the system clock and prints an indented call tree with total time, self time, and percent, then names the line where the program spent most of its life.

cargo run -- run --workload sort
cargo run -- run --workload mixed --json   # the same tree the bloom reads
$ stopwatch run --workload sort
name                               calls   total(ms)    self(ms)      pct
quick_sort                             1       0.399       0.173    81.1%
  partition                         2671       0.226       0.226    46.0%
bubble_sort                            1       0.093       0.093    18.9%

Your program spent 46.0% of its life in partition().

How Stopwatch compares

perf, flamegraph, and criterion are excellent tools solving adjacent problems. Stopwatch fills the gap between them: an embeddable, deterministic call tree.

perf

An OS-level sampling profiler, Linux only, and not deterministic. Stopwatch is pure Rust, embeds inside your own code, and its clock is injected so a test can assert exact nanosecond totals.

flamegraph

A visualizer for folded stacks. Stopwatch feeds it directly through to_flamegraph(), emitting Brendan Gregg folded-stack format, so it complements that tool rather than replacing it.

criterion

Statistical microbenchmarks for timing, not a call tree. Stopwatch gives you a call tree with self time versus total time, and still runs inside a test suite.

Stopwatch

An embeddable call-tree profiler with self versus total time, deterministic through an injected clock, one dependency for the CLI and zero for the library.

Where it runs

The same profiler, reachable four ways. Every surface reads the same call tree.

CLI

Run a built-in workload with a text report and epitaph, or --json for the raw tree, or --flamegraph for folded stacks.

Library

Wrap work in span for a plain value or try_span for a Result, then read text_report() and epitaph().

Deterministic tests

Inject MockClock instead of SystemClock and the test suite asserts exact nanosecond totals, no flaky sleeps.

MCP server

A run_workload tool over the Model Context Protocol returns the report, epitaph, or folded stacks. It never compiles or runs arbitrary code.

cargo build --release
stopwatch run --workload mixed
stopwatch run --workload sort --flamegraph | flamegraph.pl > sort.svg