OpenMetrics 1.0

  • 版本:1.0
  • 状态:已发布
  • 日期:2022 年 3 月
  • 作者:Richard Hartmann, Ben Kochie, Brian Brazil, Rob Skillington

Prometheus 创建于 2012 年,自 2015 年以来一直是云原生可观测性的默认选择。Prometheus 设计的核心部分是其文本指标暴露格式,称为 Prometheus 暴露格式 0.0.4,自 2014 年以来一直稳定。在这种格式中,我们特别注意使其易于生成、摄取和被人理解。截至 2020 年,有 700 多个公开列出的导出器、数量不详的未列出导出器以及数千个使用此格式的原生库集成。来自各种项目和公司的数十个摄取器支持使用它。

通过 OpenMetrics,我们正在清理并收紧规范,其明确目的是将其引入 IETF。我们正在记录一个具有广泛和自然采用的工作标准,同时引入最小、大部分向后兼容且经过深思熟虑的更改。截至 2020 年,数十个导出器、集成和摄取器已在使用并优先协商 OpenMetrics。

考虑到生态系统中的广泛采用和重要的协调要求,对 Prometheus 暴露格式 0.0.4 或 OpenMetrics 1.0 进行全面更改被视为超出范围。

注意OpenMetrics 2.0 正在开发中。阅读此处了解如何加入 Prometheus OM 2.0 工作组。

概述

指标是一种特定类型的遥测数据。它们表示一组数据的当前状态快照。它们不同于日志或事件,日志或事件侧重于单个事件的记录或信息。

OpenMetrics 主要是一种传输格式,独立于该格式的任何特定传输方式。该格式预计会被定期使用,并在连续的暴露中保持意义。

实现者必须响应对给定进程或设备的文档化 URL 的简单 HTTP GET 请求,以 OpenMetrics 文本格式暴露指标。此端点应命名为“/metrics”。实现者也可以通过其他方式暴露 OpenMetrics 格式的指标,例如通过 HTTP 定期将指标集推送到操作员配置的端点。

指标和时间序列

本标准将所有系统状态表示为数值;计数、当前值、枚举和布尔状态是常见示例。与指标不同,单个事件发生在特定时间。指标倾向于按时间聚合数据。虽然这可能会丢失信息,但开销的减少是许多现代监控系统中常见的工程权衡。

时间序列是随时间变化的信息记录。虽然时间序列可以支持任意字符串或二进制数据,但本 RFC 仅涵盖数值数据。

指标时间序列的常见示例包括网络接口计数器、设备温度、BGP 连接状态和警报状态。

数据模型

本节必须与 ABNF 部分一并阅读。如果两者之间存在分歧,ABNF 的限制必须优先。这减少了重复,因为文本传输格式必须得到支持。

数据类型

OpenMetrics 中的指标值必须是浮点数或整数。请注意,格式的摄取器可能只支持 float64。必须支持非实数值 NaN、+Inf 和 -Inf。NaN 不得被视为缺失值,但可以用于表示除零。

布尔值

布尔值必须遵循 1==true0==false

时间戳

时间戳必须是 Unix 纪元秒数。可以使用负时间戳。

字符串

字符串必须仅包含有效的 UTF-8 字符,并且长度可以为零。必须支持 NULL (ASCII 0x0)。

标签

标签是由字符串组成的键值对。

以_开头的标签名称是保留的,除非本标准另有规定,否则不得使用。标签名称必须遵循 ABNF 部分中的限制。

空标签值应被视为标签不存在。

标签集

标签集必须由标签组成,并且可以为空。标签名称在标签集中必须是唯一的。

指标点

每个指标点包含一组值,具体取决于指标族类型。

示例

示例是对指标集外部数据的引用。一个常见的用例是程序跟踪的 ID。

示例必须包含一个标签集和一个值,并且可以有时间戳。它们可以与指标点(MetricPoints)的标签集和时间戳不同。

示例的标签集中的标签名称和值的组合长度不得超过 128 个 UTF-8 字符码点。文本呈现中的其他字符(例如“,=)不包含在此限制内,以简化实现并保持文本和 proto 格式之间的一致性。

摄取器可以丢弃示例。

指标

指标由指标族内唯一的标签集定义。指标必须包含一个或多个指标点的列表。给定指标族的同名指标应在其标签集中具有相同的标签名称集合。

指标点不应具有明确的时间戳。

如果一个指标暴露了多个指标点,则其指标点必须具有单调递增的时间戳。

指标族

指标族可以包含零个或多个指标。指标族必须具有名称、HELP、TYPE 和 UNIT 元数据。指标族中的每个指标必须具有唯一的标签集。

名称

指标族名称是一个字符串,在指标集中必须是唯一的。名称应使用 snake_case 格式。指标名称必须遵循 ABNF 部分中的限制。

指标族名称中的冒号是保留的,用于表示该指标族是通用监控系统计算或聚合的结果。

以_开头的指标族名称是保留的,除非本标准另有规定,否则不得使用。

后缀

指标族的名称不得导致在指标集中,其在文本格式下的样本指标名称与另一个指标族根据 ABNF 产生潜在冲突。例如,名为“foo_created”的测量仪与名为“foo”的计数器可能会在文本格式中创建冲突的“foo_created”。

暴露器应避免使用可能与文本格式样本指标名称所用的后缀混淆的名称。

  • 各种类型对应的后缀是
  • 计数器:_total, _created
  • 摘要:_count, _sum, _created, `` (空)
  • 直方图:_count, _sum, _bucket, _created
  • 仪表直方图:_gcount, _gsum, _bucket
  • 信息:_info
  • 测量仪:`` (空)
  • 状态集:`` (空)
  • 未知:`` (空)
类型

类型指定指标族类型。有效值为“unknown”、“gauge”、“counter”、“stateset”、“info”、“histogram”、“gaugehistogram”和“summary”。

单位

单位指定指标族单位。如果非空,它必须是指标族名称的后缀,并由下划线分隔。请注意,进一步的生成规则可能会使其成为文本格式中的中缀。

帮助信息

帮助信息是一个字符串,应非空。它用于为人类提供指标族的简要描述,并且应足够短以便用作工具提示。

指标集

指标集是 OpenMetrics 暴露的顶层对象。它必须由指标族组成,并且可以为空。

每个指标族名称必须是唯一的。相同的标签名称和值不应出现在指标集中的每个指标上。

指标集中的指标族没有特定的排序要求。暴露器可以为了人类阅读方便而进行暴露,例如,如果性能权衡合理,可以按字母顺序排序。

如果存在,根据下面的“在基于推送和基于拉取的系统中支持目标元数据”部分,名为“target”的信息指标族应首先出现。

指标类型

测量仪

测量仪是当前测量值,例如当前使用的内存字节数或队列中的项目数。对于测量仪,用户感兴趣的是其绝对值。

类型为测量仪的指标中的指标点必须具有单个值。

测量仪的值可以随时间增加、减少或保持不变。即使它们只朝一个方向变化,它们仍然可能是测量仪而不是计数器。日志文件的大小通常只会增加,资源可能会减少,而队列大小的限制可能是常量。

测量仪可用于编码枚举,其中枚举具有多种状态并随时间变化,这是最有效但用户友好度最低的方式。

计数器

计数器测量离散事件。常见示例包括收到的 HTTP 请求数、CPU 耗时或发送的字节数。对于计数器,用户感兴趣的是它们随时间增加的速度。

类型为计数器的指标中的指标点必须具有一个名为 Total 的值。Total 值必须是非 NaN 且随时间单调非递减,从 0 开始。

类型为计数器的指标中的指标点应具有一个名为 Created 的时间戳值。这可以帮助摄取器区分新指标和之前未见过的长期运行指标。

指标计数器的总值(Total)的指标点可以重置为 0。如果存在,对应的 Created 时间也必须设置为重置的时间戳。

