OpenMetrics

  • 版本: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 主要是一种线路格式,独立于该格式的任何特定传输方式。该格式预计会定期使用,并且在连续的公开中具有意义。

实施者必须以 OpenMetrics 文本格式公开指标,以响应针对给定进程或设备的已记录 URL 的简单 HTTP GET 请求。此端点应称为“/metrics”。实施者也可以其他方式公开 OpenMetrics 格式的指标,例如通过定期将指标集推送到运营商配置的 HTTP 端点。

指标和时间序列

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

时间序列是随时间变化的记录信息。虽然时间序列可以支持任意字符串或二进制数据,但只有数值数据在此 RFC 的范围内。

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

数据模型

本节必须与 ABNF 节一起阅读。如果两者之间存在分歧,则必须以 ABNF 的限制为准。这减少了重复,因为必须支持文本线路格式。

数据类型

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

布尔值

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

时间戳

时间戳必须是 Unix 纪元时间戳,单位为秒。可以使用负时间戳。

字符串

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

标签

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

以下划线开头的标签名称为保留名称,除非本标准另有规定,否则不得使用。标签名称必须遵守 ABNF 节中的限制。

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

标签集

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

指标点

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

范例

范例是对指标集之外数据的引用。常见的用例是程序跟踪的 ID。

范例必须由标签集和一个值组成,并且可以具有时间戳。它们可以各自与指标点的标签集和时间戳不同。

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

摄取器可以丢弃范例。

指标

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

指标点不应具有显式时间戳。

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

指标族

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

名称

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

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

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

后缀

指标族的名称不得导致与指标集中文本格式中的另一个指标族产生样本指标名称的潜在冲突,如 ABNF 所述。例如,名为“foocreated”的仪表盘,因为名为“foo”的计数器可能会在文本格式中创建“foocreated”。

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

  • 各个类型的后缀是
  • 计数器:_total_created
  • 摘要:_count_sum_created、`` (空)
  • 直方图:_count_sum_bucket_created
  • GaugeHistogram:_gcount_gsum_bucket
  • 信息:_info
  • 仪表盘:`` (空)
  • 状态集:`` (空)
  • 未知:`` (空)
类型

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

单位

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

帮助

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

指标集

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

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

指标集中不需要指标族的特定排序。公开器可以使公开更易于人类阅读,例如,如果性能权衡有意义,则按字母顺序排序。

如果存在,则应首先显示根据以下“在基于推送和基于拉取的系统中支持目标元数据”部分命名的 Info 指标族“target”。

指标类型

仪表盘

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

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

仪表盘可能会随着时间的推移增加、减少或保持不变。即使它们仅在一个方向上移动,它们仍然可能是仪表盘而不是计数器。日志文件的大小通常只会增加,资源可能会减少,而队列大小的限制可能是恒定的。

仪表盘可以用于编码枚举,其中枚举具有许多状态并且随时间变化,这是最有效但用户友好性最差的方式。

计数器

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

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

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

指标的计数器的 Total 中的指标点可能会重置为 0。如果存在,则相应的 Created 时间也必须设置为重置的时间戳。

指标的计数器的 Total 中的指标点可以有一个范例。

状态集

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

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

状态集指标的标签集不得具有与指标族名称相同的标签名称。

如果编码为状态集,则 ENUM 必须恰好有一个布尔值为 true 的指标点。

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

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

信息

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

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

Info 可用于编码 ENUM,其值不会随时间变化,例如网络接口的类型。

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

直方图

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

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

直方图指标点必须有一个 +Inf 阈值的桶。桶必须是累积的。例如,对于表示请求延迟(以秒为单位)的指标,阈值为 1、2、3 和 +Inf 的桶的值必须遵循 value1 <= value2 <= value3 <= value+Inf。如果十个请求各花费 1 秒,则 1、2、3 和 +Inf 桶的值必须等于 10。

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

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

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

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

桶值可以具有范例。桶是累积的,以允许监控系统出于性能/反拒绝服务原因丢弃任何非 +Inf 桶,这种方式会损失粒度,但仍然是有效的直方图。

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

GaugeHistogram

GaugeHistogram 测量当前分布。常见的示例是在队列中等待的项目时长,或队列中请求的大小。

GaugeHistogram 指标点必须有一个 +Inf 阈值的桶,并且应包含一个 Gsum 值。每个桶必须具有阈值和值。

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

GaugeHistogram 的桶和 Gsum 在概念上是仪表盘,但是桶值不得为负数或 NaN。如果存在负阈值桶,则总和可能为负数。Gsum 不得为 NaN。桶值必须是整数。

GaugeHistogram 指标的标签集不得具有“le”标签名称。

桶值可以具有范例。

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

摘要

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

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

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

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

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

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

未知

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

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

