请参与 Prometheus 用户调研(2026 年 3 月版) ,帮助社区确定未来开发工作的优先级!

编写客户端库

本文档涵盖了 Prometheus 客户端库应提供的功能和 API,旨在实现跨库的一致性,确保简单的用例易于实现,并避免提供可能将用户引入歧途的功能。

在编写本文档时,已有 10 种语言提供支持,因此我们对如何编写客户端有了很好的认识。这些指南旨在帮助新客户端库的作者编写出优秀的库。

约定

MUST(必须)、MUST NOT(不得)、SHOULD(应该)、SHOULD NOT(不应该)、MAY(可以)具有 https://www.ietf.org/rfc/rfc2119.txt  中给出的含义。

此外,ENCOURAGED(鼓励)意味着某个功能对库而言是理想的,但如果缺失也可以接受。换句话说,是一个“锦上添花”的功能。

注意事项

  • 利用好每种语言的特性。

  • 常见用例应该简单易行。

  • 正确的方法应该是最简单的方法。

  • 应该支持更复杂的用例。

常见用例(按顺序)包括:

  • 在库/应用程序中广泛使用无标签计数器 (Counter)。

  • 在摘要 (Summaries) 或直方图 (Histograms) 中对函数/代码块进行计时。

  • 使用仪表 (Gauges) 跟踪事物的当前状态(及其限制)。

  • 批量作业的监控。

总体结构

客户端内部必须基于回调编写。客户端通常应该遵循此处描述的结构。

关键类是 Collector(收集器)。它有一个方法(通常称为“collect”),返回零个或多个指标及其样本。收集器通过 CollectorRegistry(收集器注册表)进行注册。数据通过将 CollectorRegistry 传递给类/方法/函数“桥接器”来公开,该桥接器以 Prometheus 支持的格式返回指标。每次抓取 CollectorRegistry 时,它必须回调每个收集器的 collect 方法。

大多数用户交互的接口是 Counter、Gauge、Summary 和 Histogram 收集器。它们代表单个指标,应该覆盖用户检测自己代码时绝大多数的用例。

更高级的用例(例如从另一个监控/检测系统代理数据)需要编写自定义收集器。用户可能还需要编写一个“桥接器”,该桥接器获取 CollectorRegistry 并以另一个监控/检测系统理解的格式生成数据,从而允许用户只需考虑一种检测系统。

CollectorRegistry 应提供 register()/unregister() 函数,并且允许将一个收集器注册到多个 CollectorRegistry。

客户端库必须是线程安全的。

对于 C 等非面向对象语言,客户端库应尽可能遵循此结构的原则。

命名

客户端库应遵循本文档中提到的函数/方法/类名,同时考虑它们所使用的语言的命名约定。例如,set_to_current_time() 对于 Python 方法名来说是不错的,但 Go 中 SetToCurrentTime() 更好,而 Java 中 setToCurrentTime() 是惯例。如果由于技术原因(例如不支持函数重载)名称不同,文档/帮助字符串应将用户指向其他名称。

客户端库不得提供与此处给出的名称相同或相似,但语义不同的函数/方法/类。

指标

Counter、Gauge、Summary 和 Histogram 指标类型是用户使用的主要接口。

Counter 和 Gauge 必须包含在客户端库中。Summary 和 Histogram 中至少必须提供其中一个。

这些应该主要用作文件静态变量,即定义在与它们所检测的代码同一文件中的全局变量。客户端库应该启用此功能。常见的用例是检测一段代码的整体,而不是对象实例上下文中的一段代码。用户不应该担心在代码中贯穿传递他们的指标,客户端库应该为他们完成这些工作(如果库不提供,用户往往会围绕库编写包装器来使其“更简单”——但这通常不会带来好的结果)。

必须有一个默认的 CollectorRegistry,标准指标必须默认隐式注册到其中,无需用户进行任何特殊操作。必须有一种方式让指标不注册到默认的 CollectorRegistry,以便在批处理作业和单元测试中使用。自定义收集器也应遵循此原则。

创建指标的具体方式因语言而异。对于某些语言(Java、Go),构建器方法是最好的,而对于其他语言(Python),函数参数足够丰富,可以在一次调用中完成。

例如,在 Java Simpleclient 中:

class YourClass {
  static final Counter requests = Counter.build()
      .name("requests_total")
      .help("Requests.").register();
}

这将把请求注册到默认的 CollectorRegistry。通过调用 build() 而不是 register(),指标将不会被注册(方便单元测试),你也可以向 register() 传递一个 CollectorRegistry(方便批处理作业)。

Counter(计数器)

Counter 是一个单调递增的计数器。它不得允许值减少,但可以重置为 0(例如通过服务器重启)。

