You've already forked agentic-coding-workflow
109 lines
4.1 KiB
Go
109 lines
4.1 KiB
Go
// Package metrics 提供进程内的 Prometheus 指标注册中心与一组类型化的记录助手。
|
|
//
|
|
// 设计要点:
|
|
// - 用私有 *prometheus.Registry(NOT prometheus.DefaultRegisterer),避免在
|
|
// 热重载 / 测试中重复注册默认注册表导致 panic。
|
|
// - 通过 promhttp.HandlerFor(reg, ...) 暴露 Handler();调用方(router)负责
|
|
// 鉴权门禁(/metrics 泄露成本/会话数,必须 admin/内网受限)。
|
|
// - Record* 助手对外提供窄 API:其它模块只依赖这几个方法(或 acp 包内定义的
|
|
// 窄接口),无需 import prometheus。
|
|
//
|
|
// 指标清单(spec §11 §1205-1207):
|
|
// - acp_sessions_active GaugeFunc —— 当前活跃 ACP 会话数
|
|
// - acp_session_exit_total{status} CounterVec —— 会话退出计数(exited/crashed/…)
|
|
// - acp_permission_decisions_total{outcome} CounterVec —— 权限决策计数
|
|
// (auto/approved/denied/expired)
|
|
// - jobs_dead_letter_total{type} CounterVec —— 死信任务计数(按 job type)
|
|
// - llm_cost_usd_total GaugeFunc —— 累计 LLM 成本(USD)
|
|
package metrics
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
// Metrics 持有私有注册表与各 collector。零值不可用,必须经 New 构造。
|
|
type Metrics struct {
|
|
reg *prometheus.Registry
|
|
|
|
sessionExits *prometheus.CounterVec
|
|
permDecisions *prometheus.CounterVec
|
|
deadLetters *prometheus.CounterVec
|
|
}
|
|
|
|
// New 构造 Metrics。activeSessions / llmCostTotal 是供 GaugeFunc 回调的取值
|
|
// 函数;二者可为 nil(nil 时对应 gauge 恒为 0),便于测试 / feature-off。
|
|
func New(activeSessions func() float64, llmCostTotal func() float64) *Metrics {
|
|
reg := prometheus.NewRegistry()
|
|
|
|
m := &Metrics{
|
|
reg: reg,
|
|
sessionExits: prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "acp_session_exit_total",
|
|
Help: "Total number of ACP agent session exits, labeled by terminal status.",
|
|
}, []string{"status"}),
|
|
permDecisions: prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "acp_permission_decisions_total",
|
|
Help: "Total number of ACP tool-permission decisions, labeled by outcome.",
|
|
}, []string{"outcome"}),
|
|
deadLetters: prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "jobs_dead_letter_total",
|
|
Help: "Total number of jobs that exhausted retries and were dead-lettered, by job type.",
|
|
}, []string{"type"}),
|
|
}
|
|
|
|
reg.MustRegister(m.sessionExits, m.permDecisions, m.deadLetters)
|
|
|
|
if activeSessions == nil {
|
|
activeSessions = func() float64 { return 0 }
|
|
}
|
|
if llmCostTotal == nil {
|
|
llmCostTotal = func() float64 { return 0 }
|
|
}
|
|
reg.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
|
Name: "acp_sessions_active",
|
|
Help: "Current number of active (starting/running) ACP agent sessions.",
|
|
}, activeSessions))
|
|
reg.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
|
Name: "llm_cost_usd_total",
|
|
Help: "Cumulative LLM usage cost in USD across all endpoints.",
|
|
}, llmCostTotal))
|
|
|
|
return m
|
|
}
|
|
|
|
// Handler 返回 /metrics 的 http.Handler,绑定到本 Metrics 的私有注册表。
|
|
func (m *Metrics) Handler() http.Handler {
|
|
return promhttp.HandlerFor(m.reg, promhttp.HandlerOpts{})
|
|
}
|
|
|
|
// Registry 暴露底层注册表,便于测试直接 Gather。
|
|
func (m *Metrics) Registry() *prometheus.Registry { return m.reg }
|
|
|
|
// RecordSessionExit 记录一次会话退出。status 取 acp.SessionStatus 字符串值
|
|
// (exited/crashed/…)。
|
|
func (m *Metrics) RecordSessionExit(status string) {
|
|
if m == nil {
|
|
return
|
|
}
|
|
m.sessionExits.WithLabelValues(status).Inc()
|
|
}
|
|
|
|
// RecordPermissionDecision 记录一次权限决策。outcome 取 auto/approved/denied/expired。
|
|
func (m *Metrics) RecordPermissionDecision(outcome string) {
|
|
if m == nil {
|
|
return
|
|
}
|
|
m.permDecisions.WithLabelValues(outcome).Inc()
|
|
}
|
|
|
|
// RecordDeadLetter 记录一次死信任务,按 job type 标签累加。
|
|
func (m *Metrics) RecordDeadLetter(jobType string) {
|
|
if m == nil {
|
|
return
|
|
}
|
|
m.deadLetters.WithLabelValues(jobType).Inc()
|
|
}
|