import fs from "node:fs";
import path from "node:path";
import { monitorEventLoopDelay, performance } from "node:perf_hooks";
const ENABLED = process.env.BOBBIT_CPU_DIAG === "1";
const DEFAULT_FLUSH_MS = 1000;
const parsedFlushMs = Number(process.env.BOBBIT_CPU_DIAG_FLUSH_MS);
const FLUSH_MS = Number.isFinite(parsedFlushMs) && parsedFlushMs > 0 ? parsedFlushMs : DEFAULT_FLUSH_MS;
const JSONL_PATH = process.env.BOBBIT_CPU_DIAG_JSONL;
const MAX_LABELS = 256;
const MAX_SAMPLES_PER_LABEL = 2048;
const OVERFLOW_LABEL = "__overflow__";
const disabledDiagnostics = {
    recordRest() { },
    recordWsBroadcast() { },
    recordTimer() { },
    recordChildProcess() { },
    flush() { },
    shutdown() { },
};
function safeNumber(value) {
    return Number.isFinite(value) ? value : 0;
}
function round(value, digits = 3) {
    const factor = 10 ** digits;
    return Math.round(safeNumber(value) * factor) / factor;
}
function mb(bytes) {
    return round(bytes / 1024 / 1024, 3);
}
function percentile(sorted, q) {
    if (sorted.length === 0)
        return 0;
    const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * q) - 1));
    return sorted[idx];
}
function pushSample(bucket, durationMs) {
    const duration = safeNumber(durationMs);
    bucket.count++;
    bucket.totalMs += duration;
    if (duration > bucket.maxMs)
        bucket.maxMs = duration;
    if (bucket.samples.length < MAX_SAMPLES_PER_LABEL)
        bucket.samples.push(duration);
}
function summarizeTimed(bucket) {
    const sorted = [...bucket.samples].sort((a, b) => a - b);
    return {
        count: bucket.count,
        totalMs: round(bucket.totalMs),
        p50Ms: round(percentile(sorted, 0.5)),
        p95Ms: round(percentile(sorted, 0.95)),
        maxMs: round(bucket.maxMs),
    };
}
function bucketFor(map, label, create) {
    const key = map.has(label) || map.size < MAX_LABELS ? label : OVERFLOW_LABEL;
    let bucket = map.get(key);
    if (!bucket) {
        bucket = create();
        map.set(key, bucket);
    }
    return bucket;
}
function newTimedBucket() {
    return { count: 0, totalMs: 0, maxMs: 0, samples: [] };
}
function activeHandleCounts() {
    const getter = process._getActiveHandles;
    if (typeof getter !== "function")
        return {};
    const counts = {};
    try {
        for (const handle of getter.call(process)) {
            const name = handle && typeof handle === "object" && handle.constructor?.name
                ? handle.constructor.name
                : "unknown";
            counts[name] = (counts[name] ?? 0) + 1;
        }
    }
    catch {
        return {};
    }
    return counts;
}
class EnabledCpuDiagnostics {
    rest = new Map();
    ws = new Map();
    timers = new Map();
    child = new Map();
    lastCpu = process.cpuUsage();
    lastWall = performance.now();
    lastElu = performance.eventLoopUtilization();
    delay;
    timer = null;
    shutdownCalled = false;
    outFile;
    beforeExitHandler;
    exitHandler;
    constructor() {
        this.outFile = JSONL_PATH;
        if (this.outFile)
            fs.mkdirSync(path.dirname(this.outFile), { recursive: true });
        this.delay = monitorEventLoopDelay({ resolution: 20 });
        this.delay.enable();
        this.timer = setInterval(() => this.flush("tick"), FLUSH_MS);
        if (typeof this.timer.unref === "function")
            this.timer.unref();
        this.beforeExitHandler = () => { try {
            this.flush("beforeExit");
        }
        catch { /* best-effort */ } };
        this.exitHandler = () => { try {
            this.flush("exit");
        }
        catch { /* best-effort */ } };
        process.on("beforeExit", this.beforeExitHandler);
        process.on("exit", this.exitHandler);
    }
    recordRest(label, status, durationMs, responseBytes) {
        const bucket = bucketFor(this.rest, label, () => ({ ...newTimedBucket(), status: {}, responseBytes: 0 }));
        pushSample(bucket, durationMs);
        const family = `${Math.floor(status / 100)}xx`;
        bucket.status[family] = (bucket.status[family] ?? 0) + 1;
        bucket.status[String(status)] = (bucket.status[String(status)] ?? 0) + 1;
        if (typeof responseBytes === "number" && Number.isFinite(responseBytes) && responseBytes > 0) {
            bucket.responseBytes += responseBytes;
        }
    }
    recordWsBroadcast(label, type, counters) {
        const key = `${label}:${type || "unknown"}`;
        const bucket = bucketFor(this.ws, key, () => ({ count: 0, counters: {} }));
        bucket.count++;
        for (const [name, value] of Object.entries(counters)) {
            if (typeof value !== "number" || !Number.isFinite(value))
                continue;
            bucket.counters[name] = (bucket.counters[name] ?? 0) + value;
        }
    }
    recordTimer(label, durationMs, counters) {
        const bucket = bucketFor(this.timers, label, () => ({ ...newTimedBucket(), counters: {} }));
        pushSample(bucket, durationMs);
        if (counters) {
            for (const [name, value] of Object.entries(counters)) {
                if (!Number.isFinite(value))
                    continue;
                bucket.counters[name] = (bucket.counters[name] ?? 0) + value;
            }
        }
    }
    recordChildProcess(label, durationMs, metadata) {
        const bucket = bucketFor(this.child, label, () => ({ ...newTimedBucket(), metadata: {} }));
        pushSample(bucket, durationMs);
        if (metadata) {
            for (const [name, value] of Object.entries(metadata)) {
                const valueKey = String(value);
                const values = bucket.metadata[name] ?? {};
                values[valueKey] = (values[valueKey] ?? 0) + 1;
                bucket.metadata[name] = values;
            }
        }
    }
    flush(reason = "manual") {
        const snapshot = this.buildSnapshot(reason);
        this.resetBuckets();
        this.writeSnapshot(snapshot);
    }
    shutdown() {
        if (this.shutdownCalled)
            return;
        this.shutdownCalled = true;
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
        try {
            this.flush("shutdown");
        }
        catch { /* best-effort */ }
        try {
            this.delay.disable();
        }
        catch { /* best-effort */ }
        process.off("beforeExit", this.beforeExitHandler);
        process.off("exit", this.exitHandler);
        singleton = null;
    }
    buildSnapshot(reason) {
        const now = performance.now();
        const wallMs = Math.max(1, now - this.lastWall);
        const cpu = process.cpuUsage(this.lastCpu);
        this.lastCpu = process.cpuUsage();
        this.lastWall = now;
        const cpuUserMs = cpu.user / 1000;
        const cpuSystemMs = cpu.system / 1000;
        const currentElu = performance.eventLoopUtilization();
        const eluDelta = performance.eventLoopUtilization(currentElu, this.lastElu);
        this.lastElu = currentElu;
        const memory = process.memoryUsage();
        const snapshot = {
            kind: "cpu",
            ts: Date.now(),
            pid: process.pid,
            wallMs: round(wallMs),
            cpuUserMs: round(cpuUserMs),
            cpuSystemMs: round(cpuSystemMs),
            cpuPct: round(((cpuUserMs + cpuSystemMs) / wallMs) * 100, 2),
            elu: round(eluDelta.utilization, 4),
            delayP50Ms: round(this.delay.percentile(50) / 1e6),
            delayP95Ms: round(this.delay.percentile(95) / 1e6),
            delayMaxMs: round(this.delay.max / 1e6),
            rssMb: mb(memory.rss),
            heapUsedMb: mb(memory.heapUsed),
            heapTotalMb: mb(memory.heapTotal),
            externalMb: mb(memory.external),
            handles: activeHandleCounts(),
            rest: this.snapshotRest(),
            ws: this.snapshotWs(),
            timers: this.snapshotTimers(),
            child: this.snapshotChild(),
        };
        if (reason)
            snapshot.reason = reason;
        try {
            this.delay.reset();
        }
        catch { /* best-effort */ }
        return snapshot;
    }
    snapshotRest() {
        const out = {};
        for (const [label, bucket] of this.rest) {
            out[label] = {
                ...summarizeTimed(bucket),
                status: { ...bucket.status },
                ...(bucket.responseBytes > 0 ? { responseBytes: bucket.responseBytes } : {}),
            };
        }
        return out;
    }
    snapshotWs() {
        const out = {};
        for (const [label, bucket] of this.ws) {
            out[label] = { count: bucket.count, ...bucket.counters };
        }
        return out;
    }
    snapshotTimers() {
        const out = {};
        for (const [label, bucket] of this.timers) {
            out[label] = { ...summarizeTimed(bucket), ...bucket.counters };
        }
        return out;
    }
    snapshotChild() {
        const out = {};
        for (const [label, bucket] of this.child) {
            out[label] = { ...summarizeTimed(bucket), metadata: { ...bucket.metadata } };
        }
        return out;
    }
    resetBuckets() {
        this.rest.clear();
        this.ws.clear();
        this.timers.clear();
        this.child.clear();
    }
    writeSnapshot(snapshot) {
        const line = JSON.stringify(snapshot);
        if (this.outFile) {
            fs.appendFileSync(this.outFile, `${line}\n`);
            return;
        }
        process.stderr.write(`${line}\n`);
    }
}
let singleton = null;
export function cpuDiagnosticsEnabled() {
    return ENABLED;
}
export function getCpuDiagnostics() {
    if (!ENABLED)
        return disabledDiagnostics;
    if (!singleton)
        singleton = new EnabledCpuDiagnostics();
    return singleton;
}
export const CPU_DIAGNOSTICS_ENABLED = ENABLED;
export const CPU_DIAGNOSTICS_FLUSH_MS = FLUSH_MS;
//# sourceMappingURL=cpu-diagnostics.js.map