概念定义
仪器类型决定这份数据永远能回答什么问题。counter 支持“每秒多少”,gauge 支持“现在是多少”,histogram 支持“分布长什么样”——谁也答不了别人的问题。
工作机制
counter 暴露累计值,后端按时间窗求导;gauge 暴露瞬时值且不保证历史;histogram 把观测记进桶计数,后端跨实例合并桶并插值分位数。指数直方图按几何级数定桶界,用固定资源覆盖微秒到小时的全量程。
使用场景
事件与吞吐用 counter;状态尺寸与饱和用 gauge;一切按分位数分析的用 histogram;仅单实例分位数才用 summary——舰队里几乎从不是正确选择。
常见误区
用 gauge 记请求数后,重启复位会被速率计算读成负事件。延迟记成均值 gauge 会让分位数问题永久不可回答;类型换错对历史数据是永久的——histogram 从零开始。summary 的客户端分位数不能跨实例平均,histogram 桶才能正确合并。指数直方图优雅覆盖两端量程,但自定义 SLO 阈值可能骑在桶界上——告警阈值要对照桶边界验证。
代码示例
import promClient from 'prom-client';
import express from 'express';
const registry = new promClient.Registry();
promClient.collectDefaultMetrics({ register: registry, prefix: 'svc_' });
const ordersTotal = new promClient.Counter({
name: 'orders_created_total',
help: 'Total orders created',
labelNames: ['tenant', 'channel'],
registers: [registry],
});
const requestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Request latency distribution',
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5],
labelNames: ['route', 'method'],
registers: [registry],
});
async function handleCheckout(req, res) {
const end = requestDuration.startTimer({ route: '/checkout', method: 'POST' });
try {
ordersTotal.inc({ tenant: 'acme', channel: 'web' });
res.json({ ok: true });
} finally {
end();
}
}
const app = express();
app.post('/checkout', handleCheckout);
app.get('/metrics', async (req, res) => {
res.set('content-type', registry.contentType);
res.end(await registry.metrics());
});
app.listen(3000);
from collections import defaultdict
from typing import Iterable
def escape_label_value(value: str) -> str:
return value.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
def exposition(counters: dict[tuple[str, tuple[tuple[str, str], ...]], float]) -> str:
lines: list[str] = []
seen = set()
for (name, labels), value in sorted(counters.items()):
if name not in seen:
lines.append(f'# HELP {name} Application counter.')
lines.append(f'# TYPE {name} counter')
seen.add(name)
rendered = ','.join(f'{k}="{escape_label_value(v)}"' for k, v in labels)
lines.append(f'{name}{{{rendered}}} {value}')
return '\n'.join(lines) + '\n'
def record(counters, name: str, value: float, **labels: str) -> None:
key = (name, tuple(sorted(labels.items())))
counters[key] += value