数据传输和线路格式

必须支持文本线路格式,并且它是默认格式。可以支持 protobuf 线路格式,并且必须仅在协商后使用。

OpenMetrics 格式是常规乔姆斯基语法,使编写快速和小型解析器成为可能。文本格式压缩良好,protobuf 已经是二进制且高效编码的。

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

协议协商

所有摄取器实现都必须能够摄取使用 TLS 1.2 或更高版本保护的数据。所有公开器都应能够发出使用 TLS 1.2 或更高版本保护的数据。摄取器实现应能够从不使用 TLS 的 HTTP 摄取数据。所有实现都应使用 TLS 传输数据。

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

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

文本格式

ABNF

ABNF 遵循 RFC 5234

“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)。公开必须以 EOF 结尾,并且应以 EOF\n 结尾。

完整公开的示例

# 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 等情况。幂十被覆盖以尝试确保固定点和指数渲染之间的切换是一致的,因为这在运行时之间有所不同。目标渲染等效于 float64 值的默认 Go 渲染(即 %g),如果没有小数点或指数,则附加 .0 以明确它们是浮点数。

公开器必须为正无穷大生成 +Inf 输出。

公开器应生成 0.0 到 10.0 之间的值(以 0.001 为增量)的输出,与以下示例一致:0.0 0.001 0.002 0.01 0.1 0.9 0.95 0.99 0.999 1.0 1.7 10.0

公开器应生成 1e-10 到 1e+10 之间的值(以 10 的幂为单位)的输出,与以下示例一致:1e-10 1e-09 1e-05 0.0001 0.1 1.0 100000.0 1e+06 1e+10

解析器不得拒绝规范值之外的输入,仅仅因为它们与规范值不一致。例如,1.1e-4 不得被拒绝,即使它不是 0.00011 的一致渲染。

公开器应遵循非规范数字的这些模式,并且目的是通过调整渲染算法以使其与这些值一致,从而使绝大多数其他值也将具有一致的渲染。仅使用几个特定 le/quantile 值的公开器也可以硬编码。在 C 等最小浮点渲染算法(例如 Grisu3)不易获得的语言中,公开器可以使用不同的渲染。

对 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 行之外,您不得公开以 # 开头的行。

指标

指标不得交错。

请参阅“文本格式 -> 指标点”中的示例。没有标签或时间戳且值为 0 的样本必须渲染为

bar_seconds_count 0

或像

bar_seconds_count{} 0

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

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

指标点的值的渲染可以包括其他标签(例如,直方图类型的“le”标签),这些标签必须以与指标自身的标签集相同的方式渲染。

指标点

指标点不得交错。

在指标族中存在多个指标点和样本的正确示例是

# 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

指标类型

仪表盘

类型为仪表盘的指标族的指标点的值的样本指标名称不得带有后缀。

没有标签的指标和没有时间戳的指标点的指标族示例

# 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

范例可以附加到指标点的总计样本。

状态集

类型为状态集的指标族的指标点的值的样本指标名称不得带有后缀。

状态集在指标点中必须为每个状态有一个样本。每个状态的样本必须有一个标签,标签名称为指标族名称,标签值为状态名称。如果状态为 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。样本值必须始终为 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”且标签值为测量的分位数的标签来指定测量的分位数。

没有标签的指标和一个带有总和、计数和创建时间值的指标点的示例

# 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

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

直方图

MetricPoint 的 Bucket Values Sample MetricName 必须带有后缀 _bucket。如果存在,MetricPoint 的 Sum Value Sample MetricName 必须带有后缀 _sum。如果存在,MetricPoint 的 Created Value Sample MetricName 必须带有后缀 _created。当且仅当 MetricPoint 中存在 Sum Value 时,MetricPoint 的 +Inf Bucket 值也必须出现在带有后缀 "_count" 的 MetricName 的 Sample 中。

Buckets 必须按照 "le" 标签值的数字升序排列,并且 "le" 标签的值必须遵循规范数字的规则。

一个 Metric 示例,没有标签,MetricPoint 带有 Sum、Count 和 Created 值,以及 12 个 buckets。特意展示了广泛且非典型的但有效的 “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

没有标签的 Exemplars 必须将空 LabelSet 表示为 {}。

Exemplars 示例展示了几个有效案例:“0.01” bucket 没有 Exemplar。0.1 bucket 有一个没有标签的 Exemplar。1 bucket 有一个带有一个标签的 Exemplar。10 bucket 有一个带有标签和时间戳的 Exemplar。在实践中,所有 buckets 应该具有相同的 Exemplar 样式。

# 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
GaugeHistogram