指标计数器的总值(Total)的指标点可以包含一个示例。

状态集

状态集表示一系列相关的布尔值,也称为位集。如果需要编码枚举,可以通过状态集完成。

状态集指标的一个点可以包含多个状态,并且每个状态必须包含一个布尔值。状态具有字符串类型的名称。

状态集指标的标签集不得包含与其指标族名称相同的标签名称。

如果编码为状态集,枚举在指标点内必须只有一个布尔值为真。

这适用于枚举值随时间变化且状态数量不多的情况。

状态集类型的指标族必须具有空的单位字符串。

信息

信息指标用于暴露在进程生命周期内不应更改的文本信息。常见示例包括应用程序版本、版本控制提交和编译器版本。

信息指标的指标点包含一个标签集。信息指标点的标签集不得包含与其指标的标签集中的标签名称相同的标签名称。

信息指标可用于编码值不随时间变化的枚举,例如网络接口类型。

信息类型的指标族必须具有空的单位字符串。

直方图

直方图测量离散事件的分布。常见示例包括 HTTP 请求的延迟、函数运行时或 I/O 请求大小。

直方图指标点必须包含至少一个桶,并且应包含 Sum 和 Created 值。每个桶必须具有一个阈值和一个值。

直方图指标点必须有一个带有 +Inf 阈值的桶。桶必须是累积的。例如,对于表示请求延迟(秒)的指标,其阈值为 1、2、3 和 +Inf 的桶的值必须遵循 value_1 <= value_2 <= value_3 <= value_+Inf。如果十个请求每个都花费 1 秒,则 1、2、3 和 +Inf 桶的值必须等于 10。

+Inf 桶计算所有请求。如果存在,Sum 值必须等于所有测量事件值的总和。指标点内的桶阈值必须是唯一的。

语义上,Sum 和桶值是计数器,因此不得为 NaN 或负数。可以使用负阈值桶,但此时直方图指标点不得包含 Sum 值,因为它在语义上不再是计数器。桶阈值不得等于 NaN。计数和桶值必须是整数。

直方图指标点应具有一个名为 Created 的时间戳值。这可以帮助摄取器区分新指标和之前未见过的长期运行指标。

直方图指标的标签集不得包含“le”标签名称。

桶值可以包含示例。桶是累积的,以允许监控系统出于性能/抗拒绝服务原因丢弃任何非 +Inf 的桶,这样做会损失粒度但仍是一个有效的直方图。

每个桶覆盖小于或等于其值的范围,并且示例的值必须在此范围内。示例应放入具有最高值的桶中。一个桶不得有多个示例。

仪表直方图

仪表直方图测量当前分布。常见示例包括项目在队列中等待的时间,或队列中请求的大小。

仪表直方图指标点必须有一个带有 +Inf 阈值的桶,并且应包含一个 Gsum 值。每个桶必须具有一个阈值和一个值。

仪表直方图的桶遵循与直方图相同的所有规则。

仪表直方图的桶和 Gsum 在概念上是测量仪,但桶值不得为负数或 NaN。如果存在负阈值桶,则 Sum 可以为负。Gsum 不得为 NaN。桶值必须是整数。

仪表直方图指标的标签集不得包含“le”标签名称。

桶值可以包含示例。

每个桶覆盖小于或等于其值的范围,并且示例的值必须在此范围内。示例应放入具有最高值的桶中。一个桶不得有多个示例。

摘要

摘要也测量离散事件的分布,当直方图成本过高和/或平均事件大小足够时,可以使用摘要。

它们也可以用于向后兼容,因为一些现有的仪表化库暴露预计算的分位数而不支持直方图。不应使用预计算分位数,因为分位数不可聚合,并且用户通常无法推断它们覆盖的时间范围。

摘要指标点可以包含计数、总和、创建时间以及一组分位数。

语义上,计数和总和值是计数器,因此不得为 NaN 或负数。计数必须是整数。

类型为摘要的指标中的指标点,如果包含计数或总和值,应具有一个名为 Created 的时间戳值。这可以帮助摄取器区分新指标和之前未见过的长期运行指标。Created 不得与分位数值的收集周期相关。

分位数是从分位数到值的映射。例如,在名为 myapp_http_request_duration_seconds 的指标中,分位数 0.95 的值为 0.2,这意味着在未知时间范围内,第 95 百分位的延迟为 200 毫秒。如果在相关时间范围内没有事件,则分位数的值必须为 NaN。分位数指标的标签集不得包含“quantile”标签名称。分位数必须在 0 到 1 之间(含 0 和 1)。分位数的值不得为负数。分位数的值应代表最近的值。通常这将是过去 5-10 分钟内的值。

未知

不应使用未知类型。当无法从第三方系统确定单个指标的类型时,可以使用未知类型。

未知类型指标中的一个点必须具有单个值。

数据传输和传输格式

文本传输格式必须得到支持并且是默认格式。Protobuf 传输格式可以得到支持,并且只能在协商后使用。

OpenMetrics 格式是正则乔姆斯基文法(Regular Chomsky Grammars),使得编写快速且小巧的解析器成为可能。文本格式压缩效果好,而 Protobuf 本身就是二进制且高效编码的。

部分或无效的暴露必须被视为整体错误。

协议协商

所有摄取器实现都必须能够摄取使用 TLS 1.2 或更高版本保护的数据。所有暴露器都应能够发出使用 TLS 1.2 或更高版本保护的数据。摄取器实现应能够摄取来自未加密 HTTP 的数据。所有实现都应使用 TLS 传输数据。

OpenMetrics 格式版本的协商是带外进行的。例如,对于基于 HTTP 的拉取式暴露,使用标准的 HTTP 内容类型协商,如果未请求更新版本,则必须默认为标准的旧版本(即 1.0.0)。

基于推送的协商本质上更复杂,因为暴露器通常会发起连接。生产者必须使用标准的旧版本(即 1.0.0),除非摄取器另有要求。

文本格式

ABNF

符合 RFC 5234 的 ABNF

“exposition”是 ABNF 的顶层令牌。

exposition = metricset HASH SP eof [ LF ]

metricset = *metricfamily

metricfamily = *metric-descriptor *metric

metric-descriptor = HASH SP type SP metricname SP metric-type LF
metric-descriptor =/ HASH SP help SP metricname SP escaped-string LF
metric-descriptor =/ HASH SP unit SP metricname SP *metricname-char LF

metric = *sample

metric-type = counter / gauge / histogram / gaugehistogram / stateset
metric-type =/ info / summary / unknown

sample = metricname [labels] SP number [SP timestamp] [exemplar] LF

exemplar = SP HASH SP labels SP number [SP timestamp]

labels = "{" [label *(COMMA label)] "}"

label = label-name EQ DQUOTE escaped-string DQUOTE

number = realnumber
; Case insensitive
number =/ [SIGN] ("inf" / "infinity")
number =/ "nan"

timestamp = realnumber

; Not 100% sure this captures all float corner cases.
; Leading 0s explicitly okay
realnumber = [SIGN] 1*DIGIT
realnumber =/ [SIGN] 1*DIGIT ["." *DIGIT] [ "e" [SIGN] 1*DIGIT ]
realnumber =/ [SIGN] *DIGIT "." 1*DIGIT [ "e" [SIGN] 1*DIGIT ]


; RFC 5234 is case insensitive.
; Uppercase
eof = %d69.79.70
type = %d84.89.80.69
help = %d72.69.76.80
unit = %d85.78.73.84
; Lowercase
counter = %d99.111.117.110.116.101.114
gauge = %d103.97.117.103.101
histogram = %d104.105.115.116.111.103.114.97.109
gaugehistogram = gauge histogram
stateset = %d115.116.97.116.101.115.101.116
info = %d105.110.102.111
summary = %d115.117.109.109.97.114.121
unknown = %d117.110.107.110.111.119.110

