internal/server/metrics.go
Rama main · réplica de lectura del repositorio original.
package server
import (
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
"armdash/internal/system"
)
// handleMetrics renders the Prometheus text exposition format for every
// enabled system that collects. Written by hand rather than pulling in
// client_golang: the output is a few hundred lines of text and the dependency
// would be larger than the whole application.
func (t *tree) handleMetrics(w http.ResponseWriter, r *http.Request) {
ctx, cancel := contextWithTimeout(r, 25*time.Second)
defer cancel()
var all []system.Metric
for _, sys := range t.enabled() {
c, ok := sys.(system.Collector)
if !ok {
continue
}
m, err := c.Collect(ctx)
if err != nil {
// Report the failure as a metric rather than failing the scrape:
// Prometheus then records that the collector is down instead of
// simply having a gap.
t.log.Error("collect failed", "system", sys.ID(), "err", err)
all = append(all, system.Metric{
Name: "armdash_collector_up", Type: "gauge",
Help: "1 when the system's last collection succeeded",
Labels: map[string]string{"system": sys.ID()}, Value: 0,
})
continue
}
all = append(all, system.Metric{
Name: "armdash_collector_up", Type: "gauge",
Help: "1 when the system's last collection succeeded",
Labels: map[string]string{"system": sys.ID()}, Value: 1,
})
all = append(all, m...)
}
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
_, _ = w.Write([]byte(render(all)))
}
func render(metrics []system.Metric) string {
// Group by name: HELP and TYPE are emitted once per family, and every
// sample of a family must be contiguous.
order := []string{}
byName := map[string][]system.Metric{}
for _, m := range metrics {
if _, seen := byName[m.Name]; !seen {
order = append(order, m.Name)
}
byName[m.Name] = append(byName[m.Name], m)
}
var b strings.Builder
for _, name := range order {
family := byName[name]
if h := family[0].Help; h != "" {
fmt.Fprintf(&b, "# HELP %s %s\n", name, escapeHelp(h))
}
typ := family[0].Type
if typ == "" {
typ = "gauge"
}
fmt.Fprintf(&b, "# TYPE %s %s\n", name, typ)
for _, m := range family {
b.WriteString(name)
b.WriteString(labels(m.Labels))
b.WriteByte(' ')
b.WriteString(strconv.FormatFloat(m.Value, 'g', -1, 64))
b.WriteByte('\n')
}
}
return b.String()
}
func labels(l map[string]string) string {
if len(l) == 0 {
return ""
}
keys := make([]string, 0, len(l))
for k := range l {
keys = append(keys, k)
}
sort.Strings(keys) // stable output makes diffing a scrape possible
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, k+`="`+escapeLabel(l[k])+`"`)
}
return "{" + strings.Join(parts, ",") + "}"
}
func escapeLabel(v string) string {
return strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", `\n`).Replace(v)
}
func escapeHelp(v string) string {
return strings.NewReplacer(`\`, `\\`, "\n", `\n`).Replace(v)
}