MetricPoint 的 Bucket Values Sample MetricName 必须带有后缀 _bucket。如果存在,MetricPoint 的 Sum Value Sample MetricName 必须带有后缀 _gsum。当且仅当 MetricPoint 中存在 Sum Value 时,MetricPoint 的 +Inf Bucket 值也必须出现在带有后缀 _gcount 的 MetricName 的 Sample 中。

Buckets 必须按照 "le" 标签值的数字升序排列,并且 "le" 标签的值必须遵循规范数字的规则。

一个 Metric 示例,没有标签,以及一个 MetricPoint 值,buckets 中没有 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

对于 Unknown 类型的 MetricFamily,MetricPoint 值的 sample metric name 必须没有后缀。

一个示例,Metric 没有标签,MetricPoint 没有时间戳

# TYPE foo unknown
foo 42.23

Protobuf 格式

总体结构

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

所有 payloads 必须是单个二进制编码的 MetricSet 消息,由 OpenMetrics protobuf schema 定义。

版本

protobuf 格式必须遵循 protocol buffer 语言的 proto3 版本。

字符串

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

时间戳

OpenMetrics protobuf schema 中的时间戳表示必须遵循已发布的 google.protobuf.Timestamp [timestamp] 消息。时间戳消息必须采用 Unix epoch 秒(int64)和纳秒分辨率的非负秒的小数部分(int32),从秒时间戳组件向前计数。它必须在 0 到 999,999,999 之间(包含边界值)。

Protobuf schema

Protobuf schema 目前可在此处获取:here

注意: Prometheus 和生态系统不支持 OpenMetrics protobuf schema,而是使用类似的 io.prometheus.client format 格式。关于 OpenMetrics 2.0 中 protobuf schema 未来发展的讨论 正在进行中

设计考虑

范围

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

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

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

超出范围

Ingestors 如何发现哪些 exposers 存在,以及反之亦然,超出了本标准的范围,因此未在本标准中定义。

扩展和改进

OpenMetrics 的第一个版本基于已建立且事实上的标准 Prometheus 文本格式 0.0.4,有意地没有在其之上添加主要的语法或语义扩展或优化。例如,没有尝试使 Histogram buckets 的文本表示更紧凑,而是依靠底层堆栈中的压缩来处理它们的重复性。

这是一个刻意的选择,以便该标准可以利用现有用户群的采用和势头。这确保了从 Prometheus 文本格式 0.0.4 进行相对容易的过渡。

它还确保存在一个易于实现的基本标准。这可以在标准的未来版本中进行构建。目的是标准的未来版本将始终需要支持此 1.0 版本,包括语法和语义方面。

我们希望允许监控系统从 OpenMetrics exposition 中获取可用的信息,而不会造成不必要的负担。如果有人剥离所有元数据和结构,而只是将 OpenMetrics exposition 视为一组无序的 samples,那么它应该可以独立使用。因此,也没有不透明的二进制类型,例如 sketches 或 t-digests,它们不能表示为 gauges 和 counters 的混合,因为它们需要自定义解析和处理。

此原则贯穿整个标准。例如,MetricFamily 的 unit 在名称中重复,以便 unit 可以用于不理解 unit 元数据的系统。“le” 标签是一个普通的标签值,而不是拥有自己的特殊语法,这样 ingestors 就不必添加特殊的 histogram 处理代码来 ingest 它们。再举一个例子,没有复合数据类型。例如,没有用于纬度/经度的地理位置类型,因为这可以使用单独的 gauge metrics 来完成。

Units 和 Base Units

为了跨系统的一致性并避免混淆,units 主要基于 SI base units。Base units 包括秒、字节、焦耳、克、米、比率、伏特、安培和摄氏度。应在适用的地方提供 Units。

例如,将所有 duration metrics 都设置为秒,就不会有猜测给定 metric 是纳秒、微秒、毫秒、秒、分钟、小时、天还是周,也不必处理混合 units 的风险。通过选择无前缀的 units,我们避免了诸如千毫秒是复杂系统涌现行为结果的情况。

由于值可以是浮点数,因此亚 base-unit 精度已内置于标准中。

同样,混合 bits 和 bytes 令人困惑,因此选择 bytes 作为 base。虽然开尔文在理论上是一个更好的 base unit,但在实践中,大多数现有硬件都暴露摄氏度。千克是 SI base unit,但是千克前缀存在问题,因此选择克作为 base unit。

虽然在所有可能的情况下都应使用 base units,但开尔文是一个成熟的 unit,在颜色或黑体温度等用例中,可以代替摄氏度使用,因为摄氏度和开尔文 metric 之间的比较不太可能发生。

比率是 base unit,而不是百分比。在可能的情况下,应暴露给定分子和分母的 gauges 或 counters 形式的原始数据。这对于 ingestors 中的分析和聚合具有更好的数学特性。