BS = "\"
EQ = "="
COMMA = ","
HASH = "#"
SIGN = "-" / "+"

metricname = metricname-initial-char 0*metricname-char

metricname-char = metricname-initial-char / DIGIT
metricname-initial-char = ALPHA / "_" / ":"

label-name = label-name-initial-char *label-name-char

label-name-char = label-name-initial-char / DIGIT
label-name-initial-char = ALPHA / "_"

escaped-string = *escaped-char

escaped-char = normal-char
escaped-char =/ BS ("n" / DQUOTE / BS)
escaped-char =/ BS normal-char

; Any unicode character, except newline, double quote, and backslash
normal-char = %x00-09 / %x0B-21 / %x23-5B / %x5D-D7FF / %xE000-10FFFF

整体结构

必须使用 UTF-8。不得使用字节顺序标记 (BOM)。作为对实现者的重要提醒,字节 0 是有效的 UTF-8,而例如字节 255 则不是。

内容类型必须是

application/openmetrics-text; version=1.0.0; charset=utf-8

行结束符必须使用换行符 (\n) 表示,不得包含回车符 (\r)。Expositions 必须以 EOF 结束,并且应该以 EOF\n 结束。

完整 exposition 的示例

# TYPE acme_http_router_request_seconds summary
# UNIT acme_http_router_request_seconds seconds
# HELP acme_http_router_request_seconds Latency though all of ACME's HTTP request router.
acme_http_router_request_seconds_sum{path="/api/v1",method="GET"} 9036.32
acme_http_router_request_seconds_count{path="/api/v1",method="GET"} 807283.0
acme_http_router_request_seconds_created{path="/api/v1",method="GET"} 1605281325.0
acme_http_router_request_seconds_sum{path="/api/v2",method="POST"} 479.3
acme_http_router_request_seconds_count{path="/api/v2",method="POST"} 34.0
acme_http_router_request_seconds_created{path="/api/v2",method="POST"} 1605281325.0
# TYPE go_goroutines gauge
# HELP go_goroutines Number of goroutines that currently exist.
go_goroutines 69
# TYPE process_cpu_seconds counter
# UNIT process_cpu_seconds seconds
# HELP process_cpu_seconds Total user and system CPU time spent in seconds.
process_cpu_seconds_total 4.20072246e+06
# EOF
转义

在 ABNF 注明转义的地方,必须应用以下转义:换行符,\n (0x0A) -> 字面量 \\n (字节码 0x5c 0x6e) 双引号 -> \\" (字节码 0x5c 0x22) 反斜杠 -> \\\\ (字节码 0x5c 0x5c)

应使用双反斜杠表示反斜杠字符。不应将单个反斜杠用于未定义的转义序列。例如,\\\\a 等效于 \\a 并且更可取。

数字

整数不得包含小数点。例如 2300421341298465647914

浮点数必须以小数点或科学计数法表示。例如 8903.1234211.89e-7。浮点数必须在 IEEE 754 定义的 64 位浮点值范围内,但可能需要过多的尾数位导致精度丢失。这可以用于编码纳秒级时间戳。

数字的任意整数和浮点表示不得用于“quantile”和“le”标签值,如“规范数字”部分所述。它们可以在其他任何使用数字的地方使用。

注意事项:规范数字

直方图的“le”标签值和摘要指标的“quantile”标签值中的数字比较特殊,因为它们是标签值,而标签值应是不透明的。由于最终用户可能会直接与这些字符串值交互,并且许多监控系统缺乏将其作为一流数字处理的能力,因此如果给定数字具有完全相同的文本表示,将会非常有益。

一致性是高度期望的,但实际的语言实现及其运行时使得强制执行这一点不切实际。最重要的常见分位数是 0.5、0.95、0.9、0.99、0.999 以及表示从毫秒到 10.0 秒的值的桶值,因为这些涵盖了典型 Web 服务的延迟 SLA 和 Apdex 等情况。十的幂次也包含在内,以确保定点和指数表示之间的切换一致,因为这在不同运行时之间有所差异。目标表示等同于 Go 语言 float64 值的默认渲染(即 %g),如果小数点或指数不存在,则添加 .0 以明确它们是浮点数。

Exposers 必须将正无穷大输出为 +Inf。

Exposers 应该以 0.001 的增量输出 0.0 到 10.0 之间的值,例如:0.0 0.001 0.002 0.01 0.1 0.9 0.95 0.99 0.999 1.0 1.7 10.0

Exposers 应该以十的幂次形式输出 1e-10 到 1e+10 之间的值,例如:1e-10 1e-09 1e-05 0.0001 0.1 1.0 100000.0 1e+06 1e+10

解析器不得仅仅因为输入与规范值不一致而拒绝其处于规范值之外的输入。例如,即使 1.1e-4 不是 0.00011 的一致表示,也不得拒绝它。

Exposers 应该遵循这些非规范数字的模式,其目的是通过调整渲染算法以使这些值保持一致,从而使绝大多数其他值也具有一致的渲染。只使用少数特定 le/quantile 值的 Exposers 也可以进行硬编码。在 C 等不易获得最小浮点渲染算法(如 Grisu3)的语言中,Exposers 可以使用不同的渲染方式。

对 C 语言及其他共享其 printf 实现的实现者的警告:%f%e%g 的标准精度只有六位有效数字。需要 17 位有效数字才能实现完全精度,例如 printf("%.17g", d)

时间戳

如果需要纳秒精度,时间戳不应使用指数浮点表示,因为 float64 的表示精度不足,例如 1604676851.123456789

指标家族

指标家族之间不得有显式分隔符。下一个指标家族必须通过元数据或不能属于前一个指标家族的新样本指标名称来表示。

指标家族不得交错。

指标家族元数据

有四部分元数据:指标家族名称、TYPE、UNIT 和 HELP。名为 foo 的计数器指标的元数据示例如下:

# TYPE foo counter

如果未暴露 TYPE,则指标家族必须是 Unknown 类型。

如果指定了单位,则必须在 UNIT 元数据行中提供。此外,一个下划线和单位必须作为指标家族名称的后缀。

一个名为 foo_seconds 且单位为“seconds”的有效指标示例

# TYPE foo_seconds counter
# UNIT foo_seconds seconds

一个无效示例,其中单位不是名称的后缀

# TYPE foo counter
# UNIT foo seconds

以下也是有效的:

# TYPE foo_seconds counter

如果单位已知,则应提供。

UNIT 或 HELP 行的值可以为空。这必须被视为指标家族不存在元数据行的情况。

# TYPE foo_seconds counter
# UNIT foo_seconds seconds
# HELP foo_seconds Some text and \n some \" escaping

指标家族的每种元数据行不得超过一行。顺序应为 TYPE、UNIT、HELP。

除了这些元数据和消息末尾的 EOF 行之外,不得暴露以 # 开头的行。

指标

指标不得交错。

参见“文本格式 -> MetricPoint”中的示例。没有标签或时间戳且值为 0 的样本必须渲染为如下形式:

bar_seconds_count 0

或如下形式:

bar_seconds_count{} 0

标签值可以是任何有效的 UTF-8 值,因此必须按照 ABNF 应用转义。包含两个标签的有效示例如下:

bar_seconds_count{a="x",b="escaping\" example \n "} 0

指标点的值的渲染可以包含额外的标签(例如直方图类型的“le”标签),这些标签必须以与指标自己的 LabelSet 相同的方式渲染。

指标点

指标点不得交错。

一个指标家族中包含多个指标点和样本的正确示例如下:

# TYPE foo_seconds summary
# UNIT foo_seconds seconds
foo_seconds_count{a="bb"} 0 123
foo_seconds_sum{a="bb"} 0 123
foo_seconds_count{a="bb"} 0 456
foo_seconds_sum{a="bb"} 0 456
foo_seconds_count{a="ccc"} 0 123
foo_seconds_sum{a="ccc"} 0 123
foo_seconds_count{a="ccc"} 0 456
foo_seconds_sum{a="ccc"} 0 456

指标交错的错误示例

# TYPE foo_seconds summary
# UNIT foo_seconds seconds
foo_seconds_count{a="bb"} 0 123
foo_seconds_count{a="ccc"} 0 123
foo_seconds_count{a="bb"} 0 456
foo_seconds_count{a="ccc"} 0 456

指标点交错的错误示例

# TYPE foo_seconds summary
# UNIT foo_seconds seconds
foo_seconds_count{a="bb"} 0 123
foo_seconds_count{a="bb"} 0 456
foo_seconds_sum{a="bb"} 0 123
foo_seconds_sum{a="bb"} 0 456

指标类型

仪表盘

类型为 Gauge 的指标家族的指标点值的样本指标名称不得包含后缀。

一个不带标签的指标和不带时间戳的指标点的指标家族示例

# TYPE foo gauge
foo 17.0

一个包含两个带标签的指标和不带时间戳的指标点的指标家族示例

# TYPE foo gauge
foo{a="bb"} 17.0
foo{a="ccc"} 17.0

一个不含指标的指标家族示例

# TYPE foo gauge

一个带标签的指标和带时间戳的指标点的示例

# TYPE foo gauge
foo{a="b"} 17.0 1520879607.789

一个不带标签的指标和带时间戳的指标点的示例

# TYPE foo gauge
foo 17.0 1520879607.789

一个不带标签的指标和两个带时间戳的指标点的示例

# TYPE foo gauge
foo 17.0 123
foo 18.0 456
计数器

指标点的总值样本指标名称必须带有 _total 后缀。如果存在,指标点的创建值样本指标名称必须带有 _created 后缀。

一个不带标签的指标和不带时间戳、不带创建时间的指标点的示例

# TYPE foo counter
foo_total 17.0

一个不带标签的指标和带时间戳、不带创建时间的指标点的示例

# TYPE foo counter
foo_total 17.0 1520879607.789

一个不带标签的指标和不带时间戳、带创建时间的指标点的示例

# TYPE foo counter
foo_total 17.0
foo_created 1520430000.123

一个不带标签的指标和带时间戳、带创建时间的指标点的示例

# TYPE foo counter
foo_total 17.0 1520879607.789
foo_created 1520430000.123 1520879607.789

Exemplars 可以附加到指标点的 Total 样本。

状态集

类型为 StateSet 的指标家族的指标点值的样本指标名称不得包含后缀。

状态集必须在指标点中每个状态有一个样本。每个状态的样本必须有一个标签,其标签名为指标家族名称,标签值为状态名称。如果状态为 true,则状态样本的值必须为 1;如果状态为 false,则必须为 0。

一个状态为“a”、“bb”和“ccc”的示例,其中只有值 bb 被启用,指标名称为 foo

# TYPE foo stateset
foo{foo="a"} 0
foo{foo="bb"} 1
foo{foo="ccc"} 0

指标上“entity”标签的示例

# TYPE foo stateset
foo{entity="controller",foo="a"} 1.0
foo{entity="controller",foo="bb"} 0.0
foo{entity="controller",foo="ccc"} 0.0
foo{entity="replica",foo="a"} 1.0
foo{entity="replica",foo="bb"} 0.0
foo{entity="replica",foo="ccc"} 1.0
信息

类型为 Info 的指标家族的指标点值的样本指标名称必须带有 _info 后缀。样本值必须始终为 1。

一个不带标签的指标,以及一个带“name”和“version”标签的指标点值的示例

# TYPE foo info
foo_info{name="pretty name",version="8.2.7"} 1

一个带“entity”标签的指标,以及一个带“name”和“version”标签的指标点值的示例

# TYPE foo info
foo_info{entity="controller",name="pretty name",version="8.2.7"} 1.0
foo_info{entity="replica",name="prettier name",version="8.1.9"} 1.0

指标标签和指标点值标签可以按任意顺序排列。

摘要

如果存在,指标点的总和值样本指标名称必须带有 _sum 后缀。如果存在,指标点的计数样本指标名称必须带有 _count 后缀。如果存在,指标点的创建值样本指标名称必须带有 _created 后缀。如果存在,指标点的分位数(Quantile)值必须使用一个标签来指定所测量的分位数,该标签的名称为“quantile”,标签值为所测量的分位数。

一个不带标签的指标和带 Sum、Count 和 Created 值的指标点的示例

# TYPE foo summary
foo_count 17.0
foo_sum 324789.3
foo_created 1520430000.123

一个不带标签的指标和带两个分位数的指标点的示例

# TYPE foo summary
foo{quantile="0.95"} 123.7
foo{quantile="0.99"} 150.0

分位数可以按任意顺序排列。

直方图

指标点的桶值样本指标名称必须带有 _bucket 后缀。如果存在,指标点的总和值样本指标名称必须带有 _sum 后缀。如果存在,指标点的创建值样本指标名称必须带有 _created 后缀。当且仅当指标点中存在总和值时,指标点的 +Inf 桶值也必须出现在带有 _count 后缀的指标名称的样本中。

桶必须按“le”的数值递增顺序排序,“le”标签的值必须遵循规范数字的规则。

一个不带标签的指标,以及一个带 Sum、Count 和 Created 值,并有 12 个桶的指标点的示例。特意展示了各种广泛且非典型但有效的“le”值

# TYPE foo histogram
foo_bucket{le="0.0"} 0
foo_bucket{le="1e-05"} 0
foo_bucket{le="0.0001"} 5
foo_bucket{le="0.1"} 8
foo_bucket{le="1.0"} 10
foo_bucket{le="10.0"} 11
foo_bucket{le="100000.0"} 11
foo_bucket{le="1e+06"} 15
foo_bucket{le="1e+23"} 16
foo_bucket{le="1.1e+23"} 17
foo_bucket{le="+Inf"} 17
foo_count 17
foo_sum 324789.3
foo_created 1520430000.123
示例

不带标签的示例必须将空标签集表示为 {}。

一个展示了几个有效示例的 Exemplars 示例:0.01 桶没有 Exemplar。0.1 桶有一个没有标签的 Exemplar。1 桶有一个带一个标签的 Exemplar。10 桶有一个带标签和时间戳的 Exemplar。实际上,所有桶都应具有相同样式的 Exemplars。

# TYPE foo histogram
foo_bucket{le="0.01"} 0
foo_bucket{le="0.1"} 8 # {} 0.054
foo_bucket{le="1"} 11 # {trace_id="KOO5S4vxi0o"} 0.67
foo_bucket{le="10"} 17 # {trace_id="oHg5SJYRHA0"} 9.8 1520879607.789
foo_bucket{le="+Inf"} 17
foo_count 17
foo_sum 324789.3
foo_created  1520430000.123
仪表盘直方图

指标点的桶值样本指标名称必须带有 _bucket 后缀。如果存在,指标点的总和值样本指标名称必须带有 _gsum 后缀。当且仅当指标点中存在总和值时,指标点的 +Inf 桶值也必须出现在带有 _gcount 后缀的指标名称的样本中。

桶必须按“le”的数值递增顺序排序,“le”标签的值必须遵循规范数字的规则。

一个不带标签的指标,以及一个不带 Exemplar 且桶中没有 Exemplar 的指标点值的示例

# TYPE foo gaugehistogram
foo_bucket{le="0.01"} 20.0
foo_bucket{le="0.1"} 25.0
foo_bucket{le="1"} 34.0
foo_bucket{le="10"} 34.0
foo_bucket{le="+Inf"} 42.0
foo_gcount 42.0
foo_gsum 3289.3
未知

