自驱动可观测性:从堆栈跟踪到基于性能分析衍生的指标
作者:来自 Elastic Christos Kalkanis 及 Roger Coll

性能分析衍生指标将原始堆栈跟踪转换为时间序列关键绩效指标,使每位用户都能够使用持续性能分析,并为一种能够自主检测、自主调查和自主采取行动的可观测性系统奠定基础。
持续性能分析已经取得了长足进展。随着 OpenTelemetry 性能分析信号进入 Alpha 阶段,以及由 Elastic 捐赠的 OpenTelemetry eBPF 性能分析器现已作为 OpenTelemetry Collector 的一级接收器运行,面向 Linux 的低开销、全系统性能分析终于向所有 OpenTelemetry 用户开放。
无需插桩,无需重新编译,无需重启服务。只需部署性能分析器,即可获得从内核、经过原生代码,一直到 HotSpot、Python、V8、.NET、Go、PHP、Perl、BEAM Erlang 和 Ruby 运行时的可观测性。
处理流水线非常直接:性能分析器以固定频率(默认 19Hz)对系统中的每个 CPU 核心进行采样,展开执行堆栈,对生成的堆栈跟踪进行符号解析,并将性能分析数据发送到诸如 Elasticsearch 之类的后端。
然后……
用户必须自己弄清楚接下来该怎么做。
最后这一步正是持续性能分析历史上一直面临采用挑战的地方,因为从“性能分析已开启”到“性能分析真正有用”之间的道路比应有的更陡峭。
采用的四大障碍
-
存储成本: 即使经过去重和巧妙的存储模式设计,完整堆栈跟踪在大规模集群环境下仍然具有较高的存储成本。这种成本使得持续性能分析在实践中成为一种按需启用的功能:许多潜在用户根本不会开启它,而那些启用它的用户通常也只会在部分主机上启用。
-
查询摩擦: 规范化的堆栈跟踪模式针对数据摄取和存储进行了优化,但却增加了临时查询的复杂度。“我的服务在 TLS 上消耗了多少 CPU 时间?”这是一个简单的问题,但要获得答案可能需要编写复杂的 ES|QL 查询或自定义代码。
-
对人工智能不友好的数据: 规范化堆栈跟踪数据(通常涉及多层间接引用)不利于直接进行算法分析。尤其是大语言模型很难处理这类数据,因此需要进一步将数据转换为更适合大语言模型处理的表示形式。
-
用户体验障碍: 火焰图在你知道如何阅读时极其有用,但对于不了解它的人来说却令人望而生畏。
这四个障碍相互叠加:
- 存储成本限制了覆盖范围;
- 用户体验障碍限制了能够从覆盖范围中受益的人群;
- 查询摩擦限制了用户能够提出的问题;
- 而对人工智能不友好的数据表示形式则限制了系统在用户不知道该问什么问题时所能采取的行动。
性能分析衍生指标如何工作:在边缘进行分类
核心思想非常简单:与其将完整堆栈跟踪一路发送到后端,然后让用户在那里理解这些数据,不如直接在边缘侧、OpenTelemetry Collector 流水线内部完成分类和计数,并输出普通的 OpenTelemetry 时间序列计数器。
性能分析逻辑本身并没有改变;它仍然是在 OpenTelemetry Collector 内运行的 OpenTelemetry eBPF 性能分析器。
所有新增工作都发生在 Collector 中的一个无状态连接器内:该连接器检查性能分析器生成的每个堆栈跟踪,将其中的栈帧分类到一个或多个类别中,然后递增相应计数器。
我们已经发布了性能分析指标连接器(profiling metrics connector),作为 Elastic 的 OpenTelemetry Collector 组件(OpenTelemetry Collector Components)代码仓库的一部分。
它位于 OpenTelemetry eBPF 性能分析器接收器与任意指标导出器之间,将完成符号解析的堆栈跟踪转换为带有属性的命名聚合计数器。