分贝不是 base unit,首先,分(deci)是一个 SI 前缀,其次,贝尔(bels)是对数单位。为了暴露信号/能量/功率比率,直接暴露比率会更好,或者如果可能的话,暴露原始功率/能量会更好。浮点指数足以覆盖即使是极端的科学用途。从电子伏特(~1e-19 J)到超新星释放的能量(~1e44 J)跨越 63 个数量级,而 64 位浮点数可以覆盖超过 2000 个数量级。

如果非 base units 无法避免且转换不可行,则为了清晰起见,实际 unit 仍应包含在 metric name 中。例如,焦耳是能量和功率的 base unit,因为瓦特可以用焦耳 unit 的 counter 表示。在实践中,给定的第三方系统可能只暴露瓦特,因此在这种情况下,以瓦特表示的 gauge 将是唯一现实的选择。

并非所有 MetricFamilies 都有 units。例如,HTTP 请求的计数没有 unit。从技术上讲,unit 将是 HTTP 请求,但在这种意义上,整个 MetricFamily name 就是 unit。走向极端是没有用的。应始终牢记在下游系统中为人类消费提供良好图表轴的可能性。

Statelessness

OpenMetrics 定义的 wire 格式在 expositions 之间是无状态的。之前暴露的信息不得对未来的 expositions 产生任何影响。每个 exposition 都是 exposer 当前状态的独立快照。

必须向现有和新的 ingestors 提供相同的独立 exposition。

核心设计选择是 exposers 绝不能仅仅因为某个 metric 最近没有变化或观测值而排除它。Exposer 不得对 ingestors 消耗 expositions 的频率做出任何假设。

Exposition 跨时间和 Metric 演变

当 metrics 的随时间演变可以分析时,metrics 最有用,因此,expositions 必须随着时间的推移而有意义。因此,仅仅一个单独的 exposition 本身有用且有效是不够的。Metric 语义的一些更改也可能破坏下游用户。

Parsers 通常通过缓存以前的结果来优化。因此,即使从技术上讲这不是破坏性的,也应避免更改标签在 expositions 之间的暴露顺序。这也往往使编写 exposition 的单元测试更容易。

Metrics 和 samples 不应在 exposition 之间出现和消失,例如,counter 只有在有历史记录的情况下才有用。原则上,给定的 Metric 应该从进程启动时一直存在于 exposition 中,直到进程终止。通常不可能预先知道给定的进程的生命周期内 MetricFamily 将拥有哪些 Metrics(例如,latency histogram 的标签值是 HTTP 路径,由最终用户在运行时提供),但是一旦暴露了类似 counter 的 Metric,它就应该继续暴露直到进程终止。Counter 没有递增并不意味着它仍然具有当前值是无效的。在某些情况下,停止暴露给定的 Metric 可能是有意义的;请参阅关于 Missing Data 的部分。

一般来说,更改 MetricFamily 的类型,或从其 Metrics 中添加或删除标签将对 ingestors 造成破坏。

一个值得注意的例外是,向 Info MetricPoints 的值添加标签不是破坏性的。这样做是为了您可以向现有的 Info MetricFamily 添加额外的信息,这样做是有意义的,而不是被迫创建一个带有附加标签值的新 info metric。Ingestor 系统应确保它们能够适应此类添加。

更改 MetricFamily 的 Help 不是破坏性的。对于可能的值,在 floats 和 ints 之间切换不是破坏性的。向 stateset 添加新状态不是破坏性的。在不更改 metric name 的情况下添加 unit 元数据不是破坏性的。

Histogram buckets 不应在 exposition 之间更改,因为这很可能导致性能问题并破坏 ingestors 和原因。同样,来自应用程序任何一致的二进制文件和环境的所有 expositions 都应该具有给定 Histogram MetricFamily 的相同 buckets,以便它们可以被所有 ingestors 聚合,而无需 ingestors 实现用于异构 buckets 的 histogram 合并逻辑。例外情况可能是对 buckets 进行偶尔的手动更改,这被认为是破坏性的,但当性能特征因新的软件版本而发生变化时,这可能是一个有效的权衡。

即使更改在技术上不是破坏性的,它们仍然会带来成本。例如,频繁的更改可能会导致 ingestors 的性能问题。随 exposition 变化的 Help 字符串可能会导致每个 Help 值都被存储。频繁地在 int 和 float 值之间切换可能会阻止有效的压缩。

NaN

NaN 在 OpenMetrics 中像任何其他数字一样,通常是除以零的结果,例如,如果最近没有观测值,则 summary quantile 的结果。NaN 在 OpenMetrics 中没有任何特殊含义,尤其不能用作丢失数据或其他错误数据的标记。

Missing Data

在某些有效的情况下,数据会停止存在。例如,文件系统可以被卸载,因此其用于可用磁盘空间的 Gauge Metric 不再存在。对于这种情况,没有特殊的标记或信号。后续的 expositions 只是不包含此 Metric。