类型为 Unknown 的指标家族的指标点值的样本指标名称不得包含后缀。

一个不带标签的指标和不带时间戳的指标点的示例

# TYPE foo unknown
foo 42.23

Protobuf 格式

整体结构

Protobuf 消息必须以二进制编码,并且必须将 application/openmetrics-protobuf; version=1.0.0 作为其内容类型。

所有负载必须是单个二进制编码的 MetricSet 消息,如 OpenMetrics Protobuf 模式所定义。

版本

Protobuf 格式必须遵循协议缓冲区语言的 proto3 版本。

字符串

所有字符串字段必须采用 UTF-8 编码。

时间戳

OpenMetrics Protobuf 模式中的时间戳表示必须遵循已发布的 google.protobuf.Timestamp [timestamp] 消息。时间戳消息必须是 Unix 纪元秒(int64)和纳秒级分辨率的非负秒分数(int32),该分数从秒时间戳组件开始计数。它必须在 0 到 999,999,999(包括两端)之间。

Protobuf 模式

Protobuf 模式目前可在此处获取。此处

注意Prometheus 及其生态系统不支持 OpenMetrics Protobuf 模式,而是使用类似的 io.prometheus.client 格式。关于 OpenMetrics 2.0 中 Protobuf 模式未来的讨论正在进行中

设计考量

范围

OpenMetrics 旨在为在线系统提供遥测数据。它运行在不提供硬实时或软实时保证的协议之上,因此它本身无法提供任何实时保证。OpenMetrics 的延迟和抖动特性与底层网络、操作系统、CPU 等一样不精确。它对于聚合作为决策基础来说足够精确,但不足以反映单个事件。

应支持各种规模的系统,从每小时接收少量请求的应用程序到监控 400Gb 网络端口的带宽使用情况。在任意时间段内,都应能够对传输的遥测数据进行聚合和分析。

它旨在以固定节奏传输数据传输时的状态快照。

超出范围

摄取器如何发现哪些暴露器存在,反之亦然,超出了本标准的范围,因此未在本标准中定义。

扩展和改进

OpenMetrics 的第一个版本基于成熟且事实上的标准 Prometheus 文本格式 0.0.4,特意没有在此之上添加主要的语法或语义扩展,或进行优化。例如,没有尝试使直方图桶的文本表示更紧凑,而是依赖底层堆栈中的压缩来处理其重复性。

这是一个有意的选择,以便该标准能够利用现有用户群的采用和发展势头。这确保了从 Prometheus 文本格式 0.0.4 的相对轻松的过渡。

它还确保存在一个易于实现的基本标准。这可以在标准的未来版本中进行扩展。其意图是,未来的标准版本将始终要求对该 1.0 版本的支持,无论是在语法上还是语义上。

我们希望监控系统能够从 OpenMetrics exposition 中获取可用信息,而不会造成过多的负担。如果剥离所有元数据和结构,仅将 OpenMetrics exposition 视为一组无序的样本,那么它应该能够独立使用。因此,也不存在不透明的二进制类型,例如不能以仪表盘和计数器混合形式表达的 sketches 或 t-digests,因为它们需要自定义解析和处理。

这一原则在整个标准中始终如一地应用。例如,指标家族的单位会在名称中重复,以便不理解单位元数据的系统也能获取该单位。“le”标签是一个正常的标签值,而不是有其特殊的语法,这样摄取器就不必添加特殊的直方图处理代码来摄取它们。再举一个例子,没有复合数据类型。例如,没有用于经纬度的地理位置类型,因为这可以通过单独的仪表盘指标来完成。

单位和基本单位

为了跨系统一致并避免混淆,单位主要基于 SI 基本单位。基本单位包括秒、字节、焦耳、克、米、比率、伏特、安培和摄氏度。应在适用之处提供单位。

例如,将所有持续时间指标以秒为单位,就无需猜测给定指标是纳秒、微秒、毫秒、秒、分钟、小时、天还是周,也无需处理混合单位。通过选择不带前缀的单位,我们避免了诸如千毫秒是复杂系统涌现行为结果的情况。

由于值可以是浮点数,标准中内置了亚基本单位精度。

同样,混合位和字节会造成混淆,因此选择字节作为基本单位。虽然理论上开尔文是更好的基本单位,但实际上大多数现有硬件都暴露摄氏度。千克是 SI 基本单位,但千克前缀存在问题,因此选择克作为基本单位。

尽管在所有可能的情况下都应使用基本单位,但开尔文是一个成熟的单位,在颜色或黑体温度等用例中,可以替代摄氏度使用,因为在这些情况下,摄氏度与开尔文指标之间的比较不太可能发生。

比率是基本单位,而不是百分比。在可能的情况下,应以给定分子和分母的仪表盘或计数器形式暴露原始数据。这对于摄取器中的分析和聚合具有更好的数学特性。

分贝不是基本单位,因为首先,deci 是 SI 前缀,其次,贝尔是指数级的。为了暴露信号/能量/功率比,直接暴露比率会更好,如果可能,暴露原始功率/能量则更好。浮点指数足以覆盖甚至极端的科学用途。一个电子伏特(约 1e-19 J)到超新星释放的能量(约 1e44 J)相差 63 个数量级,而一个 64 位浮点数可以覆盖超过 2000 个数量级。

如果无法避免非基本单位且转换不可行,则实际单位仍应包含在指标名称中以保持清晰。例如,焦耳是能量和功率的基本单位,因为瓦特可以表示为带焦耳单位的计数器。在实践中,给定的第三方系统可能只暴露瓦特,因此在这种情况下,以瓦特表示的仪表盘将是唯一现实的选择。

并非所有指标家族都有单位。例如,HTTP 请求计数就没有单位。严格来说,单位将是 HTTP 请求,但在这种意义上,整个指标家族名称就是单位。走到这个极端将没有用处。应始终牢记在下游系统中为人类消费在图表上拥有良好轴的可能性。

无状态性

OpenMetrics 定义的传输格式在不同 exposition 之间是无状态的。之前暴露的信息不得对未来的 exposition 产生任何影响。每个 exposition 都是暴露器当前状态的自包含快照。

必须向现有和新的摄取器提供相同的自包含 exposition。

一个核心设计选择是,暴露器不得仅仅因为指标最近没有变化或观测而将其排除。暴露器不得对摄取器消耗 exposition 的频率做出任何假设。

随时间变化的 Exposition 和指标演变

指标在分析其随时间演变时最有价值,因此 exposition 必须随时间有意义。因此,单个 exposition 本身不足以有用和有效。指标语义的一些更改也可能破坏下游用户。

解析器通常通过缓存先前结果来优化。因此,应避免在不同 exposition 之间更改标签暴露的顺序,即使这在技术上不会造成破坏。这也有助于简化 exposition 的单元测试编写。

指标和样本不应在不同的 exposition 之间出现和消失,例如,计数器只有在有历史记录的情况下才有用。原则上,给定指标应该从进程启动时开始,直到进程终止时都存在于 exposition 中。通常无法提前知道一个指标家族在给定进程的生命周期内会有哪些指标(例如,延迟直方图的标签值是一个 HTTP 路径,由最终用户在运行时提供),但一旦暴露了类似计数器的指标,它就应该继续暴露,直到进程终止。计数器没有增加并不意味着它不再具有当前值。在某些情况下,停止暴露给定指标可能是有意义的;请参阅“缺失数据”一节。

一般来说,更改指标家族的类型,或从其指标中添加或删除标签,将对摄取器造成破坏。

一个值得注意的例外是,向信息指标点的值添加标签不会造成破坏。这样,你就可以在有意义的地方向现有信息指标家族添加额外信息,而不是被迫创建一个带有额外标签值的新信息指标。摄取器系统应确保它们能够适应此类添加。

