"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregateMetric = exports.httpClientStats = exports.InternalStatsCollector = exports.flatStats = void 0;
const debug_logging_1 = require("@splunkdlt/debug-logging");
const wasm_1 = require("../abi/wasm");
const { debug } = debug_logging_1.createModuleDebug('utils:stats');
function flatStats(m, prefix) {
    let result = [];
    for (const [name, value] of Object.entries(m)) {
        const fullName = prefix ? `${prefix}.${name}` : name;
        if (typeof value === 'number') {
            result.push({ name: fullName, value });
        }
        else {
            result = result.concat(flatStats(value, fullName));
        }
    }
    return result;
}
exports.flatStats = flatStats;
class SystemStats {
    flushStats() {
        const mem = process.memoryUsage();
        const nextBaseUsage = process.cpuUsage();
        const cpu = process.cpuUsage(this.lastCpuUsage);
        this.lastCpuUsage = nextBaseUsage;
        return {
            mem: {
                ...mem
            },
            cpu: {
                ...cpu
            },
            wasm: {
                memorySize: wasm_1.getWasmMemorySize()
            },
            uptime: process.uptime()
        };
    }
}
class InternalStatsCollector {
    constructor(config) {
        this.config = config;
        this.sources = [];
        this.active = true;
        this.collectTimer = null;
        this.next = () => {
            this.collectTimer = null;
            const time = Date.now();
            const stats = this.collectStats();
            if (this.config.collect) {
                debug('Collecting stats from %d sources', this.sources.length);
                console.dir({
                    time,
                    measurements: Object.fromEntries(stats.map(({ name, value }) => [name, value]))
                });
            }
            this.start();
        };
        this.addSource(new SystemStats(), 'system');
    }
    addSource(source, prefix) {
        this.sources.push({
            source,
            prefix: [this.config.basePrefix, prefix].filter(s => !!s).join('.') || undefined
        });
    }
    collectStats() {
        return this.sources.flatMap(source => flatStats(source.source.flushStats(), source.prefix));
    }
    start() {
        if (this.active && this.collectTimer == null) {
            this.collectTimer = setTimeout(this.next, this.config.collectInterval);
        }
    }
    shutdown() {
        this.active = false;
        if (this.collectTimer != null) {
            clearTimeout(this.collectTimer);
        }
        return Promise.resolve();
    }
}
exports.InternalStatsCollector = InternalStatsCollector;
function httpClientStats(agentStatus) {
    const { createSocketCount, createSocketErrorCount, closeSocketCount, errorSocketCount, requestCount, timeoutSocketCount } = agentStatus;
    return {
        createSocketCount,
        createSocketErrorCount,
        closeSocketCount,
        errorSocketCount,
        requestCount,
        timeoutSocketCount
    };
}
exports.httpClientStats = httpClientStats;
// Calculate nth percentile with linear interpolation between closest ranks
function percentile(n, measurements) {
    const len = measurements.length;
    const pos = (len * n) / 100 - 1;
    const lower = measurements[Math.max(0, Math.floor(pos))];
    return lower + (measurements[Math.min(Math.max(0, Math.ceil(pos)), len - 1)] - lower) * (pos % 1);
}
const memo = (fn) => {
    let memoized = null;
    return () => (memoized != null ? memoized : (memoized = fn()));
};
class AggregateMetric {
    constructor() {
        this.measurements = [];
    }
    push(measurement) {
        this.measurements.push(measurement);
    }
    flush(prefix, enabledAggregations = { min: true, max: true, avg: true, count: true, sum: true, p99: true }) {
        const measurements = this.measurements;
        this.measurements = [];
        const count = measurements.length;
        if (count === 0) {
            return (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.count) ? { [`${prefix}.count`]: 0 } : {};
        }
        const last = measurements[count - 1];
        const sum = measurements.reduce((a, b) => a + b, 0);
        const sorted = memo(() => [...measurements].sort((a, b) => a - b));
        const stats = {
            count: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.count) ? count : undefined,
            sum: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.sum) ? sum : undefined,
            min: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.min) ? measurements.reduce((a, b) => Math.min(a, b), last) : undefined,
            max: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.max) ? measurements.reduce((a, b) => Math.max(a, b), last) : undefined,
            avg: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.avg) ? sum / count : undefined,
            p80: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.p80) ? percentile(80, sorted()) : undefined,
            p90: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.p90) ? percentile(90, sorted()) : undefined,
            p95: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.p95) ? percentile(95, sorted()) : undefined,
            p99: (enabledAggregations === null || enabledAggregations === void 0 ? void 0 : enabledAggregations.p99) ? percentile(99, sorted()) : undefined
        };
        return Object.fromEntries(Object.entries(stats)
            .filter(([, value]) => value != null)
            .map(([name, value]) => [`${prefix}.${name}`, value]));
    }
}
exports.AggregateMetric = AggregateMetric;