Exposition 性能

Metrics 只有在可以在合理的时间范围内收集时才有用。需要几分钟才能暴露的 metrics 被认为是没有用的。

根据经验,exposition 应该不超过一秒钟。

通过 OpenMetrics 序列化的遗留系统的 metrics 可能需要更长的时间。因此,不能做出硬性的性能假设。

Exposition 应该是最新状态的。例如,服务 exposition 请求的线程不应依赖缓存的值,以便它可以绕过任何此类缓存

并发

对于高可用性和 ad-hoc 访问,一种常见的方法是拥有多个 ingestors。为了支持这一点,必须支持并发 expositions。所有并发系统的 BCPs 都应遵循,常见的陷阱包括死锁、竞争条件和过度粗粒度的锁定,从而阻止 expositions 并发进行。

Metric 命名和命名空间

我们的目标是在 metrics 和 label names 的命名中,在可理解性、避免冲突和简洁性之间取得平衡。名称通过下划线分隔,因此 metric names 最终采用 “snake_case” 格式。

以 “httprequestseconds” 为例,它很简洁,但在大量应用程序之间会发生冲突,并且也不清楚这个 metric 到底在测量什么。例如,在复杂的系统中,它可能是在 auth middleware 之前或之后。

Metric names 应该指示它们来自哪段代码。因此,一家名为 A Company Manufacturing Everything 的公司可能会在其代码中所有 metrics 前面加上 “acme_” 前缀,如果他们有一个测量延迟的 HTTP 路由器库,它可能有一个 metric,例如 “acmehttprouter_request_seconds”,并带有 Help 字符串,指示它是总体延迟。

其目的不是防止所有应用程序之间所有潜在的冲突,因为这将需要强硬的解决方案,例如 metric 命名空间的全局注册表或基于 DNS 的非常长的命名空间。相反,目的是保持轻量级的非正式方法,以便对于给定的应用程序,在其组成库之间发生冲突的可能性很小。

在整个监控系统的给定部署中,目的是相同的 metric name 意味着不同事物的情况并不常见。例如,acmehttprouterrequestseconds 最终可能会出现在 A Company Manufacturing Everything 开发的数百个不同的应用程序中,这是正常的。如果 Another Corporation Making Entities 也在他们的 HTTP 路由器中使用 metric name acmehttprouterrequestseconds,那也没关系。如果来自两家公司的应用程序都由同一监控系统监控,则冲突是不希望的,但可以接受,因为没有应用程序试图暴露两个名称,也没有任何 target 试图(错误地)暴露相同的 metric name 两次。如果一个应用程序希望同时包含 My Example Company 和 Mega Exciting Company 的 HTTP 路由器库,那将是一个问题,并且其中一个 metric name 将需要以某种方式更改。

作为推论,库的公共性越高,其 metric names 的命名空间就应该越好,以降低此类情况发生的风险。acme_ 对于公司内部使用来说不是一个糟糕的选择,但这些公司可能会为在公司外部共享的代码选择前缀 acmeverything_ 或 acorpme_。

在按公司或组织进行命名空间划分之后,命名空间划分和命名应根据需要以分形方式继续按库/子系统/应用程序进行,例如上面的 http_router 库。目标是,如果您熟悉代码库的总体结构,则可以根据 metric name 很好地猜测给定 metric 的 instrumentation 所在的位置。

对于一个常见的、非常著名的现有软件,软件本身的名称可能就足以区分。例如,bind_ 对于 DNS 软件来说可能就足够了,即使 iscbind 会是更常用的命名。

以 scrape_ 为前缀的 Metric names 由 ingestors 用于附加与各个 expositions 相关的信息,因此不应由应用程序直接暴露。已经消耗并传递到通用监控系统的 metrics 可能在后续的 expositions 中包含此类 metric names。如果 exposer 希望提供有关单个 exposition 的信息,则可以使用 metric 前缀,例如 myexposerscrape。一个常见的例子是 gauge myexposerscrapeduration_seconds,用于表示从 exposer 的角度来看,该 exposition 花费了多长时间。

在 Prometheus 生态系统中,已经出现了一组跨所有实现保持一致的 per-process metrics,前缀为 process_。例如,对于打开的文件 ulimits,MetricFamiles processopenfds 和 processmaxfds gauges 提供了当前值和最大值。(这些名称是遗留名称,如果今天定义此类 metrics,它们更可能被称为 processfdsopen 和 processfds_limit)。一般来说,获得像这样具有相同语义的名称非常具有挑战性,这就是为什么不同的 instrumentation 应该使用不同的名称。

