feat(infra): auto-commit con 6 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 01:57:00 +02:00
parent 82f1f1bd58
commit 0a6d1b8d17
6 changed files with 636 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
---
name: parse_nats_monitor
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func ParseNatsMonitor(node string, varz, connz, jsz []byte) ([]PromSample, error)"
description: "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. Hermana de ParseUnibusHealth pero para las métricas server-level de NATS/JetStream: msgs/s, bytes, conexiones, slow consumers, memoria RSS, start epoch (proxy de reinicios), streams/messages/bytes/memory/storage de JetStream, y por stream nats_stream_messages/bytes, nats_jetstream_raft_leader y kv_bucket_msgs para los buckets KV_. Adjunta labels node e instance a cada serie. varz es el core (error si no parsea); connz y jsz son best-effort (se omiten sin abortar). La consume el unibus_exporter de fleet_monitoring como scraper local por nodo."
tags: [prometheus, metrics, nats, jetstream, monitoring, varz, connz, jsz, kv, raft, fleet-metrics, infra]
uses_functions: []
uses_types: ["PromSample_go_infra"]
returns: []
returns_optional: true
error_type: "error_go_core"
imports: ["encoding/json", "fmt", "strings", "time"]
params:
- name: node
desc: "nombre lógico del nodo (p.ej. \"magnus\"); se adjunta como labels node e instance a CADA serie y se compara con cluster.leader de cada stream para nats_jetstream_raft_leader"
- name: varz
desc: "cuerpo JSON crudo de GET http://127.0.0.1:8222/varz; core de la función (in_msgs, out_msgs, in_bytes, out_bytes, connections, slow_consumers, subscriptions, mem, start). Si no parsea, la función devuelve error"
- name: connz
desc: "cuerpo JSON crudo de GET http://127.0.0.1:8222/connz; best-effort (num_connections). Si vacío o inválido, nats_connections cae a varz.connections sin abortar"
- name: jsz
desc: "cuerpo JSON crudo de GET http://127.0.0.1:8222/jsz?streams=1; best-effort (streams, messages, bytes, memory, storage y account_details[].stream_detail[]). Si vacío o inválido, se omiten sus series sin abortar. Necesita ?streams=1 para traer stream_detail"
output: "slice de PromSample con labels base {node,instance}: nats_msgs_in/out_total, nats_bytes_in/out_total, nats_connections, nats_slow_consumers, nats_mem_bytes, nats_subscriptions, nats_server_start_seconds (omitida si start no parsea), nats_jetstream_streams/messages/bytes/memory_bytes/storage_bytes; y por stream nats_stream_messages{stream}, nats_stream_bytes{stream}, nats_jetstream_raft_leader{stream} (1 si cluster.leader==node) y, para streams KV_, kv_bucket_msgs{bucket} con el prefijo KV_ recortado. Error solo si varz no es JSON válido."
tested: true
test_file_path: "functions/infra/parse_nats_monitor_test.go"
tests:
- "TestParseNatsMonitorGolden"
- "TestParseNatsMonitorEmptyJsz"
- "TestParseNatsMonitorInvalidConnz"
- "TestParseNatsMonitorInvalidVarz"
---
# parse_nats_monitor
Función de transformación (clasificada `impure` porque devuelve `error` al fallar el
unmarshal del core; no hace I/O ni red por sí misma) que traduce las métricas
server-level de un **nats-server** a series Prometheus. Es la hermana de
`parse_unibus_health_go_infra`: aquella lee el `/healthz` de `membershipd` (posture),
esta lee el monitoring embebido de NATS (puerto 8222) para las métricas profundas que
`/healthz` no expone: msgs/s, conexiones, RAFT leader por stream, memoria, KV buckets.
Pertenece al grupo de capacidad `fleet-metrics`: se compone con
`format_prom_exposition_go_infra` (serializar) y `push_prom_remote_go_infra` (empujar a
VictoriaMetrics). La consume el `unibus_exporter` de `fleet_monitoring` en modo scraper
local por nodo, que hace los tres GET y le pasa los cuerpos crudos.
## Ejemplo
```go
package main
import (
"fmt"
"io"
"net/http"
"time"
"fn-registry/functions/infra"
)
func get(url string) []byte {
resp, err := http.Get(url)
if err != nil {
return nil // best-effort: connz/jsz pueden faltar
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
return b
}
func main() {
base := "http://127.0.0.1:8222"
varz := get(base + "/varz")
connz := get(base + "/connz")
jsz := get(base + "/jsz?streams=1")
samples, err := infra.ParseNatsMonitor("magnus", varz, connz, jsz)
if err != nil {
panic(err) // varz es el core: sin él no hay métricas
}
fmt.Print(infra.FormatPromExposition(samples, time.Now().UnixMilli()))
// nats_msgs_in_total{instance="magnus",node="magnus"} 17 ...
// kv_bucket_msgs{bucket="UNIBUS_users",instance="magnus",node="magnus"} 2 ...
// nats_jetstream_raft_leader{instance="magnus",node="magnus",stream="KV_UNIBUS_users"} 1 ...
}
```
## Cuando usarla
Úsala dentro de un exporter que monitoriza un nats-server con el monitoring HTTP
embebido activado (`http: 127.0.0.1:8222` en la config de NATS): tras hacer
`GET /varz`, `GET /connz` y `GET /jsz?streams=1` contra loopback, pasa los tres cuerpos
crudos a esta función para obtener todas las series server-level del nodo. Llámala como
scraper local por nodo (cada nodo expone su 8222 solo en loopback), no centralizado.
## Gotchas
- **Impura por contrato**: solo devuelve `error` si `varz` no es JSON válido (es el core).
`connz` y `jsz` son **best-effort**: si vienen vacíos o no parsean, sus series se omiten
sin abortar. Esto hace al scraper resistente a que un endpoint falle de forma puntual.
- **Monitoring loopback-only sin auth**: el puerto 8222 de NATS no tiene autenticación; por
eso debe bindearse a `127.0.0.1` y scrapearse localmente en cada nodo, nunca exponerse a
la red. El push agregado a VictoriaMetrics lo hace el exporter, no esta función.
- **`/jsz` necesita `?streams=1`** para traer `account_details[].stream_detail[]`. Sin ese
parámetro el cuerpo trae los totales pero no el detalle por stream, y entonces no salen
`nats_stream_*`, `nats_jetstream_raft_leader` ni `kv_bucket_msgs`.
- **`nats_connections`**: prefiere `connz.num_connections`; si `connz` no parsea, cae a
`varz.connections` para no perder la serie.
- **RAFT leader en standalone**: en un nats-server sin clúster, el objeto `cluster` puede
faltar o `leader` venir vacío; en ese caso `nats_jetstream_raft_leader` sale 0 salvo que
`cluster.leader == node`. Es esperado: en standalone no hay quorum RAFT real.
- **`kv_bucket_msgs`** solo se emite para streams cuyo nombre empieza por `KV_`, recortando
el prefijo (stream `KV_UNIBUS_users` → bucket `UNIBUS_users`).
- **`nats_server_start_seconds`** es el epoch Unix del campo `start` (RFC3339): sirve como
proxy de reinicios (un cambio de valor = el server reinició). Si el campo no parsea como
fecha válida, la serie se omite en lugar de abortar.