更改指标家族的 Help 不会造成破坏。对于可能的值,在浮点数和整数之间切换不会造成破坏。向状态集添加新状态不会造成破坏。在不更改指标名称的情况下添加单位元数据不会造成破坏。

直方图的桶不应在不同 exposition 之间更改,因为这可能会导致性能问题并破坏摄取器。类似地,来自应用程序任何一致的二进制和环境的所有 exposition 都应为给定直方图指标家族拥有相同的桶,以便所有摄取器都可以对其进行聚合,而无需摄取器为异构桶实现直方图合并逻辑。一个例外可能是偶尔的手动更改桶,这被认为是破坏性的,但当由于新软件发布导致性能特性发生变化时,这可能是一个有效的权衡。

即使更改在技术上不具有破坏性,它们仍然会带来成本。例如,频繁的更改可能会导致摄取器出现性能问题。每次 exposition 都不同的 Help 字符串可能会导致每个 Help 值都被存储。频繁地在整数和浮点数之间切换可能会阻止高效压缩。

NaN

NaN 在 OpenMetrics 中是和其他数字一样的值,通常是由于除以零而产生的,例如在最近没有观测值的情况下计算摘要分位数。NaN 在 OpenMetrics 中没有特殊含义,尤其不得用作缺失或损坏数据的标记。

缺失数据

在某些有效情况下数据会停止存在。例如,文件系统可能被卸载,从而其用于空闲磁盘空间的仪表盘指标不再存在。对于这种情况没有特殊的标记或信号。后续的 exposition 简单地不包含此指标。

Exposition 性能

指标只有在合理的时间范围内收集时才有用。需要数分钟才能暴露的指标不被认为是有效的。

一般来说,exposition 不应超过一秒。

通过 OpenMetrics 序列化的来自旧系统的指标可能需要更长的时间。因此,不能做出硬性的性能假设。

Exposition 应该反映最新状态。例如,处理 exposition 请求的线程不应依赖缓存值,只要它能够绕过任何此类缓存。

并发性

为了高可用性和即席访问,常见的方法是拥有多个摄取器。为此,必须支持并发 exposition。应遵循并发系统的所有 BCPs,常见陷阱包括死锁、竞态条件以及阻止 exposition 并发进行过于粗粒度的锁。

指标命名和命名空间

我们旨在平衡指标和标签名称的易懂性、避免冲突和简洁性。名称通过下划线分隔,因此指标名称最终采用“snake_case”格式。

举个例子,“http_request_seconds”很简洁,但会在大量应用程序之间发生冲突,而且也不清楚这个指标到底在测量什么。例如,在一个复杂系统中,它可能是在认证中间件之前或之后。

指标名称应表明它们来自哪段代码。因此,一家名为“A Company Manufacturing Everything”的公司可能会在它们的代码中为所有指标添加“acme_”前缀,如果它们有一个测量延迟的 HTTP 路由器库,它可能有一个指标,例如“acme_http_router_request_seconds”,并附带一个 Help 字符串,表明它是整体延迟。

目标并非阻止所有应用程序之间的所有潜在冲突,因为那将需要例如指标命名空间的全局注册表或基于 DNS 的超长命名空间等强硬解决方案。相反,目标是保持一种轻量级的非正式方法,以便对于给定应用程序来说,其组成库之间不太可能发生冲突。

在整个监控系统的给定部署中,目标是避免相同指标名称表示不同含义的冲突。例如,acme_http_router_request_seconds 可能最终出现在“A Company Manufacturing Everything”开发的数百个不同应用程序中,这是正常的。如果“Another Corporation Making Entities”也在其 HTTP 路由器中使用了指标名称 acme_http_router_request_seconds,那也没关系。如果来自两家公司的应用程序由同一个监控系统监控,冲突虽然不理想,但可以接受,因为没有应用程序试图暴露这两个名称,也没有一个目标试图(错误地)暴露相同的指标名称两次。如果一个应用程序希望同时包含“My Example Company”和“Mega Exciting Company”的 HTTP 路由器库,那将是一个问题,其中一个指标名称需要以某种方式更改。

因此,一个库越是公开,其指标名称的命名空间就越好,以降低出现此类情况的风险。acme_ 并不是一个糟糕的选择对于公司内部使用,但这些公司例如可以选择前缀 acmeverything_ 或 acorpme_ 用于公司外部共享的代码。

在按公司或组织划分命名空间后,应根据需要,例如上述 http_router 库那样,按库/子系统/应用程序分形地继续命名空间和命名。目标是,如果您熟悉代码库的整体结构,就可以根据给定指标的名称,很好地猜测该指标的检测代码在哪里。

对于一个常见且广为人知的现有软件,软件本身的名称可能足以区分。例如,对于 DNS 软件来说,bind_ 可能就足够了,尽管 isc_bind_ 会是更常见的命名方式。

以 scrape_ 为前缀的指标名称由摄取器用于附加与单个 exposition 相关的信息,因此不应由应用程序直接暴露。已经由通用监控系统消耗并传递的指标可以在后续 exposition 中包含此类指标名称。如果暴露器希望提供有关单个 exposition 的信息,可以使用诸如 myexposer_scrape_ 之类的指标前缀。一个常见示例是仪表盘 myexposer_scrape_duration_seconds,用于表示该 exposition 从暴露器的角度来看花费了多长时间。

在 Prometheus 生态系统内部,出现了一组进程级指标,它们在所有实现中都是一致的,并带有 process_ 前缀。例如,对于打开文件数量限制,指标家族 process_open_fds 和 process_max_fds 仪表盘提供当前值和最大值。(这些名称是遗留的,如果今天定义此类指标,它们更可能被称为 process_fds_open 和 process_fds_limit)。总的来说,获得具有相同语义的名称是非常具有挑战性的,这就是为什么不同的检测应该使用不同的名称。

避免指标名称中的冗余。避免使用“metric”、“timer”、“stats”、“counter”、“total”、“float64”等子字符串——因为通过 OpenMetrics 暴露的具有给定类型(可能还有单位)的指标已经暗示了此类信息,因此不应明确包含。出于同样的原因,您不应将指标的标签名称包含在指标名称中,此外,监控系统对指标的后续聚合可能会使此类信息不正确。

避免在您的检测中包含的指标名称中包含监控系统其他层的实现细节。例如,指标家族名称不应仅仅因为它碰巧目前通过 OpenMetrics 暴露就包含字符串“openmetrics”,或者仅仅因为您当前的监控系统是 Prometheus 就包含“prometheus”。

标签命名空间

对于标签名称,不建议按公司或库进行显式命名空间划分,考虑到标签名称的长度增加,从指标名称中获取命名空间就已足够。然而,建议采取一些最低限度的措施以避免常见冲突。

存在诸如 region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、team、job、instance、environment 和 env 等标签名称,它们极有可能与通用监控系统可能添加的用于识别目标的标签发生冲突。尽量避免使用它们,在这些情况下添加最小命名空间可能是合适的。

标签名称“type”非常通用,应避免使用。例如,对于 HTTP 相关指标,如果要区分 GET、POST 和 PUT 请求,“method”会是更好的标签名称。

虽然有关于指标名称的元数据,例如 HELP、TYPE 和 UNIT,但没有关于标签名称的元数据。这是因为这会增加格式的臃肿,而收益甚微。带外文档是暴露器向其摄取器呈现此信息的一种方式。

指标名称与标签

在某些情况下,在指标家族中使用多个指标或使用多个指标家族似乎都有意义。对指标家族进行求和或求平均值应该是有意义的,即使它并非总是很有用。例如,混合电压和风扇速度就没有意义。

提醒一下,OpenMetrics 的构建基于摄取器可以处理和对数据执行聚合的假设。

与其他指标一起暴露总和是错误的,因为这会导致下游摄取器在聚合时出现重复计数。