计数器必须具有以下方法:

  • inc():将计数器增加 1。
  • inc(double v):将计数器增加给定值。必须检查 v >= 0。

鼓励计数器具有:

一种计算给定代码块中抛出/引发异常的方法,并可选择仅计算特定类型的异常。这在 Python 中是 count_exceptions。

计数器必须从 0 开始。

Gauge(仪表盘)

Gauge 表示一个可以上下波动的数值。

仪表必须具有以下方法:

  • inc():将仪表增加 1。
  • inc(double v):将仪表增加给定值。
  • dec():将仪表减少 1。
  • dec(double v):将仪表减少给定值。
  • set(double v):将仪表设置为给定值。

仪表必须从 0 开始,你可以提供一种让给定仪表从不同数字开始的方法。

仪表应具有以下方法:

  • set_to_current_time():将仪表设置为当前的 Unix 时间(秒)。

鼓励仪表具有:

一种跟踪某段代码/函数中进行中请求的方法。这在 Python 中是 track_inprogress

一种对一段代码进行计时并将仪表设置为其持续时间(秒)的方法。这对于批处理作业很有用。这是 Java 中的 startTimer/setDuration,以及 Python 中的 time() 装饰器/上下文管理器。这应与 Summary/Histogram 的模式匹配(尽管是使用 set() 而不是 observe())。

总结

Summary 对观测值(通常是请求持续时间等)进行滑动时间窗口采样,并提供对其分布、频率和总和的实时洞察。

摘要不得允许用户将“quantile”设置为标签名称,因为它在内部用于指定摘要分位数。鼓励摘要提供分位数作为导出指标,尽管这些无法聚合且往往较慢。摘要必须允许没有分位数,因为仅使用 _count/_sum 就很有用,并且这必须是默认设置。

摘要必须具有以下方法:

  • observe(double v):观测给定的数值。

摘要应具有以下方法:

某种为用户以秒为单位对代码进行计时的方法。在 Python 中,这是 time() 装饰器/上下文管理器。在 Java 中,这是 startTimer/observeDuration。不得提供除秒以外的单位(如果用户想要其他单位,他们可以手动处理)。这应遵循与 Gauge/Histogram 相同的模式。

摘要的 _count/_sum 必须从 0 开始。

Histogram(直方图)

Histograms 允许事件的可聚合分布,例如请求延迟。其核心是每个桶 (bucket) 一个计数器。

直方图不得允许 le 作为用户设置的标签,因为 le 在内部用于指定桶。

直方图必须提供手动选择桶的方法。应提供以 linear(start, width, count)exponential(start, factor, count) 方式设置桶的方法。Count 必须包含 +Inf 桶。

直方图应具有与其他客户端库相同的默认桶。指标创建后,桶不得更改。

直方图必须具有以下方法:

  • observe(double v):观测给定的数值。

直方图应具有以下方法:

某种为用户以秒为单位对代码进行计时的方法。在 Python 中,这是 time() 装饰器/上下文管理器。在 Java 中,这是 startTimer/observeDuration。不得提供除秒以外的单位(如果用户想要其他单位,他们可以手动处理)。这应遵循与 Gauge/Summary 相同的模式。

直方图的 _count/_sum 以及桶必须从 0 开始。

进一步的指标考虑

鼓励在指标中提供超越上述文档的其他功能,只要这些功能对特定语言有意义即可。

如果有一个常见的用例你可以使其变得更简单,那就去做吧,只要它不会鼓励不良行为(例如次优的指标/标签布局,或在客户端进行计算)。

标签

标签是 Prometheus 最强大的特性之一,但也很容易被滥用。因此,客户端库在向用户提供标签时必须非常谨慎。

对于 Gauge/Counter/Summary/Histogram 或库提供的任何其他收集器,客户端库不应允许用户为同一个指标设置不同的标签名称。

来自自定义收集器的指标几乎总是应该具有一致的标签名称。由于在极少数情况下存在有效但非一致的用例,因此客户端库不应验证这一点。

虽然标签很强大,但大多数指标不会有标签。因此,API 应该允许标签,但不能被标签主导。

客户端库必须允许在 Gauge/Counter/Summary/Histogram 创建时可选地指定标签名称列表。客户端库应支持任意数量的标签名称。客户端库必须验证标签名称是否符合记录的要求

提供对指标的标签维度进行访问的通用方法是 labels() 方法,该方法接收标签值列表或从标签名称到标签值的映射,并返回一个“Child”。然后可以在 Child 上调用通常的 .inc()/.dec()/.observe() 等方法。

labels() 返回的 Child 应由用户缓存,以避免再次查找——这在延迟敏感的代码中很重要。

