[cargo-]flamegraph
一个由Rust驱动的火焰图生成器,额外支持Cargo项目!它可以用于分析任何东西,不仅仅是Rust项目!无需perl或管道 <3
如何使用火焰图:什么是火焰图,以及如何使用它来指导系统性能工作?
[!提示] 你可能还想尝试samply,它提供了一个更交互式的UI,使用Firefox的Profiler网页UI无缝集成。它也是用Rust编写的,并且对macOS支持更好。
在Linux上依赖perf,其他系统依赖dtrace。基于@jonhoo出色的Inferno全Rust火焰图生成库构建! Windows正在获得dtrace支持,如果你尝试使用,请让我们知道效果如何。:D
注意:如果你在Linux上使用lld或mold,必须使用--no-rosegment
标志。否则perf将无法生成准确的堆栈跟踪(解释)。例如,对于lld:
[target.x86_64-unknown-linux-gnu]
linker = "/usr/bin/clang"
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"]
对于mold:
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=/usr/local/bin/mold", "-Clink-arg=-Wl,--no-rosegment"]
安装
cargo install flamegraph
这将使flamegraph
和cargo-flamegraph
二进制文件在你的cargo二进制目录中可用。在大多数系统上,这通常类似于~/.cargo/bin
。
Linux上的要求:
Debian(x86和aarch)
注意:Debian bullseye(截至2022年的当前稳定版本)打包了一个过时的Rust版本,不满足flamegraph的要求。你应该使用rustup安装最新版本的Rust,或升级到Debian bookworm(当前测试版本)或更新版本。
sudo apt install -y linux-perf
Ubuntu(x86)
在aarch上不工作,使用Debian发行版,或提交PR解决Ubuntu上的问题
sudo apt install linux-tools-common linux-tools-generic linux-tools-`uname -r`
Ubuntu/Ubuntu MATE(树莓派)
sudo apt install linux-tools-raspi
Pop!_OS
sudo apt install linux-tools-common linux-tools-generic
Shell自动完成
目前,只有flamegraph
支持自动完成。支持的shell包括bash
、fish
、zsh
、powershell
和elvish
。
cargo-flamegraph
不支持自动完成,因为为自定义cargo子命令实现这一功能并不那么直接。详情请参见#153。
启用自动完成的方式取决于你的shell,例如
flamegraph --completions bash > $XDG_CONFIG_HOME/bash_completion # 或 /etc/bash_completion.d/
示例
# 如果你想分析任意可执行文件:
flamegraph [-o my_flamegraph.svg] -- /path/to/my/binary --my-arg 5
# 或者如果可执行文件已经在运行,你可以通过`-p`(或`--pid`)标志提供PID:
flamegraph [-o my_flamegraph.svg] --pid 1337
# 注意:默认情况下,perf尝试为每个样本的每个堆栈帧计算哪些函数是内联的。
# 这可能需要很长时间(参见 https://github.com/flamegraph-rs/flamegraph/issues/74)。
# 如果你不想这样,可以向flamegraph传递--no-inline:
flamegraph --no-inline [-o my_flamegraph.svg] /path/to/my/binary --my-arg 5
# 通过cargo-flamegraph二进制文件提供cargo支持!
# 默认分析cargo run --release
cargo flamegraph
# 默认使用`--release`配置,
# 但你可以覆盖这一点:
cargo flamegraph --dev
# 如果你想分析特定的二进制文件:
cargo flamegraph --bin=stress2
# 如果你想像使用cargo run那样传递参数:
cargo flamegraph -- my-command --my-arg my-value -m -f
# 如果你想使用有趣的perf或dtrace选项,使用`-c`
# 这对于关联诸如分支未命中、缓存未命中等事项很有用,
# 或者通过`perf list`或dtrace可用于你系统的任何其他选项
cargo flamegraph -c "record -e branch-misses -c 100 --call-graph lbr -g"
# 运行criterion基准测试
# 注意,最后的--bench对于`criterion 0.3`在基准模式而不是测试模式下运行是必需的。
cargo flamegraph --bench some_benchmark --features some_features -- --bench
cargo flamegraph --example some_example --features some_features
# 分析单元测试。
# 注意,如果`--unit-test`是最后一个标志,则必须使用分隔符`--`。
cargo flamegraph --unit-test -- test::in::package::with::single::crate
cargo flamegraph --unit-test crate_name -- test::in::package::with::multiple:crate
cargo flamegraph --unit-test --dev test::may::omit::separator::if::unit::test::flag::not::last::flag
# 分析集成测试。
cargo flamegraph --test test_name
用法
flamegraph
相当简单。cargo-flamegraph
更复杂:
用法: cargo flamegraph [选项] [-- <尾随参数>...]
参数:
[尾随参数]... 传递给被分析二进制文件的尾随参数
选项:
--dev 使用dev配置构建
--profile <配置> 使用指定配置构建
-p, --package <包> 包含要运行二进制文件的包
-b, --bin <二进制文件> 要运行的二进制文件
--example <示例> 要运行的示例
--test <测试> 要运行的测试二进制文件(当前分析测试工具和二进制文件中的所有测试)
--unit-test [<单元测试>] 要单元测试的crate目标,如果crate只有一个目标,可以省略<unit-test>(当前分析测试工具和二进制文件中的所有测试;测试选择可以作为尾随参数在`--`分隔符之后传递)
--bench <基准测试> 要运行的基准测试
--manifest-path <清单路径> Cargo.toml的路径
-f, --features <特性> 要启用的构建特性
--no-default-features 禁用默认特性
-r, --release 无操作。为与`cargo run --release`兼容
-v, --verbose 打印额外输出以帮助调试问题
-o, --output <输出> 输出文件 [默认: flamegraph.svg]
--open 用默认程序打开输出的.svg文件
--root 使用root权限运行(使用`sudo`)
-F, --freq <频率> 采样频率(Hz)[默认: 997]
-c, --cmd <自定义命令> 用于调用perf/dtrace的自定义命令
--deterministic 选择颜色,使函数的颜色在运行之间不变
-i, --inverted 生成上下颠倒的火焰图
--reverse 生成堆栈反转的火焰图
--notes <字符串> 在SVG中设置嵌入的注释
--min-width <浮点数> 忽略小于<浮点数>像素的函数 [默认: 0.01]
--image-width <图像宽度> 图像宽度(像素)
--palette <调色板> 颜色调色板 [可能的值: hot, mem, io, red, green, blue, aqua, yellow, purple, orange, wakeup, java, perl, js, rust]
--skip-after <函数> 在<函数>之下切断堆栈帧;可以重复
--flamechart 生成火焰图表(按时间排序,不合并堆栈)
--ignore-status 忽略perf的退出代码
--no-inline 因性能问题禁用perf脚本的内联
--post-process <后处理> 运行命令处理折叠的堆栈,从stdin输入并输出到stdout
-h, --help 打印帮助
-V, --version 打印版本
然后用浏览器打开生成的flamegraph.svg
,因为大多数图像查看器不支持交互式svg文件。
为非特权用户启用perf
要在不以root身份运行的情况下启用perf,你可以降低proc中的perf_event_paranoid
值到适合你环境的水平。
最宽松的值是-1
,但可能不适合你的安全需求等...
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
macOS上的DTrace
在macOS上,要启用DTrace,唯一的方法是以超级用户身份运行。这应该通过调用sudo flamegraph ...
或cargo flamegraph --root ...
来完成。不要使用sudo cargo flamegraph ...
;
这可能会因为Cargo的构建系统以root身份运行而导致问题。
请注意,如果被测试的二进制文件是用户感知的,这确实会改变其行为。
改善使用--release
运行时的输出
由于优化等原因...有时在分析发布版本时,火焰图中呈现的信息质量会受到影响。
为了在某种程度上抵消这一点,你可以在Cargo.toml
文件中设置以下内容:
[profile.release]
debug = true
或设置环境变量CARGO_PROFILE_RELEASE_DEBUG=true。
请注意,测试、单元测试和基准测试在发布模式下使用bench
配置(参见这里)。
与基准测试一起使用
为了对现有基准测试进行性能分析,你应该设置一些配置。
在你的Cargo.toml
文件中设置以下内容以运行基准测试:
[profile.bench]
debug = true
使用perf和dtrace的自定义路径
如果设置了PERF
或DTRACE
环境变量,
它将被用作相应工具的命令。
例如,要使用~/bin
中的perf
:
env PERF=~/bin/perf flamegraph /path/to/my/binary
火焰图指导的系统性能工作
火焰图用于可视化你的程序中时间消耗的位置。 每秒多次中断程序的线程,并记录你代码中的当前位置 (基于线程的指令指针),以及到达那里所调用的函数链。 这称为堆栈采样。然后处理这些样本, 共享公共函数的堆栈被加在一起。然后生成一个SVG, 显示测量的调用堆栈,宽度与包含它们的 所有堆栈样本的比例成正比。
y轴显示堆栈深度编号。在查看火焰图时, 你程序的主函数会更靠近底部,被调用的函数 会堆叠在上面,它们调用的函数会堆叠在它们上面,以此类推...
x轴跨越所有样本。它不显示从左到右的时间流逝。 从左到右的顺序没有意义。
每个框的宽度显示该函数在CPU上的总时间 或是调用堆栈的一部分。如果一个函数的框比其他函数宽, 这意味着它每次执行消耗的CPU比其他函数多, 或者它被调用的次数比其他函数多。
每个框的颜色没有特殊意义,是随机选择的。
火焰图适合可视化你程序中运行时最昂贵的部分, 这很棒,因为...
人类在猜测性能方面很糟糕!
特别是从C和C++转到Rust的人经常会过度优化 LLVM自己能够优化掉的代码。在开始 微优化、最小化分配等之前,总是更好 以清晰明显的方式编写Rust...
许多看起来会有糟糕性能的东西在Rust中实际上 是廉价或 火焰图显示了占用时间的事物,但它们是一种用于对被测系统进行高层次和初步观察的采样技术。它们非常适合找出需要更仔细研究的内容,通常根据火焰图就能明显看出如何改进某些东西,但它们更多是用于选择优化目标,而不是优化测量工具本身。它们粒度较粗,难以进行差异比较(尽管这项功能可能很快就会得到支持)。此外,由于火焰图是基于某事物占总时间的比例,如果你不小心使其他内容变得非常慢,它会在火焰图上显示所有其他内容变小,即使整个程序运行时间变慢了很多,你希望优化的项目看起来反而变小了。
使用火焰图来确定你想要优化的内容,然后设置一个测量环境,以便确定实际上是否有改进,这是个好主意。
- 使用火焰图找出一组优化目标
- 为这些优化目标创建基准测试,并在适当的情况下使用类似cachegrind和cg_diff这样的工具来测量CPU指令,并将它们与之前的版本进行比较。
- 在许多情况下,测量CPU指令通常比测量运行工作负载所需的时间更好,因为你的机器上的后台任务可能会导致某些东西在物理时间上变慢,但如果你真的使实现变快了,它很可能与减少的总CPU指令数有更强的相关性。
- CPU上花费的时间并不是全部情况,因为还有等待IO完成的时间,这在像perf这样只测量CPU上消耗时间的工具中是不会被计算的。查看Brendan Gregg关于Off-CPU Accounting的文章以获取更多相关信息!
性能理论101:定量工程基础
- 在真实硬件上使用真实工作负载,否则你的数据不一定与生产环境中发生的情况有很大关系
- 我们所有的猜测在某种程度上都是错误的,所以我们必须测量我们工作的效果。通常,看起来不应该快的简单代码实际上比看起来优化过的代码快得多。我们需要测量我们的优化,以确保我们没有使代码既难以阅读又变慢。
- 在改变任何东西之前进行测量,并将结果保存在安全的地方!许多分析工具在再次运行时会覆盖旧的输出,所以确保在开始之前保存数据,以便你可以比较前后的结果。
- 在已经预热且没有做其他事情的机器上进行测量,并给它时间从上一个工作负载中冷却下来。CPU在空闲时会进入睡眠状态和节能模式,如果温度过高,它们也会降频(有时SIMD会导致运行变慢,因为它会使温度升高到核心必须降频的程度)。
性能理论202:USE方法
USE方法是一种快速定位性能问题的方式,同时最大限度地减少发现工作。它更多是关于发现生产问题而不是直接与火焰图相关,但如果你要进行性能分类,这是一个很好的技术工具,火焰图可以帮助识别需要深入分析队列的组件。
计算机中的一切都可以被视为一个资源,前面有一个队列,可以同时处理一个或多个请求。我们计算机和程序中的各种系统在一段时间内可以完成一定量的工作,然后请求开始堆积并等待,直到它们能够被服务。
有些资源可以处理越来越多的工作而不会降低性能,直到它们达到最大利用率。网络设备在很大程度上可以被认为是以这种方式工作的。其他资源在达到最大利用率之前就开始饱和,比如磁盘。
磁盘(特别是旋转磁盘,但即使是SSD)如果允许更多的工作排队,它们会做更多的工作,直到达到工作负载的最大吞吐量,但每个请求的延迟会在达到100%利用率之前增加,因为磁盘开始服务每个请求的时间会变长。调优磁盘性能通常涉及测量各种IO队列深度,以确保它们足够高以获得良好的吞吐量,但又不至于高到使延迟变得不理想。
无论如何,我们系统中几乎所有东西都可以分解为基于3个高层次特征进行分析:
- 利用率是被测系统实际做有用工作服务请求的时间量,可以测量为可用时间中用于服务请求的百分比
- 饱和度是请求在被服务之前必须等待的情况。这可以通过一段时间内的队列深度来测量
- 错误是当事情开始失败时,比如当队列不再能接受任何新请求时 - 就像当TCP连接被拒绝因为系统的TCP积压已经充满了尚未被用户空间程序accept的连接。
这形成了开始应用USE方法来定位复杂系统中与性能相关问题的必要背景!
方法是:
- 列举可能表现不佳的各种资源 - 也许通过创建火焰图并查找占总运行时间比预期更多的函数
- 选择其中一个
- (错误)检查错误,如TCP连接失败、其他IO失败、日志中的不良信息等...
- (利用率)测量系统的利用率,看看它的吞吐量是否接近已知的最大值,或者是已知开始出现饱和的点
- (饱和度)实际上是否发生了饱和?请求是否在被服务之前在排队等待?延迟是否在增加而吞吐量保持不变?
这些探测性问题作为一个锐利的手电筒,大多数时候能快速识别出潜在的问题。
如果你想了解更多关于这方面的信息,可以查看Brendan Gregg的博客文章。我倾向于建议,任何正在成为SRE的人都应该把Brendan的系统性能这本书作为他们阅读的第一批内容之一,以了解如何在生产系统中快速测量这些东西。
USE方法源于一个称为队列理论的研究领域,这个领域对计算机世界以及人类所从事的许多其他物流工作产生了巨大影响。
性能法则
如果你想更深入地研究理论,了解这些法则!