避免 metric names 中的冗余。避免像 “metric”、“timer”、“stats”、“counter”、“total”、“float64” 等子字符串 - 由于是通过 OpenMetrics 暴露的具有给定类型(和可能的 unit)的 metric,因此此类信息已被暗示,因此不应显式包含。出于相同的原因,您不应在 metric name 中包含 metric 的 label names,此外,监控系统对 metric 的后续聚合可能会使此类信息不正确。

避免在 instrumentation 中包含的 metric names 中包含来自监控系统其他层的实现细节。例如,MetricFamily name 不应包含字符串 “openmetrics”,仅仅因为它恰好当前通过 OpenMetrics 在某处暴露,或者不应包含 “prometheus”,仅仅因为您当前的监控系统是 Prometheus。

Label Namespacing

对于 label names,不建议按公司或库进行显式命名空间划分,当考虑到 label name 的长度增加时,从 metric name 进行命名空间划分就足够了。但是,建议稍微注意避免常见的冲突。

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

Label name “type” 是高度通用的,应避免使用。例如,对于 HTTP 相关 metrics,如果您要区分 GET、POST 和 PUT 请求,“method” 将是一个更好的 label name。

虽然存在关于 metric names 的元数据,例如 HELP、TYPE 和 UNIT,但 label names 没有元数据。这是因为这样做会使格式臃肿,而收益甚微。带外文档是 exposers 可以向其 ingestors 提供此信息的一种方式。

Metric Names 与 Labels

在某些情况下,在 MetricFamily 中使用多个 Metrics 或多个 MetricFamilies 似乎都是有意义的。即使并非总是很有用,对 aMetricFamily 求和或求平均值也应该是有意义的。例如,混合电压和风扇速度是没有意义的。

提醒一下,OpenMetrics 的构建假设 ingestors 可以处理数据并对其执行聚合。

将总和与其他 metrics 一起暴露是错误的,因为这将导致下游 ingestors 在聚合时重复计数。

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

Metric 的 labels 应尽可能少,以确保唯一性,因为每个额外的 label 都是用户在确定下游要使用的 Labels 时需要考虑的更多因素。可以应用于许多 MetricFamilies 的 Labels 是移动到类似于数据库 {{normalization}} 的 _info metrics 的候选对象。如果实际上 Metric 的所有用户都希望使用额外的 label,那么将其添加到所有 MetricFamilies 可能是更好的权衡。例如,如果您有一个与不同 SQL 语句相关的 MetricFamily,其中唯一性由包含完整 SQL 语句哈希值的 label 提供,那么拥有另一个带有 SQL 语句前 500 个字符的 label 以提高人类可读性是可以的。

经验表明,下游 ingestors 发现使用单独的总计和失败 MetricFamiles 比在一个 MetricFamily 中使用 {result="success"} 和 {result="failure"} Labels 更容易。此外,通常最好暴露单独的读写和发送接收 MetricFamiles,因为全双工系统很常见,并且下游 ingestors 更可能关心这些值,而不是聚合值。

所有这些都不像听起来那么容易。这是一个需要 exposition 和暴露系统领域的特定领域专家进行经验和工程权衡的领域,以找到良好的平衡。Metric 和 Label Name 字符

OpenMetrics 构建于现有的、广泛采用的 Prometheus 文本 exposition 格式以及围绕它形成的生态系统之上。向后兼容性是核心设计目标。扩展或收缩 Prometheus 文本格式支持的字符集将不利于该目标。破坏向后兼容性将具有比 wire 格式更广泛的意义。特别是,为处理 Prometheus 生态系统中传输的数据而创建或采用的查询语言依赖于这些精确的字符集。Label values 支持完整的 UTF-8,因此该格式可以表示多语言 metrics。

Metadata 的类型

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

“Target metadata” 通常是 exposer 外部的 metadata。常见的例子是来自服务发现、CMDB 或类似的数据,例如关于数据中心区域的信息,服务是否是特定部署的一部分,还是生产或测试环境。这可以通过 exposer 或 ingestor 向捕获此 metadata 的所有 Metrics 添加 labels 来实现。通过 ingestor 执行此操作是首选的,因为它更灵活且开销更少。在灵活性方面,硬件维护团队可能关心机器所在的服务器机架,而使用同一机器的数据库团队可能关心它包含生产数据库的副本号 2。在开销方面,硬编码或配置此信息需要额外的分发路径。

“Exposer metadata” 来自 exposer 内部。常见的例子是软件版本、编译器版本或 Git commit SHA。

在基于 Push 和基于 Pull 的系统中支持 Target Metadata

在基于 push 的消费中,exposer 通常向 ingestor 提供相关的 target metadata。在基于 pull 的消费中,可以采用基于 push 的方法,但更常见的情况是 ingestor 已经预先知道 target 的 metadata,例如来自机器数据库或服务发现系统,并将其与 metrics 关联,因为它消耗了 exposition。