由于 profilingmetricsconnector 位于标准 OpenTelemetry Collector 流水线内部,因此它生成的每个指标都会像其他遥测数据一样流经相同的处理器。
在下面的示例中,资源检测处理器(resourcedetectionprocessor)会为每个计数器补充从主机派生出的属性。
connectors:
profilingmetrics:
flush_interval: 30s
receivers:
profiling: {}
exporters:
elasticsearch:
endpoints:
- # ENDPOINT
api_key: # API_KEY
mapping:
mode: otel
processors:
resourcedetection:
detectors: ["system"]
system:
hostname_sources: ["os"]
resource_attributes:
host.name:
enabled: true
host.id:
enabled: false
host.arch:
enabled: true
os.description:
enabled: true
os.type:
enabled: true
service:
pipelines:
profiles:
receivers: [ profiling ]
exporters: [ profilingmetrics ]
metrics:
receivers: [ profilingmetrics ]
processors: [resourcedetection]
exporters: [ elasticsearch ]
性能分析衍生的 CPU 指标:会输出什么
该连接器内置了一组基于实用分类规则预先构建好的计数器。
每个指标都表示其叶子栈帧匹配特定类别的堆栈跟踪样本数量,其中采样频率值可作为 CPU 消耗量的近似表示。
| 指标 | 分类对象 | 附加元数据 |
|---|---|---|
| 内核 CPU 时间 | 内核叶子栈帧 | 内核符号、内核类别(文件系统、网络、调度器、内存管理、锁、定时器等) |
| 原生代码 CPU 时间 | 原生 C/C++/Rust 叶子栈帧 | 共享库名称(OpenSSL、glibc、libstdc++ 等) |
| Java CPU 时间、Python CPU 时间、Go CPU 时间等 | 运行时特定叶子栈帧 | 运行时特定属性 |
内核分类值得更深入地了解,因为现代 Linux 内核包含超过 400 个系统调用。
然而,在 CPU 堆栈跟踪中出现的大部分内容实际上都集中在少数几个子系统中:
-
文件系统读写
-
网络读写
-
内存管理
-
调度
-
同步机制
某些系统调用(例如 read、write)本身具有歧义,仅凭系统调用名称无法判断其具体用途。只有继续查看调用栈中更深层的栈帧,才能确定其实际所属类别:
-
vfs_read指向文件系统操作; -
sock_recvmsg指向网络操作。
该连接器会在遍历栈帧的过程中自动完成这种消歧处理。

原生栈帧通常除了共享库名称之外缺乏更多符号信息,但这些名称本身仍然具有很高的信息价值:
-
libssl和libcrypto表示 OpenSSL 或其变体正在执行加密相关工作; -
libzstd表示压缩操作; -
clrjit表示 .NET 即时编译器(JIT)正在工作。
我们不需要静态枚举所有库。连接器会动态生成 library 属性值,并使用裁剪后的库名称(例如 libssl,而不是 libssl.so.3),从而保持基数的整洁和可控。
目前,对于每个堆栈跟踪,连接器都会计算一个 自身 CPU(Self CPU)计数,即叶子栈帧匹配某个类别时对应的计数,这反映的是独占 CPU 使用量。
对于某些细粒度内核类别(例如块设备输入输出),情况会稍微复杂一些。实际的叶子栈帧通常是设备驱动程序调用,而这些调用本身并不适合作为分类依据。
对此,我们会尝试匹配调用栈中更上层的栈帧。例如,仅检测到 submit_bio 就足以正确地将样本归类为块设备输入输出相关工作。
用户还可以定义自己的分类规则,只需指定一个栈帧匹配模式(例如某个函数或软件包),连接器便会自动为其生成对应的计数器。
性能分析衍生指标对可观测性的价值
从表面上看,这种变化似乎很小 —— “我们只是输出了一些计数器” —— 但它实际上改变了性能分析在可观测性体系中的几乎一切。
-
存储成本降低多个数量级: 相比于其所提炼出的完整堆栈跟踪,在 5 秒、30 秒或 1 分钟窗口内聚合得到的计数器要便宜得多。预聚合时间间隔是可配置的,其权衡点是时间分辨率,而不是分类准确性。对于大多数“CPU 时间花在哪里了?”的问题来说,30 秒粒度已经足够。
-
默认开启: 由于存储成本已经接近普通指标,因此性能分析衍生指标可以默认对所有用户、所有主机启用。从部署性能分析器的第一天开始,用户就能够获得按运行时、系统调用、内核类别以及共享库划分的 CPU 消耗明细。
-
标准仪表板: 这些本质上是普通的 OpenTelemetry 时间序列计数器,因此可以直接使用 Kibana 原生支持的可视化方式进行分析,例如堆叠柱状图、饼图、Top N 面板以及其他可视化组件。用于应用指标的相同 Lens 视图和基于时序数据库的分析视图同样适用于这些指标。

