metrics 包的全局指标(UpstreamTotal 等)默认是 nil,只有 main 调过 metrics.Init() 之后才有值。任何不经过 main 的调用路径都会在 controller/tts.go -> adapter/volcano/synthesis.go -> metrics.AdapterRecorder 处 nil 解引用,整个进程崩掉。 生产路径本来没问题(二进制一定先 Init),但这类崩溃只在运行时暴露,且漏掉指标初始化属于很容易踩的装配错误,不该以 panic 收场。 改动: Counter.Add / Gauge.Set / Gauge.Add / Histogram.Observe 增加空接收者判断,nil 时静默忽略(与 noop 语义一致);在包注释里写成显式设计约定,避免以后被当成冗余代码删掉。
119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
package telemetry
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// DefaultLatencyBuckets 适合 HTTP/TTS 场景的默认桶(秒)。
|
|
var DefaultLatencyBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30}
|
|
|
|
// Histogram 累计分布型指标,记录观测值的分布。
|
|
//
|
|
// 内部为每个 child 维护:
|
|
// - buckets[i] 累计计数(<= le_i 的观测数,不含 +Inf 桶)
|
|
// - count 全部观测计数
|
|
// - sum 全部观测值之和
|
|
type Histogram struct {
|
|
metricName string
|
|
help string
|
|
labelNames []string
|
|
buckets []float64 // 用户声明的上界,不含 +Inf
|
|
|
|
mu sync.RWMutex
|
|
values map[string]*histChild
|
|
}
|
|
|
|
type histChild struct {
|
|
labels Labels
|
|
buckets []atomic.Uint64 // 累计计数
|
|
count atomic.Uint64
|
|
sumBits atomic.Uint64 // float64
|
|
}
|
|
|
|
func newHistogram(name, help string, buckets []float64, labelNames []string) *Histogram {
|
|
bs := append([]float64(nil), buckets...)
|
|
sort.Float64s(bs)
|
|
return &Histogram{
|
|
metricName: name,
|
|
help: help,
|
|
labelNames: append([]string(nil), labelNames...),
|
|
buckets: bs,
|
|
values: make(map[string]*histChild),
|
|
}
|
|
}
|
|
|
|
// Observe 记录一个观测值。
|
|
// 空接收者(nil *Histogram)安全,见 Counter.Add 的说明。
|
|
func (h *Histogram) Observe(v float64, labels Labels) {
|
|
if h == nil {
|
|
return
|
|
}
|
|
child := h.getOrCreate(labels)
|
|
for {
|
|
bits := child.sumBits.Load()
|
|
cur := float64frombits(bits)
|
|
next := float64bits(cur + v)
|
|
if child.sumBits.CompareAndSwap(bits, next) {
|
|
break
|
|
}
|
|
}
|
|
child.count.Add(1)
|
|
for i, le := range h.buckets {
|
|
if v <= le {
|
|
child.buckets[i].Add(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Histogram) getOrCreate(labels Labels) *histChild {
|
|
key := labelKey(h.labelNames, labels)
|
|
h.mu.RLock()
|
|
if c, ok := h.values[key]; ok {
|
|
h.mu.RUnlock()
|
|
return c
|
|
}
|
|
h.mu.RUnlock()
|
|
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if c, ok := h.values[key]; ok {
|
|
return c
|
|
}
|
|
c := &histChild{
|
|
labels: copyLabels(labels, h.labelNames),
|
|
buckets: make([]atomic.Uint64, len(h.buckets)),
|
|
}
|
|
h.values[key] = c
|
|
return c
|
|
}
|
|
|
|
func (h *Histogram) collect(w io.Writer) {
|
|
fmt.Fprintf(w, "# HELP %s %s\n", h.metricName, h.help)
|
|
fmt.Fprintf(w, "# TYPE %s histogram\n", h.metricName)
|
|
|
|
h.mu.RLock()
|
|
keys := make([]string, 0, len(h.values))
|
|
for k := range h.values {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
defer h.mu.RUnlock()
|
|
|
|
for _, k := range keys {
|
|
child := h.values[k]
|
|
for i, le := range h.buckets {
|
|
merged := mergeLabels(child.labels, Labels{"le": formatFloat(le)})
|
|
fmt.Fprintf(w, "%s_bucket%s %d\n", h.metricName, formatLabels(merged), child.buckets[i].Load())
|
|
}
|
|
merged := mergeLabels(child.labels, Labels{"le": "+Inf"})
|
|
fmt.Fprintf(w, "%s_bucket%s %d\n", h.metricName, formatLabels(merged), child.count.Load())
|
|
sum := float64frombits(child.sumBits.Load())
|
|
fmt.Fprintf(w, "%s_sum%s %s\n", h.metricName, formatLabels(child.labels), formatFloat(sum))
|
|
fmt.Fprintf(w, "%s_count%s %d\n", h.metricName, formatLabels(child.labels), child.count.Load())
|
|
}
|
|
}
|