OpenMetrics 是无状态的,并向所有 ingestors 提供相同的 exposition,这与 push 风格的方法相冲突。此外,push 风格的方法会破坏 pull 风格的 ingestors,因为会暴露不需要的 metadata。

一种方法是让 push 风格的 ingestors 基于操作员的带外配置提供 target metadata,例如作为 HTTP 标头。虽然这将为 push 风格的 ingestors 传输 target metadata,并且本标准并未排除这种做法,但它的缺点是,即使 pull 风格的 ingestors 应该使用自己的 target metadata,但仍然经常需要访问 exposer 本身知道的 metadata。

首选的解决方案是将此 target metadata 作为 exposition 的一部分提供,但以不影响整个 exposition 的方式提供。Info MetricFamilies 就是为此而设计的。Exposer 可以包含一个名为 “target” 的 Info MetricFamily,其中包含一个没有 labels 的 Metric 和 metadata。文本格式的示例如下

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

当 exposer 为此目的提供此 metric 时,它应该在 exposition 中排在第一位。这是为了提高效率,以便依赖它获取 target metadata 的 ingestors 不必缓冲 exposition 的其余部分,然后才能应用基于其内容的业务逻辑。

Exposers 不得将 target metadata labels 添加到来自 exposition 的所有 Metrics,除非为特定的 ingestor 显式配置。Exposers 不得根据 target metadata 前缀 MetricFamily names 或以其他方式更改 MetricFamily names。一般来说,相同的 Label 不应出现在 exposition 的每个 Metric 上,但在极少数情况下,这可能是涌现行为的结果。同样,来自 exposer 的所有 MetricFamily names 可能恰好在非常小的 expositions 中共享一个前缀。例如,由 A Company Manufacturing Everything 用 Go 语言编写的应用程序很可能包含带有 acme_、go_、process_ 前缀的 metrics,以及来自任何正在使用的第三方库的 metric 前缀。

Exposers 可以将 exposer metadata 作为 Info MetricFamilies 暴露。

以上讨论是在单个 exposers 的背景下进行的。来自通用监控系统的 exposition 可能包含来自多个单独 targets 的 metrics,因此可能暴露多个 target info Metrics。这些 metrics 可能已经在 ingestion 过程中将 target metadata 作为 labels 添加到其中。Metric names 不得根据 target metadata 而变化。例如,即使所有 metrics 都源自 staging 环境中的 targets,所有 metrics 最终都以 staging_ 为前缀也是不正确的)。

客户端计算和派生 Metrics

Exposers 应该将任何数学或计算留给 ingestors 处理。一个值得注意的例外是 Summary quantile,不幸的是,为了向后兼容性而需要它。Exposition 应该是原始值,这些原始值在任意时间段内都很有用。

例如,您不应该公开一个 gauge,其中包含 counter 在过去 5 分钟内的平均增长率。让 ingestor 计算它们在 expositions 中消耗的数据点上的增长量,具有更好的数学特性,并且更能抵抗抓取失败。

另一个例子是 histogram/summary 的平均事件大小。公开自应用程序启动或 Metric 创建以来 counter 的平均增长率,会产生与前面示例相同的问题,并且还会阻止聚合。

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

另一个例子是避免公开请求失败率,而是公开单独的 counter 用于记录失败的请求和总请求。

数字类型

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

Summary quantile 必须是 float64,因为它们是估计值,因此从根本上是不准确的。

公开时间戳

OpenMetrics 的核心假设之一是 exposers 公开他们正在公开内容的最新快照。

虽然将时间戳附加到公开数据有一些有限的用例,但这些用例非常罕见。以前附加了时间戳的数据,特别是已摄取到通用监控系统中的数据,可能会携带时间戳。实时或原始数据不应携带时间戳。跨 expositions 公开具有相同时间戳的相同 metric MetricPoint 值是有效的,但是如果底层 metric 现在丢失,则这样做是无效的。

时间同步是一个难题,数据在每个系统中都应该保持内部一致性。因此,ingestors 应该能够将他们角度的当前时间戳附加到数据,而不是基于 exposer 设备上的系统时间。

对于带有时间戳的 metrics,通常无法检测到 Metric 在跨 expositions 时何时丢失。但是,对于不带时间戳的 metrics,ingestor 可以使用来自 Metric 不再存在的 exposition 的自身时间戳。

所有这一切都是为了说明,总的来说,不应公开 MetricPoint 时间戳,因为应该由 ingestor 将他们自己的时间戳应用于他们摄取的样本。

跟踪 Metrics 上次更改时间

假设您有一个 counter my_counter,它被初始化,然后在时间 123 被递增 1。这将是在文本格式中公开它的正确方法

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

