--- 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.