共计 10542 个字符,预计需要花费 27 分钟才能阅读完成。
** 原文 URL:http://www.brendangregg.com/o…
此文章为翻译,并非原创,仅供学习交换,不得转载。**
性能问题可分为以下两种类型之一:
* On-CPU:线程在 CPU 上运行的工夫。* Off-CPU:计时器、分页 / 替换等上阻塞时所破费的工夫。
Off-CPU 是一种用于测量和钻研 CPU 工夫之外以及上下文堆栈跟踪的性能分析方法。它不同于 On-CPU 仅剖析在 CPU 上执行的线程。它的指标是剖析被阻止线程状态,如下图中的蓝色所示。
Off-CPU 外剖析是对 On-CPU 剖析的补充,因而能够 100% 地理解线程工夫。此办法也不同于应用程序阻塞的跟踪技术,因为此办法针对内核调度程序被阻塞起因的剖析,比应用程序阻塞剖析的利用场景更广。
造成线程 Off-CPU 的起因有很多,包含 I/O 和锁,但也有一些与以后线程的执行无关的起因,包含因为对 CPU 资源的高需要而导致的非被迫上下文切换和中断。无论出于什么起因,如果在工作负载申请(同步代码门路)期间产生这种状况,则造成提早。
在本文中,我将介绍 Off-CPU 工夫分析方法。
目录:
1. 先决条件
2. 介绍
3. 开销
4. Linux
5. 敞开 CPU 工夫
6. CPU 外剖析
7. 申请上下文
8. 注意事项
9. 火焰图
10. 唤醒
11. 其余工作
12. 产地
13. 摘要
14. 更新
先决条件
Off-CPU 剖析须要堆栈跟踪可用。许多应用程序都是应用 -fomit-frame-pointer 的 gcc 选项编译的,这突破了基于帧指针的堆栈办法。VM 运行时(如 Java)可自在编译办法,在没有额定帮忙的状况下可能无奈找到其符号信息,从而导致仅能十六进制堆栈跟踪。请参阅对于修复 Stack Traces 和 JIT Symbols for perf。
介绍
为了解释 Off-CPU 剖析的作用,我先总结 CPU 采样和跟踪进行比拟。而后,我将总结两种 Off-CPU 分析方法:跟踪和采样。尽管十多年来我始终在推广 Off-CPU 剖析,但它依然不是一个宽泛应用的办法,局部起因是在生产 Linux 环境中不足工具来测量它。当初,随着 eBPF 和较新的 Linux 内核(4.8+)的不断更新,状况正在发生变化。
- CPU 采样
许多传统的剖析工具应用所有 CPU 的流动定时采样,收集以后指令地址(程序计数器)的快照或以特定工夫距离或速率(例如 99 赫兹)的整个堆栈回溯的快照。这将提供运行函数或堆栈跟踪的计数,从而能够计算 CPU 周期的破费地位的正当估计值。在 Linux 上,采样模式下的 perf 工具(例如 -F 99)执行工夫 CPU 采样。
思考应用程序函数 A()调用函数 B(),它使阻塞零碎调用:
下面的 CPU 采样尽管这对于钻研 On-CPU 的问题(包含热代码门路和自适应互斥旋转)十分无效,但当应用程序被阻止并处于 Off-CPU 期待时,它不会收集数据。
- 应用程序跟踪
应用程序跟踪对函数进行检测,以便当工夫戳开始 ”(” 和 ” 完结 ”)时收集工夫戳,以便计算函数中破费的工夫。如果工夫戳包含通过的工夫和 CPU 工夫(eg, using times(2) or getrusage(2)),那么也能够计算哪些性能在 CPU 上速度较慢,而哪些性能因为被阻止而在 CPU 上速度较慢。与采样不同,这些工夫戳的分辨率(纳秒)十分高。
这个办法尽管无效,但毛病是须要跟踪所有应用程序函数,这会对性能产生重大影响(并影响您尝试测量的性能),或者抉择个别函数跟踪,并心愿不会错过其余可能被阻塞的函数。
- Off-CPU 跟踪
我将在这里总结一下,而后在下一节中更具体地解释它。
应用这种办法,仅跟踪切换线程 Off-CPU 的内核函数,工夫戳以及用户堆栈跟踪。这侧重于跟踪 Off-CPU 事件,而无需跟踪所有应用程序性能,也不须要晓得应用程序是什么。此办法实用于任何阻塞事件,实用于任何应用程序:MySQL、Apache、Java 等。
Off-CPU tracing captures all wait events for any application.
稍后在此文中,我将跟踪内核 Off-CPU 事件,并包含一些应用程序级检测以筛选出异步等待时间(例如,期待工作的线程)。与应用程序级检测不同,我不须要寻找每个可能阻塞 Off-CPU 的地位; 我只须要确定应用程序位于工夫敏感的代码门路中(例如,在 MySQL 查问期间),以便提早与工作负载同步。
Off-CPU 跟踪是我用于 Off-CPU 剖析的次要办法。但也有采样。
- Off-CPU 采样
此办法应用工夫采样从未在 CPU 上运行的线程捕捉阻塞的堆栈跟踪。它也能够通过 wall-time 探查器来实现:它始终采样所有线程,无论它们是 On-CPU 上还是 Off-CPU。而后能够筛选 wall-time 配置文件输入,以查找 Off-CPU 堆栈。
零碎探查器很少应用 Off-CPU 采样。采样通常实现为每 CPU 计时器中断,而后查看以后正在运行的中断过程:生成 CPU 上配置文件。Off-CPU 采样器必须以不同的形式工作:要么在每个应用程序线程中设置计时器来唤醒它们并捕捉堆栈,要么让内核每隔一段时间走走所有线程并捕捉它们的堆栈。
开销
正告:应用 Off-CPU 跟踪时,调度程序事件可能十分频繁(在极其状况下,每天数百万事件),只管跟踪器可能只会给每个事件减少大量开销,但因为开销可能减少并变得显著,因而只能减少大量开销。Off-CPU 采样也有开销问题,因为零碎可能有数以万计的线程必须一直采样,比 CPU 采样的开销仅高于 CPU 采样数量。
要应用 Off-CPU 剖析,须要留神每一步的开销。将每个事件转储到用户空间进行前期解决的跟踪器很容易变得令人望而生畏,每分钟创立 1 Gb 的跟踪数据。该数据必须写入文件系统和存储设备,前期解决也会破费更多的 CPU。这就是为什么可能执行内核摘要(如 Linux eBPF)的跟踪器对于缩小开销和使 Off-CPU 剖析切实可行十分重要的起因。还要提防反馈循环:跟踪器跟踪本人造成的事件。
如果我齐全应用新的调度程序跟踪器,我将开始仅跟踪十分之一秒(0.1 秒),而后从那里向上加长,同时亲密测量对系统 CPU 利用率、应用程序申请速率和应用程序提早的影响。我还将思考上下文切换的速率(例如,通过 vmstat 中的 ”cs” 列进行测量),并在具备较高速率的服务器上更加小心。
为了让您理解开销,我测试了运行 Linux 4.15 的 8 CPU 零碎,大量 MySQL 负载导致 102k 个上下文替换 / 秒。服务器以 CPU 饱和度(0% 闲暇)运行,因而任何跟踪开销都会导致应用程序性能显著损失。而后,我比拟了通过调度程序跟踪进行 CPU 剖析与 Linux perf 和 eBPF,它们演示了不同的办法:用于事件转储的 perf 和用于内核中的 eBPF 汇总:
- 应用 perf 跟踪每个调度程序事件导致跟踪时吞吐量降落 9%,在 perf.data 捕捉文件刷新到磁盘时,吞吐量偶然会降落 12%。该文件最终为 224 MB,用于 10 秒的跟踪。而后通过运行 perf 脚本进行符号转换对文件进行公布解决,这会破费 13% 的性能降落(失落 1 个 CPU)35 秒。您能够总结这一点,说 10 秒 perf 跟踪破费 9-13% 开销 45 秒。
- 应用 eBPF 对内核上下文中的堆栈进行计数,在 10 秒跟踪期间导致吞吐量降落 6%,从初始化 eBPF 时 1 秒内的吞吐量降落 13% 开始,随后是 6 秒的后处理(已汇总堆栈的符号分辨率),老本降落了 13%。因而,10 秒跟踪须要 6-13% 的开销 17 秒。
减少跟踪持续时间时会产生什么?对于 eBPF,它只捕捉和转换惟一堆栈,不会随着跟踪持续时间线性扩大。我通过将跟踪从 10 秒减少到 60 秒来测试这一点,这仅将 eBPF 后处理从 6 秒减少到 7 秒。perf 将其前期解决从 35 秒减少到 212 秒,因为它须要解决 6 倍的数据量。为了齐全理解这一点,值得注意的是,前期解决是一种用户级流动,能够调整以缩小对生产工作负载的烦扰,例如应用不同的打算程序优先级。设想一下,此流动的 CPU 下限为 10%:性能损失可能微不足道,eBPF 后处理阶段可能须要 70 秒 – 还不算太坏。然而,perf 脚本工夫可能须要 2120 秒(35 分钟),这将妨碍考察。perf 的开销不仅仅是 CPU,它也开销磁盘 I/O。
此 MySQL 示例与生产工作负载相比如何?它过后在进行 102k 的上下文开关 / 秒,这是绝对较高的:目前我看到的许多生产零碎都在 20-50k/ s 范畴内。这表明,这里形容的开销比我在这些生产零碎上看到的要高 2 倍。然而,MySQL 堆栈深度绝对较轻,通常只有 20-40 帧,而生产应用程序能够超过 100 帧。这也很重要,而且可能意味着我的 MySQL 堆栈行走开销可能只有我在生产中看到的一半。因而,这能够均衡我的生产零碎。
Linux: perf, eBPF
Off-CPU 剖析是一种通用办法,可在任何操作系统上工作。我将演示应用 Off-CPU 跟踪在 Linux 上执行该办法,而后在后两节中总结其余操作系统。
Linux 上有许多用于 Off-CPU 剖析的跟踪器。我将在这里应用 eBPF,因为它能够很容易地执行堆栈跟踪和工夫内核中的摘要。eBPF 是 Linux 内核的一部分,我通过 bcc 前端工具应用它。这些至多须要 Linux 4.8 来反对堆栈跟踪。
你可能想晓得我在 eBPF 之前是如何进行 CPU 外剖析的。许多不同的办法,包含每种阻塞类型的齐全不同的办法:存储 I/O 的存储跟踪、调度程序提早的内核统计信息等等。要理论执行 Off-CPU 剖析之前,我曾应用过 SystemTap,也应用过 perf 事件日志记录,只管这具备更高的开销(我在 perf_events 非 CPU 工夫火焰图中写过)。有一次,我写了一个简略的 wall-time 内核堆栈探查器 proc-profiler.pl,它采样 /proc/PID/ 堆栈给定的 PID。它工作得很好。我也不是第一个破解这样的墙工夫分析器,看 poormansprofiler 和 Tanel Poder’s quick’n’dirty 故障排除。
Off-CPU Time
这是线程期待 Off-CPU(阻塞工夫)而不是 On-CPU 的工夫。它能够作为持续时间(已由 /proc 统计信息提供)的总计进行测量,或测量每个阻塞事件(通常须要一个跟踪器)。
首先,我将展现您可能曾经相熟的工具的总敞开 CPU 工夫。The time(1) command. Eg, timing tar(1):
tar 运行大概一分钟,但工夫命令显示,它只花了 1.0 秒的用户模式 CPU 工夫,以及 11.6 秒的内核模式 CPU 工夫,总共 50.8 秒的运行工夫。咱们错过了 38.2 秒!这是 tar 命令在 Off-CPU 的工夫,毫无疑问,作为其存档生成一部分,执行存储 I/O。
为了更具体地查看 Off-CPU 工夫,能够应用内核调度程序函数的动静跟踪或应用 sched 跟踪点的动态跟踪。bcc/eBPF 我的项目包含由 Sasha Goldshtein 开发的 cpudist,该我的项目具备测量 Off-CPU 工夫的 -O 模式。这须要 Linux 4.4 或更高版本。
Measuring tar’s off-CPU time:
这表明大多数阻塞事件在 64 到 511 微秒之间,这与闪存存储 I/O 提早(这是基于闪存的零碎)统一。跟踪时最慢的阻塞事件达到 65 到 131 毫秒的范畴(此直方图中的最初一个存储桶)。
此 Off-CPU 敞开工夫由什么组成?从线程阻塞到再次开始运行的所有内容,包含调度程序提早。
在编写本文时,cpudist 应用 kprobes(内核动静跟踪)来检测 finish_task_switch()内核函数。(出于 API 稳定性起因,它应该应用 sched 跟踪点,但第一次尝试未胜利,当初已 reverted。)
finish_task_switch()的原型是:
为了让您理解此工具的工作原理:finish_task_switch()函数在下一个运行线程的上下文中调用。eBPF 程序能够应用 kprobes 检测此函数和参数,获取以后 PID(通过 bpf_get_current_pid_tgid()),还能够获取高分辨率工夫戳(bpf_ktime_get_ns())。这是上述摘要所需的全副信息,它应用 eBPF 映射在内核上下文中无效地存储直方图存储桶。这里是 cpudist 的残缺起源。
eBPF 不是 Linux 上测量 Off-CPU 外工夫的惟一工具。perf 工具在其 perf sched timehist 工夫设置输入中提供 ”wait time” 列,该列排除了打算程序工夫,因为它别离显示在相邻列中。该输入显示每个调度程序事件的等待时间,并且与 eBPF 直方图摘要更须要测量的开销。
将 Off-CPU 工夫作为直方图进行测量有点有用,但不是很多。咱们真正想晓得的是上下文 – 为什么线程会阻塞和 Off-CPU。这是 Off-CPU 剖析的重点。
Off-CPU Analysis
Off-CPU 剖析是剖析 Off-CPU 工夫以及堆栈跟踪以确定线程阻塞起因的办法。因为以下起因,Off-CPU 跟踪剖析技术很容易实现:
Application stack traces don’t change while off-CPU.
这意味着咱们只须要在 Off-CPU 周期的开始或完结时测量一次堆栈跟踪。通常测量完结更容易,因为你总是在记录时间距离。以下是用于应用堆栈跟踪测量 Off-CPU 工夫的伪代码:
对此有一些注意事项:所有测量都产生在一个检测点,上下文切换例程的开端,该例程位于下一个线程的上下文中(例如,Linux finish_task_switch()函数)。这样,咱们能够在检索该持续时间的上下文的同时计算 Off-CPU 持续时间,只需获取以后上下文(pid、execname、用户堆栈、内核堆栈),跟踪器就很容易了。
这就是我的 fcputime bcc / eBPF 程序所做,它至多须要 Linux 4.8 能力工作。我将演示应用 bcc/eBPF 的 fcputime 来测量 tar 程序的阻塞堆栈。我将限度为内核堆栈,仅从(-K)开始):
我曾经将输入截断为最初三个堆栈。最初一个,显示总共 18.4 秒的 Off-CPU 工夫,完结在读取零碎调用 io_schedule()– 这示意 tar 读取文件内容,并被磁盘 I/ O 阻塞。它下面的堆栈显示 662 毫秒 Off-CPU 工夫,最终完结在存储 I /O xfs_buf_submit_wait()函数。顶部堆栈总共为 203 毫秒,完结在 getdents 零碎调用(目录列表)函数上。
解释这些堆栈跟踪须要对源代码的一些相熟,这取决于应用程序的复杂程度及其语言。这样做的越快,你就能辨认出雷同的函数和堆栈。
我当初将包含用户级堆栈:
这不起作用:用户级堆栈只是 ”[unknown]”。起因是,默认版本的 tar 编译时没有帧指针,并且此版本的 bcc/eBPF 须要它们来跟踪堆栈跟踪。我想展现这个 gotcha 是什么样子的,以防你打它。
我修复了 tar 的堆栈(请参阅后面的先决条件),以查看它们看起来像什么:
好的,所以看起来 tar 具备文件系统树的递归行走算法。
这些堆栈跟踪很棒 – 它显示了应用程序阻止和期待 CPU 外,以及多长时间。这正是我通常要找的信息。然而,阻止堆栈跟踪并不总是那么乏味,因为有时您须要查找申请同步上下文。
Request-Synchronous Context
期待工作的应用程序(如具备期待套接字的线程池的 Web 服务器)对 Off-CPU 剖析提出了挑战:通常大多数阻塞工夫将位于堆栈中期待工作,而不是执行工作。这会用不太乏味的堆栈来吞没输入。
作为这种景象的示例,上面是 MySQL 服务器过程的 Off-CPU 堆栈,该过程不执行任何操作,秒零申请:
各种线程轮询工作和其余后台任务。这些后盾堆栈能够主导输入,即便对于忙碌的 MySQL 服务器。我通常要查找的是数据库查问或命令期间的 CPU 敞开工夫。这是重要的工夫 – 挫伤最终客户的工夫。要在输入中查找堆栈,我须要在查问上下文中查找堆栈。
例如,当初从忙碌的 MySQL 服务器:
此堆栈标识查问期间的一些工夫(提早)。do_command()-> mysql_execute_command()代码门路是放弃。我晓得这一点,因为我相熟来自此堆栈的所有局部的代码:MySQL 和内核外部。
能够设想编写一个简略的文本后处理器,它依据一些特定于应用程序的模式匹配来挑选出感兴趣的堆栈。这兴许工作失常。还有另外一种办法,尽管还须要应用程序细节,但效率更高一些:扩大跟踪程序以检测应用程序申请(do_command()函数,在此 MySQL 服务器示例中),而后仅记录在应用程序申请期间产生的 CPU 敞开工夫。我以前做过,它会有所帮忙。
Caveats
最大的正告是 Off-CPU 剖析的开销,如后面的开销局部所述,而后是获取堆栈跟踪的工作,我在后面的 ” 先决条件 ” 局部中总结了这一点。还有一些调度程序提早和非被迫上下文切换须要留神,我将在这里总结,并唤醒堆栈,我将在下一节中探讨。
Scheduler Latency
这些堆栈中短少的是,如果 Off-CPU 工夫包含期待 CPU 运行队列所破费的工夫。此工夫称为打算程序提早、运行队列提早或调度程序队列提早。如果 CPU 以饱和状态运行,则每次线程阻塞时,它可能会接受额定的工夫期待其关上 CPU 后被唤醒。该工夫将蕴含在 Off-CPU 工夫中。
您能够应用额定的跟踪事件来辨别 Off-CPU 工夫与打算程序提早,但在实践中,CPU 饱和度很容易发现,因而,当您有已知的 CPU 饱和问题要解决时,您不太可能破费大量工夫钻研 Off-CPU 工夫。
Involuntary Context Switching
如果您看到用户级堆栈跟踪没有意义(显示没有理由阻止和 Off-CPU),这可能是因为非被迫的上下文切换。当 CPU 饱和,内核 CPU 调度程序让线程关上 CPU,而后在它们达到其工夫切片时启动它们时,通常会产生这种状况。线程能够随时启动,例如在 CPU 重代码门路的两头,生成的 CPU 外堆栈跟踪没有意义。
上面是一个示例堆栈从 fcputime,可能是一个非被迫的上下文开关:
不分明(基于函数名称)为什么此线程在 Item_func:type()。我狐疑这是一个非被迫的上下文开关,因为服务器是 CPU 饱和的。
应用 offcputime 的解决办法是筛选 TASK_UNINTERRUPTIBLE(2):
在 Linux 上,非被迫上下文切换产生在状态 TASK_RUNNING(0),而咱们通常感兴趣的阻塞事件位于 TASK_INTERRUPTIBLE(1)或 TASK_UNINTERRUPTIBLE(2)中,在应用 –state,这些阻塞事件能够匹配。我在 Linux Load Averages: Solving the Mystery 中应用了此性能。
Flame Graphs
Flame Graphs 是剖析堆栈变动的可视化工具,对于疾速了解可由 Off-CPU 剖析生成的数百页堆栈跟踪输入十分有用。Yichun Zhang 最先应用 SystemTap 制作了 Off-CPU 工夫火焰图。
offcputime 工具具备一个 -f 选项,用于以 “folded format”: semi-colon delimited on one line, followed by the metric。这是我的 FlameGraph 软件作为输出的格局。
例如,为 mysqld 创立 Off-CPU 火焰图:
Then open out.svg in a web browser. It looks like this (SVG, PNG):
当初看起来更好了:这显示了所有 Off-CPU 堆栈跟踪,堆栈深度在 y 轴上,宽度对应于每个堆栈中的总工夫。从左到右排序没有意义。内核和用户堆栈之间有分隔帧 ”-“,这些帧由 offcputime 的 -d 选项插入。
您能够单击以缩放。例如,单击右 do_command 的 ”do_command(THD*)” 框架,可放大查问期间产生的阻塞门路。您可能心愿生成仅显示这些门路的火焰图,这些门路能够像 grep 一样简略,因为折叠格局是每个堆栈一行:
The resulting flame graph (SVG, PNG):
That looks great.
For more on off-CPU flame graphs, see my Off-CPU Flame Graphs page.
Wakeups
当初,您曾经晓得如何进行 Off-CPU 跟踪并生成火焰图,您开始真正查看这些火焰图并解释它们。您可能会发现许多 Off-CPU 堆栈显示阻塞门路,但不包含阻止门路的全副起因。起因是代码门路在另一个线程,即调用阻塞线程上的唤醒线程。这种状况常常产生。
我在 Off-CPU Flame Graphs 中介绍此主题,以及两种工具:wakeuptime and offwaketime,以测量唤醒堆栈,并将其与 Off-CPU 堆栈关联。
Other Operating Systems
- Solaris: DTrace can be used for off-cpu tracing. Here is my original page on this: Solaris Off-CPU Analysis.
- FreeBSD: Off-CPU analysis can be performed using procstat -ka for kernel-stack sampling, and DTrace for user- and kernel-stack tracing. I’ve created a separate page for this: FreeBSD Off-CPU Analysis.
Origin
在摸索了 DTrace sched 提供程序及其 sched 的用处后,我开始在 2005 年应用这种办法 desched:::off-cpu。在 DTrace 探测器名称之后,我称它为 Off-CPU 剖析和 Off-CPU 工夫测量(这不是一个完满的名字:2005 年在阿德莱德传授 DTrace 课程时,一位太阳工程师说我不应该称它为 Off-CPU,因为 CPU 不是 ” 敞开 ”)。Solaris 动静跟踪指南提供了一些测量从 sched:敞开 cpu 到 sched:在 cpu 上某些特定案例和过程状态的工夫的示例。我没有筛选过程状态,而是捕捉了所有 Off-CPU 事件,并蕴含堆栈跟踪来解释起因。我认为这种办法是不言而喻的,所以我狐疑我是第一个这样做,但我仿佛是惟一真正推广这种办法的人一段时间。我在 2007 年写了 DTracing Off-CPU Time,并在起初的帖子和谈话中写过。
Summary
Off-CPU 剖析是查找线程阻止并期待其余事件的提早类型的无效办法。通过从上下文切换线程的内核调度程序函数中跟踪,能够以雷同形式剖析所有 Off-CPU 提早类型,而无需跟踪多个源。若要查看 OFF-CPU 事件的上下文以理解事件产生的起因,能够检查用户和内核堆栈回跟踪。
借助 CPU 剖析和 Off-CPU 剖析,您能够全面理解线程破费工夫的形式。这些是互补技术。
For more on off-CPU analysis, see the visualizations in Off-CPU Flame Graphs and Hot/Cold Flame Graphs.
Updates
My first post on off-CPU analysis was in 2007: DTracing Off-CPU Time.
Updates from 2012:
- I included off-CPU analysis as a part of a Stack Profile Method in my USENIX LISA 2012 talk. The Stack Profile Method is the technique of collecting both CPU and off-CPU stacks, to study all the time spent by threads.
Updates from 2013:
- Yichun Zhang created off-CPU flame graphs using SystemTap and gave the talk Introduction to off CPU Time Flame Graphs (PDF).
- I included off-CPU flame graphs in my USENIX LISA 2013 talk Blazing Performance with Flame Graphs.
Updates from 2015:
- I posted FreeBSD Off-CPU Flame Graphs to explore the procstat -ka off-CPU sampling approach.
- I posted Linux perf_events Off-CPU Time Flame Graph to show how these could be created with perf event logging – if all you had was perf (use eBPF instead).
Updates from 2016:
- I posted Linux eBPF Off-CPU Flame Graph to show the value of this and help make the case for stack traces in eBPF (I had to hack them in for this proof of concept).
- I posted Linux Wakeup and Off-Wake Profiling to show wakeup stack analysis.
- I posted Who is waking the waker? (Linux chain graph prototype as a proof of concept of walking a chain of wakeups.
- I described the importance of off-CPU analysis at the start of my Facebook Performance@Scale talk: Linux BPF Superpowers (slides).
Updates from 2017:
- I used a few off-CPU flame graphs in my Linux Load Averages: Solving the Mystery post.
- I summarized off-CPU, wakeup, off-wake, and chain graphs in my USENIX ATC 2017 talk on Flame Graphs: youtube, slides.