-
对 AI 和查询友好: 标准时间序列数据可以被 ES|QL、机器学习作业、异常检测器以及大语言模型直接消费。“在过去六小时内,按支付命名空间筛选,展示 CPU 时间最高的服务”这一类查询,不仅系统可以轻松回答,大语言模型也可以很容易生成对应查询。
-
跨信号关联: 由于这些指标在标准 OpenTelemetry Collector 流水线中流动,它们会继承与日志、其他指标和追踪相同的资源属性(例如
service.name、host.name、deployment.environment、cloud.region等),从而实现统一关联分析。 -
即时价值 + 深度路径: 只想知道“是谁在消耗我的 CPU”的用户可以在不打开火焰图的情况下获得有意义的答案;而需要深入分析的用户仍然可以使用底层的 eBPF 性能分析器,随时获取完整的堆栈跟踪。
可编程性能分析与自适应采样
更长远的方向是让性能分析器不再只是用户 “消费” 的工具,而是可以被 “编程” 的系统。用户自定义指标只是第一步,后续还会结合按需(全量)性能分析与自适应采样机制。
性能分析衍生指标或其他信号可以作为触发条件,在系统检测到特定事件时自动在某个主机或服务上开启全量性能分析,以捕获完整堆栈跟踪。这样,完整性能分析的处理与存储成本只在真正需要时才产生。
我们还可以将同样的思路应用到采样频率上。19Hz 可以作为稳态运行的合理基线,但当指标信号检测到异常事件时,系统可以自动将采样率提升到 100Hz 或更高,在相关时间窗口内获取高保真数据,随后再恢复到基线采样率。
性能分析衍生指标如何实现 “自驱动可观测性”
如今大多数可观测性栈采用的是一种开环模型:性能分析器按固定配置输出数据,然后由人去查看火焰图和仪表板,可能再与日志、其他指标和追踪进行关联,形成假设并触发更深入的调查。这个链条中的每一步都依赖人的决策,没有任何反馈能以足够快的速度回流到性能分析器本身,系统也无法基于自身观测进行主动行动。
性能分析衍生指标改变了这一点,并闭合了这个反馈回路。
-
一个 “重要主机事件” 指标、
CPU 使用异常或延迟突增:某个信号超过阈值。 -
性能分析器根据反馈进行调整:提高采样率,并在受影响的主机上开启全量性能分析。
-
更丰富的数据被大语言模型、人类或两者结合进行分析,并与日志、追踪及其他指标进行关联。使跨信号关联变得简单的资源属性,同样也让系统自身分析变得容易。
-
根因被识别,系统执行或建议修复措施,指标恢复正常,循环继续运行。
这就是所谓的自驱动可观测性。
在这个模型中,性能分析器不再只是一个被动使用的观测工具,而是一个自治反馈回路的“感知器官”:一个能够观察自身、决定需要更深入观察哪里,并根据所见动态调整自身配置的系统。
下一步:包含性 CPU、非 CPU 时间指标与运行时专属分析
任何在堆栈跟踪中可见的数据都可以成为指标来源,多个扩展方向已经在路线图中。
-
包含性 CPU 指标: 当前预构建计数器基于叶子栈帧(排他 CPU)进行归因。而包含性 CPU 指标将归因于整个调用链,这在你关心函数调用的总成本(函数本身 + 所有递归调用)时非常有用,而不仅仅是函数内部直接消耗的 CPU。
-
运行时专属指标: 包括 GC 时间、JSON/Protobuf 序列化时间、RPC 框架开销、FFI 边界等。这些是每个团队最终都会关心的运行时问题,将被默认回答。
-
非 CPU 指标: CPU 上的性能分析告诉你 CPU 花在哪里,而非 CPU(Off-CPU)性能分析告诉你 CPU “在哪里没用”(例如等待 I/O、锁竞争等)。同样的分类逻辑适用,只是信号来源不同。
性能分析衍生指标是 Elastic 内部的一个活跃研究方向,而 性能分析指标连接器(profilingmetricsconnector)是目前可以直接上手实验的起点。配套的 Kibana 集成 已经提供了上述所有指标的仪表板。
如果你已经在使用 Elastic 的持续性能分析,这些指标将作为一等公民进入 Elastic 生态。如果还没有,这也是一个非常低门槛的切入方式——不需要火焰图经验,存储成本也极低。
火焰图不会消失,但它将不再是性能分析唯一的结果表达方式。
原文:https://www.elastic.co/observability-labs/blog/otel-profiling-metrics
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)