指标模型

简要概述

兼容 prometheus

使用 OTLP 模型传输数据,然后导出到现有系统中,数据模型可以明确转换为 Prometheus Remote Write 协议,而不会丢失数据。

已明确三种保持语义的指标数据转换方式,这些转换在构建指标采集系统时能有效控制成本、保障可靠性并优化资源配置,指标数据模型支持在SDK内部数据生成阶段,或通过收集器内的再处理阶段实现以下转换:

  • 时序重聚合

高频采集的指标数据可被重新聚合为更长的时间间隔,从而生成低分辨率时间序列数据用于预计算或替代原始指标数据。

  • 空间重聚合

对于包含冗余属性的指标数据,可通过重聚合操作生成属性更精简的指标数据集; 对不需要的属性的指标可以重新聚合,以丢弃这些属性,降低存储空间。

  • 增量转累计

采用增量时间性的指标数据在输入输出时,可避免客户端维护高基数状态。通过使用增量数据,下游服务既可承担转换为累计时间序列的计算成本,也可直接计算速率值以规避转换开销。

示例 JSON 结构

  1. 数据结构定义
  2. 示例 json 数据
{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my.service"
            }
          }
        ]
      },
      "scopeMetrics": [
        {
          "scope": {
            "name": "my.library",
            "version": "1.0.0",
            "attributes": [
              {
                "key": "my.scope.attribute",
                "value": {
                  "stringValue": "some scope attribute"
                }
              }
            ]
          },
          "metrics": [
            {
              "name": "my.counter",
              "unit": "1",
              "description": "I am a Counter",
              "sum": {
                "aggregationTemporality": 1,
                "isMonotonic": true,
                "dataPoints": [
                  {
                    "asDouble": 5,
                    "startTimeUnixNano": "1544712660300000000",
                    "timeUnixNano": "1544712660300000000",
                    "attributes": [
                      {
                        "key": "my.counter.attr",
                        "value": {
                          "stringValue": "some value"
                        }
                      }
                    ]
                  }
                ]
              }
            },
            {
              "name": "my.gauge",
              "unit": "1",
              "description": "I am a Gauge",
              "gauge": {
                "dataPoints": [
                  {
                    "asDouble": 10,
                    "timeUnixNano": "1544712660300000000",
                    "attributes": [
                      {
                        "key": "my.gauge.attr",
                        "value": {
                          "stringValue": "some value"
                        }
                      }
                    ]
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}

数据结构

ResourceMetrics

// 一个 ResourceMetrics 表示「某个资源(例如一台主机或容器)」下的一组指标
message ResourceMetrics {
  // 与此消息中指标相关联的资源信息
  // 如果该字段未设置,则表示无法确定任何资源信息
  opentelemetry.proto.resource.v1.Resource resource = 1;

  // 属于该资源的指标集合列表。
  // 每个元素表示一个来自特定 Instrumentation Scope 的指标集。
  repeated ScopeMetrics scope_metrics = 2;
}

Resource

// 资源信息。
message Resource {
  // 描述资源的属性集合。
  // 属性键必须唯一(不允许存在重复键)。
  //
  // 属性值建议遵循以下规则:
  // - 不应包含空值
  // - 不应包含字节类型
  // - 不应包含非字符串数组、布尔数组、整数数组或浮点数组的数组类型
  // - 不应包含 kvlist 类型
  // 接收端在处理违反这些规则的属性时行为可能不可预测。
  // 这些限制可能会在小版本中改变。
  // 限制来源于 OpenTelemetry 规范:
  // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.47.0/specification/common/README.md#attribute
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;

  // 被丢弃的属性数量。如果值为 0,则表示没有属性被丢弃。
  uint32 dropped_attributes_count = 2;

ScopeMetrics

// 一组由某个 Instrumentation Scope(采集作用域)生成的指标集合
message ScopeMetrics {
  // 此消息中指标所属的 “Instrumentation Scope” 信息。
  // 如果该字段未设置,则语义上等同于 “Instrumentation Scope 名称为空(unknown)”。
  opentelemetry.proto.common.v1.InstrumentationScope scope = 1;

  // 一个指标列表,这些指标都来源于同一个 Instrumentation Library(采集库/模块)。
  repeated Metric metrics = 2;
}

Scope

// InstrumentationScope 表示采集器(instrumentation)的作用域信息
// 例如完整名称和版本号。
message InstrumentationScope {
  // 表示采集器作用域的名称。
  // 代表 SDK 或库的名称,例如 opentelemetry-go、grpc-client 等。
  string name = 1;

  // 定义采集器作用域的版本号。
  // 代表 SDK 或库的版本号,例如 1.47.0。
  string version = 2;

  // 描述作用域的附加属性 [可选]。
  // 属性键必须唯一(不允许存在重复键)。
  repeated KeyValue attributes = 3;

  // 被丢弃的属性数量。
  // 属性可能因为键过长或属性数量过多而被丢弃。
  // 如果值为 0,则表示没有属性被丢弃。
  uint32 dropped_attributes_count = 4;
}

Metric

// 表示一个指标(Metric)实体的定义。
// 每个 Metric 代表一种可测量的数据,例如请求时延、CPU 使用率、网络吞吐量等。
message Metric {
  // 指标的名称。
  string name = 1;

  // 指标的描述信息,可用于文档或可视化展示,帮助人类理解指标含义。
  string description = 2;

  // 指标值的度量单位(可选)。
  // 需遵循 https://unitsofmeasure.org/ucum.html 标准定义,
  // 例如 "s"(秒)、"By"(字节)、"1"(无单位)等。
  string unit = 3;

  // 指标的数据类型(聚合类型和取值形式)。
  // 该字段是一个 oneof(互斥字段),即同时只能出现一个。
  // 用于决定该指标属于哪种类型:
  // - Gauge(仪表盘):表示瞬时值,例如当前内存使用量、CPU 使用率等。
  // - Sum(总和):表示累计值,例如请求总次数、错误总次数等。
  // - Histogram(直方图):表示分布统计,例如请求时延分布、响应大小分布等。
  // - ExponentialHistogram(指数直方图):与直方图类似,但用于处理大范围值。
  // - Summary(摘要):表示统计摘要,例如 95% 响应时间、99% 响应时间等。
  oneof data {
    Gauge gauge = 5;
    Sum sum = 7;
    Histogram histogram = 9;
    ExponentialHistogram exponential_histogram = 10;
    Summary summary = 11;
  }
}

指标数据点

Gauge

// 表示一种标量指标类型(scalar metric),它始终为每个数据点导出当前值(current value)。
// 当指标的聚合方式未知(unknown)时,应使用 Gauge。
//
// Gauge 不支持不同的聚合时间特性(Aggregation Temporality)。
// 因为其聚合方式未知,数据点之间无法通过相同的聚合逻辑进行合并,
// 因此 AggregationTemporality 字段不会包含在 Gauge 中。
// 同样地,这也意味着 "StartTimeUnixNano" 字段对 Gauge 数据点无效(被忽略)。
message Gauge {
  // 时间序列数据点。
  // 注意:同一时间戳可能包含多个数据点,它们具有不同属性(attributes)。
  repeated NumberDataPoint data_points = 1;
}

一组数据点,表示在给定时间的采样值,每个数据点包含一组独立的属性名称值对。

指标流中的一个点表示给定时间窗口内的最后采样事件,一般用于以下场景:

  1. 采样值(例如当前CPU温度)
  2. 对值进行采样时的时间戳(time_unix_ano)
  3. 时间戳(start_time_unix_ano),它最能代表可以记录测量的第一个可能时刻。这通常设置为度量收集系统启动时的时间戳。

jpg

使用 Gauge 进行采样的基本时间序列,在给定的时间间隔内进行多次采样,但只有最后一个值通过 OTLP 在指标流中报告。

区别于 Sum 该不提供聚合语义,而是在执行时间窗口或调整精度等操作时使用“最后样本值”。

可以通过转换为直方图或其他度量类型来聚合度量。默认情况下不会执行这些操作,并且需要直接的用户配置。

Sum

// Sum 表示标量指标类型(scalar metric),它是一个时间区间内所有上报测量值的总和。
message Sum {
  // 时间序列数据点。
  // 注意:同一时间戳可能包含多个数据点,它们具有不同属性(attributes)。
  repeated NumberDataPoint data_points = 1;

  // 描述聚合器的时间特性:
  // - DELTA: 自上次报告以来的增量
  // - CUMULATIVE: 自固定起始时间以来的累计值
  AggregationTemporality aggregation_temporality = 2;

  // 指示该 Sum 是否为单调递增(monotonic)。
  // 通常计数器指标(如网络字节数、请求总数)是单调递增的。
  bool is_monotonic = 3;
}

一组数据点,聚合方式为增量(Delta)或累积(Cumulative),每个数据点包含独立的属性名称值对。

在各种用例中,具体使用哪种聚合方式存在各种权衡,例如:

  1. 检测进程重新启动(累积值重新由 0 开始)
  2. 计算费率(计算增量,当前时间指标值 - 前一个时间指标值)
  3. 基于推送与拉取的指标报告

OTLP 支持这两种模型,并允许 API、SDK 和用户为其用例确定最佳折衷方案。

  • 对指标做临时增量(Delta)

jpg

当聚合时间性是增量时(类似 prometheus rate 计算函数),指标流的时间窗口没有重叠,值不会出现负数。

  • 对指标做临时累积(Cumulative)

jpg

当聚合时间性是累积时,计算自应用启动以来的时间窗口(起始,结束])所有值的求和,值为单调递增,也就是当前值不会小于前一个。

Histogram

// Histogram 表示一种指标类型,它在一个时间区间内对所有上报测量值进行直方图聚合。
message Histogram {
  // 时间序列数据点。
  // 注意:同一时间戳可能包含多个数据点,它们具有不同属性(attributes)。
  repeated HistogramDataPoint data_points = 1;

  // 描述聚合器的时间特性:
  // - DELTA: 自上次报告以来的增量
  // - CUMULATIVE: 自固定起始时间以来的累计值
  AggregationTemporality aggregation_temporality = 2;
}

直方图以压缩格式记录测量的总体,它将一组事件捆绑成具有总体事件计数和所有事件的总和。

jpg

指标会自动压缩聚合,默认以 “bucket” “sum” “count” 三中类型组成:

  1. “count” 该指标被观测到的次数,计数累计值
  2. “sum” 该指标被观测到的值总和,值累计总和
  3. “bucket” 该指标被观测到的值被放到一个预先定义好值边界 (le1, le2] 范围内,计数累计值

以 otelgrpc 中指标 “rpc.server.duration” 为示例,并以 prometheus 风格导出:

# HELP rpc_server_duration_milliseconds 
# TYPE rpc_server_duration_milliseconds histogram
rpc_server_duration_milliseconds_bucket{rpc_grpc_status_code="0",rpc_method="Test",rpc_system="grpc",le="0"} 0
rpc_server_duration_milliseconds_bucket{rpc_grpc_status_code="0",rpc_method="Test",rpc_system="grpc",le="5"} 0
......
rpc_server_duration_milliseconds_bucket{rpc_grpc_status_code="0",rpc_method="Test",rpc_system="grpc",le="10000"} 1
rpc_server_duration_milliseconds_bucket{rpc_grpc_status_code="0",rpc_method="Test",rpc_system="grpc",le="+Inf"} 1
rpc_server_duration_milliseconds_sum{rpc_grpc_status_code="0",rpc_method="Test",rpc_system="grpc"} 369
rpc_server_duration_milliseconds_count{rpc_grpc_status_code="0",rpc_method="Test",rpc_system="grpc"} 1

Bucket 的上界是包含的(上界为 +Inf 的情况除外),而 Bucket 的下界是互斥。也就是说,bucket 表示大于其下界且小于或等于其上界的值的数量。

ExponentialHistogram

// ExponentialHistogram 表示一种指标类型,它在一个时间区间内对所有上报的 double 值进行指数直方图聚合。
message ExponentialHistogram {
  // 时间序列数据点。
  // 注意:同一时间戳可能包含多个数据点,它们具有不同属性(attributes)。
  repeated ExponentialHistogramDataPoint data_points = 1;

  // 描述聚合器的时间特性:
  // - DELTA: 自上次报告以来的增量
  // - CUMULATIVE: 自固定起始时间以来的累计值
  AggregationTemporality aggregation_temporality = 2;
}

Summary

// Summary 指标类型用于传递分位数汇总。
// Summary 数据点不能总是以有意义的方式合并。
// 虽然在某些应用场景下有用,但建议新应用使用 Histogram。
// Summary 类型没有 aggregation_temporality 字段。
// 因为 SummaryDataPoint 的 count 和 sum 字段被假定为累计值(cumulative)。
message Summary {
  // data_points:
  // 时间序列数据点。
  // 注意:同一时间戳可能包含多个数据点,它们具有不同属性(attributes)。
  repeated SummaryDataPoint data_points = 1;
}

摘要指标数据点用于传递分位数摘要信息,例如"我的HTTP服务器的第99百分位延迟是多少",与 OpenTelemetry 中的其他点类型不同,摘要点通常无法以有意义的方式进行合并,不建议在新应用中使用此点类型,其存在主要用于兼容其他格式。

摘要包含以下组成部分:

一组数据点,每个数据点包含:

  • 独立的属性名值对集合
  • 数值采样时间戳(time_unix_nano)
  • (可选)表示摘要观测收集开始时间的时间戳(start_time_unix_nano)
  • 数据点总体中的观测值数量计数
  • 总体中各数值的总和
  • 一组严格递增的分位数值,其中每个分位数包含:
    • 分布的分位数,取值范围为[0.0, 1.0]。例如,数值0.9表示第90百分位
    • 该分位数对应的数值,必须为非负数

特别说明:

  • 0.0 和 1.0 分位数分别定义为等于最小值和最大值
  • 分位数值不需要代表 start_time_unix_nano 到 time_unix_nano 期间观测到的数值,通常应基于最近时间窗口(通常是最近5-10分钟)计算得出

AggregationTemporality

// 用于定义指标聚合器(aggregator)如何报告聚合后的数值,它描述了这些数值与聚合时间区间的关系。
enum AggregationTemporality {
  // UNSPECIFIED 表示未指定的聚合时间性,作为默认值使用,但不能在实际场景中使用。
  AGGREGATION_TEMPORALITY_UNSPECIFIED = 0;

  // DELTA(增量模式)表示聚合器在每个时间区间内报告自上次报告以来的变化量。
  // 每次上报的指标值仅代表本次测量周期内的数据,与历史无关。
  //
  // 即:每次上报的度量是“本周期新增的值”,周期之间互不重叠。
  //
  // 举例:假设系统统计每秒收到的请求数量,并以 DELTA 模式上报。
  //
  //   1. 系统在时间 t₀ 启动。
  //   2. 收到一个请求,计数 +1。
  //   3. 又收到两个请求,累计 +2。
  //   4. 经过 1 秒(t₀ ~ t₀+1),系统上报本周期请求总数 = 3。
  //   5. 下个周期(t₀+1 ~ t₀+2),又收到 2 个请求。
  //   6. 上报值为 2(即这一秒内的增量)。
  //
  // DELTA 模式下,指标值仅反映当前周期内的数据变化。
  AGGREGATION_TEMPORALITY_DELTA = 1;

  // CUMULATIVE(累积模式)表示聚合器报告自固定起始时间以来的累积变化量。
  // 即:每次上报的值包含从开始时间起到当前时间为止的所有测量结果。
  //
  // 这种模式下,采集端必须维护“自启动以来”的状态;
  // 如果状态丢失(例如程序重启),则必须重置为新的起始时间。
  //
  // 举例:系统统计自启动以来累计收到的请求数量,并以 CUMULATIVE 模式上报。
  //
  //   1. 系统在时间 t₀ 启动。
  //   2. 收到 3 个请求。
  //   3. 经过 1 秒(t₀ ~ t₀+1),上报总请求数 = 3。
  //   4. 下一个周期又收到 2 个请求。
  //   5. 经过 1 秒(t₀ ~ t₀+2),上报总请求数 = 5。
  //   6. 系统故障导致状态丢失。
  //   7. 系统在时间 t₁ 恢复,重新统计。
  //   8. 收到 1 个请求。
  //   9. 新的报告周期上报总请求数 = 1(从 t₁ 开始重新计算)。
  //
  // 注意:
  // 即使在每次上报周期只包含增量时,也可以使用 CUMULATIVE;
  // 但通常不推荐这样做,因为像 Prometheus 这种系统依赖 `start_time`
  // 来判断聚合重置的时间,如果使用不当会造成指标歧义。
  AGGREGATION_TEMPORALITY_CUMULATIVE = 2;
}

DataPointFlags

enum DataPointFlags {
  // 枚举的零值(默认值)。不应直接用于比较。
  // 如果需要检测标志,应使用按位“与”运算符和对应的掩码(如上例)。
  DATA_POINT_FLAGS_DO_NOT_USE = 0;

  // 表示该 DataPoint 有效,但没有记录实际数值。
  // 当需要明确表示数据缺失时应使用此标志,
  // 类似于 Prometheus 中的“陈旧标记(staleness marker)”概念。
  DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK = 1;

  // 位 2–31 保留,供未来扩展使用。
}

Exemplar

// Exemplar 表示一个示例(采样的输入测量值)。
// 它不仅包含测量值本身,还记录了测量时的环境信息,
// 例如在记录该示例时处于活动状态的 Span ID 和 Trace ID
message Exemplar {
  // 一组键值对(KeyValue),表示被聚合器过滤掉但仍被记录下来的属性。
  // 只有那些被聚合器过滤掉的属性才应包含在此字段中。
  repeated opentelemetry.proto.common.v1.KeyValue filtered_attributes = 7;

  // 表示该示例被记录时的精确时间。
  fixed64 time_unix_nano = 2;

  // 记录的测量值。
  // Exemplar 中只允许存在一个值字段(通过 oneof 定义)。
  // 若所有值字段都为空,则该 Exemplar 被视为无效。
  oneof value {
    double as_double = 3; // 浮点型测量值
    sfixed64 as_int = 6;  // 整型测量值
  }

  // (可选)该示例对应追踪的 Span ID。
  // 如果测量未在追踪中记录,或该追踪未被采样,则 span_id 可能缺失。
  bytes span_id = 4;

  // (可选)该示例对应追踪的 Trace ID。
  // 如果测量未在追踪中记录,或该追踪未被采样,则 trace_id 可能缺失。
  bytes trace_id = 5;
}

NumberDataPoint

// NumberDataPoint 是时间序列 (timeseries) 中的一个单独数据点,
// 用于描述某个指标(metric)在时间变化中的标量值 (scalar value)。
message NumberDataPoint {
  // 表示该数据点的标签(key/value 对),用来唯一标识某个时间序列(Timeseries)。
  // 有以下约束说明:
  // 1. 每个数据点的标签(key/value 对)必须唯一。
  // 2. value 不应为空。
  // 3. value 不应为 bytes、kvlist 类型。
  // 4. 允许的数组类型仅限 array<string>, array<bool>, array<int>, array<double>。
  // The restrictions take origin from the OpenTelemetry specification:
  // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.47.0/specification/common/README.md#attribute.
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 7;

  // 可选字段
  // 数据点的起始时间(纳秒精度),表示聚合周期开始时间
  // 在累计型(Cumulative)指标中非常重要,用于标识统计区间的开始点
  // 在瞬时型(Gauge)中通常可忽略
  fixed64 start_time_unix_nano = 2;

  // 必填字段
  // 数据点的采样时间戳(纳秒精度)
  // 表示该数值被观测的时刻
  fixed64 time_unix_nano = 3;

  // 实际的指标值,二选一,可以是:
  // 1. 浮点数
  // 2. 有符号整数(sfixed64)
  oneof value {
    double as_double = 4;
    sfixed64 as_int = 6;
  }

  // 可选字段
  // 样本点,用于提供与此数据点相关的具体观测样本
  // 例如 Prometheus 的 exemplar 概念:用于把单个指标值关联到 trace/span ID
  // 方便实现 "指标 → trace" 的关联分析
  repeated Exemplar exemplars = 5;

  // 为该数据点设置的标志位,用于表示特殊状态,比如:
  // 1. 数据点是否有效(是否缺失值等)
  // 2. 数据点是否是预聚合值(是否需要特殊处理)
  uint32 flags = 8;
}

HistogramDataPoint

// 直方图数据点是时间序列中的单个数据点,用于描述直方图随时间变化的数值
// 直方图包含一组数值的汇总统计信息,可选择性地包含这些数值在不同桶中的分布情况。
// 若直方图包含数值分布信息,则必须同时定义"explicit_bounds"(显式边界)和"bucket_counts"(桶计数)字段。
// 若直方图不包含数值分布信息,则必须省略"explicit_bounds"和"bucket_counts"字段,此时仅能获取"count"(总数)和"sum"(总和)两个指标。
message HistogramDataPoint {
  reserved 1;

  // 同 NumberDataPoint
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 9;
  fixed64 start_time_unix_nano = 2;
  fixed64 time_unix_nano = 3;

  // 样本总数,必须 >= 0
  // 如果提供桶分布,count 必须等于 bucket_counts 的元素和
  fixed64 count = 4;

  // 所有样本值的和(若 count=0 则 sum 必须为 0)
  // 应仅在测量非负的离散事件时填写(例如请求时延的正值、字节数等),并假定为单调(用于兼容 OpenMetrics)
  // see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#histogram
  // 如果样本可能为负(例如带正负的值),不填写 sum
  optional double sum = 5;

  // 每个桶内的计数(数组);数组长度 = len(explicit_bounds) + 1(最后一个桶是 (+infty))
  // 若提供分布,bucket_counts 与 explicit_bounds 必须同时存在;若 bucket_counts 长度为 0,则 explicit_bounds 也必须为 0
  // 也就是:sum(bucket_counts) == count
  // 与 Prometheus 映射:对应多个 metric_bucket{le="<bound>"} 的值(上界为 explicit_bounds[i]),最后一个 le="+Inf" 为 bucket_counts[last]
  repeated fixed64 bucket_counts = 6;

  // 可选字段
  // 显式定义的桶上界数组(严格递增)
  // 上界包含(inclusive),最后桶为无限上界,与 bucket_counts 配合描述完整分布
  // 桶定义(索引 i):
  // 1. 当 i == 0 时,桶范围为 (-∞, explicit_bounds[0]]
  // 2. 当 0 < i < n 时,桶范围为 (explicit_bounds[i-1], explicit_bounds[i]]
  // 3. 当 i == n 时,桶范围为 (explicit_bounds[n-1], +∞)
  repeated double explicit_bounds = 7;

  // 同 NumberDataPoint
  repeated Exemplar exemplars = 8;
  uint32 flags = 10;

  // 窗口内的最小值((start_time, end_time] 区间)
  optional double min = 11;

  // 窗口内的最大值((start_time, end_time] 区间)
  optional double max = 12;
}

ExponentialHistogramDataPoint

// 是时间序列中的一个单独数据点,用于描述双精度数值的 ExponentialHistogram 随时间变化的取值
// 包含一组数值的汇总统计信息,并且可以选择性地包含这些数值在一组桶中的分布。
// 传统直方图(HistogramDataPoint)使用固定桶边界 (explicit_bounds)
// 它对传统 HistogramDataPoint 的改进:在大数量级范围和动态分布情况下,可以用更少内存、更高精度地表达样本分布。
message ExponentialHistogramDataPoint {
  // 同 NumberDataPoint
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
  fixed64 start_time_unix_nano = 2;
  fixed64 time_unix_nano = 3;

  // The number of values in the population. Must be
  // non-negative. This value must be equal to the sum of the "bucket_counts"
  // values in the positive and negative Buckets plus the "zero_count" field.
  fixed64 count = 4;

  // 同 HistogramDataPoint
  optional double sum = 5;

  // 每个桶的上下界按 base 的幂递增:base = 2^(2^-scale)
  // 桶索引为整数(可以负),其值代表区间 (base^index, base^(index+1)]
  // 越大的 scale → 越多桶(更精细),越小的 scale → 越少桶(更粗略)
  sint32 scale = 6;

  // 表示恰好为零,或在允许精度范围内被视为零值区域内的数值计数
  // 此桶用于存储无法通过标准指数公式表示的数值,以及被四舍五入为零的数值。
  // 有些值接近零,但因浮点精度或量化误差无法归入正/负桶,于是:
  // zero_count:零值或“足够接近零”的样本数量;
  // zero_threshold:零区间宽度,表示 [−threshold, +threshold] 被视为零。
  // 实现方案可认为零值桶的概率质量等于(zero_count / count)
  fixed64 zero_count = 7;

  // 正数范围的桶
  Buckets positive = 8;

  // 负数范围(按绝对值同样定义)的桶
  Buckets negative = 9;

  message Buckets {
    // 第一个桶的索引
    sint32 offset = 1;

    // 从 offset 开始的连续计数
    // 其中 bucket_counts[i] 表示索引为 (offset+i) 的桶的计数值
    // bucket_counts[i] 统计的是大于 base^(offset+i) 且小于等于 base^(offset+i+1) 的数值数量
    repeated uint64 bucket_counts = 2;
  }

  // 同 HistogramDataPoint
  uint32 flags = 10;
  repeated Exemplar exemplars = 11;
  optional double min = 12;
  optional double max = 13;

  // 可选择性设置以表示零值区域的宽度,零值区域定义为闭区间 [-ZeroThreshold, ZeroThreshold]
  // 如果 zero_threshold = 0.001,那么 [-0.001, +0.001] 的值都落在零桶
  double zero_threshold = 14;
}

SummaryDataPoint

// 是一种对连续观测值进行统计汇总的度量类型,常用于监控延迟、请求时长、处理时间等
// 和 Histogram 类似,它也是对分布进行描述,但它不需要固定 bucket,而是统计特定分位值。
// 它在一个时间段内记录:
// 1. 样本总数(count)
// 2. 样本总和(sum)
// 3. 特定分位点的数值(quantiles),例如:
//    p50(中位数)
//    p90(90% 的请求小于此值)
//    p99(99% 的请求小于此值)
message SummaryDataPoint {
  // 同 NumberDataPoint
  repeated opentelemetry.proto.common.v1.KeyValue attributes = 7;
  fixed64 start_time_unix_nano = 2;
  fixed64 time_unix_nano = 3;

  // 样本总数,大于等于0
  fixed64 count = 4;

  // 样本总和,如果 count 为 0 则此值也必须为 0
  double sum = 5;

  // 各个分位点的值(例如 0.5 → 200ms)
  // 表示分布在给定分位数处的数值。
  // 记录最小值和最大值时遵循以下约定:
  // - 1.0 分位数等同于观测到的最大值
  // - 0.0 分位数等同于观测到的最小值
  // 记录最小值和最大值时,分位点值必须非负。
  message ValueAtQuantile {
    // 分位点,范围 [0.0, 1.0]
    double quantile = 1;

    // 对应 quantile 的值(非负)
    double value = 2;
  }

  // (Optional) list of values at different quantiles of the distribution calculated
  // from the current snapshot. The quantiles must be strictly increasing.
  repeated ValueAtQuantile quantile_values = 6;

  // Flags that apply to this specific data point.  See DataPointFlags
  // for the available flags and their meaning.
  uint32 flags = 8;
}

模型详情

事件模型

是记录数据的地方,它的基础是仪器(Instruments),主要数据是指标数据点(时间戳+值),用于通过事件记录观测数据,然后将这些原始事件发送到其他系统之前以某种方式进行转换。

jpg

上图显示了仪器(Instruments)如何将事件转换为多种类型的指标流。

尽管观测事件数据可以直接报告给后端,但在生产环境中这是不建议的,因为可观测性系统中使用的数据量太大,但可用于遥测收集的网络等资源有限,所以在上报数据前最后要想办法降低指标基数,如采用直方图指标类型压缩指标等。

时间序列模型

表示后端如何存储指标数据。

时间序列由几个元数据属性组成的实体定义:

  1. 指标名称:Metric name
  2. 属性(维度):Attributes (dimensions)
  3. 数据点的值(整数、浮点等):Value type of the point (integer, floating point, etc)
  4. 计量单位:Unit of measurement

每个时间序列的主要数据都是按顺序排列的(时间戳、值)点,具有以下值类型之一:

  1. 计数器(单调,累计):Counter (Monotonic, Cumulative)
  2. 测量:Gauge
  3. 直方图:Histogram
  4. 指数直方图:Exponential Histogram

这个模型类似 Prometheus Remote Write 协议,但它并不是 OTLP 映射的唯一时间序列模型。

指标流模型

通过 OpenTelemetry 协议(OTLP)定义指标数据流在事件模型和时间序列存储之间传输与处理的方式。

指标流被分组为各个度量对象,由以下各项标识:

  1. 原始资源属性 Resource
  2. 检测范围(例如,检测库名称、版本)Scope
  3. 指标流的名称 name

除名称外,指标对象还由以下属性定义:

  1. 数据点类型:Sum, Gauge, Histogram ExponentialHistogram, Summary
  2. 指标流的单位:unit
  3. 指标流的描述:description
  4. 内在数据点属性,如适用:聚合临时性、单调性

数据点类型、指标流的单位和内在数据点属性被认为是可识别的,而指标流的描述在本质上显然不是可识别的。

特定点的外在特性不被认为是可识别的;这些包括但不限于:

  1. 直方图数据点的桶边界
  2. 指数直方图数据点的刻度或桶计数。

Metric对象包含由属性集标识的各个流。在各个流中,点由一个或两个时间戳标识,细节因数据点类型而异。

在某些数据点类型(例如总和和仪表)中,允许数值点值发生变化;在这种情况下,相关的变化(即浮点与整数)不被认为是可识别的。

应用场景

Summary 与 Histogram 的对比

特性SummaryHistogramExponentialHistogram
是否记录桶❌ 否✅ 显式桶✅ 指数桶
是否有 exemplar❌ 无✅ 有✅ 有
是否能算分位数✅ 已直接给出✅ 可通过桶估算✅ 可通过桶估算
是否能算任意 quantile❌ 只能用已有 quantiles✅ 可插值估算✅ 可插值估算
是否能合并❌ 很难准确合并✅ 容易✅ 容易
网络体积较小较大中等
用途上报分位统计结果分布观测、trace 关联高精度分布观测

典型使用场景

场景推荐类型
请求延迟分位数监控(轻量)✅ Summary
请求延迟分布分析 + Trace 跳转✅ Histogram
高频采样 + 跨数量级分布✅ ExponentialHistogram
跨节点聚合 / 联邦场景❌ 不建议 Summary(用 Histogram)

与 prometheus 类型对比

Prometheus MetricType对应 OTLP Metric TypeOTLP message / aggregationAggregationTemporality说明
METRIC_TYPE_COUNTERSumSum message通常为 CUMULATIVECounter 表示单调递增量,OTLP 的 Sum 设置 is_monotonic=true。
METRIC_TYPE_GAUGEGaugeGauge message无需 temporalityGauge 是瞬时测量值,对应 OTLP Gauge。
METRIC_TYPE_HISTOGRAMHistogramHistogram messageCUMULATIVE 或 DELTA传统桶式直方图,与 OTLP Histogram 完全对应。
METRIC_TYPE_GAUGEHISTOGRAMHistogramHistogram message通常为 DELTA 或“快照”式GaugeHistogram 在 Prometheus 中代表某一时刻的桶分布,OTLP 没有直接同名,但用 Histogram 表示瞬时分布。
METRIC_TYPE_SUMMARYSummarySummary message始终 CUMULATIVEquantile(分位数)形式的度量,OTLP 直接支持 Summary。
METRIC_TYPE_INFOGauge(或 KeyValue attribute)Gauge 或 Resource Attribute不适用Prometheus Info 通常是静态标签值,OTLP 推荐以 Resource 或 InstrumentationScope 属性表示。
METRIC_TYPE_STATESETGauge (boolean/int)Gauge message瞬时StateSet 表示状态机(例如连接状态),可映射为多个布尔型 Gauge 或 Enum-like Gauge。
METRIC_TYPE_UNSPECIFIED(无对应)无无未指定类型。应在数据导入时推断。

在 Go 语言中使用

同步或异步

仪器(Instruments)支持同步或异步导出指标值,且值类型为 int64 或 float64 两个类型。

同步行为

是在被调用时进行测量,测量在程序执行期间作为另一个调用来完成,就像执行其他业务函数一样。 这些测量值的聚合由配置的导出器定期导出。由于测量与导出值解耦,因此导出周期可能包含零个或多个聚合测量。

异步行为

是在 SDK 导出时才会调用创建提供给仪器(Instruments)的回调函数。 此回调为 SDK 提供了一个立即导出的指标值,异步仪器上的所有测量在每个输出周期执行一次。

异步主要在以下几种场景使用:

  1. 当更新指标值在函数内代价较高,并且不希望当前执行线程等待测量函数;
  2. 导出指标需要以与程序执行无关的频率发生(即当与请求生命周期联系在一起时,无法准确测量);
  3. 测量值没有已知的时间戳。

计数器 Counter

累积计数器,支持非负增量的仪器,也就是这些值永远不会减少(服务重启时重置为0),运行期间逐渐累加:

Int64Counter
Int64ObservableCounter

Float64Counter
Float64ObservableCounter

导出为 prometheus 格式时自动添加 “_count” 后缀。

计数器 UpDown

递增或者递减计数器,允许您观察递增或递减的累积值:

Int64UpDownCounter
Int64ObservableUpDownCounter

Float64UpDownCounter
Float64ObservableUpDownCounter

导出为 prometheus 格式时同 Gauge 类型。

测量 Gauge

比较随机值非累加,当只需要传达关于异步测量的最新数据时使用:

Int64ObservableGauge
Float64ObservableGauge

直方图 Histogram

当需要传达更多关于采集周期内进行的所有同步测量的信息时,应使用直方图:

Int64Histogram
Float64Histogram