Files
nvllama-watchdog/parsers/prometheus.go
2026-10-11 13:29:19 +02:00

202 lines
4.9 KiB
Go

package parsers
import (
"fmt"
"sort"
"strings"
"time"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/common/model"
"git.govekar.net/LovroG05/nvllama-watchdog/core"
)
func init() {
model.NameValidationScheme = model.LegacyValidation
}
// ParsePrometheusText parses Prometheus text exposition format and returns
// a flat list of metrics. Histograms and summaries are expanded into
// _bucket, _sum, and _count metrics.
func ParsePrometheusText(text string) ([]core.Metric, error) {
var parser = expfmt.NewTextParser(model.LegacyValidation)
families, err := parser.TextToMetricFamilies(strings.NewReader(text))
if err != nil {
return nil, fmt.Errorf("prometheus parse: %w", err)
}
now := time.Now()
var metrics []core.Metric
for name, family := range families {
switch family.GetType() {
case dto.MetricType_COUNTER:
for _, m := range family.GetMetric() {
labels := labelsToMap(m.GetLabel())
metrics = append(metrics, core.Metric{
Name: name,
Help: family.GetHelp(),
Type: "counter",
Value: m.GetCounter().GetValue(),
Labels: labels,
Timestamp: now,
})
}
case dto.MetricType_GAUGE:
for _, m := range family.GetMetric() {
labels := labelsToMap(m.GetLabel())
metrics = append(metrics, core.Metric{
Name: name,
Help: family.GetHelp(),
Type: "gauge",
Value: m.GetGauge().GetValue(),
Labels: labels,
Timestamp: now,
})
}
case dto.MetricType_HISTOGRAM:
for _, m := range family.GetMetric() {
base := labelsToMap(m.GetLabel())
h := m.GetHistogram()
// _bucket (cumulative)
for _, b := range h.GetBucket() {
lb := copyLabels(base)
lb["le"] = fmt.Sprintf("%g", b.GetUpperBound())
metrics = append(metrics, core.Metric{
Name: name + "_bucket",
Help: family.GetHelp(),
Type: "counter",
Value: float64(b.GetCumulativeCount()),
Labels: lb,
Timestamp: now,
})
}
// +Inf bucket (always present in Prometheus, may be missing in source)
lb := copyLabels(base)
lb["le"] = "+Inf"
metrics = append(metrics, core.Metric{
Name: name + "_bucket",
Help: family.GetHelp(),
Type: "counter",
Value: float64(h.GetSampleCount()),
Labels: lb,
Timestamp: now,
})
// _sum
metrics = append(metrics, core.Metric{
Name: name + "_sum",
Help: family.GetHelp(),
Type: "counter",
Value: h.GetSampleSum(),
Labels: base,
Timestamp: now,
})
// _count
metrics = append(metrics, core.Metric{
Name: name + "_count",
Help: family.GetHelp(),
Type: "counter",
Value: float64(h.GetSampleCount()),
Labels: base,
Timestamp: now,
})
}
case dto.MetricType_SUMMARY:
for _, m := range family.GetMetric() {
base := labelsToMap(m.GetLabel())
s := m.GetSummary()
// quantiles
for _, q := range s.GetQuantile() {
lb := copyLabels(base)
lb["quantile"] = fmt.Sprintf("%g", q.GetQuantile())
metrics = append(metrics, core.Metric{
Name: name,
Help: family.GetHelp(),
Type: "gauge",
Value: q.GetValue(),
Labels: lb,
Timestamp: now,
})
}
// _sum
metrics = append(metrics, core.Metric{
Name: name + "_sum",
Help: family.GetHelp(),
Type: "counter",
Value: s.GetSampleSum(),
Labels: base,
Timestamp: now,
})
// _count
metrics = append(metrics, core.Metric{
Name: name + "_count",
Help: family.GetHelp(),
Type: "counter",
Value: float64(s.GetSampleCount()),
Labels: base,
Timestamp: now,
})
}
case dto.MetricType_UNTYPED:
// Fallback: try gauge first, then counter
for _, m := range family.GetMetric() {
labels := labelsToMap(m.GetLabel())
val := m.GetGauge().GetValue()
if val == 0 && m.GetCounter().GetValue() != 0 {
val = m.GetCounter().GetValue()
}
metrics = append(metrics, core.Metric{
Name: name,
Help: family.GetHelp(),
Type: "gauge",
Value: val,
Labels: labels,
Timestamp: now,
})
}
}
}
// Stable ordering
sort.Slice(metrics, func(i, j int) bool {
if metrics[i].Name != metrics[j].Name {
return metrics[i].Name < metrics[j].Name
}
return fmt.Sprint(metrics[i].Labels) < fmt.Sprint(metrics[j].Labels)
})
return metrics, nil
}
func labelsToMap(pairs []*dto.LabelPair) map[string]string {
if len(pairs) == 0 {
return map[string]string{}
}
m := make(map[string]string, len(pairs))
for _, p := range pairs {
m[p.GetName()] = p.GetValue()
}
return m
}
func copyLabels(src map[string]string) map[string]string {
dst := make(map[string]string, len(src)+1)
for k, v := range src {
dst[k] = v
}
return dst
}