在最后的博客文章中,我们讨论了CPU分析器的基础知识为Go,C ++和Rust等编译语言。我们通过表示我们想要一个基于采样的探查器,符合这两个要求:
不需要重新编译或重新部署:这对Pixie的自动遥测方法至关重要。您不应该对仪器甚至重新运行您的应用程序以获得可观察性。
具有非常低的开销:这是连续(始终开启)分析器所必需的,这是希望尽可能低成本的性能分析。
一些现有的探查器满足了这些要求,包括Linux PERF工具。最后,我们在Brendan Gregg [1]开发的基于BCC的EBPF的分析器上作为最佳参考。使用EBPF已经在Pixie平台的核心,它是一种自然的合适,EBPF的效率是不可否认的。
如果您熟悉EBPF,值得查看BCC实现的源代码。对于此博客,我们已准备好我们将更详细地检查的简化版本。
可以在此处找到基于简单的EBPF的分析器的代码,其中包含此博客末尾的进一步说明(请参阅运行演示探查器)。我们将解释它的工作原理,所以现在是克隆回购的好时机。
此外,在潜入代码之前,我们应该提到Linux开发人员已经投入专用钩子,用于在内核中收集堆栈迹线。这些是我们用于收集堆栈迹线的主要API(这是官方BCC Profiler如何运作的方式)。但是,我们不会进入Linux的实现这些API,因为这超出了这个博客的范围。
有了这一说,让我们看看一些BCC EBPF代码。我们的基本结构有三个主要组成部分:
一个bpf_stack_trace数据结构,称为stack_traces来保存采样堆栈痕迹。每个条目都是表示堆栈跟踪的地址列表。通过分配的堆栈跟踪ID访问堆栈跟踪。
一个名为直方图的bpf_hash数据结构,它是从代码中的采样位置的地图到我们采样该位置的次数。
将定期触发的函数sample_stack_trace。此EBPF功能的目的是在调用时抓住当前堆栈跟踪,并适当地填充/更新Stack_traces和直方图数据结构。
由于我们稍后将在更详细地看到,我们将在定期计时器上设置我们的BPF代码来触发。这意味着每一个x毫秒,我们都会中断CPU并触发EBPF探测器来对堆栈进行示例。请注意,无论CPU上是否有哪个进程都会发生这种情况,因此EBPF Profiler实际上是一个系统范围的分析器。我们可以稍后过滤结果,仅包含属于我们应用程序的堆栈迹线。
令人惊讶的是,就是这样!这是我们分析器的全部内容代码。让我们打破它......
请记住,eBPF探测器在触发时在上下文中运行,因此当此探测器触发时,它具有在CPU上运行的任何程序的上下文。然后它基本上拨打了两个调用stack_traces.get_stackId():一个来获取当前用户代码堆栈跟踪,另一个用于获取内核堆栈跟踪。如果代码中断时不在内核空间中,则第二个呼叫只是返回EExist,并且没有堆栈跟踪。您可以看到所有繁重的升降机都是由Linux内核完成的。
接下来,我们要更新代码中我们在此确切位置的次数的计数。为此,我们只需将计数器递增,以便在与元组{pid,user_stack_id,kernel_stack_id}相关联的直方图中的条目中的条目。请注意,我们也将PID抛入直方图,因为稍后将帮助我们知道堆栈跟踪所属的进程。
虽然上面的EBPF代码样本我们想要的堆栈痕迹,但我们仍有一点工作要做。其余任务涉及:
设置我们的BPF程序以定期运行,以便相当容易。再次,信用进入BCC和EBPF开发人员。此设置的关键如下:
在这里,我们将通过基于perf_type_software / perf_count_sw_cpu_clock设置一个事件,通过设置事件来设置基于CPU时钟的触发器来设置触发器。每次此值达到SAMPLING_PERIOD_MILLIS的倍数时,BPF探测器将触发并调用指定的探测器_FN,这恰好是我们的SAMPLE_STACK_TRACE BPF程序。在我们的演示代码中,我们将采样周期设置为每10毫秒,这将收集100个样本/秒。这足以提供超过一分钟左右的洞察力,但也很少发生足够的事情,因此它不会添加明显的开销。
部署我们的BPF代码后,我们必须从BPF地图中收集结果。我们使用BCC API从用户空间访问地图:
最后,我们希望将我们的地址转换为符号,并连接我们的用户和内核堆栈痕迹。幸运的是,BCC再次让我们的生活很容易。特别是,有一个Call Stack_traces.get_stack_symbol,它将将堆栈跟踪中的地址列表转换为符号列表。此功能需要PID,因为它将在进程对象文件中查找调试符号以执行翻译。
它实际上证明将堆栈迹线转换为符号的过程是此过程的一部分引入最大的开销。堆叠迹线的实际采样可忽略不计。我们的下一个博客文章将讨论使这一基本探查器准备生产的基本探查者的性能挑战。
可以在此处找到运行简单的EBPF的分析器的代码和说明。
该代码旨在具有尽可能少的依赖项,但您需要安装BCC。遵循Readme.md中的说明。有关构建分析器和玩具应用程序的更多详细信息,请访问Profiler。
然后,您可以通过在系统中运行其他正在运行的进程来进行实验。某些过程可能没有安装调试符号,在这种情况下,您将看到[未知]标记。
另一个有趣的实验是将其与用不同语言编写的程序运行,如Python或Java。你会看到堆栈痕迹,但它们可能不是您所期待的。例如,使用Python,您将看到的是Python解释器的堆栈迹线而不是Python应用程序(注意:您需要翻译的调试符号来查看功能;在Ubuntu上,您可以通过运行类似的东西来获得这些功能安装python3.6-dbg)。我们将在未来的帖子中介绍java和解释语言的分析。
在本系列的第三部分中,我们将讨论将此简单的探查器建立在生产准备探查器中的挑战。