使用 Grafana Tempo 与 Pyroscope 实现 Trace 到 Profile 关联分析:tracing/tempo 示例全解析
2026/9/15 15:48:07 网站建设 项目流程

使用 Grafana Tempo 与 Pyroscope 实现 Trace 到 Profile 关联分析:tracing/tempo 示例全解析

【免费下载链接】pyroscopeContinuous Profiling Platform. Debug performance issues down to a single line of code项目地址: https://gitcode.com/GitHub_Trending/py/pyroscope

本篇文章基于仓库examples/tracing/tempo示例,系统讲解如何在 Grafana 中打通「分布式追踪(Tempo)」与「持续剖析(Pyroscope)」两条观测链路:从一条 Trace 的某个 Span 一键跳转到对应时间段内的 CPU Profile 火焰图,定位性能问题到具体代码行。读完本文,你将掌握示例的完整架构组成、本地一键启动方式、Grafana Tempo 数据源trace-to-profiles的配置原理与参数含义,以及底层埋点(OTel + Pyroscope SDK)是如何把 Span 与 Profile 关联起来的。

示例概览:一套可本地运行的 Trace ↔ Profile 联动演示

examples/tracing/tempo/README.md描述的是一个用 Docker Compose 编排的端到端演示环境,它由四类组件构成:

  • Ride share 演示应用(rideshare):模拟打车场景的多语言业务服务,同时产生链路追踪与剖析数据;
  • Tempo:链路追踪后端,负责接收、存储和查询 Trace;
  • Pyroscope:持续剖析平台,负责接收、存储和查询 Profile;
  • Grafana:统一可视化入口,预置好 Tempo 与 Pyroscope 数据源。

rideshare应用产生的 Trace 和 Profiling 数据都会汇入 Grafana,且 Pyroscope、Tempo 数据源由 Grafana provisioning 机制自动配置,无需手工录入。整个编排定义在 docker-compose.yml 中,下面先拆解它的服务组成。

docker-compose 服务拓扑

examples/tracing/tempo/docker-compose.yml 中定义了 8 个服务,其中应用侧按语言/区域拆分出 4 个实例,外加 1 个负载生成器:

服务说明关键配置
rideshare-go-ap-southGo 版 rideshare,区域 ap-south暴露 5000 端口,PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040OTLP_URL=tempo:4318
rideshare-go-eu-northGo 版 rideshare,区域 eu-north复用基础环境变量,仅覆盖REGION
rideshare-dotnet-eu-west.NET 版 rideshare,区域 eu-west走 OTLP gRPC(tempo:4317),OTEL_RESOURCE_ATTRIBUTES设置host.namePYROSCOPE_LABELS设置hostname
rideshare-python-eu-eastPython 版 rideshare,区域 us-east同样走 OTLP gRPC,设置PYROSCOPE_LABELS: hostname=...
load-generator负载生成器依次向 4 个 rideshare 实例的 5000 端口发起请求,制造持续流量
grafana可视化平台预装grafana-pyroscope-app插件,开启traceToProfilestracesEmbeddedFlameGraph特性开关
tempo链路追踪后端镜像grafana/tempo:2.10.8,开放 OTLP、Jaeger、Zipkin 等接收端口
pyroscope持续剖析后端镜像grafana/pyroscope:latest,暴露 4040 端口

几点值得注意的编排细节:

  1. 应用实例的「身份标签」是关联链路与剖析数据的关键。例如rideshare-dotnet-eu-west通过OTEL_RESOURCE_ATTRIBUTEShost.name=rideshare-dotnet-eu-west写入 Span 资源属性,同时通过PYROSCOPE_LABELShostname=rideshare-dotnet-eu-west写入剖析标签——这正是后文「Tempo 数据源 tags 配置」能生效的前提(README 中host.name → hostname的映射即源于此)。
  2. Grafana 通过环境变量开启了两个关键特性开关GF_FEATURE_TOGGLES_ENABLE=traceToProfiles tracesEmbeddedFlameGraph。前者启用「从 Trace 跳转到 Profile」,后者允许在 Trace 视图中直接内嵌渲染火焰图。
  3. 服务配置统一由模板生成:grafana、tempo、pyroscope 三个服务的配置均标注为「由examples/_templates/模板生成,修改后需执行make examples/sync-templates」,说明该示例与仓库其他示例共用一套模板,保持配置一致。

