"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TraceProcessorAdmissionError = exports.TP_UPLOAD_RESERVE_BYTES_ENV = exports.TP_OS_SAFETY_RESERVE_BYTES_ENV = exports.TP_MIN_ESTIMATE_BYTES_ENV = exports.TP_ESTIMATE_MULTIPLIER_ENV = exports.TP_RAM_BUDGET_FACTOR_ENV = exports.TP_RAM_BUDGET_BYTES_ENV = exports.TP_ADMISSION_CONTROL_ENV = void 0;
exports.resolveTraceProcessorMachineFactor = resolveTraceProcessorMachineFactor;
exports.estimateTraceProcessorRssBytes = estimateTraceProcessorRssBytes;
exports.traceProcessorAdmissionEnabled = traceProcessorAdmissionEnabled;
exports.getTraceProcessorRamBudgetStats = getTraceProcessorRamBudgetStats;
exports.decideTraceProcessorAdmission = decideTraceProcessorAdmission;
exports.assertTraceProcessorAdmission = assertTraceProcessorAdmission;
const os_1 = __importDefault(require("os"));
exports.TP_ADMISSION_CONTROL_ENV = 'SMARTPERFETTO_TP_ADMISSION_CONTROL';
exports.TP_RAM_BUDGET_BYTES_ENV = 'SMARTPERFETTO_TP_RAM_BUDGET_BYTES';
exports.TP_RAM_BUDGET_FACTOR_ENV = 'SMARTPERFETTO_TP_RAM_BUDGET_FACTOR';
exports.TP_ESTIMATE_MULTIPLIER_ENV = 'SMARTPERFETTO_TP_ESTIMATE_MULTIPLIER';
exports.TP_MIN_ESTIMATE_BYTES_ENV = 'SMARTPERFETTO_TP_MIN_ESTIMATE_BYTES';
exports.TP_OS_SAFETY_RESERVE_BYTES_ENV = 'SMARTPERFETTO_TP_OS_SAFETY_RESERVE_BYTES';
exports.TP_UPLOAD_RESERVE_BYTES_ENV = 'SMARTPERFETTO_TP_UPLOAD_RESERVE_BYTES';
const GIB = 1024 * 1024 * 1024;
const DEFAULT_ESTIMATE_MULTIPLIER = 1.5;
const DEFAULT_MIN_ESTIMATE_BYTES = 128 * 1024 * 1024;
function parsePositiveNumber(value) {
    if (!value)
        return null;
    const parsed = Number(value);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
function parseFeatureFlag(value) {
    if (value === undefined)
        return null;
    const normalized = value.trim().toLowerCase();
    if (['1', 'true', 'yes', 'on', 'enabled'].includes(normalized))
        return true;
    if (['0', 'false', 'no', 'off', 'disabled'].includes(normalized))
        return false;
    return null;
}
function resolveTraceProcessorMachineFactor(totalMemoryBytes = os_1.default.totalmem(), env = process.env) {
    const configured = parsePositiveNumber(env[exports.TP_RAM_BUDGET_FACTOR_ENV]);
    if (configured !== null)
        return Math.min(configured, 1);
    if (totalMemoryBytes < 16 * GIB)
        return 0.60;
    if (totalMemoryBytes <= 32 * GIB)
        return 0.75;
    return 0.85;
}
function estimateTraceProcessorRssBytes(traceSizeBytes, env = process.env) {
    const multiplier = parsePositiveNumber(env[exports.TP_ESTIMATE_MULTIPLIER_ENV]) ?? DEFAULT_ESTIMATE_MULTIPLIER;
    const minEstimate = parsePositiveNumber(env[exports.TP_MIN_ESTIMATE_BYTES_ENV]) ?? DEFAULT_MIN_ESTIMATE_BYTES;
    const size = Number.isFinite(traceSizeBytes) && traceSizeBytes > 0 ? traceSizeBytes : 0;
    return Math.max(Math.ceil(size * multiplier), minEstimate);
}
function traceProcessorAdmissionEnabled(env = process.env) {
    const configured = parseFeatureFlag(env[exports.TP_ADMISSION_CONTROL_ENV]);
    if (configured !== null)
        return configured;
    return true;
}
function getTraceProcessorRamBudgetStats(processors, env = process.env) {
    const totalMemoryBytes = os_1.default.totalmem();
    const nodeRssBytes = process.memoryUsage().rss;
    const osSafetyReserveBytes = Math.floor(parsePositiveNumber(env[exports.TP_OS_SAFETY_RESERVE_BYTES_ENV]) ?? Math.max(GIB, totalMemoryBytes * 0.05));
    const uploadReserveBytes = Math.floor(parsePositiveNumber(env[exports.TP_UPLOAD_RESERVE_BYTES_ENV]) ?? 0);
    const machineFactor = resolveTraceProcessorMachineFactor(totalMemoryBytes, env);
    const configuredBudget = parsePositiveNumber(env[exports.TP_RAM_BUDGET_BYTES_ENV]);
    const derivedAvailable = Math.max(0, totalMemoryBytes - nodeRssBytes - osSafetyReserveBytes - uploadReserveBytes);
    const budgetBytes = Math.floor(configuredBudget ?? (derivedAvailable * machineFactor));
    const observedProcessorRssBytes = processors.reduce((sum, processor) => {
        return sum + (processor.rssBytes && processor.rssBytes > 0 ? processor.rssBytes : 0);
    }, 0);
    const unknownRssProcessorCount = processors.filter(processor => processor.rssBytes === null).length;
    return {
        enabled: traceProcessorAdmissionEnabled(env),
        totalMemoryBytes,
        nodeRssBytes,
        osSafetyReserveBytes,
        uploadReserveBytes,
        machineFactor,
        budgetBytes,
        observedProcessorRssBytes,
        availableForNewLeaseBytes: Math.max(0, budgetBytes - observedProcessorRssBytes),
        activeProcessorCount: processors.length,
        unknownRssProcessorCount,
        estimateMultiplier: parsePositiveNumber(env[exports.TP_ESTIMATE_MULTIPLIER_ENV]) ?? DEFAULT_ESTIMATE_MULTIPLIER,
        minEstimateBytes: parsePositiveNumber(env[exports.TP_MIN_ESTIMATE_BYTES_ENV]) ?? DEFAULT_MIN_ESTIMATE_BYTES,
    };
}
function decideTraceProcessorAdmission(input) {
    const env = input.env ?? process.env;
    const estimatedRssBytes = estimateTraceProcessorRssBytes(input.traceSizeBytes, env);
    const stats = getTraceProcessorRamBudgetStats(input.processors, env);
    if (!stats.enabled) {
        return {
            admitted: true,
            traceId: input.traceId,
            traceSizeBytes: input.traceSizeBytes,
            estimatedRssBytes,
            stats,
        };
    }
    if (estimatedRssBytes <= stats.availableForNewLeaseBytes) {
        return {
            admitted: true,
            traceId: input.traceId,
            traceSizeBytes: input.traceSizeBytes,
            estimatedRssBytes,
            stats,
        };
    }
    return {
        admitted: false,
        traceId: input.traceId,
        traceSizeBytes: input.traceSizeBytes,
        estimatedRssBytes,
        stats,
        reason: `estimated trace_processor RSS ${estimatedRssBytes} exceeds available budget ${stats.availableForNewLeaseBytes}`,
    };
}
class TraceProcessorAdmissionError extends Error {
    constructor(decision) {
        super(decision.reason || 'Trace processor RAM admission rejected');
        this.decision = decision;
        this.name = 'TraceProcessorAdmissionError';
    }
}
exports.TraceProcessorAdmissionError = TraceProcessorAdmissionError;
function assertTraceProcessorAdmission(input) {
    const decision = decideTraceProcessorAdmission(input);
    if (!decision.admitted) {
        throw new TraceProcessorAdmissionError(decision);
    }
    return decision;
}
//# sourceMappingURL=traceProcessorRamBudget.js.map