202 lines
4.9 KiB
Go
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
|
|
}
|