Tempo 与 Pyroscope 的服务端配置

Tempo 的配置位于 examples/tracing/tempo/tempo/tempo.yml,它开启了多种接收协议:

  • distributor receivers:同时启用 Jaeger(thrift_http / grpc / thrift_binary / thrift_compact)、Zipkin 与 OTLP(HTTP0.0.0.0:4318、gRPC0.0.0.0:4317),注释中特别提醒:生产环境应只按需开启实际使用的 receiver;
  • query_frontend:设置了搜索与按 TraceID 查询的 SLO(5s 延迟目标);
  • storage:使用local后端,WAL 与 block 分别落在/tmp/tempo/wal/tmp/tempo/blocks,适合本地演示。

Pyroscope 的配置位于 examples/tracing/tempo/pyroscope/pyroscope.yml,只包含两个关键项:

tracing: enabled: true profiling_enabled: true pyroscopedb: max_block_duration: 5m

它表明Pyroscope 自身也被插桩tracing.enabled开启 Pyroscope 的内部链路追踪,profiling_enabled开启对 Pyroscope 自身运行时的剖析采集(README 中明确说明 pyroscope 自身使用 OpenTelemetry 与otel-profiling-go做剖析集成),pyroscopedb.max_block_duration: 5m控制存储 block 的最大时间跨度。

快速启动:两条命令拉起整套环境

README 给出的启动方式极为简洁,分两步:

# Pull latest pyroscope and grafana images: docker pull grafana/pyroscope:latest docker pull grafana/grafana:latest docker-compose up

操作说明:

  1. 先拉取最新版 Pyroscope 与 Grafana 镜像(Tempo 镜像已由 compose 文件锁定为grafana/tempo:2.10.8);
  2. 在 examples/tracing/tempo 目录下执行docker-compose up(新版本 Docker 也可使用docker compose up)即可启动全部服务。

启动后访问 Grafana 的Explore 页面,即可在 Tempo 数据源中查询ride-sharing-app服务的 Trace。README 给出了一个可直接粘贴到浏览器地址栏的深链,其核心查询参数是:Tempo 数据源(datasource=tempo)、traceqlSearch查询类型、service.name = ride-sharing-app资源过滤条件,以及now-6hnow的时间范围。

选中一条 Trace 后,点击带有 Profile 关联标记的 Span(README 配图中展示:Span 右侧出现「View profile」类入口),即可从追踪视图直接跳转到 Pyroscope 的火焰图,形成「Trace 定位请求 → Profile 定位 CPU 热点」的完整排查闭环。README 的截图演示了从 Tempo 的 Span 详情进入 Pyroscope CPU 火焰图的界面效果。

关联原理:Span 与 Profile 是如何绑定到一起的

README 对该示例的关联机制做了三点关键说明,这是理解整套方案的核心:

  1. 默认只有根 Span 被标记关联(root span,即本地创建的第一个 Span)。这类 Span 会显示link(链接)图标,并且其属性中带有pyroscope.profile.id,值对应当前 Span 的 Span ID;
  2. pyroscope.profile.id属性存在 ≠ Span 一定有 Profile。由于采样是按固定时间间隔进行的,当某个 Span 实际消耗的 CPU 时间**小于采样间隔(10ms)**时,可能采集不到任何堆栈样本,因而没有可用的 Profile 数据;
  3. 在 Explore 中,从 Trace 跳转 Profile 依赖 Tempo 数据源的trace-to-profiles配置(详见下一节)。

底层实现可以结合样例应用源码印证。在 examples/language-sdk-instrumentation/golang-push/rideshare/main.go 中,应用把 OTel TracerProvider 包装为 Pyroscope 提供的otelpyroscope.NewTracerProvider(tp)

// Set the Tracer Provider and the W3C Trace Context propagator as globals. // We wrap the tracer provider to also annotate goroutines with Span ID so // that pprof would add corresponding labels to profiling samples. otel.SetTracerProvider(otelpyroscope.NewTracerProvider(tp))