wrong_metric{label="a"} 1
wrong_metric{label="b"} 6
wrong_metric{label="total"} 7

指标的标签应尽可能少,以确保唯一性,因为每个额外的标签都会增加用户在下游确定要使用的标签时需要考虑的因素。可以应用于多个指标家族的标签是移入类似数据库{{normalization}} 的 _info 指标的候选。如果几乎所有指标用户都希望有额外的标签,则将其添加到所有指标家族可能是一个更好的权衡。例如,如果您有一个与不同 SQL 语句相关的指标家族,其中唯一性由包含完整 SQL 语句哈希值的标签提供,那么可以有另一个标签,其中包含 SQL 语句的前 500 个字符以提高可读性。

经验表明,下游摄取器更倾向于使用单独的总计和失败指标家族,而不是在一个指标家族中使用 {result="success"} 和 {result="failure"} 标签。此外,通常最好暴露单独的读写和发送接收指标家族,因为全双工系统很常见,并且下游摄取器更可能关心这些值的单独情况而非聚合情况。

所有这些听起来并不容易。这是一个需要暴露和被暴露系统领域的专家根据经验和工程权衡来找到良好平衡的领域。指标和标签名称字符

OpenMetrics 建立在现有广泛采用的 Prometheus 文本暴露格式及其形成的生态系统之上。向后兼容性是一个核心设计目标。扩展或缩小 Prometheus 文本格式支持的字符集将违背这一目标。破坏向后兼容性将不仅仅对传输格式产生更广泛的影响。特别是,为处理 Prometheus 生态系统内传输的数据而创建或采用的查询语言依赖于这些精确的字符集。标签值支持完整的 UTF-8,因此该格式可以表示多语言指标。

元数据类型

元数据可以来自不同的来源。多年来,出现了两个主要来源。虽然它们在功能上通常是相同的,但讨论它们的概念差异有助于理解。

“目标元数据”是通常暴露器外部的元数据。常见示例包括来自服务发现、CMDB 或类似系统的数据,例如数据中心区域信息、服务是否属于特定部署,或生产或测试。这可以通过暴露器或摄取器向捕获此元数据的所有指标添加标签来实现。首选通过摄取器来实现,因为它更灵活且开销更小。在灵活性方面,硬件维护团队可能关心机器所在的服务器机架,而使用同一台机器的数据库团队可能关心它是否包含生产数据库的副本 2。在开销方面,硬编码或配置此信息需要额外的分发路径。

“暴露器元数据”来自暴露器内部。常见示例包括软件版本、编译器版本或 Git 提交 SHA。

在推拉两种系统中支持目标元数据

在推式消费中,暴露器通常会向摄取器提供相关的目标元数据。在拉式消费中,可以采用推式方法,但更常见的是摄取器预先知道目标的元数据,例如来自机器数据库或服务发现系统,并在消费 exposition 时将其与指标关联。

OpenMetrics 是无状态的,并向所有摄取器提供相同的 exposition,这与推式方法相冲突。此外,推式方法会破坏拉式摄取器,因为会暴露不需要的元数据。

一种方法是让推式摄取器根据操作员配置以带外方式提供目标元数据,例如作为 HTTP 标头。虽然这将为推式摄取器传输目标元数据,并且本标准不排除这种方式,但其缺点是,即使拉式摄取器应使用自己的目标元数据,但通常仍然有必要访问暴露器本身所知的元数据。

首选的解决方案是将此目标元数据作为 exposition 的一部分提供,但其方式不影响整个 exposition。信息指标家族就是为此设计的。暴露器可以包含一个名为“target”的信息指标家族,其中包含一个没有标签的指标,并带有元数据。文本格式的示例如下:

# TYPE target info
# HELP target Target metadata
target_info{env="prod",hostname="myhost",datacenter="sdc",region="europe",owner="frontend"} 1

当暴露器为此目的提供此指标时,它应在 exposition 中排在第一位。这是为了效率,以便依赖它获取目标元数据的摄取器在根据其内容应用业务逻辑之前无需缓冲其余的 exposition。

暴露器不得向 exposition 中的所有指标添加目标元数据标签,除非为特定摄取器明确配置。暴露器不得对指标家族名称添加前缀,或以其他方式根据目标元数据更改指标家族名称。一般来说,相同的标签不应出现在 exposition 的每个指标上,但在极少数情况下,这可能是涌现行为的结果。同样,在非常小的 exposition 中,来自暴露器的所有指标家族名称可能碰巧共享一个前缀。例如,一家名为“A Company Manufacturing Everything”的公司用 Go 语言编写的应用程序很可能包含带有 acme_、go_、process_ 前缀的指标,以及任何正在使用的第三方库的指标前缀。

暴露器可以将暴露器元数据作为信息指标家族暴露。

以上讨论是在单个暴露器的背景下进行的。来自通用监控系统的 exposition 可能包含来自许多单个目标的指标,因此可能暴露多个目标信息指标。这些指标可能已经在摄取过程中被添加了目标元数据作为标签。指标名称不得根据目标元数据进行变化。例如,即使所有指标都源自测试环境中的目标,但将所有指标都加上 staging_ 前缀是错误的。

客户端计算和派生指标

暴露器应将任何数学或计算留给摄取器。一个值得注意的例外是摘要分位数,遗憾的是,为了向后兼容性,它是必需的。Exposition 应该是原始值,这些值在任意时间段内都很有用。

例如,您不应暴露一个仪表盘,其中包含计数器在过去 5 分钟内的平均增长率。让摄取器计算其在不同 exposition 中消耗的数据点的增长量,具有更好的数学特性,并且更能抵御抓取失败。

另一个例子是直方图/摘要的平均事件大小。暴露计数器自应用程序启动或指标创建以来的平均增长率存在与先前示例相同的问题,并且还阻止了聚合。

标准偏差也属于这一类别。将平方和暴露为计数器将是正确的方法。它没有作为直方图值包含在本标准中,因为 64 位浮点精度不足以在实践中实现这一点。由于平方运算,只有一半的 53 位尾数可用于精度。例如,一个每秒观测 1 万个事件的直方图会在 2 小时内失去精度。使用 64 位整数也不会更好,因为浮点小数点会丢失,导致通常跟踪一秒长度事件的纳秒级分辨率整数在 19 次观测后溢出。当 128 位浮点数变得普遍时,可以重新审视此设计决策。

另一个例子是避免暴露请求失败率,而是分别暴露失败请求和总请求的计数器。

数字类型

对于一个每秒递增一百万次的计数器,使用 float64 会在一个多世纪后才开始失去精度,因为它有 53 位尾数。然而,一个 100 Gbps 网络接口的八位组吞吐量精度可能在大约 20 小时内开始用 float64 失去。虽然对于一个 100Gbps 网络接口来说,在几年内损失 1KB 的精度在实践中不太可能成为问题,但对于具有如此高吞吐量的整数数据,int64 是一种选择。

摘要分位数必须是 float64,因为它们是估计值,因此从根本上是不精确的。

暴露时间戳

OpenMetrics 的核心假设之一是暴露器暴露其所暴露内容的最新快照。

虽然将时间戳附加到暴露数据的使用场景有限,但这些场景非常罕见。以前附加过时间戳的数据,特别是已摄取到通用监控系统中的数据,可能携带时间戳。实时或原始数据不应携带时间戳。在不同 exposition 中暴露具有相同时间戳的相同指标点值是有效的,但是如果底层指标现在缺失,则这样做是无效的。

时间同步是一个难题,每个系统中的数据都应该内部一致。因此,摄取器应该能够从其自身角度将当前时间戳附加到数据,而不是基于暴露器设备的系统时间。

对于带时间戳的指标,通常无法检测到指标在不同 exposition 之间何时消失。然而,对于不带时间戳的指标,摄取器可以使用它自己的时间戳,即指标不再存在的那个 exposition 的时间戳。

