Files
pikasTech-HWLAB/internal/dev-entrypoint/metrics-sidecar.mjs
T
2026-06-05 09:12:42 +08:00

160 lines
7.2 KiB
JavaScript

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 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 } = {}) {
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) => {
response.resume();
response.on("end", () => finish({ success: Boolean(response.statusCode && response.statusCode >= 200 && response.statusCode < 300), statusCode: response.statusCode || 0 }));
});
request.on("timeout", () => request.destroy());
request.on("error", () => finish({}));
request.end();
});
}
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, 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 };
return [
"# 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)}`,
""
].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 payload = buildMetricsText({ env, probe });
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();
}