Files
pikasTech-HWLAB/internal/dev-entrypoint/metrics-sidecar.mjs
T
2026-06-05 11:14:27 +08:00

224 lines
11 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 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();
}