综上所述,一般来说,指标点时间戳不应暴露,因为应由摄取器自行对其摄取的样本应用时间戳。

跟踪指标上次更改时间

假设您有一个计数器 my_counter,它被初始化,然后在时间 123 增加 1。这在文本格式中是正确的暴露方式:

# HELP my_counter Good increment example
# TYPE my_counter counter
my_counter_total 1

根据上一节所述,摄取器应可以自由附加自己的时间戳,因此这将是错误的:

# HELP my_counter Bad increment example
# TYPE my_counter counter
my_counter_total 1 123

如果计数器上次更改的具体时间很重要,则应按以下方式进行:

# HELP my_counter Good increment example
# TYPE my_counter counter
my_counter_total 1
# HELP my_counter_last_increment_timestamp_seconds When my_counter was last incremented
# TYPE my_counter_last_increment_timestamp_seconds gauge
# UNIT my_counter_last_increment_timestamp_seconds seconds
my_counter_last_increment_timestamp_seconds 123

通过将上次更改的时间戳作为值放入其自身的仪表盘中,摄取器可以自由地将自己的时间戳附加到两个指标上。

经验表明,暴露绝对时间戳(此处纪元被认为是绝对的)比暴露经过时间、秒数等更健壮。在任何一种情况下,它们都将是仪表盘。例如:

# TYPE my_boot_time_seconds gauge
# HELP my_boot_time_seconds Boot time of the machine
# UNIT my_boot_time_seconds seconds
my_boot_time_seconds 1256060124

优于

# TYPE my_time_since_boot_seconds gauge
# HELP my_time_since_boot_seconds Time elapsed since machine booted
# UNIT my_time_since_boot_seconds seconds
my_time_since_boot_seconds 123

相反,对示例时间戳没有最佳实践限制。请记住,由于竞态条件或设备之间时间未完全同步,示例时间戳可能相对于摄取器的系统时钟或来自同一 exposition 的其他指标显得略微超前。同样,指标点的“_created”时间可能略晚于该指标点的示例或样本时间戳。

请记住,常用的监控系统支持从纳秒到秒的各种分辨率,因此如果两个指标点在截断为秒级分辨率时具有相同的时间戳,可能会导致摄取器中出现明显的重复。在这种情况下,必须使用具有最早时间戳的指标点。

阈值

暴露系统所需的界限可能是有意义的,但需要适当的注意。对于普遍真实的值,为这些阈值发出 Gauge 指标可能是有意义的。例如,数据中心 HVAC 系统知道当前测量值、设定点和警报设定点。它对所需的系统状态具有全局有效和正确的视图。作为反例,某些阈值可能随着规模、部署模型或时间而变化。在一种设置下可接受的 CPU 使用量在另一种设置下可能不理想。值的聚合可以进一步改变可接受的值。在这样的系统中,暴露界限可能会适得其反。

例如,队列的最大大小可以与队列中当前的项目数一起暴露,如下所示:

# HELP acme_notifications_queue_capacity The capacity of the notifications queue.
# TYPE acme_notifications_queue_capacity gauge
acme_notifications_queue_capacity 10000
# HELP acme_notifications_queue_length The number of notifications in the queue.
# TYPE acme_notifications_queue_length gauge
acme_notifications_queue_length 42

大小限制

本标准未规定单次暴露所暴露的样本数量、可能存在的标签数量、状态集可能拥有的状态数量、信息值中的标签数量或指标名称/标签名称/标签值/帮助字符的任何特定限制。

具体的限制有阻碍合理用例的风险,例如,虽然给定的暴露在通过通用监控系统后可能具有适当数量的标签,但可能会添加一些目标标签,从而使其超出限制。对这些数字的具体限制也无法捕捉通用监控系统的实际成本所在。因此,这些指南旨在帮助暴露者和摄入者了解什么是合理的。

另一方面,在某些维度上过大的暴露可能会导致显著的性能问题,与暴露指标的收益相比。因此,关于任何单次暴露大小的一些指南将是有用的。

摄入器可以选择自行施加限制,特别是为了防止攻击或中断。然而,摄入器需要考虑合理的用例,并尽量不要对其造成不成比例的影响。如果任何单个值/指标/暴露超出这些限制,则必须拒绝整个暴露。

通常,有三件事会影响通用监控系统摄入时间序列数据的性能:唯一时间序列的数量、这些序列中随时间变化的样本数量以及诸如指标名称、标签名称、标签值和 HELP 等唯一字符串的数量。摄入器可以控制摄入的频率,因此这方面无需进一步考虑。

唯一时间序列的数量大致相当于文本格式中非注释行的数量。截至2020年,总计1000万个时间序列被认为是大量的,通常是任何单实例摄入器上限的量级。任何单一暴露在未进行尽职调查的情况下不应超过1万个时间序列。一个常见的考虑是横向扩展:如果您的实例数量增加1-2个数量级会发生什么?30年前,在单个部署中拥有上千个机架顶交换机是难以想象的。如果一个目标是单例(例如,暴露与整个集群相关的指标),那么几十万个时间序列可能是合理的。重要的不是唯一 MetricFamilies 的数量或单个标签/桶/状态集的基数,而是时间序列的总量级。1000个每个包含一个 Metric 的 Gauge 与一个包含1000个 Metrics 的单个 Gauge 成本相同。

如果特定类型的所有目标都暴露相同的时间序列集,那么每个额外的目标的字符串不会给大多数合理现代的监控系统带来增量成本。然而,如果每个目标都有唯一的字符串,则会产生这样的成本。举一个极端的例子,一个被许多目标使用的单一10k字符指标名称,在实践中本身不太可能成为问题。相反,假设采用现代方法,一千个目标每个暴露一个唯一的36字符 UUID,在存储字符串方面,其成本是该单一10k字符指标名称的三倍以上。此外,如果这些字符串随时间变化,旧字符串仍需要存储至少一段时间,从而产生额外成本。假设上一段提到的1000万个时间序列,每小时100MB的唯一字符串可能表明用例更像是事件日志,而非指标时间序列。

示例长度有128个UTF-8字符的硬性限制,以防止滥用该功能进行跟踪跨度数据和其他事件日志。

安全

实现者可以(MAY)选择提供认证、授权和计费;如果他们选择这样做,则应(SHOULD)在 OpenMetrics 之外处理。

所有暴露器实现都应(SHOULD)能够使用 TLS 1.2 或更高版本保护其 HTTP 流量。如果暴露器实现不支持加密,操作者在可行的情况下应(SHOULD)使用反向代理、防火墙和/或 ACL。

指标暴露应独立于暴露给最终用户的生产服务;因此,对于使用 OpenMetrics 的公开暴露服务,通常不鼓励在 TCP/80、TCP/443、TCP/8080 和 TCP/8443 等端口上设置 /metrics 端点。

IANA

尽管目前 Prometheus 暴露格式的大多数实现都使用 {{PrometheusPorts}} 非正式注册表中未经 IANA 注册的端口,但 OpenMetrics 可以在一个明确定义的端口上找到。

IANA 为暴露数据的客户端分配的端口是 <9099,要求与历史保持一致>。

如果一个共同的 IP 地址和端口需要可访问多个指标端点,操作者可以考虑使用反向代理,通过 localhost 地址与暴露器通信。为了便于多路复用,端点应(SHOULD)在其路径中包含自己的名称,即 /node_exporter/metrics。不应(SHOULD NOT)将暴露合并为单个暴露,原因已在“支持基于推送和基于拉取系统中的目标元数据”中阐述,并且为了允许独立摄入而不存在单点故障。

OpenMetrics 希望注册两种 MIME 类型:application/openmetrics-textapplication/openmetrics-proto

本页内容