带标签的指标应支持与 labels() 签名相同的 remove() 方法,这将从指标中删除一个 Child,使其不再被导出;并支持 clear() 方法,该方法删除指标中的所有 Child。这些操作会使 Child 的缓存失效。

应该有一种方法可以用默认值初始化给定的 Child,通常只需调用 labels() 即可。无标签的指标必须始终初始化,以避免缺少指标的问题

指标名称

指标名称必须遵循规范。与标签名称一样,这对于 Gauge/Counter/Summary/Histogram 的使用以及库提供的任何其他收集器中的使用必须得到满足。

许多客户端库提供分三部分设置名称:namespace_subsystem_name,其中只有 name 是强制性的。

应阻止使用动态/生成的指标名称或指标名称的子部分,除非是自定义收集器从其他检测/监控系统进行代理时。生成的/动态的指标名称是一个信号,表明你应该改用标签。

指标描述与帮助

Gauge/Counter/Summary/Histogram 必须要求提供指标描述/帮助。

随客户端库提供的任何自定义收集器必须对其指标具有描述/帮助。

建议将其设为强制参数,但不建议检查其长度,因为如果有人真的不想写文档,我们也无法说服他们。库提供的收集器(实际上是生态系统中任何可能的地方)都应具有良好的指标描述,以起到示范作用。

发布 (Exposition)

客户端必须实现发布格式文档中概述的基于文本的发布格式。

如果可以在没有显著资源成本的情况下实现,鼓励发布指标的顺序具有可重现性(特别是对于人类可读的格式)。

标准收集器与运行时收集器

客户端库应尽可能提供如下所述的标准导出。

这些应实现为自定义收集器,并在默认的 CollectorRegistry 上默认注册。应该有一种方式可以禁用它们,因为在某些非常小众的用例中,它们会造成阻碍。

进程指标

这些指标具有前缀 process_。如果在使用语言或运行时的情况下获取必要的值存在困难,甚至是不可能的,客户端库应宁愿省略相应的指标,也不要导出虚假、不准确或特殊的值(如 NaN)。所有内存值单位均为字节,所有时间单位均为 Unix 时间/秒。

指标名称帮助字符串单位
process_cpu_seconds_total用户和系统 CPU 总时间消耗(秒)。
process_open_fds打开的文件描述符数量。文件描述符
process_max_fds打开的文件描述符最大数量。文件描述符
process_virtual_memory_bytes虚拟内存大小(字节)。字节
process_virtual_memory_max_bytes可用的最大虚拟内存量(字节)。字节
process_resident_memory_bytes驻留内存大小(字节)。字节
process_heap_bytes进程堆大小(字节)。字节
process_start_time_seconds进程自 Unix 纪元以来的启动时间(秒)。
process_threads进程中的操作系统线程数。线程

运行时指标

此外,鼓励客户端库还提供其语言运行时指标中任何有意义的内容(例如垃圾回收统计),并带有适当的前缀,如 go_hotspot_ 等。

单元测试

客户端库应具有覆盖核心检测库和发布的单元测试。

鼓励客户端库提供方便用户对其检测代码进行单元测试的方法。例如,Python 中的 CollectorRegistry.get_sample_value

打包与依赖

理想情况下,客户端库可以包含在任何应用程序中以添加一些检测,而不会破坏应用程序。

因此,在向客户端库添加依赖项时需要谨慎。例如,如果你添加了一个使用 Prometheus 客户端的库,它需要 x.y 版本的库,但应用程序在其他地方使用了 x.z 版本,这会对应用程序产生不利影响吗?

建议在可能出现这种情况的地方,将核心检测与以特定格式发布指标的桥接器分开。例如,Java simpleclient 的 simpleclient 模块没有依赖项,而 simpleclient_servlet 包含 HTTP 相关部分。

性能考虑

由于客户端库必须是线程安全的,因此需要某种形式的并发控制,并且必须考虑多核机器和应用程序的性能。

根据我们的经验,性能最差的是互斥锁 (mutexes)。

处理器原子指令处于中等水平,通常是可以接受的。

避免不同 CPU 修改同一段内存的方法效果最好,例如 Java simpleclient 中的 DoubleAdder。不过这会有内存成本。

如上所述,labels() 的结果应该是可缓存的。往往作为标签指标后端的并发映射通常相对较慢。对无标签指标进行特殊处理以避免 labels() 类似的查找会有很大帮助。

指标在进行增/减/设置等操作时应避免阻塞,因为在抓取过程中整个应用程序被挂起是不可取的。

鼓励对主要检测操作(包括标签)进行基准测试。

执行发布时,应考虑到资源消耗,特别是 RAM。考虑通过流式传输结果来减少内存占用,并可能限制并发抓取的数量。

本页内容