从注释可以确认关联机制:该包装器会在剖析采样时用当前 Span ID 标注 goroutine,使 pprof 采集到的剖析样本携带对应的 Span 标签,Pyroscope 侧再以这些标签为索引建立 Span → Profile 的映射,最终以pyroscope.profile.id属性的形式呈现在 Span 上。而剖析数据的产生,来自 rideshare.go 中通过pyroscope-goSDK 启动的 Profiler(应用名取自PYROSCOPE_APPLICATION_NAME,默认ride-sharing-app;上报地址取自PYROSCOPE_SERVER_ADDRESS,默认http://localhost:4040)。

Grafana Tempo 数据源配置:trace-to-profiles 详解

要让「Trace 跳转 Profile」真正可用,必须对 Tempo 数据源做如下配置(README 列出的四个要点):

  1. Profile 数据源(Data source of the profiling data):指定保存剖析数据的数据源,本示例为 Pyroscope;
  2. 查询中使用的标签(Tags to use in the query):指定把 Span 上的哪些标签映射为 Pyroscope 查询标签;
  3. Profile 类型(Profile type):目前只有 CPU time 类型的 Profile 得到完整支持
  4. 查询覆盖(Query override):是否使用自定义查询替代默认生成的查询。

这些配置在示例的 Grafana 预置文件中有完整落地。见 examples/tracing/tempo/grafana-provisioning/datasources/tempo.yml:

--- apiVersion: 1 datasources: - name: Tempo type: tempo access: proxy orgId: 1 url: http://tempo:3200 basicAuth: false isDefault: true version: 1 editable: false apiVersion: 1 uid: tempo jsonData: httpMethod: GET serviceMap: datasourceUid: prometheus tracesToProfiles: customQuery: false datasourceUid: "pyroscope" profileTypeId: "process_cpu:cpu:nanoseconds:cpu:nanoseconds" tags: - key: "service.name" value: "service_name"

各字段含义如下:

字段示例值作用
tracesToProfiles.datasourceUidpyroscopeProfile 数据源 UID,指向同目录 pyroscope.yml 中定义的grafana-pyroscope-datasource
tracesToProfiles.profileTypeIdprocess_cpu:cpu:nanoseconds:cpu:nanoseconds要跳转的 Profile 类型,对应CPU 时间剖析(印证了 README「只有 CPU time 完全支持」的说明)
tracesToProfiles.customQueryfalse关闭自定义查询,使用 Grafana 自动生成的默认查询(对应 README 的「Query override」选项)
tracesToProfiles.tagsservice.name → service_name标签映射列表,将 Span 上的标签映射为 Pyroscope 查询标签

仓库中还有一份等价变体 examples/tracing/tempo/grafana/provisioning/datasources/datasources.yml,它把标签映射配置为host.name → hostname,并同时注册了 Tempo 与 Pyroscope 两个数据源(Pyroscope 指向http://pyroscope:4040)。这与 README 中「配置host.name标签用于 Pyroscope 查询中的hostname标签」的表述一致。

标签(Tags)配置:可选但强烈建议

README 强调:标签配置是可选的,但强烈建议配置,因为它直接决定查询性能。示例中的做法是:

  • 在 Tempo 侧配置host.name标签,映射为 Pyroscope 查询中的hostname标签;
  • 效果是:跳转查询会把数据集限定在特定主机上,从而让查询始终快速返回(restricts the data set for lookup to a specific host)。

为什么能加速?结合 docker-compose.yml 可以看到,每个 rideshare 实例的hostnamePYROSCOPE_LABELS都被设置为各自唯一的服务名(如rideshare-dotnet-eu-west)。当 Tempo 数据源用host.name=rideshare-dotnet-eu-west去查询 Pyroscope 时,Pyroscope 只需在带hostname=rideshare-dotnet-eu-west标签的样本集合中查找,缩小了扫描范围。

同时 README 给出一条必须遵守的前置条件:你配置的标签必须真实存在于 Span 的属性(attributes)或资源(resources)中,否则 Trace 到 Profile 的跳转链接不会出现。也就是说:

  • 标签的 key 必须与 Span 上的属性/资源 key 一致(例如host.name);
  • 标签的 value 必须与 Pyroscope 侧的标签值对应(例如hostname标签值),两侧命名需要按映射关系对齐。

在 .NET 实例中可以看到这种对齐的落地方式:OTEL_RESOURCE_ATTRIBUTES: host.name=rideshare-dotnet-eu-west(Span 资源属性)+PYROSCOPE_LABELS: hostname=rideshare-dotnet-eu-west(剖析标签)。若两者不一致或 Span 上缺少该属性,Grafana 无法生成有效的关联查询。

埋点侧实现:多语言 OTel 集成的样板

README 的「Instrumentation」一节说明了示例的插桩方案:

  • rideshare演示应用使用 OpenTelemetry 埋点:
    • Go 侧使用Go OTel integrationotel-profiling-go);
    • Java 侧使用Java OTel integrationotel-profiling-java);
  • pyroscope自身同样使用 OpenTelemetry 与otel-profiling-go做剖析集成。

