Rust Profiling with DTrace and FlameGraph on OSX
This is an alternative to my post Rust Profiling with Instruments and FlameGraph on OSX: CPU/Time, since XCode disabled exporting of the Time/CPU data from Instruments. Many thanks to this post of BrendanGregg’s that has examples of using DTrace to get the data to feed into his FlameGraph generation scripts, and GolDDranks on Stack Overflow who figured out how to get OSX’s DTrace to find Rust debugging symbols – I didn’t have the same problem, I’m not sure why, but they inspired me to try DTrace instead of Instruments and write an update blog post!
So here’s the new workflow that worked for me!
Prerequisites
These are the same.
- Turn on debug symbols in your release profile in Cargo.toml
- This lets the profiling tools match up to your source code.
-
As documented on crates.io, this is done by adding this to your
Cargo.toml
:[profile.release] debug = true
- Build with
cargo build --release
then run./target/release/[binary]
; or usecargo run --release
- Forgetting to build in release mode (which turns on optimizations) is the #1 cause of unexpected Rust slowness.
- By default, cargo builds in debug mode, which is faster to compile but runs more slowly.
- We want to profile the optimized code.
DTrace
OSX (yes yes I know it’s macOS now but it’ll always be OSX to me) comes with dtrace
! I’m on Sierra, and dtrace -V
says dtrace: Sun D 1.13
for me.
TL;DR: the command I ran to profile my program named zopfli
was:
sudo dtrace -c './zopfli large-file' -o out.stacks -n 'profile-997 /execname == "zopfli"/ { @[ustack(100)] = count(); }'
Here’s the explanation of all the pieces:
-
sudo: Because of Reasons™, you have to run
dtrace
as root on OSX. -
-c
: A lot of the tutorials and blog posts I’m looking at usedtrace
by running the program, then finding the PID of the running process, then passing the PID as an argument todtrace
– I don’t want to do all that, I want to havedtrace
run my program and watch just what it’s running. Luckily, that’s what-c
does! I have a bunch of symlinks set up because Other Reasons™, so the command I use to run my Rust program is./zopfli large-file
. So that’s what I’m doing with the-c './zopfli large-file'
part, replace my command with the one you want to run under dtrace. -
-o
: This is the output filename that we’ll be passing to the flamegraphs scripts in the next section. -
-n
: This argument specifies the probe names that you want to enable. Breaking this down:profile-997
: interrupt the program 997 times per second. See the profile provider in the DTrace book for more info./execname == "zopfli"/
is a predicate that will fire if the name of the current process’s executable file is equal to the string we’ve specified; you’ll want to change this to be your program. I’m not sure why this is needed exactly sincedtrace
should know the executable from the-c
argument, but I couldn’t figure out the right syntax to get the-n
argument to work without this.{ @[ustack(100)] = count(); }
is the code to run if the predicate is true. This code stores an aggregate (@[]
) count (count()
) of the number of times thatdtrace
sees a particular user stack trace (ustack
) 100 frames deep.
FlameGraph
Now that you have the output from dtrace in a file, the flamegraph steps are
similar. Once you have, the FlameGraph
code, and assuming you gave -o
the argument out.stacks
, run:
$ ./stackcollapse.pl out.stacks | ./flamegraph.pl > pretty-graph.svg
Tada!!! You have pretty flame graph SVGs again!!!