Blondie
Collect CPU callstack samples from a windows process
Install / Use
/learn @nico-abram/BlondieREADME
blondie
<img alt="github" src="https://img.shields.io/badge/github-nico--abram/blondie-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20"> <img alt="crates.io" src="https://img.shields.io/crates/v/blondie.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20"> <img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-blondie-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20"> <img alt="build status" src="https://img.shields.io/github/workflow/status/nico-abram/blondie/blondie/main?style=for-the-badge" height="20">
Collect CPU callstack samples from a windows process.
Since the "SampledProfile" ETW events we use come from a "kernel event provider"(PerfInfo) we must use the ETW "Kernel Logger session", which requires elevated priviledges. Therefore, you must run blondie as administrator in order for it to work.
The blondie binary can be used to generate a text file with the sample count of each call stack, or a flamegraph using the inferno library.
The blondie_dtrace binary can be used as a dtrace replacement in cargo-flamegraph via the DTRACE environment variable.
Examples:
./blondie.exe flamegraph ./target/debug/some_binary_with_debuginfo.exe arg1 arg2
./flamegraph.svg
cargo build --release --bin blondie_dtrace
$ENV:DTRACE = "current_dir/target/release/blondie_dtrace.exe" # Or set DTRACE="current_dir/target/release/blondie_dtrace.exe" in cmd.exe
cd some/other/project
cargo flamegraph ; ./flamegraph.svg
I wrote this to be able to get flamegraphs using https://github.com/flamegraph-rs on windows.
This is built using the ETW(Event Tracing for Windows) API to collect CPU samples and DLL/EXE load events, and the DbgHelp Symbol Handler API to translate the virtual addresses ETW gives us to symbol names.
Future work?
- Test on windows 7 and probably fix the things that don't work.
- ~~Make it possible to trace a pre-existing pid instead of requiring a command to launch.~~ (Thanks to @felstead for implementing this!)
- Use ETW filters to only receive events for the target process (We currently filter ourselves and discard events from other processes). See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-starttracea https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties_v2 https://docs.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor specifically EVENT_FILTER_TYPE_PID (note: Does not work on Win7 via StartTrace, would need to use EnableTraceEx2 if we care about Win7)
- Write a general ETW rust library.
Example output
Example flamegraph from a hello world rust program:
<img src="./img/example_output.png">Here's a bit of example output from running blondie on dust:
<details> <summary>Example output</summary>
dust`__chkstk+0x37
dust`std::sys::windows::fs::File::file_attr+0x11
dust`std::sys::windows::fs::stat+0x6E
dust`std::fs::metadata<ref$<std::path::Path> >+0x4C
dust`std::path::Path::metadata+0x1B
dust`dust::platform::get_metadata+0x47
dust`dust::node::build_node+0x10B
dust`dust::dir_walker::walk::closure$0+0x2AF
dust`rayon::iter::filter_map::impl$6::consume<enum$<core::result::Result<std::fs::DirEntry,std::io::error::Error> >,dust::node::Node,rayon::iter::fold::FoldFolder<rayon::iter::map::MapFolder<rayon::iter::reduce::ReduceFolder<alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >),alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>)>,alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>,alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>,dust::node::Node)>,dust::dir_walker::walk::closure_env$0>+0x96
dust`rayon::iter::par_bridge::impl$3::fold_with<std::fs::ReadDir,rayon::iter::filter_map::FilterMapFolder<rayon::iter::fold::FoldFolder<rayon::iter::map::MapFolder<rayon::iter::reduce::ReduceFolder<alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >),alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>)>,alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>,alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>,dust::node::Node)>,dust::dir_walker::walk::closure_env$0> >+0x13F
dust`rayon::iter::plumbing::bridge_unindexed_producer_consumer<rayon::iter::par_bridge::IterParallelProducer<std::fs::ReadDir>,rayon::iter::filter_map::FilterMapConsumer<rayon::iter::fold::FoldConsumer<rayon::iter::map::MapConsumer<rayon::iter::reduce::ReduceConsumer<alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >),alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)()>,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>)>,alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> (*)(),alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>,dust::node::Node)>,dust::dir_walker::walk::closure_env$0> >+0x664
dust`rayon::iter::plumbing::bridge_unindexed_producer_consumer::closure$0<rayon::iter::par_bridge::IterParallelProducer<std::fs::ReadDir>,rayon::iter::filter_map::FilterMapConsumer<rayon::iter::fold::FoldConsumer<rayon::iter::map::MapConsumer<rayon::iter::reduce::ReduceConsumer<alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >),alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)()>,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>)>,alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> (*)(),alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> (*)(alloc::vec::Vec<dust::node::Node,alloc::alloc::Global>,dust::node::Node)>,dust::dir_walker::walk::closure_env$0> >+0xC5
dust`rayon_core::join::join_context::call_a::closure$0<alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >,rayon::iter::plumbing::bridge_unindexed_producer_consumer::closure_env$0<rayon::iter::par_bridge::IterParallelProducer<std::fs::ReadDir>,rayon::iter::filter_map::FilterMapConsumer<rayon::iter::fold::FoldConsumer<rayon::iter::map::MapConsumer<rayon::iter::reduce::ReduceConsumer<alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)(alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >,alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> >),alloc::collections::linked_list::LinkedList<alloc::vec::Vec<dust::node::Node,alloc::alloc::Global> > (*)()>
Related Skills
node-connect
338.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.6kCommit, push, and open a PR