按照父章节的规定,ingestors 应该可以自由地附加他们自己的时间戳,因此这将是不正确的

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

如果 counter 上次更改的具体时间很重要,这将是正确的方法

# 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

通过将上次更改的时间戳放入其自身的 Gauge 作为值,ingestors 可以自由地将他们自己的时间戳附加到两个 Metrics。

经验表明,公开绝对时间戳(epoch 在这里被认为是绝对的)比经过的时间、自那时起的秒数或类似的更可靠。在任何一种情况下,它们都将是 gauges。例如

# 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

相反,对于 exemplars 时间戳没有最佳实践限制。请记住,由于竞争条件或设备之间的时间并非完美同步,因此 exemplar 时间戳可能看起来略微领先于 ingestor 的系统时钟或来自同一 exposition 的其他 metrics。同样,MetricPoint 的 "_created" 可能看起来略微晚于同一 MetricPoint 的 exemplar 或 sample 时间戳。

请记住,常见的监控系统中使用的系统支持从纳秒到秒的分辨率,因此当截断到秒分辨率时,具有相同时间戳的两个 MetricPoints 可能会导致 ingestor 中出现明显的重复项。在这种情况下,必须使用时间戳最早的 MetricPoint。

阈值

公开系统的期望边界可能是有意义的,但需要适当注意。对于普遍为真的值,为这些阈值发出 Gauge metrics 可能是有意义的。例如,数据中心 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

大小限制

此标准未规定对单个 exposition 公开的样本数量、可能存在的标签数量、stateset 可能具有的状态数量、info 值中的标签数量或 metric 名称/标签名称/标签值/help 字符限制的任何特定限制。

特定限制有阻止合理用例的风险,例如,虽然给定的 exposition 在通过通用监控系统后可能具有适当数量的标签,但可能添加了一些目标标签,这将使其超出限制。对此类数字的特定限制也不会捕捉到通用监控系统的真正成本所在。因此,这些指南旨在帮助 exposers 和 ingestors 理解什么是合理的。

另一方面,在某些维度上过大的 exposition 可能会导致显着的性能问题,相比于公开的 metrics 的好处。因此,关于任何单个 exposition 大小的一些指南将是有用的。

ingestors 可以选择自行施加限制,特别是为了防止攻击或中断。尽管如此,ingestors 需要考虑合理的用例,并尽量避免对其产生过度的影响。如果任何单个值/metric/exposition 超过这些限制,则必须拒绝整个 exposition。

一般来说,有三件事会影响通用监控系统摄取时间序列数据的性能:唯一时间序列的数量、这些序列中随时间的样本数量,以及唯一字符串的数量,例如 metric 名称、标签名称、标签值和 HELP。ingestors 可以控制他们摄取的频率,因此这方面不需要进一步考虑。

唯一时间序列的数量大致相当于文本格式中非注释行的数量。截至 2020 年,总共 1000 万个时间序列被认为是一个很大的数量,并且通常是任何单实例 ingestor 上限的数量级。任何单个 exposition 不应超过 1 万个时间序列,除非经过适当的尽职调查。一个常见的考虑因素是水平扩展:如果您将实例计数扩展 1-2 个数量级会发生什么?在 30 年前,在一个部署中拥有数千个顶架式交换机是难以想象的。如果目标是单例的(例如,公开与整个集群相关的 metrics),那么几十万个时间序列可能是合理的。重要的不是 MetricFamilies 的数量或单个标签/buckets/statesets 的基数,而是时间序列的总数量级。1,000 个每个 Metric 有一个 gauge 与一个有 1,000 个 Metrics 的 gauge 的成本相同。

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

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

安全

实现者可以选择提供身份验证、授权和计费;如果他们选择这样做,则应该在 OpenMetrics 之外处理。

所有 exposer 实现都应能够使用 TLS 1.2 或更高版本来保护其 HTTP 流量。如果 exposer 实现不支持加密,则运营商应在可行的情况下使用反向代理、防火墙和/或 ACL。

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

IANA

虽然目前 Prometheus exposition 格式的大多数实现都在使用来自 {{PrometheusPorts}} 的非 IANA 注册端口的非正式注册表,但 OpenMetrics 可以在明确定义的端口上找到。

IANA 为客户端公开数据分配的端口是 <9099 requested for historical consistency>。

如果需要在公共 IP 地址和端口上访问多个 metric 端点,运营商可以考虑使用反向代理,通过 localhost 地址与 exposers 通信。为了简化多路复用,端点应在其路径中携带自己的名称,例如 /node_exporter/metrics。Expositions 不应合并为一个 exposition,原因在“在基于推送和基于拉取的系统中支持目标元数据”下以及为了允许独立摄取而没有单点故障。

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

本文档是开源的。请通过提交问题或拉取请求来帮助改进它。