ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Grafana Tempo 中的 OTTL Profile Context:面向 OTLP Profile 数据的转换与过滤编程指南

Grafana Tempo 中的 OTTL Profile Context:面向 OTLP Profile 数据的转换与过滤编程指南 Grafana Tempo 中的 OTTL Profile Context面向 OTLP Profile 数据的转换与过滤编程指南【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo导读本文围绕 OTTL Profile Context 展开它是 OpenTelemetry Collector Contrib 中 OTTLOpenTelemetry Transformation Language为pdata ProfilesOTLP profile 数据的内部表示提供的专用上下文实现。在 Grafana Tempo 这类以 OTLP 为数据面的分布式追踪后端中OTTL Profile Context 是编写 Profile 数据过滤与变换语句如 filter processor 的profile_conditions时的核心编程入口。读完本文你将掌握 Profile Context 支持的完整路径清单、各字段的读写语义、其与资源/作用域上下文的层级关系以及如何在 filter processor 中落地实战。[!NOTE] 本文涉及的文档与源码均来自当前仓库的 vendor 依赖目录文档声明该能力仅适用于0.124.0及更高版本。一、背景OTTL 与 Profile 数据模型OTTL 是 OpenTelemetry Collector Contrib 提供的一套面向遥测数据的变换/过滤语言它通过上下文Context把语言表达式与具体的遥测数据结构绑定。目前仓库 vendor 中提供了一系列上下文实现ottldatapointottllogottlmetricottlprofile本文主题ottlresourceottlscopeottlspanottlspaneventProfile Context 是其中的Profile 专用实现它面向 pdata Profiles即 collector 内部对 OTLP profile 数据的表示。凡是需要与 OTLP profiles 交互的场景例如按 Profile 字段过滤采样、改写 profile 属性、读取/写入 profile 时间与周期都应使用 Profile Context 作为语句求值的载体。二、Profile Context 的核心机制2.1 上下文层级从源码结构看Profile 数据在 OTLP 中遵循ResourceProfiles - ScopeProfiles - Profile的三级嵌套关系。TransformContext正是对这一层级的封装。见 ottlprofile/profile.gotype TransformContext struct { profile pprofile.Profile dictionary pprofile.ProfilesDictionary instrumentationScope pcommon.InstrumentationScope resource pcommon.Resource cache pcommon.Map scopeProfiles pprofile.ScopeProfiles resourceProfiles pprofile.ResourceProfiles }它同时实现了ctxresource.Context、ctxscope.Context、ctxprofile.Context三个内部接口见同文件 L37-L42这意味着在一条 OTTL 语句中你可以无缝访问 Resource 层与 InstrumentationScope 层的字段——这正是下文路径表中resource.*、instrumentation_scope.*得以存在的底层原因。2.2 实例生命周期对象池复用为了支撑高吞吐的 profile 处理TransformContext通过sync.Pool复用实例见 profile.govar tcPool sync.Pool{ New: func() any { return TransformContext{cache: pcommon.NewMap()} }, }NewTransformContextPtr(...)从池中取出实例并填充字段使用完毕后必须调用Close()该方法会清空各字段并归还池中见 profile.goNewTransformContext非指针版本已在v0.145.0被标记为 Deprecated新代码应使用指针版本。2.3 枚举与 Context 名ContextName ctxprofile.Name其值为字符串profile见 ctxprofile/context.go。Profile Context 不支持枚举符号parseEnum直接返回enum symbol not found错误见 profile.go。三、支持的路径Paths完整清单Profile Context 整体遵循 profiles proto 中的字段命名。所有整数类型均以int64读写所有双精度浮点类型均以float64读写。下表是文档声明支持的全部路径字段类型引用自 pdata 数据类型pathfield accessedtypecache当前变换上下文临时缓存的值。cache可在复杂变换中作为数据临时占位符pcommon.Mapcache[]cache 中某个条目的值。支持多重索引以访问嵌套字段。string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte 或 nilresource被处理 profile 的 resourcepcommon.Resourceresource.attributes被处理 profile 的 resource 属性pcommon.Mapresource.attributes[]被处理 profile 的 resource 属性值。支持多重索引以访问嵌套字段。string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte 或 nilinstrumentation_scope被处理 profile 的 instrumentation scopepcommon.InstrumentationScopeinstrumentation_scope.name被处理 profile 的 instrumentation scope 名称stringinstrumentation_scope.version被处理 profile 的 instrumentation scope 版本stringinstrumentation_scope.attributes被处理数据点的 instrumentation scope 属性pcommon.Mapinstrumentation_scope.attributes[]被处理数据点的 instrumentation scope 属性值。支持多重索引以访问嵌套字段。string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte 或 nilprofile.attributes被处理 profile 的属性pcommon.Mapprofile.attributes[]被处理 profile 的属性值。支持多重索引以访问嵌套字段。string, bool, int64, float64, pcommon.Map, pcommon.Slice, []byte 或 nilprofile.sample_type被处理 profile 的 sample typepprofile.ValueTypeprofile.sample_type.typeprofile.sample_type关联的类型stringprofile.sample_type.unitprofile.sample_type关联的单位stringprofile.sample被处理 profile 的 samplespprofile.SampleSliceprofile.time_unix_nano被处理 profile 的 unix nano 时间int64profile.time被处理 profile 的time.Time时间time.Timeprofile.duration_unix_nano被处理 profile 的 unix nano 时长int64profile.duration被处理 profile 的纳秒时长int64profile.period_type被处理 profile 的 period typepprofile.ValueTypeprofile.period_type.typeprofile.period_type关联的类型stringprofile.period_type.unitprofile.period_type关联的单位stringprofile.period被处理 profile 的 periodint64profile.profile_id被处理 profile 的 profile idpprofile.ProfileIDprofile.profile_id.stringprofile id 的字符串表示stringprofile.attribute_indices被处理 profile 的 attribute indices[]int64profile.dropped_attributes_count被处理 profile 的 dropped attributes countint64profile.original_payload_format被处理 profile 的原始载荷格式stringprofile.original_payload被处理 profile 的原始载荷[]byteotelcol.*ottlotelcol 上下文暴露的所有路径varies注上表为原文档的完整表格未做删减其中cache[]、resource.attributes[]等索引写法中的表示具体键名/索引。四、路径的源码级实现每个字段的 Get/Set 语义路径的解析入口在 ctxprofile/profile.go 的PathGetSetter它对path.Name()做 switch 分发把sample_type、sample、time_unix_nano、time、duration_unix_nano、duration、period_type、period、profile_id、attribute_indices、dropped_attributes_count、original_payload_format、original_payload、attributes等字段分别映射到对应的 accessor。理解这些 accessor 的实现能帮你预判 OTTL 语句在读写时会遇到的行为与边界条件。4.1 时间与时长int64 与非负校验profile.time_unix_nano的 Getter 返回Time().AsTime().UnixNano()Setter 接受int64并通过pcommon.NewTimestampFromTime(time.Unix(0, i))写回profile.go L89-L103。profile.time的 Getter 直接返回time.TimeSetter 同样接受time.TimeL105-L119。profile.duration_unix_nano与profile.duration在 Setter 中对负值直接报错duration_unix_nano must be non-negative因为底层DurationNano是uint64L121-L157。这意味着负时长无法通过 OTTL 写入。4.2 Profile ID二进制与十六进制字符串profile.profile_id读写pprofile.ProfileID且 Setter 拒绝空 IDprofile ids must not be emptyL189-L206。profile.profile_id.string将 ID 以hex.EncodeToString编码为小写十六进制字符串返回Setter 则通过ctxcommon.ParseProfileID解析字符串并校验非空L208-L230。在条件表达式中比较 profile ID 时使用.string形式更便于与日志中常见的十六进制表示对齐。4.3 ValueType 与字符串字典strindexprofile.sample_type与profile.period_type都是pprofile.ValueType其底层存储并非直接字符串而是指向 profile 数据字典字符串表的索引TypeStrindex/UnitStrindex。见 ctxprofile/value_type.go读取profile.sample_type.type时先检查currIndex是否越界strindex %d is out of range再从ProfilesDictionary.StringTable()中取回字符串写入时若当前索引对应的字符串与新值相同则复用索引否则调用pprofile.SetString向字符串表追加新值并返回新索引从而在保持 profile 数据字典化存储的同时提供字符串级读写语义。对使用方而言这意味着你可以像操作普通字符串一样读写sample_type.type、sample_type.unit、period_type.type、period_type.unit而无需关心字典索引细节。4.4 属性与原始载荷profile.attributes支持两种形态无索引时返回整个pcommon.Map通过ctxprofilecommon.AccessAttributes带索引如profile.attributes[key]时返回具体值AccessAttributesKeyL60-L67与resource.attributes[]、instrumentation_scope.attributes[]行为一致。profile.original_payload以[]byte形式读写OriginalPayload().FromRaw(...)可用于携带 pprof 等原始格式L275-L288。profile.attribute_indices以[]int64形式读写底层int32切片L232-L241。五、实战在 Filter Processor 中使用 Profile ContextProfile Context 最典型的生产场景是 filter processor它允许通过profile_conditions按 OTTL 布尔表达式丢弃匹配的 profile。在 filter processor 的 profiles.go 中可以看到其调用方式dic : pd.Dictionary() pd.ResourceProfiles().RemoveIf(func(rp pprofile.ResourceProfiles) bool { resource : rp.Resource() // 先按 resource 级条件过滤 ... rp.ScopeProfiles().RemoveIf(func(sp pprofile.ScopeProfiles) bool { sp.Profiles().RemoveIf(func(profile pprofile.Profile) bool { tCtx : ottlprofile.NewTransformContextPtr(rp, sp, profile, dic) defer tCtx.Close() skip, err : fpp.skipProfileExpr.Eval(ctx, tCtx) ... }) return sp.Profiles().Len() 0 }) return rp.ScopeProfiles().Len() 0 })对应到配置文件中filter processor 的 config.go 定义了ProfileFilters结构其中ProfileConditions的注释明确说明如果任一条件求值为 true该 profile 将被丢弃。filter processor 支持两种等价写法二者不可混用混用会报cannot use context inferred profile conditions ...错误见 config.go写法一统一的profile_conditions推荐支持resource.、scope.、profile.前缀混合processors: filter/profiles: error_mode: ignore profile_conditions: - resource.attributes[host.name] test - profile.duration_unix_nano 3000 - instrumentation_scope.name pyroscope说明profile_conditions通过condition.NewProfileParserCollection构建配置了WithProfileCommonParsers与WithProfileParser因此同一列表中既可出现resource./scope.前缀的表达式resource/scope 上下文也可出现profile.前缀的表达式profile 上下文前缀决定了语句归属的求值层级。写法二分层的profiles.resource与profiles.profile旧式已标记 Deprecatedprocessors: filter/profiles: error_mode: ignore profiles: resource: - resource.attributes[host.name] test profile: - profile.duration_unix_nano 3000两者的求值顺序与丢弃语义在源码中体现为先对 ResourceProfiles 求值skipResourceExpr命中则整组 ResourceProfiles 被移除profiles.go L110-L123再逐层下钻到 Profile对每个 profile 求值skipProfileExpr命中则移除该 profileL127-L142处理完后如果ResourceProfiles().Len() 0processor 返回ErrSkipProcessingData跳过下游L101-L103并通过pd.SampleCount()前后差值记录被过滤的样本数L84-L94。六、上下文扩展otelcol.*路径与路径上下文名6.1otelcol.*委托路径路径表中最后一行的otelcol.*表示 Profile Context 继承了 ottlotelcol 上下文暴露的全部路径典型如otelcol.resource.attributes等与 collector 元信息相关的字段。在pathExpressionParser见 profile.go中ctxotelcol.PathGetSetter与ctxresource、ctxscope、ctxprofile并列注册实现了这一委托关系。6.2 显式路径前缀EnablePathContextNames对于存在歧义的语句例如同一路径名在不同上下文中的含义不同可以启用EnablePathContextNames()选项见 profile.go L161-L171它注册了以下合法上下文前缀profilectxprofile.Namescope与scopelegacy 名resourceotelcol启用后所有语句路径必须带合法上下文前缀否则报错。该选项被标记为 Experimental未来可能变更或移除。6.3 错误模式与语句序列NewParser支持注入自定义函数functions map[string]ottl.Factory[*TransformContext]与 OTTL 选项NewStatementSequence/NewConditionSequence分别支持通过WithStatementSequenceErrorMode/WithConditionSequenceErrorMode设置ErrorMode如ignore、propagate、silent这是 filter processor 中error_mode配置的底层实现入口见 profile.go L146-L209。七、调试与可观测性TransformContext实现了zapcore.ObjectMarshaler见 profile.go L56-L62序列化输出包含resource、scope、profile、cache四部分。这在开启 OTTL 调试日志时非常有用——filter processor 的 README 示例展示了形如condition evaluation result {match: true, TransformContext: {resource: ..., scope: ..., profile: ..., cache: {}}}的日志输出可直接查看条件求值时上下文中的实际字段值用于排查条件未命中或误命中问题。八、小结OTTL Profile Context 将 OTLP profile 的 pdata 模型以路径形式完整暴露给 OTTL 语言是编写 profile 过滤、变换、校验逻辑的统一入口。其要点可归纳为层级完整一个 TransformContext 同时承载resource、instrumentation_scope、profile三层数据语句可跨层访问类型统一整数以int64、双精度浮点以float64读写复合类型Map/Slice/ValueType/ProfileID遵循 pdata 类型字典透明sample_type/period_type的 type/unit 虽底层存储为字符串表索引但对 OTTL 用户呈现字符串语义实战落地在 filter processor 中通过profile_conditions或已弃用的profiles.profile即可基于上述路径完成 profile 级过滤配合error_mode控制错误行为。如需进一步深入可继续阅读仓库中的相关实现Profile Context 源码、路径 accessor 实现、filter processor profiles 处理 以及 filter processor 配置定义。【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进