Counter是单调递增的累计指标,仅可增加或重启时归零,适用于请求总数等;Gauge是可增可减的瞬时值指标,反映当前状态,如活跃连接数或内存使用量。

怎么用 cpp-prometheus 定义 Counter 和 Gauge
cpp-prometheus 是 C++ 生态中最常用的 Prometheus 客户端,它不依赖 Boost,轻量且线程安全。Counter 适合累计值(如请求总数),Gauge 适合可增可减的瞬时值(如当前活跃连接数)。关键点是:必须在全局或静态生命周期内创建指标对象,不能在函数栈上临时构造。
常见错误是把 prometheus::Counter 或 prometheus::Gauge 声明在局部作用域里,导致对象析构后指标注册失效,Prometheus 抓取时返回空或报错 no metrics found。
- 使用
prometheus::Registry注册指标,通常一个进程只需一个全局 registry - 通过
registry.Add()添加指标,返回智能指针(std::shared_ptr),确保生命周期可控 - Counter 调用
.Increment(),Gauge 支持.Set()、.Increment()、.Decrement() - 避免在多线程中对同一 Gauge 频繁
.Set()—— 内部有原子操作但高并发下仍有开销,不如先算好再设一次
// 全局 registry 和指标
auto registry = std::make_shared<:registry>();
auto http_requests_total = registry->Add<:counter>(
prometheus::Counter::Builder().Name("http_requests_total").Help("Total HTTP requests.").Register());
auto active_connections = registry->Add<:gauge>(
prometheus::Gauge::Builder().Name("active_connections").Help("Current active connections.").Register());
// 在 handler 中
http_requests_total->Increment();
active_connections->Set(current_conn_count);
</:gauge></:counter></:registry>
怎么给 Histogram 加 label 并正确观测延迟
Histogram 不是直接记录原始值,而是按预设 bucket 划分区间并计数。C++ 客户端默认 bucket 是 {.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}(单位秒),如果业务延迟普遍在毫秒级,这些 bucket 会全部落在第一个桶里,导致直方图完全失真。
必须显式调用 .Bucket(...) 构造器重设 bucket,单位统一用秒(float),别错用毫秒整数。
- label 必须在构建时声明,例如
.Label("method"),之后观测时用.Observe(value, {{"method", "GET"}}) - 不要在循环里反复调用
.Observe()带不同 label 组合 —— 每种 label 组合都会生成独立时间序列,label 维度爆炸会拖垮 Prometheus 存储和查询 - 如果只有少量 method 类型(如 GET/POST),label 合理;如果 method 来自 URL path 参数,应改用通用 label(如
"endpoint")或聚合到"other"
auto request_latency_ms = registry->Add<:histogram>(
prometheus::Histogram::Builder()
.Name("http_request_duration_seconds")
.Help("HTTP request latency in seconds.")
.Label("method")
.Bucket(.001, .005, .01, .025, .05, .1) // 注意:单位是秒,对应 1ms–100ms
.Register());
// 正确:label 值固定、有限
request_latency_ms->Observe(elapsed_seconds, {{"method", "GET"}});
// 错误:path 动态拼接 → 产生无限 series
// request_latency_ms->Observe(elapsed_seconds, {{"path", req.path()}});
</:histogram>
怎么暴露指标 endpoint 并避免线程阻塞
cpp-prometheus 自带 prometheus::Exposer,但它默认用阻塞式 socket 实现 HTTP server,不支持 HTTPS、gzip、超时控制,更关键的是:若某个抓取请求卡住(如客户端网络异常),整个 exposer 线程会 hang 住,后续所有指标无法更新。
生产环境强烈建议把指标导出交给主 HTTP 框架(如 cpp-httplib、crow 或 nginx 反向代理),只用 cpp-prometheus 管理指标数据,不负责 HTTP 服务。
- 调用
registry->Collect()获取std::vector<:metricfamily>,再手动序列化为文本格式 - 序列化必须用
prometheus::TextSerializer,不能自己拼字符串 —— label 值含空格、引号、反斜杠时会破坏格式,Prometheus 解析失败报text format parsing error - 如果坚持用
Exposer,务必设置exposer.Start(port, "127.0.0.1")绑定本地回环,禁止公网暴露;并监控其线程状态(比如定期 check 是否 alive)
// 用 httplib 提供 /metrics endpoint
server.Get("/metrics", [&](const auto& req, auto& res) {
auto families = registry->Collect();
prometheus::TextSerializer serializer;
std::stringstream ss;
serializer.Serialize(families, ss);
res.set_content(ss.str(), "text/plain; version=0.0.4");
});
为什么 Counter 的值有时“倒退”或归零
Counter 语义上不应下降,但 C++ 客户端本身不做单调性校验。实际出现“倒退”基本只有两个原因:进程重启导致内存中 Counter 重置为 0;或者多个进程共用同一指标名但没加 instance label,Prometheus 做 federation 或 remote_write 时发生覆盖。
另一个隐蔽问题是:用 prometheus::Counter::Add(n) 传入负数 —— 编译能过,运行时不会报错,但违反 Counter 语义,下游计算 rate() 时会出现负速率,Grafana 图表断崖式下跌。
- 永远用
.Increment()或.Increment(1.0),避免手写.Add(-1) - 多实例部署时,在 registry 构建前,给所有指标加
.Label("instance"),值设为hostname:port或唯一 ID - 如果必须跨进程共享计数(如分布式任务完成数),Counter 不适用,该换用外部存储(Redis + Lua)或 Pushgateway(仅限批处理场景)
真正棘手的是 histogram 的 bucket 边界和 label 组合,这两处一旦上线就很难调整 —— Prometheus 不支持删除或 rename 指标,旧 series 会长期残留。上线前务必用真实流量压测,确认 bucket 分布合理、label cardinality 可控。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











