让指标和追踪回答同一个问题
目标
只用跨度转储文件数出请求数、错误数和延迟分布,与指标一侧的暴露格式文件对照,找出名称和值错位的地方并调整埋点。换着用采样器,亲手让同一份数据得出不同的错误率,用采样概率的倒数校正之后,再做一座为每个区间挑选代表性跟踪的桥梁,把规则固化成文件,应用到第二个服务。
为什么重要
指标和跟踪往往是分别做出来的。指标一侧由框架放入路径模板,跟踪一侧手工放入地址,于是九条时间序列的指标,要和三十多个跨度分组面对面。在这种状态下说“就打开这个峰值里的一个慢请求看看”,没有人能回答。更糟的是用跨度来数比例——在错误全部保留、成功每五个只保留一个这种常见的采样策略下,按跨度数出来的错误率会比实际膨胀将近四倍,如果没有把采样概率写在跨度上,连还原的办法都没有。把两个信号用同样的名称、同样的值连起来,并把概率一起写下,个数和比例就从指标里读,跟踪用来找例子,各回各位。指标 SDK 的累积与增量是别的模块的事,这里做的是属性规则文件和对照两个信号的检查器。
步骤
- 创建
/root/tp-metrics/collect.py(默认转储路径/root/tp-metrics/01-spans.jsonl)。依次处理材料tracelab.tp_metrics.traffic的SHOP,为每个请求创建根跨度GET <주소>(占位符为地址),并在开始跨度时传入五个属性——request.id、request.index、http.target(请求的path)、http.response.status_code(请求的status)、user.id(请求的user)。在跨度内调用traffic.work(tracer, req)。然后在/root/tp-metrics/01-from-spans.tsv中写用制表符分隔的两列四行——requests<탭><루트 스팬 수>、errors<탭><상태 코드 500 이상인 루트 수>、p50_ms<탭><값>、p95_ms<탭><값>(占位符依次为制表符、根跨度数、制表符、状态码 500 以上的根跨度数、制表符、值、制表符、值)。分位数是把根跨度的duration_ms按升序排列,从 1 开始数,取第올림(비율 × 개수)(占位符依次为向上取整、比例、个数)个值,保留到小数点后第三位。 - 指标管道给出的文件在
/opt/app/tracelab/tp_metrics/metrics/shop-api.prom中(Prometheus 暴露格式)。在/root/tp-metrics/02-mapping.tsv中写用制表符分隔的三列三行——第一列是指标标签,依次为handler、code、svc,第二列是在第 1 步转储文件里打算与该标签对应的位置(http.target、http.response.status_code、service.name),第三列是这两者的值是否原样一致,写yes或no。再在/root/tp-metrics/02-gap.txt中写两行——metric_series=后面写该文件中http_requests_total的时间序列数,span_groups=后面写把第 1 步转储文件的根跨度按http.target的值分组时得到的组数。 - 创建
/root/tp-metrics/aligned.py(默认转储路径/root/tp-metrics/03-aligned.jsonl)。处理与第 1 步相同的流量,但在根跨度上额外传入http.route(请求的route,即路径模板),跨度名称也定为GET <경로 틀>(占位符为路径模板)。用于调查的http.target保持不变。采样器用samplers.keep_all()。运行之后,在/root/tp-metrics/03-joined.tsv中,每个时间序列写一行用制表符分隔的四列——<handler><탭><code><탭><지표 값><탭><그 짝의 루트 스팬 수>(占位符依次为 handler、制表符、code、制表符、指标值、制表符、与其配对的根跨度数),按 handler 升序,相同则按 code 升序。两个数字在每个时间序列上必须相同。 - 创建
/root/tp-metrics/sampled.py。从命令行参数接收nth5或errbias,分别用samplers.every_nth(5)和samplers.errors_and_nth(5)作为采样器,其余与第 3 步完全相同地埋点。默认转储路径是/root/tp-metrics/04-<인자>.jsonl(占位符为命令行参数)。运行两次,做出/root/tp-metrics/04-nth5.jsonl和/root/tp-metrics/04-errbias.jsonl之后,连同第 3 步的转储文件一共三个,在/root/tp-metrics/04-rates.tsv中写用制表符分隔的四列三行——第一列依次为full、nth5、errbias(full是第 3 步的转储文件),后面是<오류 루트 수><탭><전체 루트 수><탭><비율>(占位符依次为错误根跨度数、制表符、根跨度总数、制表符、比率),比率保留到小数点后第四位。 - 两个样本转储文件的根跨度上写有
sampling.probability。一个跨度代表的请求数是该值的倒数。在/root/tp-metrics/05-adjusted.tsv中写用制表符分隔的四列两行——第一列依次为nth5、errbias,后面是<보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율>(占位符依次为校正后的错误数、制表符、校正后的总数、制表符、校正后的比率),校正后的错误数和总数保留到小数点后第四位,比率也保留到小数点后第四位。再在/root/tp-metrics/05-limits.txt中写两行——limit1=和limit2=后面,各写一条即使校正也无法还原的东西(各至少 40 个字)。与第 4 步的full比率比较,确认校正能把结果拉回到什么程度。 - 创建
/root/tp-metrics/bridge.py(默认转储路径/root/tp-metrics/06-bridge.jsonl)。像第 3 步那样埋点并留下转储文件之后,重新读取这个转储文件,为每个时间序列挑出最慢的一个根跨度,写成表格。表的路径是把转储路径中的.jsonl换成.tsv的位置(默认就是/root/tp-metrics/06-bridge.tsv)。每行是用制表符分隔的四列<http.route><탭><상태 코드><탭><duration_ms(소수 셋째 자리)><탭><trace_id>(占位符依次为 http.route、制表符、状态码、制表符、duration_ms 保留到小数点后第三位、制表符、trace_id),按http.route升序,相同则按状态码升序。再在/root/tp-metrics/06-limits.txt中用limit1=、limit2=两行,各写至少 40 个字,说明这座桥梁无法回答的东西。 - 在
/root/tp-metrics/07-contract.tsv中写用制表符分隔的三列七行。第一列是跨度属性名称,依次为http.route、http.response.status_code、service.name、http.target、request.id、user.id、sampling.probability。第二列是该属性在指标里使用的标签名称,不放在指标里的写-。第三列是both(在两个信号中用相同的值)或trace-only(只放在跟踪里)。这张表必须与第 3 步的转储文件和/opt/app/tracelab/tp_metrics/metrics/shop-api.prom对照后实际相符——both的属性必须全部出现在第 3 步转储文件的根跨度上,trace-only的属性名称不得作为标签出现在指标文件里。 - 创建
/root/tp-metrics/pay.py(默认转储路径/root/tp-metrics/08-pay.jsonl)。用服务名称pay-api给材料的traffic.PAY埋点,但原样留下第 7 步规则中的属性,根跨度名称为POST <경로 틀>(占位符为路径模板),采样器是samplers.keep_all()。然后创建/root/tp-metrics/agree.py——用python3 agree.py <스팬덤프> <노출형식파일>(占位符依次为跨度转储文件、暴露格式文件)运行时,逐个时间序列比较指标值和根跨度数,对每个不同的时间序列输出一行mismatch<탭><handler><탭><code><탭><지표 값><탭><스팬 수>(占位符依次为制表符、handler、制表符、code、制表符、指标值、制表符、跨度数)并以退出码 1 结束;全部相同则输出一行ok<탭><계열 수>(占位符依次为制表符、时间序列数)并以 0 结束。把检查器在/opt/app/tracelab/tp_metrics/metrics/pay-api.prom上运行的输出保存到/root/tp-metrics/08-agree.txt。
参考
- 工作目录是
/root/tp-metrics。如果不存在,先创建。 - 带埋点的程序必须用
/opt/otel-lab/bin/python <파일>(占位符为文件)运行。系统python3中没有 OpenTelemetry SDK。反过来,只读取转储文件和指标文件的程序用系统python3运行。 - 材料是回放用的流量
/opt/app/tracelab/tp_metrics/traffic.py(SHOP120 条、PAY80 条)、采样器/opt/app/tracelab/tp_metrics/samplers.py、指标一侧的暴露格式文件/opt/app/tracelab/tp_metrics/metrics/shop-api.prom、pay-api.prom、pay-api-broken.prom。生成这些文件的生成器是/opt/app/tracelab/tp_metrics/make_metrics.py,公共接线是/opt/app/tracelab/dump.py,读取转储文件的辅助模块是/opt/lab/checks/_tplib.py。 - 这个镜像里没有 Prometheus。指标作为暴露格式文本文件来处理,读取并对照它就是本实验的范围,真正存储或查询 exemplar,在这里做不到。
- 常见错误:把用于采样判定的属性事后用
set_attribute加上。采样决定是在跨度开始时做出的,所以采样器只看得到那时传入的属性。 - HTTP 跨度语义约定 · HTTP 指标语义约定 · 概率采样与 adjusted count · Prometheus 暴露格式 · Prometheus 命名与标签
只用跨度转储文件数出请求数和延迟分布
创建 /root/tp-metrics/collect.py(默认转储路径 /root/tp-metrics/01-spans.jsonl)。依次处理材料 tracelab.tp_metrics.traffic 的 SHOP,为每个请求创建根跨度 GET <주소>(占位符为地址),并在开始跨度时传入五个属性——request.id、request.index、http.target(请求的 path)、http.response.status_code(请求的 status)、user.id(请求的 user)。在跨度内调用 traffic.work(tracer, req)。然后在 /root/tp-metrics/01-from-spans.tsv 中写用制表符分隔的两列四行——requests<탭><루트 스팬 수>、errors<탭><상태 코드 500 이상인 루트 수>、p50_ms<탭><값>、p95_ms<탭><값>(占位符依次为制表符、根跨度数、制表符、状态码 500 以上的根跨度数、制表符、值、制表符、值)。分位数是把根跨度的 duration_ms 按升序排列,从 1 开始数,取第 올림(비율 × 개수)(占位符依次为向上取整、比例、个数)个值,保留到小数点后第三位。
子跨度(db.query)也会进入转储文件,所以数请求时只能数没有 parent_id 的行。分位数是第 math.ceil(0.95 * n) - 1 个格子(从 0 开始数的 Python 索引)。耗时会因机器而略有不同,所以评分器会重新读取你的转储文件,用同样的方法计算后比较——不是要你对上绝对数值。重新生成转储文件之前要先删除该文件。
找出指标一侧的名称、值与跨度属性错位的地方
指标管道给出的文件在 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 中(Prometheus 暴露格式)。在 /root/tp-metrics/02-mapping.tsv 中写用制表符分隔的三列三行——第一列是指标标签,依次为 handler、code、svc,第二列是在第 1 步转储文件里打算与该标签对应的位置(http.target、http.response.status_code、service.name),第三列是这两者的值是否原样一致,写 yes 或 no。再在 /root/tp-metrics/02-gap.txt 中写两行——metric_series= 后面写该文件中 http_requests_total 的时间序列数,span_groups= 后面写把第 1 步转储文件的根跨度按 http.target 的值分组时得到的组数。
暴露格式的一行是 이름{라벨="값",...} 값(占位符依次为名称、标签、标签值、样本值),以 # 开头的行是说明。把两个数字并排放在一起,为什么无法配对一目了然——一边是能数得过来的数,另一边则随着每个地址而增长。service.name 不在跨度的 attributes 里,而在 resource 里。
用相同的名称、相同的值把两个信号连起来
创建 /root/tp-metrics/aligned.py(默认转储路径 /root/tp-metrics/03-aligned.jsonl)。处理与第 1 步相同的流量,但在根跨度上额外传入 http.route(请求的 route,即路径模板),跨度名称也定为 GET <경로 틀>(占位符为路径模板)。用于调查的 http.target 保持不变。采样器用 samplers.keep_all()。运行之后,在 /root/tp-metrics/03-joined.tsv 中,每个时间序列写一行用制表符分隔的四列——<handler><탭><code><탭><지표 값><탭><그 짝의 루트 스팬 수>(占位符依次为 handler、制表符、code、制表符、指标值、制表符、与其配对的根跨度数),按 handler 升序,相同则按 code 升序。两个数字在每个时间序列上必须相同。
指标用标签 handler 和 code 来区分时间序列,跨度则用属性 http.route 和 http.response.status_code 分组。状态码在指标里是字符串,在跨度里是整数,所以配对时要统一成其中一种。用了 keep_all(),所有根跨度上的 sampling.probability 都会写成 1.0,第 5 步会用到这个值。
换了样本,同一份数据会得出不同的错误率
创建 /root/tp-metrics/sampled.py。从命令行参数接收 nth5 或 errbias,分别用 samplers.every_nth(5) 和 samplers.errors_and_nth(5) 作为采样器,其余与第 3 步完全相同地埋点。默认转储路径是 /root/tp-metrics/04-<인자>.jsonl(占位符为命令行参数)。运行两次,做出 /root/tp-metrics/04-nth5.jsonl 和 /root/tp-metrics/04-errbias.jsonl 之后,连同第 3 步的转储文件一共三个,在 /root/tp-metrics/04-rates.tsv 中写用制表符分隔的四列三行——第一列依次为 full、nth5、errbias(full 是第 3 步的转储文件),后面是 <오류 루트 수><탭><전체 루트 수><탭><비율>(占位符依次为错误根跨度数、制表符、根跨度总数、制表符、比率),比率保留到小数点后第四位。
采样器是根据 request.index 和 http.response.status_code 来做决定的,所以如果不在开始跨度时传入这两个属性,采样器就什么也看不到。三个比率中,只有一个会突然变大——想一想是哪个、为什么。转储文件是追加的,运行之前要先删除。
用采样概率的倒数校正重新计数,并写下修不了的东西
两个样本转储文件的根跨度上写有 sampling.probability。一个跨度代表的请求数是该值的倒数。在 /root/tp-metrics/05-adjusted.tsv 中写用制表符分隔的四列两行——第一列依次为 nth5、errbias,后面是 <보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율>(占位符依次为校正后的错误数、制表符、校正后的总数、制表符、校正后的比率),校正后的错误数和总数保留到小数点后第四位,比率也保留到小数点后第四位。再在 /root/tp-metrics/05-limits.txt 中写两行——limit1= 和 limit2= 后面,各写一条即使校正也无法还原的东西(各至少 40 个字)。与第 4 步的 full 比率比较,确认校正能把结果拉回到什么程度。
校正后的数是把每个根跨度的 1 / sampling.probability 相加得到的值。子跨度上没有写概率,所以自然只数根跨度。errbias 一侧校正之后会非常接近第 4 步的 full 比率,nth5 一侧本来也不会差得太远。在思考无法还原的东西时,想一想“样本里一个都没有进来的组合”和“分位数”。
为每个区间挑选代表性跟踪,做一座可以从指标跳过去的桥梁
创建 /root/tp-metrics/bridge.py(默认转储路径 /root/tp-metrics/06-bridge.jsonl)。像第 3 步那样埋点并留下转储文件之后,重新读取这个转储文件,为每个时间序列挑出最慢的一个根跨度,写成表格。表的路径是把转储路径中的 .jsonl 换成 .tsv 的位置(默认就是 /root/tp-metrics/06-bridge.tsv)。每行是用制表符分隔的四列 <http.route><탭><상태 코드><탭><duration_ms(소수 셋째 자리)><탭><trace_id>(占位符依次为 http.route、制表符、状态码、制表符、duration_ms 保留到小数点后第三位、制表符、trace_id),按 http.route 升序,相同则按状态码升序。再在 /root/tp-metrics/06-limits.txt 中用 limit1=、limit2= 两行,各写至少 40 个字,说明这座桥梁无法回答的东西。
之所以要从转储路径推出表的路径,是有原因的——评分器会在自己的临时目录里把你的程序再运行一次,如果把表写到固定路径,就会覆盖你提交的文件。挑选代表这件事本身,标准里称为 exemplar,但这个环境里没有 Prometheus,无法真正存储或查询。写限制时,想一想“普通的请求”和“被样本丢掉的请求”。
把哪些属性在两个信号里共用,固化成规则文件
在 /root/tp-metrics/07-contract.tsv 中写用制表符分隔的三列七行。第一列是跨度属性名称,依次为 http.route、http.response.status_code、service.name、http.target、request.id、user.id、sampling.probability。第二列是该属性在指标里使用的标签名称,不放在指标里的写 -。第三列是 both(在两个信号中用相同的值)或 trace-only(只放在跟踪里)。这张表必须与第 3 步的转储文件和 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 对照后实际相符——both 的属性必须全部出现在第 3 步转储文件的根跨度上,trace-only 的属性名称不得作为标签出现在指标文件里。
划分的标准是基数。指标的标签,值的种类数会直接乘到时间序列数上,所以放进地址或用户标识符,时间序列就会爆炸。相反,跟踪的目的是找出一个请求,所以这样的值必须放在那里。service.name 在跨度的 resource 里,但仍然是 both——因为它是把两个信号按服务聚合起来的键。
把规则应用到第二个服务,并用检查器对照
创建 /root/tp-metrics/pay.py(默认转储路径 /root/tp-metrics/08-pay.jsonl)。用服务名称 pay-api 给材料的 traffic.PAY 埋点,但原样留下第 7 步规则中的属性,根跨度名称为 POST <경로 틀>(占位符为路径模板),采样器是 samplers.keep_all()。然后创建 /root/tp-metrics/agree.py——用 python3 agree.py <스팬덤프> <노출형식파일>(占位符依次为跨度转储文件、暴露格式文件)运行时,逐个时间序列比较指标值和根跨度数,对每个不同的时间序列输出一行 mismatch<탭><handler><탭><code><탭><지표 값><탭><스팬 수>(占位符依次为制表符、handler、制表符、code、制表符、指标值、制表符、跨度数)并以退出码 1 结束;全部相同则输出一行 ok<탭><계열 수>(占位符依次为制表符、时间序列数)并以 0 结束。把检查器在 /opt/app/tracelab/tp_metrics/metrics/pay-api.prom 上运行的输出保存到 /root/tp-metrics/08-agree.txt。
检查器不需要 otel,所以写成能在系统 python3 上运行的样子。只在指标一侧有的时间序列和只在跨度一侧有的时间序列,都算错位,所以要遍历两个键集合的并集。评分器会把你的检查器也在故意写错的 /opt/app/tracelab/tp_metrics/metrics/pay-api-broken.prom 上运行,所以不能按文件名或特定值来判定。