Skip to content

Commit

Permalink
Add symbolize feature for online symbolization
Browse files Browse the repository at this point in the history
This patch adds a `symbolize` feature which performs online
symbolization of profiles using the `backtrace` crate.

This is often much more convenient than having to manually obtain
binaries and symbolize the profiles, and matches the behavior of e.g.
the `pprof-rs` CPU profiler. It will also allow emitting e.g. flamegraph
SVGs directly, which I'll submit in a follow-up PR.

Online symbolization uses a fair amount of memory for symbol caches, but
this is often a worthwhile tradeoff.
  • Loading branch information
erikgrinaker committed Feb 3, 2025
1 parent 00cdef9 commit 0b146a1
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 7 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ num = "0.4"
errno = "0.3"
util = { path = "./util", version = "0.6", package = "pprof_util" }
mappings = { path = "./mappings", version = "0.6" }
backtrace = "0.3"

[dependencies]
util.workspace = true
Expand All @@ -50,6 +51,9 @@ tracing.workspace = true
tempfile.workspace = true
tokio.workspace = true

[features]
symbolize = ["util/symbolize"]

[dev-dependencies]
tikv-jemallocator = "0.6"
axum = "0.7"
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ capi:
fmt:
cargo fmt --all -- --check

# Run Clippy with default and all features, to ensure both variants compile.
.PHONY: lint
lint:
cargo clippy --workspace -- -D warnings
cargo clippy --workspace --all-features -- -D warnings

.PHONY: doc
doc:
RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo doc --all-features --no-deps

.PHONY: test
test: fmt lint doc
cargo test --workspace
cargo test --workspace --all-features

.PHONY: clean
clean:
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,14 @@ curl localhost:3000/debug/pprof/heap > heap.pb.gz
pprof -http=:8080 heap.pb.gz
```

> Note: The profiling data is not symbolized, so either `addr2line` or `llvm-addr2line` needs to be available in the path and pprof needs to be able to discover the respective debuginfos.
> Note: if symbolization is not enabled, either `addr2line` or `llvm-addr2line` needs to be available in the path and pprof needs to be able to discover the respective debuginfos.
To generate symbolized profiles, enable the `symbolize` crate feature:

```toml
[dependencies]
jemalloc_pprof = { version = "0.6", features = ["symbolize"] }
```

### Writeable temporary directory

Expand Down
4 changes: 4 additions & 0 deletions util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ prost.workspace = true
anyhow.workspace = true
num.workspace = true
paste.workspace = true
backtrace = { workspace = true, optional = true }

[features]
symbolize = ["dep:backtrace"]
58 changes: 53 additions & 5 deletions util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ impl StackProfile {
}

let mut location_ids = BTreeMap::new();
#[cfg(feature = "symbolize")]
let mut function_ids = BTreeMap::new();
for (stack, anno) in self.iter() {
let mut sample = proto::Sample::default();

Expand All @@ -193,15 +195,61 @@ impl StackProfile {
// pprof_types.proto says the location id may be the address, but Polar Signals
// insists that location ids are sequential, starting with 1.
let id = u64::cast_from(profile.location.len()) + 1;
let mapping_id = profile

#[allow(unused_mut)] // for feature = "symbolize"
let mut mapping = profile
.mapping
.iter()
.find(|m| m.memory_start <= addr && m.memory_limit > addr)
.map_or(0, |m| m.id);
.iter_mut()
.find(|m| m.memory_start <= addr && m.memory_limit > addr);

// If online symbolization is enabled, resolve the function and line.
#[allow(unused_mut)]
let mut line = Vec::new();
#[cfg(feature = "symbolize")]
backtrace::resolve(addr as *mut std::ffi::c_void, |symbol| {
let Some(symbol_name) = symbol.name() else {
return;
};
let function_name = format!("{symbol_name:#}");
let lineno = symbol.lineno().unwrap_or(0) as i64;

let function_id = *function_ids.entry(function_name).or_insert_with_key(
|function_name| {
let function_id = profile.function.len() as u64 + 1;
let system_name = String::from_utf8_lossy(symbol_name.as_bytes());
let filename = symbol
.filename()
.map(|path| path.to_string_lossy())
.unwrap_or(std::borrow::Cow::Borrowed(""));

if let Some(ref mut mapping) = mapping {
mapping.has_functions = true;
mapping.has_filenames |= !filename.is_empty();
mapping.has_line_numbers |= lineno > 0;
}

profile.function.push(proto::Function {
id: function_id,
name: strings.insert(function_name),
system_name: strings.insert(&system_name),
filename: strings.insert(&filename),
..Default::default()
});
function_id
},
);

line.push(proto::Line {
function_id,
line: lineno,
});
});

profile.location.push(proto::Location {
id,
mapping_id,
mapping_id: mapping.map_or(0, |m| m.id),
address: addr,
line,
..Default::default()
});
id
Expand Down

0 comments on commit 0b146a1

Please sign in to comment.