"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpCacheStats = exports.HttpStats = exports.PackageCacheStats = exports.LookupStats = void 0;
exports.makeTimingReport = makeTimingReport;
const tslib_1 = require("tslib");
const logger_1 = require("../logger");
const memCache = tslib_1.__importStar(require("./cache/memory"));
const url_1 = require("./url");
function makeTimingReport(data) {
    const count = data.length;
    const totalMs = data.reduce((a, c) => a + c, 0);
    const avgMs = count ? Math.round(totalMs / count) : 0;
    const maxMs = Math.max(0, ...data);
    const sorted = data.sort((a, b) => a - b);
    const medianMs = count ? sorted[Math.floor(count / 2)] : 0;
    return { count, avgMs, medianMs, maxMs, totalMs };
}
class LookupStats {
    static write(datasource, duration) {
        const data = memCache.get('lookup-stats') ?? {};
        data[datasource] ??= [];
        data[datasource].push(duration);
        memCache.set('lookup-stats', data);
    }
    static async wrap(datasource, callback) {
        const start = Date.now();
        const result = await callback();
        const duration = Date.now() - start;
        LookupStats.write(datasource, duration);
        return result;
    }
    static getReport() {
        const report = {};
        const data = memCache.get('lookup-stats') ?? {};
        for (const [datasource, durations] of Object.entries(data)) {
            report[datasource] = makeTimingReport(durations);
        }
        return report;
    }
    static report() {
        const report = LookupStats.getReport();
        logger_1.logger.debug(report, 'Lookup statistics');
    }
}
exports.LookupStats = LookupStats;
class PackageCacheStats {
    static writeSet(duration) {
        const data = memCache.get('package-cache-sets') ?? [];
        data.push(duration);
        memCache.set('package-cache-sets', data);
    }
    static async wrapSet(callback) {
        const start = Date.now();
        const result = await callback();
        const duration = Date.now() - start;
        PackageCacheStats.writeSet(duration);
        return result;
    }
    static writeGet(duration) {
        const data = memCache.get('package-cache-gets') ?? [];
        data.push(duration);
        memCache.set('package-cache-gets', data);
    }
    static async wrapGet(callback) {
        const start = Date.now();
        const result = await callback();
        const duration = Date.now() - start;
        PackageCacheStats.writeGet(duration);
        return result;
    }
    static getReport() {
        const packageCacheGets = memCache.get('package-cache-gets') ?? [];
        const get = makeTimingReport(packageCacheGets);
        const packageCacheSets = memCache.get('package-cache-sets') ?? [];
        const set = makeTimingReport(packageCacheSets);
        return { get, set };
    }
    static report() {
        const report = PackageCacheStats.getReport();
        logger_1.logger.debug(report, 'Package cache statistics');
    }
}
exports.PackageCacheStats = PackageCacheStats;
class HttpStats {
    static write(data) {
        const httpRequests = memCache.get('http-requests') ?? [];
        httpRequests.push(data);
        memCache.set('http-requests', httpRequests);
    }
    static getDataPoints() {
        const httpRequests = memCache.get('http-requests') ?? [];
        // istanbul ignore next: sorting is hard and not worth testing
        httpRequests.sort((a, b) => {
            if (a.url < b.url) {
                return -1;
            }
            if (a.url > b.url) {
                return 1;
            }
            return 0;
        });
        return httpRequests;
    }
    static getReport() {
        const dataPoints = HttpStats.getDataPoints();
        const requests = dataPoints.length;
        const urls = {};
        const rawRequests = [];
        const hostRequests = {};
        for (const dataPoint of dataPoints) {
            const { url, reqMs, queueMs, status } = dataPoint;
            const method = dataPoint.method.toUpperCase();
            const parsedUrl = (0, url_1.parseUrl)(url);
            if (!parsedUrl) {
                logger_1.logger.debug({ url }, 'Failed to parse URL during stats reporting');
                continue;
            }
            const { hostname, origin, pathname } = parsedUrl;
            const baseUrl = `${origin}${pathname}`;
            urls[baseUrl] ??= {};
            urls[baseUrl][method] ??= {};
            urls[baseUrl][method][status] ??= 0;
            urls[baseUrl][method][status] += 1;
            rawRequests.push(`${method} ${url} ${status} ${reqMs} ${queueMs}`);
            hostRequests[hostname] ??= [];
            hostRequests[hostname].push(dataPoint);
        }
        const hosts = {};
        for (const [hostname, dataPoints] of Object.entries(hostRequests)) {
            const count = dataPoints.length;
            const reqTimes = dataPoints.map((r) => r.reqMs);
            const queueTimes = dataPoints.map((r) => r.queueMs);
            const reqReport = makeTimingReport(reqTimes);
            const queueReport = makeTimingReport(queueTimes);
            hosts[hostname] = {
                count,
                reqAvgMs: reqReport.avgMs,
                reqMedianMs: reqReport.medianMs,
                reqMaxMs: reqReport.maxMs,
                queueAvgMs: queueReport.avgMs,
                queueMedianMs: queueReport.medianMs,
                queueMaxMs: queueReport.maxMs,
            };
        }
        return {
            urls,
            rawRequests,
            hostRequests,
            hosts,
            requests,
        };
    }
    static report() {
        const { urls, rawRequests, hostRequests, hosts, requests } = HttpStats.getReport();
        logger_1.logger.trace({ rawRequests, hostRequests }, 'HTTP full statistics');
        logger_1.logger.debug({ hosts, requests }, 'HTTP statistics');
        logger_1.logger.trace({ urls }, 'HTTP URL statistics');
    }
}
exports.HttpStats = HttpStats;
function sortObject(obj) {
    const result = {};
    for (const key of Object.keys(obj).sort()) {
        result[key] = obj[key];
    }
    return result;
}
class HttpCacheStats {
    static getData() {
        return memCache.get('http-cache-stats') ?? {};
    }
    static read(key) {
        return (this.getData()?.[key] ?? {
            hit: 0,
            miss: 0,
        });
    }
    static write(key, data) {
        const stats = memCache.get('http-cache-stats') ?? {};
        stats[key] = data;
        memCache.set('http-cache-stats', stats);
    }
    static getBaseUrl(url) {
        const parsedUrl = (0, url_1.parseUrl)(url);
        if (!parsedUrl) {
            logger_1.logger.debug({ url }, 'Failed to parse URL during cache stats');
            return null;
        }
        const { origin, pathname } = parsedUrl;
        const baseUrl = `${origin}${pathname}`;
        return baseUrl;
    }
    static incLocalHits(url) {
        const baseUrl = HttpCacheStats.getBaseUrl(url);
        if (baseUrl) {
            const host = baseUrl;
            const stats = HttpCacheStats.read(host);
            stats.localHit ??= 0;
            stats.localHit += 1;
            HttpCacheStats.write(host, stats);
        }
    }
    static incLocalMisses(url) {
        const baseUrl = HttpCacheStats.getBaseUrl(url);
        if (baseUrl) {
            const host = baseUrl;
            const stats = HttpCacheStats.read(host);
            stats.localMiss ??= 0;
            stats.localMiss += 1;
            HttpCacheStats.write(host, stats);
        }
    }
    static incRemoteHits(url) {
        const baseUrl = HttpCacheStats.getBaseUrl(url);
        if (baseUrl) {
            const host = baseUrl;
            const stats = HttpCacheStats.read(host);
            stats.hit += 1;
            HttpCacheStats.write(host, stats);
        }
    }
    static incRemoteMisses(url) {
        const baseUrl = HttpCacheStats.getBaseUrl(url);
        if (baseUrl) {
            const host = baseUrl;
            const stats = HttpCacheStats.read(host);
            stats.miss += 1;
            HttpCacheStats.write(host, stats);
        }
    }
    static report() {
        const data = HttpCacheStats.getData();
        let report = {};
        for (const [url, stats] of Object.entries(data)) {
            const parsedUrl = (0, url_1.parseUrl)(url);
            if (parsedUrl) {
                const { origin, pathname } = parsedUrl;
                report[origin] ??= {};
                report[origin][pathname] = stats;
            }
        }
        for (const [host, hostStats] of Object.entries(report)) {
            report[host] = sortObject(hostStats);
        }
        report = sortObject(report);
        logger_1.logger.debug(report, 'HTTP cache statistics');
    }
}
exports.HttpCacheStats = HttpCacheStats;
//# sourceMappingURL=stats.js.map