自驱动可观测性:从堆栈跟踪到基于性能分析衍生的指标
作者来自 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从而保持基数的整洁和可控。目前对于每个堆栈跟踪连接器都会计算一个自身 CPUSelf 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 花在哪里而非 CPUOff-CPU性能分析告诉你 CPU “在哪里没用”例如等待 I/O、锁竞争等。同样的分类逻辑适用只是信号来源不同。性能分析衍生指标是 Elastic 内部的一个活跃研究方向而性能分析指标连接器profilingmetricsconnector是目前可以直接上手实验的起点。配套的Kibana 集成已经提供了上述所有指标的仪表板。如果你已经在使用 Elastic 的持续性能分析这些指标将作为一等公民进入 Elastic 生态。如果还没有这也是一个非常低门槛的切入方式——不需要火焰图经验存储成本也极低。火焰图不会消失但它将不再是性能分析唯一的结果表达方式。原文https://www.elastic.co/observability-labs/blog/otel-profiling-metrics