性能问题可分为以下两种类型之一:
在 CPU 上:线程花韶光在 CPU 上运行的地方。CPU 外:在 I/O、锁、计时器、分页/交流等上壅塞时等待的韶光。CPU 外剖析是一种性能方法,用于丈量和研究 CPU 外韶光以及堆栈跟踪等高下文。它与 CPU 剖析不同,后者仅在线程在 CPU 上实行时检讨线程。此处,目标是被阻挡和分开 CPU 的线程状态,如右侧图中的蓝色所示。
CPU 外剖析与 CPU 剖析相辅相成,因此可以理解 100% 的线程韶光。此方法也不同于检测常日壅塞的运用程序功能的跟踪技能,由于此方法针对内核调度程序的壅塞观点,这是一种方便的捕获观点。

线程可能出于多种缘故原由离开 CPU,包括 I/O 和锁,但也有一些与当前哨程的实行无关的缘故原由,包括由于对 CPU 资源的高需求而导致的非志愿高下文切换和中断。无论出于何种缘故原由,如果在事情负载要求(同步代码路径)期间发生这种情形,则会引入延迟。
在本页中,我将先容 CPU 外韶光作为指标,并总结 CPU 外剖析的技能。 作为 CPU 外剖析的示例实现,我将把它运用到 Linux,然后在后面的章节中总结其他操作系统。
目录
1. 先决条件2.弁言3.开销4.Linux5.CPU 外韶光6.CPU 外剖析7.要求高下文8.警告 9. 火焰图10.唤醒11.其他操作系统12.起源13.小结14.更新先决条件CPU 外剖析哀求堆栈跟踪可供跟踪器利用,您可能须要首先修复此问题。许多运用程序都是利用 -fomit-frame-pointer gcc 选项编译的,从而冲破了基于帧指针的堆栈遍历。VM 运行时(如动态的 Java 编译方法)和跟踪器在没有其他帮助的情形下可能无法找到其符号信息,从而导致堆栈跟踪仅为十六进制。还有其他陷阱。请参阅我之前关于修复堆栈跟踪和JIT 符号的性能的文章。
先容为理解释 CPU 外剖析的浸染,我将首先总结 CPU 采样和跟踪进行比较。然后,我将总结两种 CPU 外剖析方法:跟踪和采样。虽然十多年来我一贯在推广 CPU 外剖析,但它仍旧不是一种广泛利用的方法,部分缘故原由是生产 Linux 环境中缺少工具来衡量它。现在,随着eBPF和更新的Linux内核(4.8+)的涌现,这种情形正在发生变革。
1. 中心处理器采样许多传统的剖析工具利用跨所有CPU的活动定时采样,以特定的韶光间隔或速率(例如,99赫兹)网络当前指令地址(程序计数器)或全体堆栈回溯跟踪的快照。这将给出正在运行的函数或堆栈跟踪的计数,从而可以合理估计 CPU 周期的花费位置。在 Linux 上,采样模式下的性能工具(例如,-F 99)实行定时 CPU 采样。
考虑运用程序函数 A() 调用函数 B(),它进行壅塞系统调用:
CPU Sampling -----------------------------------------------> | | | | | | | | | | | | A A A A B B B B A A A A A(---------. .----------) | | B(--------. .--) | | user-land - - - - - - - - - - syscall - - - - - - - - - - - - - - - - - | | kernel X Off-CPU | block . . . . . interrupt
虽然这对付研究 CPU 上的问题(包括热代码路径和自适应互斥体旋转)非常有效,但当运用程序壅塞并等待 CPU 外时,它不会网络数据。
2. 运用程序跟踪 App Tracing ------------------------------------------------> | | | | A( B( B) A) A(---------. .----------) | | B(--------. .--) | | user-land - - - - - - - - - - syscall - - - - - - - - - - - - - - - - - | | kernel X Off-CPU | block . . . . . interrupt
在这里,对函数进行检测,以便在它们开始“(”和结束“)”时网络韶光戳,以便可以打算在函数中花费的韶光。如果韶光戳包括经由的韶光和 CPU 韶光(例如,利用 times(2) 或 getrusage(2)),那么还可以打算哪些函数在 CPU 上很慢,哪些函数很慢,由于它们在 CPU 外被壅塞。与采样不同,这些韶光戳可以具有非常高的分辨率(纳秒)。
虽然这有效,但缺陷是,您要么跟踪所有运用程序功能,这可能会对性能产生重大影响(并影响您考试测验丈量的性能),要么选择可能阻挡的功能,并希望您没有错过任何功能。
3. 非 CPU 跟踪我将在这里总结这一点,然后不才一节中更详细地阐明它。
Off-CPU Tracing --------------------------------------------> | | B B A A A(---------. .----------) | | B(--------. .--) | | user-land - - - - - - - - - - syscall - - - - - - - - - - - - - - - - - | | kernel X Off-CPU | block . . . . . interrupt
利用此方法,仅跟踪将线程切换为 CPU 的内核函数,以及时间戳和用户空间堆栈跟踪。这侧重于 CPU 外事宜,无需跟踪所有运用程序功能,也不须要知道运用程序是什么。这种方法适用于任何壅塞事宜,任何运用程序:MySQL,Apache,Java等。
CPU 外跟踪捕获任何运用程序的所有等待事宜。
在本页的后面部分,我将跟踪内核 CPU 外事宜,并包括一些运用程序级检测来过滤掉异步等待韶光(例如,等待事情的线程)。与运用程序级检测不同,我不须要探求可能壅塞 CPU 外的每个位置;我只须要确定运用程序是否位于韶光敏感的代码路径中(例如,在MySQL查询期间),以便延迟与事情负载同步。
CPU 外跟踪是我一贯用于非 CPU 剖析的紧张方法。但也有抽样。
4. CPU 外采样 Off-CPU Sampling -------------------------------------------> | | | | | | | O O O O O O O A(---------. .----------) | | B(--------. .--) | | user-land - - - - - - - - - - syscall - - - - - - - - - - - - - - - - - | | kernel X Off-CPU | block . . . . . interrupt
此方法利用定时采样从未在 CPU 上运行的线程捕获壅塞的堆栈跟踪。它也可以通过挂机韶光探查器来完成:一个始终对所有线程进行采样的探查器,无论它们是在 CPU 上还是在 CPU 外。然后可以过滤挂断韶光配置文件输出,以仅查找 CPU 外堆栈。
系统概述很少利用非 CPU 采样。采样常日作为每 CPU 计时器中断实现,然后检讨当前正在运行的中断进程:天生 CPU 上配置文件。非 CPU 采样器必须以不同的办法事情:在每个运用程序线程中设置计时器以唤醒它们并捕获堆栈,或者让内核按一定间隔遍历所有线程并捕获其堆栈。
开销警告:对付 CPU 外跟踪,请把稳调度程序事宜可能非常频繁 - 在极度情形下,每秒数百万个事宜 - 只管跟踪器可能只为每个事宜增加少量开销,但由于事宜速率,开销可能会加起来并变得很主要。CPU 外采样也存在开销问题,由于系统可能有数万个线程必须不断采样,这比仅跨 CPU 计数的 CPU 采样开销高几个数量级。
要利用 CPU 外剖析,您须要把稳每一步的开销。将每个事宜转储到用户空间进行后处理的跟踪器很随意马虎变得令人望而生畏,每分钟创建 GB 的跟踪数据。这些数据必须写入文件系统和存储设备并进行后处理,这会花费更多的CPU。这便是为什么可以实行内核内择要的跟踪器(如 Linux eBPF)对付减少开销和使 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%。该文件在 10 秒的跟踪中终极达到 224 MB。然后通过运行 perf 脚本对文件进行后处理以实行符号转换,这会导致 13% 的性能低落(丢失 1 个 CPU)持续 35 秒。你可以总结这一点,说 10 秒的性能跟踪在 45 秒内花费了 9-13% 的开销。相反,利用 eBPF 对内核高下文中的堆栈进行计数会导致 10 秒跟踪期间吞吐量低落 6%,从初始化 eBPF 时 1 秒低落 13% 开始,然后是 6 秒的后处理(已汇总堆栈的符号分辨率),本钱低落 13%。因此,10 秒的跟踪会在 17 秒内花费 6-13% 的开销。好多了。增加跟踪持续韶光时会发生什么情形?对付 eBPF,它只是捕获和转换唯一的堆栈,不会随跟踪持续韶光线性扩展。我通过将跟踪从 10 秒增加到 60 秒来测试这一点,这只会将 eBPF 后处理从 6 秒增加到 7 秒。与 perf 相同,将其后处理韶光从 35 秒增加到 212 秒,由于它须要处理 6 倍的数据量。为了完备理解这一点,值得把稳的是,后处理是一项用户级活动,可以对其进行调度以减少对生产事情负载的滋扰,例如通过利用不同的操持程序优先级。想象一下,对付此活动,将 CPU 上限为 10%(一个 CPU):性能丢失可能可以忽略不计,然后 eBPF 后处理阶段可能须要 70 秒——还不错。但是,perf 脚本韶光可能须要 2120 秒(35 分钟),这将使调查停滞。性能的开销不仅仅是 CPU,还有磁盘 I/O。
此 MySQL 示例与生产事情负载比较如何?它每秒进行 102k 高下文切换,这是相对较高的:我目前看到的许多生产系统都在 20-50k/s 范围内。这表明这里描述的开销比我在这些生产系统上看到的要赶过大约 2 倍。但是,MySQL 堆栈深度相对较轻,常日只有 20-40 帧,而生产运用程序可以超过 100 帧。这也很主要,可能意味着我的MySQL堆栈开销可能只是我在生产中看到的一半。因此,这可能会平衡我的生产系统。
Linux: perf, eBPFCPU 外剖析是一种通用方法,该当适用于任何操作系统。我将演示如何利用 CPU 外跟踪在 Linux 上实行此操作,然后在后面的部分中总结其他操作系统。
Linux 上有许多跟踪器可用于 CPU 外剖析。我将在这里利用eBPF,由于它可以轻松地对堆栈跟踪和韶光进行内核内择要。eBPF是Linux内核的一部分,我将通过bcc前端工具利用它。这些至少须要 Linux 4.8 才能支持堆栈跟踪。
你可能想知道我在 eBPF 之前是如何进行 CPU 外剖析的。许多不同的方法,包括每种壅塞类型的完备不同的方法:存储 I/O 的存储跟踪、调度程序延迟的内核统计信息等。为了实际进行 CPU 外剖析,我之前利用了 SystemTap 和perf事宜日志记录 - 只管这有更高的开销(我在perf_events CPU 外韶光火焰图中写过)。有一次,我写了一个名为proc-profiler.pl 的大略的挂机韶光内核堆栈剖析器,它对给定PID的/proc/PID/stack进行了采样。它事情得很好。我也不是第一个破解这种墙上韶光阐发器的人,请参阅poormans剖面器和Tanel Poder的快速'n'脏故障打消。
非 CPU 韶光这是线程在 CPU 外等待所花费的韶光(壅塞韶光),而不是在 CPU 上运行所花费的韶光。它可以作为持续韶光内的总计进行度量(已由 /proc 统计信息供应),也可以针对每个壅塞事宜进行度量(常日须要跟踪器)。
首先,我将展示您可能已经熟习的工具的总 CPU 外韶光。time(1) 命令。例如,定时焦油(1):
$ time tar cf archive.tar linux-4.15-rc2real0m50.798suser0m1.048ssys0m11.627s
tar 运行大约须要一分钟,但 time 命令显示它只花费了 1.0 秒的用户模式 CPU 韶光和 11.6 秒的内核模式 CPU 韶光,统共运行了 50.8 秒的韶光。我们错过了 38.2 秒!
那是 tar 命令在 CPU 之外被阻挡的时候,毫无疑问,将存储 I/O 作为其归档天生的一部分。
要更详细地检讨 CPU 外韶光,可以利用内核调度程序函数的动态跟踪或利用调度跟踪点的静态跟踪。bcc/eBPF项目包括由Sasha Goldshtein开拓的cpudist,它具有-O模式来丈量CPU外韶光。这须要 Linux 4.4 或更高版本。丈量 tar 的 CPU 外韶光:
# /usr/share/bcc/tools/cpudist -O -p `pgrep -nx tar`Tracing off-CPU time... Hit Ctrl-C to end.^C usecs : count distribution 0 -> 1 : 3 | | 2 -> 3 : 50 | | 4 -> 7 : 289 | | 8 -> 15 : 342 | | 16 -> 31 : 517 | | 32 -> 63 : 5862 | | 64 -> 127 : 30135 | | 128 -> 255 : 71618 || 256 -> 511 : 37862 | | 512 -> 1023 : 2351 | | 1024 -> 2047 : 167 | | 2048 -> 4095 : 134 | | 4096 -> 8191 : 178 | | 8192 -> 16383 : 214 | | 16384 -> 32767 : 33 | | 32768 -> 65535 : 8 | | 65536 -> 131071 : 9 | |
这表明大多数壅塞事宜在 64 到 511 微秒之间,这与闪存 I/O 延迟同等(这是一个基于闪存的系统)。跟踪时,最慢的壅塞事宜达到 65 到 131 毫秒的范围(此直方图中的末了一个存储桶)。
这个 CPU 外韶光由什么组成?从线程壅塞到再次开始运行的所有内容,包括调度程序延迟。
在撰写本文时,cpudist 利用 kprobes(内核动态跟踪)来检测 finish_task_switch() 内核函数。(出于 API 稳定性缘故原由,它该当利用调度跟踪点,但第一次考试测验没有成功,目前已还原。
finish_task_switch() 的原型是:
static struct rq finish_task_switch(struct task_struct prev)
为了让您理解此工具的事情事理:finish_task_switch() 函数不才一个运行的线程的高下文中调用。eBPF 程序可以利用 kprobes 检测此函数和参数,获取当前 PID(通过 bpf_get_current_pid_tgid()),还可以获取高分辨率韶光戳 (bpf_ktime_get_ns())。这是上述择要所需的所有信息,该择要利用 eBPF 映射在内核高下文中有效地存储直方图存储桶。这是cpudist 的完全来源。
eBPF并不是Linux上唯一用于丈量CPU外韶光的工具。perf工具在其perf 调度 timehist输出中供应了一个“等待韶光”列,该列不包括操持程序韶光,由于它单独显示在相邻列中。该输出显示每个调度程序事宜的等待韶光,并且比 eBPF 直方图择要须要更多的开销来衡量。
将 CPU 外韶光丈量为直方图有点用,但不是很多。我们真正想知道的是高下文 - 为什么线程壅塞并离开CPU。这是 CPU 外剖析的重点。
非 CPU 剖析CPU 外剖析是剖析非 CPU 韶光以及堆栈跟踪以确定线程壅塞缘故原由的方法。由于以下事理,CPU 外跟踪剖析技能可以轻松实现:
运用程序堆栈跟踪在 CPU 外时不会变动。
这意味着我们只须要在 CPU 离线周期的开始或结束时丈量一次堆栈跟踪。结束常日更随意马虎,由于无论如何您都在记录韶光间隔。以下是利用堆栈跟踪来丈量 CPU 外韶光的跟踪伪代码:
on context switch finish:sleeptime[prev_thread_id] = timestampif !sleeptime[thread_id]returndelta = timestamp - sleeptime[thread_id]totaltime[pid, execname, user stack, kernel stack] += deltasleeptime[thread_id] = 0on tracer exit:for each key in totaltime:print keyprint totaltime[key]
关于这一点的一些把稳事变:所有丈量都从一个检测点发生,即高下文切换例程的末端,这是不才一个线程(例如,Linux finish_task_switch() 函数)的高下文中。这样,我们可以通过大略地获取当前高下文(pid、execname、用户堆栈、内核堆栈)来打算 CPU 外持续韶光,同时检索该持续韶光的高下文,跟踪器使这变得随意马虎。
这便是我的offcputime bcc / eBPF程序所做的,它至少须要Linux 4.8才能事情。我将演示如何利用 bcc/eBPF offcputime 来丈量 tar 程序的壅塞堆栈。我将它限定为内核堆栈,仅以 (-K) 开头:
# /usr/share/bcc/tools/offcputime -K -p `pgrep -nx tar`Tracing off-CPU time (us) of PID 15342 by kernel stack... Hit Ctrl-C to end.^C[...] finish_task_switch __schedule schedule schedule_timeout __down down xfs_buf_lock _xfs_buf_find xfs_buf_get_map xfs_buf_read_map xfs_trans_read_buf_map xfs_da_read_buf xfs_dir3_block_read xfs_dir2_block_getdents xfs_readdir iterate_dir SyS_getdents entry_SYSCALL_64_fastpath - tar (18235) 203075 finish_task_switch __schedule schedule schedule_timeout wait_for_completion xfs_buf_submit_wait xfs_buf_read_map xfs_trans_read_buf_map xfs_imap_to_bp xfs_iread xfs_iget xfs_lookup xfs_vn_lookup lookup_slow walk_component path_lookupat filename_lookup vfs_statx SYSC_newfstatat entry_SYSCALL_64_fastpath - tar (18235) 661626 finish_task_switch __schedule schedule io_schedule generic_file_read_iter xfs_file_buffered_aio_read xfs_file_read_iter __vfs_read vfs_read SyS_read entry_SYSCALL_64_fastpath - tar (18235) 18413238
我已将输出截断到末了三个堆栈。末了一个,显示统共 18.4 秒的 CPU 外韶光,在读取系统调用路径中以 io_schedule() 结尾 – 这是 tar 读取文件内容,并在磁盘 I/O 上壅塞。它上面的堆栈在统计系统调用中显示 662 毫秒,终极也会通过 xfs_buf_submit_wait() 等待存储 I/O。顶部堆栈统共为 203 毫秒,在实行 getdents 系统调用(目录列表)时彷佛在锁上显示 tar 壅塞。
阐明这些堆栈跟踪须要轻微熟习源代码,这取决于运用程序的繁芜程度及其措辞。你这样做的次数越多,你就会变得越快,由于你会识别相同的函数和堆栈。
我现在将包括用户级堆栈:
# /usr/share/bcc/tools/offcputime -p `pgrep -nx tar`Tracing off-CPU time (us) of PID 18311 by user + kernel stack... Hit Ctrl-C to end.[...] finish_task_switch __schedule schedule io_schedule generic_file_read_iter xfs_file_buffered_aio_read xfs_file_read_iter __vfs_read vfs_read SyS_read entry_SYSCALL_64_fastpath [unknown] - tar.orig (30899) 9125783
这不起浸染:用户级堆栈只是“[未知]”。缘故原由是默认版本的tar是在没有帧指针的情形下编译的,而这个版本的bcc/eBPF须要它们来遍历堆栈跟踪。我想展示这个陷阱的样子,以防你也击中它。
我确实修复了tar的堆栈(请参阅前面的先决条件)以查看它们的外不雅观:
# /usr/share/bcc/tools/offcputime -p `pgrep -nx tar`Tracing off-CPU time (us) of PID 18375 by user + kernel stack... Hit Ctrl-C to end.[...] finish_task_switch __schedule schedule io_schedule generic_file_read_iter xfs_file_buffered_aio_read xfs_file_read_iter __vfs_read vfs_read SyS_read entry_SYSCALL_64_fastpath __read_nocancel dump_file0 dump_file dump_dir0 dump_dir dump_file0 dump_file dump_dir0 dump_dir dump_file0 dump_file dump_dir0 dump_dir dump_file0 dump_file create_archive main __libc_start_main [unknown] - tar (15113) 426525[...]
好的,以是看起来tar对文件系统树有一个递归的步辇儿算法。
这些堆栈跟踪很棒 - 它显示了运用程序壅塞和等待 CPU 之外的缘故原由,以及多永劫光。这正是我常日探求的信息。但是,壅塞堆栈跟踪并不总是那么有趣,由于有时您须要查找要求同步高下文。
要求同步高下文等待事情的运用程序(例如在套接字上等待线程池的 Web 做事器)对 CPU 外剖析提出了寻衅:常日大部分壅塞韶光将在堆栈中等待事情,而不是实行事情。这会用不太有趣的堆栈淹没输出。
作为这种征象的一个例子,这里是MySQL做事器进程的CPU外堆栈,它什么都不做。每秒零要求数:
# /usr/share/bcc/tools/offcputime -p `pgrep -nx mysqld`Tracing off-CPU time (us) of PID 29887 by user + kernel stack... Hit Ctrl-C to end.^C[...] finish_task_switch __schedule schedule do_nanosleep hrtimer_nanosleep sys_nanosleep entry_SYSCALL_64_fastpath __GI___nanosleep srv_master_thread start_thread - mysqld (29908) 3000333 finish_task_switch __schedule schedule futex_wait_queue_me futex_wait do_futex sys_futex entry_SYSCALL_64_fastpath pthread_cond_timedwait@@GLIBC_2.3.2 os_event::wait_time_low(unsigned long, long) srv_error_monitor_thread start_thread - mysqld (29906) 3000342 finish_task_switch __schedule schedule read_events do_io_getevents SyS_io_getevents entry_SYSCALL_64_fastpath [unknown] LinuxAIOHandler::poll(fil_node_t, void, IORequest) os_aio_handler(unsigned long, fil_node_t, void, IORequest) fil_aio_wait(unsigned long) io_handler_thread start_thread - mysqld (29896) 3500863[...]
Various threads are polling for work and other background tasks. These background stacks can dominate the output, even for a busy MySQL server. What I'm usually looking for is off-CPU time during a database query or command. That's the time that matters – the time that's hurting the end customer. To find those in the output, I need to hunt around for stacks in query context.
For example, now from a busy MySQL server:
# /usr/share/bcc/tools/offcputime -p `pgrep -nx mysqld`Tracing off-CPU time (us) of PID 29887 by user + kernel stack... Hit Ctrl-C to end.^C[...] finish_task_switch __schedule schedule io_schedule wait_on_page_bit_common __filemap_fdatawait_range file_write_and_wait_range ext4_sync_file do_fsync SyS_fsync entry_SYSCALL_64_fastpath fsync log_write_up_to(unsigned long, bool) trx_commit_complete_for_mysql(trx_t) [unknown] ha_commit_low(THD, bool, bool) TC_LOG_DUMMY::commit(THD, bool) ha_commit_trans(THD, bool, bool) trans_commit_stmt(THD) mysql_execute_command(THD, bool) mysql_parse(THD, Parser_state) dispatch_command(THD, COM_DATA const, enum_server_command) do_command(THD) handle_connection pfs_spawn_thread start_thread - mysqld (13735) 1086119[...]
This stack identifies some time (latency) during a query. The do_command() -> mysql_execute_command() code path is a give away. I know this because I'm familiar with the code from all parts of this stack: MySQL and kernel internals.
您可以想象编写一个大略的文本后处理器,它根据一些特定于运用程序的模式匹配来提取感兴趣的堆栈。这可能事情正常。还有另一种方法,效率更高一些,只管还须要运用程序细节:扩展跟踪程序以检测运用程序要求(在此MySQL做事器示例中为do_command()函数),然后仅在运用程序要求期间发生时记录CPU外韶光。我以前做过,它可以供应帮助。
警告最大的警告是 CPU 外剖析的开销,如前面的开销部分所述,然后是使堆栈跟踪正常事情,我在前面的先决条件部分中对此进行了总结。还有须要把稳的调度程序延迟和非志愿高下文切换,我将在这里总结,以及我将在后面的部分中谈论的唤醒堆栈。
调度程序延迟这些堆栈中短缺的是,CPU 外韶光是否包括等待 CPU 运行行列步队所花费的韶光。此韶光称为调度程序延迟、运行行列步队延迟或调度程序行列步队延迟。如果 CPU 以饱和状态运行,则每当线程壅塞时,它可能会在唤醒后等待轮到 CPU 的额外韶光。该韶光将包含在 CPU 外韶光中。
您可以利用额外的跟踪事宜将 CPU 外韶光划分为韶光壅塞与调度程序延迟,但实际上,CPU 饱和很随意马虎创造,因此当您有已知的 CPU 饱和问题须要处理时,您不太可能花费大量韶光研究 CPU 外韶光。
非志愿高下文切换如果您看到没故意义的用户级堆栈跟踪 - 没有情由阻挡和离开CPU - 这可能是由于非志愿高下文切换。这常日发生在 CPU 饱和时,内核 CPU 调度程序让线程打开 CPU,然后在它们到达其时间片时启动它们。线程可以随时启动,例如在 CPU 繁重的代码路径中间,并且天生的 CPU 外堆栈跟踪毫无意义。
下面是来自 offcputime 的示例堆栈,它可能是非志愿的高下文切换:
# /usr/share/bcc/tools/offcputime -p `pgrep -nx mysqld`Tracing off-CPU time (us) of PID 29887 by user + kernel stack... Hit Ctrl-C to end.[...] finish_task_switch __schedule schedule exit_to_usermode_loop prepare_exit_to_usermode swapgs_restore_regs_and_return_to_usermode Item_func::type() const JOIN::make_join_plan() JOIN::optimize() st_select_lex::optimize(THD) handle_query(THD, LEX, Query_result, unsigned long long, unsigned long long) [unknown] mysql_execute_command(THD, bool) Prepared_statement::execute(String, bool) Prepared_statement::execute_loop(String, bool, unsigned char, unsigned char) mysqld_stmt_execute(THD, unsigned long, unsigned long, unsigned char, unsigned long) dispatch_command(THD, COM_DATA const, enum_server_command) do_command(THD) handle_connection pfs_spawn_thread start_thread - mysqld (30022) 13[...]
目前尚不清楚(基于函数名称)为什么这个线程在 Item_func::type() 中被壅塞。我疑惑这是一个非志愿的高下文切换,由于做事器已 CPU 饱和。
offcputime 的办理方法是筛选TASK_UNINTERRUPTIBLE状态 (2):
# /usr/share/bcc/tools/offcputime -p `pgrep -nx mysqld` --state 2
在 Linux 上,状态 TASK_RUNNING (0) 会发生非志愿的高下文切换,而我们常日感兴趣的壅塞事宜是 TASK_INTERRUPTIBLE (1) 或 TASK_UNINTERRUPTIBLE (2),offcputime 可以利用 --state 匹配它们。我在我的Linux 负载均匀值:办理谜团帖子中利用了此功能。
火焰图火焰图是剖析堆栈跟踪的可视化,对付快速理解可通过非 CPU 剖析天生的数百页堆栈跟踪输出非常有用。张一春首先利用SystemTap创建了CPU外的韶光火焰图。
offcputime 工具有一个 -f 选项,用于以“折叠格式”发出堆栈跟踪:分号分隔在一行上,后跟指标。这是我的FlameGraph软件作为输入的格式。
例如,为 mysqld 创建一个 CPU 外火焰图:
# /usr/share/bcc/tools/offcputime -df -p `pgrep -nx mysqld` 30 > out.stacks[...copy out.stacks to your local system if desired...]# git clone https://github.com/brendangregg/FlameGraph# cd FlameGraph# ./flamegraph.pl --color=io --title="Off-CPU Time Flame Graph" --countname=us < out.stacks > out.svg
然后在 Web 浏览器中打开.svg。它看起来像这样(SVG,PNG):
好多了:这显示了所有 CPU 外堆栈跟踪,y 轴上的堆栈深度,宽度对应于每个堆栈中的总韶光。从左到右的顺序没有任何意义。内核和用户堆栈之间有分隔符帧“-”,由 offcpputime 的 -d 选项插入。
您可以单击以缩放。例如,单击右下角的“do_command(THD)”帧,以放大查询期间发生的壅塞路径。您可能希望天生仅显示这些路径的火焰图,这些路径可以像 grep 一样大略,由于折叠格式是每个堆栈一行:
# grep do_command < out.stacks | ./flamegraph.pl --color=io --title="Off-CPU Time Flame Graph" --countname=us > out.svg
天生的火焰图(SVG,PNG):
这看起来很棒。
有关 CPU 外火焰图的更多信息,请参阅我的CPU 外火焰图页面。
唤醒现在您已经知道如何实行 CPU 外跟踪并天生火焰图,您开始真正查看这些火焰图并阐明它们。您可能会创造许多 CPU 外堆栈显示壅塞路径,但未显示壅塞路径的完全缘故原由。该缘故原由和代码路径与另一个线程有关,该线程在壅塞的线程上调用唤醒。这种情形一贯在发生。
我在CPU 外火焰图页面中先容了这个主题,以及两个工具:唤醒韶光和关闭唤醒韶光,用于丈量唤醒堆栈并将它们与 CPU 外堆栈干系联。
其他操作系统Solaris:DTrace 可用于 CPU 外跟踪。这是我的原始页面:Solaris Off-CPU Analysis。FreeBSD:可以利用 procstat -ka 进行内核堆栈采样,利用 DTrace 进行用户和内核堆栈跟踪来实行 CPU 外剖析。我为此创建了一个单独的页面:FreeBSD Off-CPU Analysis。起源大约在 2005 年,在探索了 DTrace 调度供应程序及其 sched:::off-cpu 探测器的用场之后,我开始利用这种方法。我称它为 off-CPU 剖析和指标 off-CPU 韶光,以 DTrace 探测器名称命名(不是一个完美的名字:2005 年在阿德莱德教授 DTrace 课程时,一位 Sun 工程师说我不应该称它为 off-CPU,由于 CPU 不是“关闭”的)。《Solaris 动态跟踪指南》供应了一些示例,用于丈量某些特定情形和进程状态从 sched:::off-cpu 到 sched:::on-cpu 的韶光。我没有筛选进程状态,而是捕获了所有 CPU 外事宜,并包含堆栈跟踪来阐明缘故原由。我认为这种方法是显而易见的,以是我疑惑我是第一个这样做的人,但我彷佛是唯一一个真正推广这种方法的人。我在 2007 年的DTracing Off-CPU Time 以及后来的帖子和演讲中写过它。
总结CPU 外剖析是查找线程壅塞并等待其他事宜的延迟类型的有效方法。通过从高下文切换线程的内核调度程序函数中跟踪这一点,可以以相同的办法剖析所有 CPU 外延迟类型,而无需跟踪多个源。要查看 off-CPU 事宜的高下文以理解其发生缘故原由,可以检讨用户和内核堆栈回溯跟踪。
通过 CPU 剖析和 CPU 外剖析,您可以全面理解线程花费韶光的位置。这些是互补的技能。
有关 CPU 外剖析的详细信息,请参阅CPU 外火焰图和热/冷焰图中的可视化效果。
更新我关于 CPU 外剖析的第一篇文章是在 2007 年:DTracing Off-CPU Time。
2012年更新:
我在USENIX LISA 2012演讲中将 CPU 外剖析作为堆栈配置文件方法的一部分。堆栈配置文件方法是网络 CPU 和非 CPU 堆栈的技能,用于研究线程花费的所有韶光。2013年更新:
张一春利用 SystemTap 创建了 CPU 外火焰图,并先容了CPU 外韶光火焰图(PDF)。我在 USENIX LISA 2013 演讲中利用火焰图的炽热性能中加入了 CPU 外火焰图。2015年更新:
我发布了FreeBSD Off-CPU Flame Graphs来探索 procstat -ka off-CPU 采样方法。我发布了Linux perf_events Off-CPU Time Flame Graph来展示如何利用 perf 事宜日志记录创建这些 - 如果你只有 perf(改用 eBPF)。2016年更新:
我发布了Linux eBPFOff-CPU Flame Graph来展示它的代价,并帮助在eBPF中建立堆栈跟踪(我不得不破解它们以进行观点验证)。我发布了Linux 唤醒和关闭唤醒剖析来显示唤醒堆栈剖析。我发布了谁在唤醒唤醒者?(Linux链图原型作为走持续串唤醒的观点证明。我在Facebook Performance@Scale演讲开始时描述了CPU外剖析的主要性:Linux BPF Superpowers(幻灯片)。2017年更新:
我在我的Linux 负载均匀值:办理谜团帖子中利用了一些 CPU 外火焰图。我在 USENIX ATC 2017 关于 Flame Graphs:youtube,slides 的演讲中总结了 CPU、唤醒、唤醒和链图。