--- name: format_prom_exposition kind: function lang: go domain: infra version: "1.0.0" purity: pure signature: "func FormatPromExposition(samples []PromSample, timestampMs int64) string" description: "Convierte un slice de PromSample en texto con formato Prometheus exposition (una linea por sample: name{k=\"v\"} value timestampMs). Ordena labels por clave (salida determinista), escapa backslash/comilla/newline en valores de label, sanitiza el nombre de metrica a [a-zA-Z0-9_:], formatea el valor con FormatFloat 'g'. Si timestampMs<=0 omite el timestamp; sin labels omite las llaves. Funcion pura." tags: [prometheus, exposition, metrics, format, fleet-metrics, infra, monitoring] uses_functions: [] uses_types: ["PromSample_go_infra"] returns: [] returns_optional: false error_type: "" imports: ["sort", "strconv", "strings"] params: - name: samples desc: "slice de PromSample a serializar; cada uno aporta una linea de exposition" - name: timestampMs desc: "timestamp en milisegundos epoch a adjuntar a cada linea; si es <=0 se omite el campo timestamp" output: "string con el texto exposition Prometheus, una linea por sample terminada en \\n. String vacio si samples esta vacio." tested: true tests: - "TestFormatPromExposition" test_file_path: "functions/infra/format_prom_exposition_test.go" file_path: "functions/infra/format_prom_exposition.go" --- ## Ejemplo ```go samples := []PromSample{ {Name: "node_load1", Value: 0.42}, {Name: "node_cpu_core_percent", Labels: map[string]string{"core": "0"}, Value: 12.5}, {Name: "node_disk_used_bytes", Labels: map[string]string{"mount": "/"}, Value: 1024}, } text := FormatPromExposition(samples, 1700000000000) // node_load1 0.42 1700000000000 // node_cpu_core_percent{core="0"} 12.5 1700000000000 // node_disk_used_bytes{mount="/"} 1024 1700000000000 ``` ## Cuando usarla Cuando tengas un slice de PromSample (tipicamente de collect_host_metrics) y necesites serializarlo al formato de texto que entienden los endpoints de ingestion Prometheus (`/api/v1/import/prometheus` de VictoriaMetrics, pushgateway, etc.). Es el paso intermedio del capability group `fleet-metrics`: colecta -> formatea -> empuja. Al ser pura y determinista, tambien sirve para snapshots reproducibles y golden tests. ## Gotchas - El timestamp es **milisegundos** epoch (Prometheus exposition usa ms), no segundos. Pasa `time.Now().UnixMilli()`. - `timestampMs <= 0` (incluido 0) omite el campo timestamp por completo. - La label `instance` NO se gestiona aqui: si esta en `Labels` se serializa tal cual, pero la convencion del grupo es dejarla fuera y aƱadirla en el push via extra_label. - No agrupa por nombre ni emite lineas `# HELP` / `# TYPE`: salida cruda de series, suficiente para ingestion pero no para un endpoint /metrics canonico. - El nombre de metrica se sanitiza de forma destructiva: `node.cpu-percent!` se convierte en `node_cpu_percent_`. Nombra bien los samples en origen.