41 个版本
0.13.0 | 2023 年 9 月 27 日 |
---|---|
0.12.1 | 2023 年 7 月 26 日 |
0.11.1 | 2023 年 2 月 22 日 |
0.11.0 | 2022 年 11 月 3 日 |
0.3.12 | 2019 年 11 月 27 日 |
#2 in 性能分析
447,672 每月下载量
用于 182 个 crates(直接使用 163 个)
96KB
2K SLoC
pprof
pprof
是一个易于集成到 Rust 程序中的 CPU 分析器。
使用方法
首先,获取一个守护者以开始分析。分析将在守护者被丢弃后继续。
let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&["libc", "libgcc", "pthread", "vdso"]).build().unwrap();
在分析期间,您可以使用守护者获取报告。
if let Ok(report) = guard.report().build() {
println!("report: {:?}", &report);
};
Debug
为 Report
实现了。它将打印一个可读的堆栈计数器报告。以下是一个示例
FRAME: backtrace::backtrace::trace::h3e91a3123a3049a5 -> FRAME: pprof::profiler::perf_signal_handler::h7b995c4ab2e66493 -> FRAME: Unknown -> FRAME: prime_number::is_prime_number::h70653a2633b88023 -> FRAME: prime_number::main::h47f1058543990c8b -> FRAME: std::rt::lang_start::{{closure}}::h4262e250f8024b06 -> FRAME: std::rt::lang_start_internal::{{closure}}::h812f70926ebbddd0 -> std::panicking::try::do_call::h3210e2ce6a68897b -> FRAME: __rust_maybe_catch_panic -> FRAME: std::panicking::try::h28c2e2ec1c3871ce -> std::panic::catch_unwind::h05e542185e35aabf -> std::rt::lang_start_internal::hd7efcfd33686f472 -> FRAME: main -> FRAME: __libc_start_main -> FRAME: _start -> FRAME: Unknown -> THREAD: prime_number 1217
FRAME: backtrace::backtrace::trace::h3e91a3123a3049a5 -> FRAME: pprof::profiler::perf_signal_handler::h7b995c4ab2e66493 -> FRAME: Unknown -> FRAME: alloc::alloc::box_free::h82cea48ed688e081 -> FRAME: prime_number::main::h47f1058543990c8b -> FRAME: std::rt::lang_start::{{closure}}::h4262e250f8024b06 -> FRAME: std::rt::lang_start_internal::{{closure}}::h812f70926ebbddd0 -> std::panicking::try::do_call::h3210e2ce6a68897b -> FRAME: __rust_maybe_catch_panic -> FRAME: std::panicking::try::h28c2e2ec1c3871ce -> std::panic::catch_unwind::h05e542185e35aabf -> std::rt::lang_start_internal::hd7efcfd33686f472 -> FRAME: main -> FRAME: __libc_start_main -> FRAME: _start -> FRAME: Unknown -> THREAD: prime_number 1
FRAME: backtrace::backtrace::trace::h3e91a3123a3049a5 -> FRAME: pprof::profiler::perf_signal_handler::h7b995c4ab2e66493 -> FRAME: Unknown -> FRAME: prime_number::main::h47f1058543990c8b -> FRAME: std::rt::lang_start::{{closure}}::h4262e250f8024b06 -> FRAME: std::rt::lang_start_internal::{{closure}}::h812f70926ebbddd0 -> std::panicking::try::do_call::h3210e2ce6a68897b -> FRAME: __rust_maybe_catch_panic -> FRAME: std::panicking::try::h28c2e2ec1c3871ce -> std::panic::catch_unwind::h05e542185e35aabf -> std::rt::lang_start_internal::hd7efcfd33686f472 -> FRAME: main -> FRAME: __libc_start_main -> FRAME: _start -> FRAME: Unknown -> THREAD: prime_number 1
功能
cpp
启用 cpp 解包。flamegraph
启用 flamegraph 报告格式。prost-codec
通过prost
启用 pprof protobuf 报告格式。protobuf-codec
通过protobuf
crate 启用 pprof protobuf 报告格式。frame-pointer
通过帧指针获取回溯。 仅限 nightly 版本
Flamegraph
pprof = { version = "0.13", features = ["flamegraph"] }
如果启用了 flamegraph
功能,您可以从报告中生成 flamegraph。 Report
结构有一个 flamegraph
方法,可以生成 flamegraph 并将其写入 Write
。
if let Ok(report) = guard.report().build() {
let file = File::create("flamegraph.svg").unwrap();
report.flamegraph(file).unwrap();
};
此外,可以指定自定义 flamegraph 选项。
if let Ok(report) = guard.report().build() {
let file = File::create("flamegraph.svg").unwrap();
let mut options = pprof::flamegraph::Options::default();
options.image_width = Some(2500);
report.flamegraph_with_options(file, &mut options).unwrap();
};
以下是一个生成的 flamegraph 示例
帧后处理器
在生成报告之前,提供了 frame_post_processor
作为修改原始统计数据的接口。如果您想对多个符号/线程进行分组或对某些符号进行解包,这个功能将有助于您。
例如
fn frames_post_processor() -> impl Fn(&mut pprof::Frames) {
let thread_rename = [
(Regex::new(r"^grpc-server-\d*$").unwrap(), "grpc-server"),
(Regex::new(r"^cop-high\d*$").unwrap(), "cop-high"),
(Regex::new(r"^cop-normal\d*$").unwrap(), "cop-normal"),
(Regex::new(r"^cop-low\d*$").unwrap(), "cop-low"),
(Regex::new(r"^raftstore-\d*$").unwrap(), "raftstore"),
(Regex::new(r"^raftstore-\d*-\d*$").unwrap(), "raftstore"),
(Regex::new(r"^sst-importer\d*$").unwrap(), "sst-importer"),
(
Regex::new(r"^store-read-low\d*$").unwrap(),
"store-read-low",
),
(Regex::new(r"^rocksdb:bg\d*$").unwrap(), "rocksdb:bg"),
(Regex::new(r"^rocksdb:low\d*$").unwrap(), "rocksdb:low"),
(Regex::new(r"^rocksdb:high\d*$").unwrap(), "rocksdb:high"),
(Regex::new(r"^snap sender\d*$").unwrap(), "snap-sender"),
(Regex::new(r"^snap-sender\d*$").unwrap(), "snap-sender"),
(Regex::new(r"^apply-\d*$").unwrap(), "apply"),
(Regex::new(r"^future-poller-\d*$").unwrap(), "future-poller"),
];
move |frames| {
for (regex, name) in thread_rename.iter() {
if regex.is_match(&frames.thread_name) {
frames.thread_name = name.to_string();
}
}
}
}
if let Ok(report) = guard.frames_post_processor(frames_post_processor()).report().build() {
let file = File::create("flamegraph.svg").unwrap();
report.flamegraph(file).unwrap();
}
与 pprof
一起使用
启用 protobuf
功能后,pprof-rs
还可以输出 profile.proto
格式。
match guard.report().build() {
Ok(report) => {
let mut file = File::create("profile.pb").unwrap();
let profile = report.pprof().unwrap();
let mut content = Vec::new();
profile.encode(&mut content).unwrap();
file.write_all(&content).unwrap();
println!("report: {}", &report);
}
Err(_) => {}
};
然后您可以使用 pprof
命令与 profile.pb
一起使用。例如
~/go/bin/pprof -svg profile.pb
然后,pprof
将根据配置生成一个 SVG 文件。
与 criterion
集成
启用 criterion
功能后,在 pprof-rs
中提供了一个 criterion 自定义分析器。
use pprof::criterion::{PProfProfiler, Output};
criterion_group!{
name = benches;
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
targets = bench
}
criterion_main!(benches);
运行基准测试后,您可以在 target/criterion/<name-of-benchmark>/profile.svg
找到 flamegraph。使用 Output::Protobuf
选项还可以获得 protobuf
输出,这些输出位于 target/criterion/<name-of-benchmark>/profile.pb
。
有关更多详细信息,您可以查看 examples/criterion.rs
,以及 criterion
的分析文档。要快速入门,您可以使用以下命令运行此示例:cargo run --example criterion --release --features="flamegraph criterion" -- --bench --profile-time 5
为什么不...
已经有很多分析器了,为什么我们还要创建一个新的?在这里,我们比较了 pprof-rs
和其他流行的分析器,以帮助您选择最适合的分析器。
gperftools
gperftools
也是一个集成的分析器。Rust 中也有一个名为 cpuprofiler
的 gperftools
包装器,这使得它可以在 Rust 程序中编程。
优点
pprof-rs
拥有一个现代化的构建系统,并且可以轻松地集成到 Rust 程序中,而编译gperftools
静态版本存在错误。pprof-rs
有一个本地的 Rust 接口,而gperftools
的包装器只是一个包装器。- 使用 Rust 编程可以保证线程安全。
缺点
gperftools
是一组性能分析工具,包括 CPU 分析器、堆分析器等。而pprof-rs
目前专注于 CPU 分析器。
perf
perf
是 Linux 中的一个性能分析工具。
优点
- 使用
pprof-rs
,您不需要启动另一个进程来使用perf
。 pprof-rs
可以轻松地集成到 Rust 程序中,这意味着您不需要安装任何其他程序。pprof-rs
拥有一个现代化的可编程接口,可以对其进行修改。pprof-rs
理论上支持所有 POSIX 系统,并且可以轻松地支持更多系统。
缺点
perf
比比pprof-rs
更丰富。perf
与 Linux 高度集成。
实现
当开始性能分析时,使用了 setitimer
系统调用来设置一个定时器,该定时器会以固定的时间间隔向此程序发送 SIGPROF 信号。
接收到 SIGPROF 信号时,信号处理程序将捕获堆栈跟踪并增加计数。过了一段时间后,分析器可以获取所有可能的堆栈跟踪及其计数。最后,我们可以生成包含分析器数据的报告。
然而,现实世界充满了荆棘。在实现过程中有许多值得注意的部分。
堆栈跟踪
不幸的是,没有100%健壮的堆栈跟踪方法。gperftools已经进行了一些相关研究。[一些相关研究](https://github.com/gperftools/gperftools/wiki/gperftools%27-stacktrace-capturing-methods-and-their-issues)。pprof-rs
使用 backtrace-rs
,最终使用由 libgcc
提供的 libunwind
。
警告:如前述 gperftools 文档所述,libgcc
提供的 libunwind
不是信号安全的。
libgcc 的 unwind 方法在信号处理程序中使用是不安全的。一个特定的死锁原因是在程序抛出异常时,性能分析计时器发生。
可以通过添加一个 blocklist 来解决这个问题
let guard = pprof::ProfilerGuardBuilder::default().frequency(1000).blocklist(&["libc", "libgcc", "pthread", "vdso"]).build().unwrap();
应该也将 vdso
添加到 blocklist 中,因为在某些发行版中(例如 ubuntu 18.04),vdso 中的 dwarf 信息是不正确的。
帧指针
pprof-rs
还支持通过帧指针进行 unwind,而无需使用 libunwind
。然而,Rust 编译器附带的标准库中并非每个函数都有正确的帧指针,因此您需要使用 cargo +nightly -Z build-std
来从源代码构建标准库。
由于我们无法在信号处理程序中获取堆栈边界,因此也无法确保安全性。如果帧指针被设置为一个错误的值,程序将崩溃。
信号安全性
保证信号安全性是困难的,但并非那么困难。
首先,我们必须避免死锁。当分析器采样或报告时,它将在分析器上获取全局锁。特别是,当正在运行的程序从分析器获取报告(这将持有锁)时,同时触发 SIGPROF 信号,分析器想要采样(也将持有锁),就会发生死锁。所以我们不在信号处理程序中等待锁,而是在信号处理程序中 try_lock
。如果无法获取全局锁,分析器将直接放弃。
然后,信号安全 POSIX 函数相当有限,如 [此处](http://man7.org/linux/man-pages/man7/signal-safety.7.html) 所列。最令人烦恼的问题是,我们无法在信号处理程序中使用 malloc
。因此,我们只能在分析器中使用预分配的内存。最简单的方法是将每个样本串行写入文件。我们使用一个固定大小的 hashmap 进行优化,该 hashmap 具有固定数量的桶,每个桶是一个具有固定数量的项的数组。如果 hashmap 已满,则弹出具有最小计数的项并将其写入临时文件。
已添加单元测试以确保样本函数中没有使用 malloc
。
futex
也不安全在信号处理程序中使用。因此,我们使用自旋锁来避免使用 futex
。
TODO
- 停止分析器后,恢复原始 SIGPROF 处理程序。
最低支持的 Rust 版本
Rust 1.64 或更高版本。
最低支持的Rust版本将来可能会改变,但将通过增加小版本号来实现。
许可证
依赖项
~7–21MB
~334K SLoC