import { createServer, request as httpRequest } from "node:http"; import { request as httpsRequest } from "node:https"; import { realpathSync } from "node:fs"; import { pathToFileURL } from "node:url"; const DEFAULT_PORT = 9100; const DEFAULT_TARGET_TIMEOUT_MS = 2000; const DEFAULT_EXTRA_METRICS_MAX_BYTES = 64 * 1024; const startedAtMs = Date.now(); export function sanitizeLabelValue(value, fallback = "unknown") { const text = String(value ?? "").trim(); const sanitized = text.replace(/[^A-Za-z0-9_.:-]/gu, "_").slice(0, 120); return sanitized || fallback; } export function escapeLabelValue(value) { return String(value ?? "").replace(/\\/gu, "\\\\").replace(/\n/gu, "\\n").replace(/"/gu, "\\\""); } function metricLabels(env) { const service = sanitizeLabelValue(env.HWLAB_METRICS_SERVICE_ID || env.HWLAB_SERVICE_ID); const namespace = sanitizeLabelValue(env.HWLAB_METRICS_NAMESPACE || env.POD_NAMESPACE || "hwlab-v02"); const gitopsTarget = sanitizeLabelValue(env.HWLAB_METRICS_GITOPS_TARGET || env.HWLAB_GITOPS_TARGET || "v02"); return `service="${escapeLabelValue(service)}",namespace="${escapeLabelValue(namespace)}",gitops_target="${escapeLabelValue(gitopsTarget)}"`; } export function requestTarget({ targetUrl, timeoutMs = DEFAULT_TARGET_TIMEOUT_MS, collectBody = false, maxBodyBytes = DEFAULT_EXTRA_METRICS_MAX_BYTES } = {}) { const configured = typeof targetUrl === "string" && targetUrl.trim().length > 0; if (!configured) return Promise.resolve({ configured: false, success: false, statusCode: 0, durationSeconds: 0 }); let url; try { url = new URL(targetUrl); } catch { return Promise.resolve({ configured: true, success: false, statusCode: 0, durationSeconds: 0 }); } const requestImpl = url.protocol === "https:" ? httpsRequest : url.protocol === "http:" ? httpRequest : null; if (!requestImpl) return Promise.resolve({ configured: true, success: false, statusCode: 0, durationSeconds: 0 }); const started = Date.now(); return new Promise((resolve) => { const finish = (result) => resolve({ configured: true, success: false, statusCode: 0, durationSeconds: (Date.now() - started) / 1000, ...result }); const request = requestImpl(url, { method: "GET", timeout: timeoutMs }, (response) => { const chunks = []; let bytes = 0; if (collectBody) { response.on("data", (chunk) => { const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); bytes += buffer.length; if (bytes <= maxBodyBytes) chunks.push(buffer); }); } else { response.resume(); } response.on("end", () => finish({ success: Boolean(response.statusCode && response.statusCode >= 200 && response.statusCode < 300), statusCode: response.statusCode || 0, body: collectBody ? Buffer.concat(chunks).toString("utf8") : undefined, bodyBytes: collectBody ? Math.min(bytes, maxBodyBytes) : 0, truncated: collectBody ? bytes > maxBodyBytes : false })); }); request.on("timeout", () => request.destroy()); request.on("error", () => finish({})); request.end(); }); } export async function probeExtraMetrics({ targetUrl, timeoutMs = DEFAULT_TARGET_TIMEOUT_MS, maxBodyBytes = DEFAULT_EXTRA_METRICS_MAX_BYTES, probeImpl = requestTarget } = {}) { const configured = typeof targetUrl === "string" && targetUrl.trim().length > 0; if (!configured || typeof probeImpl !== "function") { return { configured: false, success: false, statusCode: 0, durationSeconds: 0, text: "" }; } try { const result = await probeImpl({ targetUrl, timeoutMs, collectBody: true, maxBodyBytes }); return { configured: true, success: Boolean(result.success), statusCode: result.statusCode || 0, durationSeconds: result.durationSeconds || 0, text: result.success ? sanitizeExtraMetricsText(result.body || "") : "" }; } catch { return { configured: true, success: false, statusCode: 0, durationSeconds: 0, text: "" }; } } export function sanitizeExtraMetricsText(text) { const lines = String(text ?? "").split(/\r?\n/gu); return lines.filter((line) => { if (!line) return true; if (/^#\s+(?:HELP|TYPE)\s+hwlab_[A-Za-z_:][A-Za-z0-9_:]*\b/u.test(line)) return true; return /^hwlab_[A-Za-z_:][A-Za-z0-9_:]*(?:\{[^\n{}]*\})?\s+[-+0-9.eE]+$/u.test(line); }).join("\n"); } export async function probeTarget({ targetUrl, timeoutMs = DEFAULT_TARGET_TIMEOUT_MS, probeImpl = requestTarget } = {}) { const configured = typeof targetUrl === "string" && targetUrl.trim().length > 0; if (!configured || typeof probeImpl !== "function") { return { configured: false, success: false, statusCode: 0, durationSeconds: 0 }; } try { return await probeImpl({ targetUrl, timeoutMs }); } catch { const started = Date.now(); return { configured: true, success: false, statusCode: 0, durationSeconds: (Date.now() - started) / 1000 }; } } export function buildMetricsText({ env = process.env, probe = null, extraMetrics = null, now = Date.now() } = {}) { const labels = metricLabels(env); const uptimeSeconds = Math.max(0, (now - startedAtMs) / 1000); const healthProbe = probe ?? { configured: false, success: false, statusCode: 0, durationSeconds: 0 }; const extraProbe = extraMetrics ?? { configured: false, success: false, statusCode: 0, durationSeconds: 0, text: "" }; const lines = [ "# HELP hwlab_service_up HWLAB metrics sidecar availability.", "# TYPE hwlab_service_up gauge", `hwlab_service_up{${labels}} 1`, "# HELP hwlab_service_info Static HWLAB service metrics identity without high-cardinality request identifiers.", "# TYPE hwlab_service_info gauge", `hwlab_service_info{${labels}} 1`, "# HELP hwlab_service_process_uptime_seconds Metrics sidecar process uptime in seconds.", "# TYPE hwlab_service_process_uptime_seconds gauge", `hwlab_service_process_uptime_seconds{${labels}} ${uptimeSeconds.toFixed(3)}`, "# HELP hwlab_service_health_probe_configured Whether the sidecar has a cluster-local health probe target.", "# TYPE hwlab_service_health_probe_configured gauge", `hwlab_service_health_probe_configured{${labels}} ${healthProbe.configured ? 1 : 0}`, "# HELP hwlab_service_health_probe_success Whether the most recent cluster-local health probe succeeded.", "# TYPE hwlab_service_health_probe_success gauge", `hwlab_service_health_probe_success{${labels}} ${healthProbe.success ? 1 : 0}`, "# HELP hwlab_service_health_probe_status_code Most recent cluster-local health probe HTTP status code, or 0 when unavailable.", "# TYPE hwlab_service_health_probe_status_code gauge", `hwlab_service_health_probe_status_code{${labels}} ${Number.isInteger(healthProbe.statusCode) ? healthProbe.statusCode : 0}`, "# HELP hwlab_service_health_probe_duration_seconds Most recent cluster-local health probe duration in seconds.", "# TYPE hwlab_service_health_probe_duration_seconds gauge", `hwlab_service_health_probe_duration_seconds{${labels}} ${Number(healthProbe.durationSeconds || 0).toFixed(3)}`, "# HELP hwlab_service_extra_metrics_configured Whether the sidecar has an additional cluster-local metrics target.", "# TYPE hwlab_service_extra_metrics_configured gauge", `hwlab_service_extra_metrics_configured{${labels}} ${extraProbe.configured ? 1 : 0}`, "# HELP hwlab_service_extra_metrics_success Whether the most recent additional metrics fetch succeeded.", "# TYPE hwlab_service_extra_metrics_success gauge", `hwlab_service_extra_metrics_success{${labels}} ${extraProbe.success ? 1 : 0}`, "# HELP hwlab_service_extra_metrics_status_code Most recent additional metrics HTTP status code, or 0 when unavailable.", "# TYPE hwlab_service_extra_metrics_status_code gauge", `hwlab_service_extra_metrics_status_code{${labels}} ${Number.isInteger(extraProbe.statusCode) ? extraProbe.statusCode : 0}`, "# HELP hwlab_service_extra_metrics_duration_seconds Most recent additional metrics fetch duration in seconds.", "# TYPE hwlab_service_extra_metrics_duration_seconds gauge", `hwlab_service_extra_metrics_duration_seconds{${labels}} ${Number(extraProbe.durationSeconds || 0).toFixed(3)}`, "" ]; if (extraProbe.text) { lines.push(extraProbe.text.trimEnd(), ""); } return lines.join("\n"); } function sendJson(response, statusCode, body) { const payload = JSON.stringify(body) + "\n"; response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "content-length": Buffer.byteLength(payload) }); response.end(payload); } export function createMetricsSidecarServer({ env = process.env, probeImpl = requestTarget } = {}) { return createServer(async (request, response) => { const url = new URL(request.url || "/", "http://hwlab-metrics.local"); if (request.method === "GET" && (url.pathname === "/health" || url.pathname === "/health/live")) { sendJson(response, 200, { serviceId: sanitizeLabelValue(env.HWLAB_METRICS_SERVICE_ID || env.HWLAB_SERVICE_ID), status: "ok", metricsPath: "/metrics" }); return; } if (request.method === "GET" && url.pathname === "/metrics") { const timeoutMs = Number.parseInt(env.HWLAB_METRICS_TARGET_TIMEOUT_MS || "", 10) || DEFAULT_TARGET_TIMEOUT_MS; const probe = await probeTarget({ targetUrl: env.HWLAB_METRICS_TARGET_URL, timeoutMs, probeImpl }); const extraTimeoutMs = Number.parseInt(env.HWLAB_METRICS_EXTRA_TIMEOUT_MS || "", 10) || DEFAULT_TARGET_TIMEOUT_MS; const extraMetrics = await probeExtraMetrics({ targetUrl: env.HWLAB_METRICS_EXTRA_URL, timeoutMs: extraTimeoutMs, probeImpl }); const payload = buildMetricsText({ env, probe, extraMetrics }); response.writeHead(200, { "content-type": "text/plain; version=0.0.4; charset=utf-8", "content-length": Buffer.byteLength(payload) }); response.end(payload); return; } sendJson(response, 404, { error: "not_found", path: url.pathname }); }); } export function startMetricsSidecar({ env = process.env } = {}) { const port = Number.parseInt(env.HWLAB_METRICS_PORT || env.PORT || "", 10) || DEFAULT_PORT; const server = createMetricsSidecarServer({ env }); server.listen(port, "0.0.0.0", () => { process.stdout.write(JSON.stringify({ serviceId: env.HWLAB_METRICS_SERVICE_ID || "hwlab-unknown", status: "metrics-listening", port }) + "\n"); }); return server; } export function isMainModule({ moduleUrl = import.meta.url, argvPath = process.argv[1], realpath = realpathSync } = {}) { if (!argvPath) return false; if (moduleUrl === pathToFileURL(argvPath).href) return true; try { return moduleUrl === pathToFileURL(realpath(argvPath)).href; } catch { return false; } } if (isMainModule()) { startMetricsSidecar(); }