仓库中对应的多语言样例位于 examples/language-sdk-instrumentation/golang-push/rideshare,以及examples/language-sdk-instrumentation下的dotnetjavapythonrubynodejs等目录,可对照参考。

以 Go 样例为例,其 OTel 初始化链路在 main.go 的setupOTEL中完成:分别构建 TracerProvider、LoggerProvider、MeterProvider,并把包装后的 TracerProvider 设置为全局;HTTP 路由通过otelhttp.NewHandler包装(如BikeHandlerCarHandler),使每个请求自动生成对应 Span。rideshare.go中的ReadConfig()则集中读取环境变量,包括:

  • PYROSCOPE_APPLICATION_NAME:剖析应用名,默认ride-sharing-app
  • PYROSCOPE_SERVER_ADDRESS:Pyroscope 上报地址,默认http://localhost:4040
  • PYROSCOPE_BASIC_AUTH_USER/PASSWORD:连接带认证的 Pyroscope 服务(如 Grafana Cloud)时使用;
  • OTLP_URL/OTLP_INSECURE:OTLP 上报地址与是否使用非加密连接(compose 中指向tempo:4318);
  • REGIONhostname等被写入剖析标签Tags的键值。

Profiler()函数(rideshare.go)用这些配置调用pyroscope.Start(config)启动持续剖析,与 OTel Trace 上报并行工作,二者在 Pyroscope/Tempo 后端汇合后即可被 Grafana 关联。

使用限制与注意事项

综合 README 与仓库配置,使用该方案时有几点需要牢记:

  1. Profile 类型限制:目前trace-to-profiles只完整支持 CPU time 剖析(process_cpu:cpu:nanoseconds:cpu:nanoseconds),其他 profile 类型的关联能力有限;
  2. 短 Span 可能无 Profile:由于采样间隔为 10ms,CPU 时间低于采样间隔的 Span 可能没有堆栈样本,pyroscope.profile.id属性存在并不代表一定有可跳转的 Profile;
  3. 默认仅根 Span 标记:示例默认只对根 Span 建立关联并显示链接图标;
  4. 标签必须存在:配置的 tags 必须出现在 Span 的属性或资源中,否则跳转链接不会出现;标签映射应尽量选择区分度高的属性(如host.name)以缩小 Pyroscope 查询范围、保证查询性能;
  5. 生产环境按需裁剪:Tempo 配置中默认开启了 Jaeger/Zipkin/OTLP 全部接收协议,生产部署应只保留实际需要的协议与端口。

小结

examples/tracing/tempo示例展示了一条完整的「分布式追踪 + 持续剖析」联动链路:多语言 rideshare 应用通过 OTel 与 Pyroscope SDK 同时产生 Trace 和 Profile;Tempo 与 Pyroscope 分别负责两类数据的存储与查询;Grafana 借助 Tempo 数据源的trace-to-profiles配置(Profile 数据源、标签映射、Profile 类型、查询覆盖四个要素)把二者关联起来,让开发者从一条 Trace 的 Span 一键进入对应的 CPU 火焰图。想要在自有环境中复刻这一能力,核心动作就是:为应用接入 OTel 与 Pyroscope SDK 并保证标签对齐,然后按本文所述配置 Tempo 数据源即可。

延伸阅读:同一仓库的 examples/tracing 目录下还提供了dotnetgolang-pushjavajava-wallpythonruby等多个语言的同主题示例,可对照学习不同语言生态下的 Trace/Profile 关联接入方式。

【免费下载链接】pyroscopeContinuous Profiling Platform. Debug performance issues down to a single line of code项目地址: https://gitcode.com/GitHub_Trending/py/pyroscope

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询