订单只有 6 笔,仪表盘却画出了 9 笔
目标
用真实的 OpenTelemetry Python SDK,依次测量 reader 的 temporality、默认直方图边界、通过视图(View)修改边界与属性以及丢弃指标的方法、异步 Instrument 的上报方式,最后把三个视图组装进同一个 MeterProvider。
为什么重要
同一个 Counter,按累积(cumulative)发送还是按增量(delta)发送,后端需要解读的含义并不相同。把两者混着读,订单会被计算两次;把以秒为单位的值放进为毫秒设计的默认边界,直方图会挤进同一个桶,分位数就失去意义。客户 ID 这类属性会增加数据点数量,推高成本。不修改埋点代码,而是通过 SDK 配置(reader、视图)来纠正这些问题,正是 API 与 SDK 分离的原因。
已准备的环境
/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py init 会在 /root/otca-metrics/ 中放入 shop.py(按固定顺序记录订单、购物车、结算耗时和调试指标)与 pipeline.py(需要填写的七个函数)。/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show N 会用你的 pipeline.py,基于 lab-dev 镜像中的 OpenTelemetry Python SDK 1.44.0(/opt/otel-lab)组装 MeterProvider,并输出 shop 记录之后两次采集到的数据点。不使用网络、Collector 和 API key。评分器会用同一个 SDK 重新运行你的函数,对照行为与你填写的数字。务必用 /opt/otel-lab/bin/python 运行。
步骤
- 用
/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py init生成材料。把/root/otca-metrics/pipeline.py的reader_cumulative()改为返回默认的InMemoryMetricReader后,运行/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 1。shop.py 会记录 3 笔订单并采集,再记录 3 笔订单并再次采集。在/root/otca-metrics/01-cumulative.txt中写入first=、second=(orders 合计)。 - 把
reader_delta()改为返回仅对Counter以 DELTA 请求的 reader,然后运行/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 2。在/root/otca-metrics/02-delta.txt中写入first=、second=以及double_counted=(把第 1 步的两个累积值误当作增量相加时,仪表板会画出的合计)。 - 修改
reader_all_delta(),让它对 Counter、UpDownCounter、Histogram、ObservableCounter 全部以 DELTA 请求,然后运行/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 3。在/root/otca-metrics/03-updown.txt中写入cumulative_second=(用 reader_delta 看到的 cart.items 第二个值)与delta_second=(用 reader_all_delta 看到的值)。 - 用
/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 4查看没有视图时采集到的checkout.duration(单位 s,值为 0.2、0.35、0.8、1.4)的直方图。在/root/otca-metrics/04-default-buckets.txt中写入boundaries=(默认边界的个数)、nonzero_bucket_index=(有值的桶的位置,从 0 开始)、nonzero_bucket_count=。 - 修改
views_buckets(),让它返回只对checkout.duration应用边界 0.25、0.5、1、2 的 View 列表。把/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 5结果中各桶的个数,以bucket_counts=(逗号分隔,最后一项是大于 2 的部分)的形式写入/root/otca-metrics/05-view-buckets.txt。其他指标必须保持不变。 - 修改
views_cardinality(),让它返回把orders的属性键只保留为route的 View 列表。查看/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 6,并在/root/otca-metrics/06-view-attributes.txt中写入points_before=(没有视图时第一次采集的 orders 数据点数)、points_after=、checkout_sum=(挂上视图后第一次采集的 /checkout 值)。 - 在
register_async(meter, source)中,让queue.processed用 ObservableCounter 观测source.processed(),让queue.depth用 ObservableGauge 观测source.depth()。查看/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 7(使用 reader_all_delta),并在/root/otca-metrics/07-async.txt中写入processed_delta_second=、depth_second=。source 每次调用会依次返回 processed 10、25 和 depth 7、4。 - 修改
provider(reader),让它返回带有所收到的 reader 和三个视图的 MeterProvider,三个视图分别是第 5 步的桶、第 6 步的属性精简,以及丢弃debug.cache.lookups的DropAggregation。评分器会用 reader_all_delta() 采集两次,同时检查 orders(按 route 的增量)、checkout.duration(新边界)、cart.items 以及被丢弃的 debug 指标。先用/opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 8查看。
参考
- 数据点的 temporality 由 reader 请求的值决定。埋点代码(shop.py)一行也不会改变。
- 常见错误:在
preferred_temporality中只放 Counter,却以为 UpDownCounter 也变了;在异步计数器的回调中返回增量;不缩小视图的作用对象。 - 本实验使用 InMemoryMetricReader,由你自己决定采集时机。周期性 reader 与 OTLP exporter 的默认 temporality 取决于配置(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE)。
- Metrics data model、Metrics SDK
采集两次,3 之后是 6
用 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py init 生成材料。把 /root/otca-metrics/pipeline.py 的 reader_cumulative() 改为返回默认的 InMemoryMetricReader 后,运行 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 1。shop.py 会记录 3 笔订单并采集,再记录 3 笔订单并再次采集。在 /root/otca-metrics/01-cumulative.txt 中写入 first=、second=(orders 合计)。
如果 reader 没有单独请求 temporality,SDK 会发送从启动时刻起的累积值。
按增量接收,以及混着读时会怎样
把 reader_delta() 改为返回仅对Counter以 DELTA 请求的 reader,然后运行 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 2。在 /root/otca-metrics/02-delta.txt 中写入 first=、second= 以及 double_counted=(把第 1 步的两个累积值误当作增量相加时,仪表板会画出的合计)。
preferred_temporality 是从 Instrument 类映射到 temporality 的字典。把累积值相加,前面的区间会被计算两次。
UpDownCounter 必须单独请求
修改 reader_all_delta(),让它对 Counter、UpDownCounter、Histogram、ObservableCounter 全部以 DELTA 请求,然后运行 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 3。在 /root/otca-metrics/03-updown.txt 中写入 cumulative_second=(用 reader_delta 看到的 cart.items 第二个值)与 delta_second=(用 reader_all_delta 看到的值)。
reader_delta 只改了 Counter。购物车依次记录 +5、-2,然后是 +1。
以秒为单位的值挤进了一个桶
用 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 4 查看没有视图时采集到的 checkout.duration(单位 s,值为 0.2、0.35、0.8、1.4)的直方图。在 /root/otca-metrics/04-default-buckets.txt 中写入 boundaries=(默认边界的个数)、nonzero_bucket_index=(有值的桶的位置,从 0 开始)、nonzero_bucket_count=。
SDK 的默认边界是按毫秒级延迟设计的。请看第一个边界和第二个边界。
用视图修改边界
修改 views_buckets(),让它返回只对 checkout.duration 应用边界 0.25、0.5、1、2 的 View 列表。把 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 5 结果中各桶的个数,以 bucket_counts=(逗号分隔,最后一项是大于 2 的部分)的形式写入 /root/otca-metrics/05-view-buckets.txt。其他指标必须保持不变。
View 通过 instrument_name 选择对象,通过 aggregation 修改聚合方式。不选择对象时,会作用于所有 Instrument。
丢掉客户 ID,数据点就会减少
修改 views_cardinality(),让它返回把 orders 的属性键只保留为 route 的 View 列表。查看 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 6,并在 /root/otca-metrics/06-view-attributes.txt 中写入 points_before=(没有视图时第一次采集的 orders 数据点数)、points_after=、checkout_sum=(挂上视图后第一次采集的 /checkout 值)。
每种属性组合对应一个数据点。只选择要保留的键,其余键不同的测量就会被合并,而合计保持不变。
异步计数器上报的是累积值
在 register_async(meter, source) 中,让 queue.processed 用 ObservableCounter 观测 source.processed(),让 queue.depth 用 ObservableGauge 观测 source.depth()。查看 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 7(使用 reader_all_delta),并在 /root/otca-metrics/07-async.txt 中写入 processed_delta_second=、depth_second=。source 每次调用会依次返回 processed 10、25 和 depth 7、4。
异步计数器的回调返回的是到目前为止的累积绝对值。增量由 SDK 按与上一次观测的差值计算。Gauge 不做累加。
三个视图放进同一个 provider
修改 provider(reader),让它返回带有所收到的 reader 和三个视图的 MeterProvider,三个视图分别是第 5 步的桶、第 6 步的属性精简,以及丢弃 debug.cache.lookups 的 DropAggregation。评分器会用 reader_all_delta() 采集两次,同时检查 orders(按 route 的增量)、checkout.duration(新边界)、cart.items 以及被丢弃的 debug 指标。先用 /opt/otel-lab/bin/python /opt/fixtures/otca_metrics_lab.py show 8 查看。
视图在创建 MeterProvider 时传入。一个 Instrument 匹配多个视图时会产生多个流,所以要为每个视图缩小对象范围。