Ctrl K
ComunidadGuías…

internal/server/metrics.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
}