feat(infra): auto-commit con 6 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// natsVarz refleja los campos relevantes de la respuesta JSON del endpoint
|
||||
// /varz del monitoring HTTP embebido de un nats-server (puerto 8222, loopback).
|
||||
// Solo se mapean los campos que producen series; el resto se ignora.
|
||||
type natsVarz struct {
|
||||
InMsgs int64 `json:"in_msgs"`
|
||||
OutMsgs int64 `json:"out_msgs"`
|
||||
InBytes int64 `json:"in_bytes"`
|
||||
OutBytes int64 `json:"out_bytes"`
|
||||
Connections int `json:"connections"`
|
||||
SlowConsumers int `json:"slow_consumers"`
|
||||
Subscriptions int `json:"subscriptions"`
|
||||
Mem int64 `json:"mem"`
|
||||
Start string `json:"start"`
|
||||
}
|
||||
|
||||
// natsConnz refleja los campos relevantes de /connz.
|
||||
type natsConnz struct {
|
||||
NumConnections int `json:"num_connections"`
|
||||
}
|
||||
|
||||
// natsStreamDetail refleja un stream dentro de account_details[].stream_detail[].
|
||||
type natsStreamDetail struct {
|
||||
Name string `json:"name"`
|
||||
Cluster struct {
|
||||
Leader string `json:"leader"`
|
||||
} `json:"cluster"`
|
||||
State struct {
|
||||
Messages int64 `json:"messages"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
} `json:"state"`
|
||||
}
|
||||
|
||||
// natsJsz refleja los campos relevantes de /jsz?streams=1.
|
||||
type natsJsz struct {
|
||||
Streams int64 `json:"streams"`
|
||||
Messages int64 `json:"messages"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
Memory int64 `json:"memory"`
|
||||
Storage int64 `json:"storage"`
|
||||
AccountDetails []struct {
|
||||
StreamDetail []natsStreamDetail `json:"stream_detail"`
|
||||
} `json:"account_details"`
|
||||
}
|
||||
|
||||
// ParseNatsMonitor convierte las respuestas JSON del endpoint de monitoring HTTP
|
||||
// embebido de un nats-server (puerto 8222, loopback) en una serie de PromSample
|
||||
// lista para empujar a VictoriaMetrics. Es la hermana de ParseUnibusHealth para
|
||||
// las métricas server-level de NATS/JetStream (msgs/s, conexiones, KV bucket
|
||||
// msgs, RAFT leader por stream, memoria). La consume el unibus_exporter de
|
||||
// fleet_monitoring en modo scraper local por nodo.
|
||||
//
|
||||
// node es el nombre lógico del nodo (p.ej. "magnus"); se adjunta a CADA serie
|
||||
// como las labels "node" e "instance" para distinguir los nodos cuando un único
|
||||
// exporter scrapea varios.
|
||||
//
|
||||
// varz, connz y jsz son los cuerpos crudos de GET /varz, GET /connz y
|
||||
// GET /jsz?streams=1 respectivamente:
|
||||
// - varz es el core: si NO parsea como JSON válido devuelve (nil, error).
|
||||
// - connz y jsz son best-effort: si vienen vacíos o no parsean, sus series se
|
||||
// omiten sin abortar (no error), para que el scraper resista que un endpoint
|
||||
// falle. nats_connections cae a varz.connections cuando connz no parsea.
|
||||
func ParseNatsMonitor(node string, varz, connz, jsz []byte) ([]PromSample, error) {
|
||||
var v natsVarz
|
||||
if err := json.Unmarshal(varz, &v); err != nil {
|
||||
return nil, fmt.Errorf("parse nats varz for node %q: %w", node, err)
|
||||
}
|
||||
|
||||
// mk construye un PromSample con las labels base {node, instance} más, de
|
||||
// forma opcional, labels extra (clave/valor alternados). Las labels base no
|
||||
// se pueden sobreescribir desde extra.
|
||||
mk := func(name string, val float64, extra ...string) PromSample {
|
||||
labels := map[string]string{"node": node, "instance": node}
|
||||
for i := 0; i+1 < len(extra); i += 2 {
|
||||
labels[extra[i]] = extra[i+1]
|
||||
}
|
||||
return PromSample{Name: name, Labels: labels, Value: val}
|
||||
}
|
||||
|
||||
out := []PromSample{
|
||||
mk("nats_msgs_in_total", float64(v.InMsgs)),
|
||||
mk("nats_msgs_out_total", float64(v.OutMsgs)),
|
||||
mk("nats_bytes_in_total", float64(v.InBytes)),
|
||||
mk("nats_bytes_out_total", float64(v.OutBytes)),
|
||||
}
|
||||
|
||||
// nats_connections: prefiere connz.num_connections; si connz no parsea, cae
|
||||
// a varz.connections para no perder la serie.
|
||||
connections := float64(v.Connections)
|
||||
if len(connz) > 0 {
|
||||
var c natsConnz
|
||||
if err := json.Unmarshal(connz, &c); err == nil {
|
||||
connections = float64(c.NumConnections)
|
||||
}
|
||||
}
|
||||
out = append(out,
|
||||
mk("nats_connections", connections),
|
||||
mk("nats_slow_consumers", float64(v.SlowConsumers)),
|
||||
mk("nats_mem_bytes", float64(v.Mem)),
|
||||
mk("nats_subscriptions", float64(v.Subscriptions)),
|
||||
)
|
||||
|
||||
// nats_server_start_seconds: epoch (segundos Unix) del campo start (RFC3339).
|
||||
// Proxy de reinicios del nats-server: un cambio de este valor = el server
|
||||
// reinició. Si el parse de la fecha falla, se omite la serie (no se aborta).
|
||||
if t, err := time.Parse(time.RFC3339, v.Start); err == nil {
|
||||
out = append(out, mk("nats_server_start_seconds", float64(t.Unix())))
|
||||
}
|
||||
|
||||
// jsz es best-effort: si vacío o inválido, se omiten todas sus series.
|
||||
if len(jsz) > 0 {
|
||||
var j natsJsz
|
||||
if err := json.Unmarshal(jsz, &j); err == nil {
|
||||
out = append(out,
|
||||
mk("nats_jetstream_streams", float64(j.Streams)),
|
||||
mk("nats_jetstream_messages", float64(j.Messages)),
|
||||
mk("nats_jetstream_bytes", float64(j.Bytes)),
|
||||
mk("nats_jetstream_memory_bytes", float64(j.Memory)),
|
||||
mk("nats_jetstream_storage_bytes", float64(j.Storage)),
|
||||
)
|
||||
for _, acc := range j.AccountDetails {
|
||||
for _, sd := range acc.StreamDetail {
|
||||
out = append(out,
|
||||
mk("nats_stream_messages", float64(sd.State.Messages), "stream", sd.Name),
|
||||
mk("nats_stream_bytes", float64(sd.State.Bytes), "stream", sd.Name),
|
||||
)
|
||||
leader := 0.0
|
||||
if sd.Cluster.Leader == node {
|
||||
leader = 1
|
||||
}
|
||||
out = append(out, mk("nats_jetstream_raft_leader", leader, "stream", sd.Name))
|
||||
|
||||
if bucket, ok := strings.CutPrefix(sd.Name, "KV_"); ok {
|
||||
out = append(out, mk("kv_bucket_msgs", float64(sd.State.Messages), "bucket", bucket))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user