"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SystemInformationResponse = exports.SystemInformationRequest = void 0;
exports.getTimeInfo = getTimeInfo;
exports.getCPUInfo = getCPUInfo;
exports.getMemoryInfo = getMemoryInfo;
exports.getHDBProcessInfo = getHDBProcessInfo;
exports.getDiskInfo = getDiskInfo;
exports.getNetworkInfo = getNetworkInfo;
exports.getSystemInformation = getSystemInformation;
exports.getTableSize = getTableSize;
exports.getMetrics = getMetrics;
exports.systemInformation = systemInformation;
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const systeminformation_1 = __importDefault(require("systeminformation"));
const harper_logger_ts_1 = __importDefault(require("../logging/harper_logger.js"));
const hdbTerms = __importStar(require("../hdbTerms.js"));
const storageReclamation_ts_1 = require("../../server/storageReclamation.js");
const lmdbGetTableSize_ts_1 = require("../../dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js");
const manageThreads_js_1 = require("../../server/threads/manageThreads.js");
const env = __importStar(require("./environmentManager.js"));
const databases_ts_1 = require("../../resources/databases.js");
const TableSizeObject_ts_1 = require("../../dataLayer/harperBridge/TableSizeObject.js");
const rocksdb_js_1 = require("@harperfast/rocksdb-js");
env.initSync();
//this will hold the system_information which is static to improve performance
let systemInformationCache = undefined;
class SystemInformationRequest {
    operator;
    attributes;
    constructor(attributes) {
        this.operator = hdbTerms.OPERATIONS_ENUM.SYSTEM_INFORMATION;
        this.attributes = attributes;
    }
}
exports.SystemInformationRequest = SystemInformationRequest;
class SystemInformationResponse {
    system;
    time;
    cpu;
    memory;
    disk;
    network;
    harperdb_processes;
    table_size;
    metrics;
    threads;
    constructor(system, time, cpu, memory, disk, network, harperdbProcesses, tableSize, metrics, threads) {
        this.system = system;
        this.time = time;
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
        this.network = network;
        this.harperdb_processes = harperdbProcesses;
        this.table_size = tableSize;
        this.metrics = metrics;
        this.threads = threads;
    }
}
exports.SystemInformationResponse = SystemInformationResponse;
/**
 * Returns the current local time, uptime, timezone, and timezone name.
 */
function getTimeInfo() {
    return systeminformation_1.default.time();
}
/**
 * Detects CPU information such as manufacturer, brand, vendor, speed, cores, physical cores, and
 * processors.
 */
async function getCPUInfo() {
    try {
        const [cpu, cpu_speed, loadInfo] = await Promise.all([systeminformation_1.default.cpu(), systeminformation_1.default.cpuCurrentSpeed(), systeminformation_1.default.currentLoad()]);
        const { manufacturer, brand, vendor, speed, cores, physicalCores, performanceCores, efficiencyCores, processors, flags, virtualization, } = cpu;
        const { avgLoad, cpus, currentLoad, currentLoadUser, currentLoadSystem, currentLoadNice, currentLoadIdle, currentLoadIrq, } = loadInfo;
        return {
            manufacturer,
            brand,
            vendor,
            speed,
            cores,
            physicalCores,
            performanceCores,
            efficiencyCores,
            processors,
            flags,
            virtualization,
            cpu_speed,
            current_load: {
                avgLoad,
                cpus: cpus.map(({ load, loadUser, loadSystem, loadNice, loadIdle, loadIrq }) => ({
                    load,
                    loadUser,
                    loadSystem,
                    loadNice,
                    loadIdle,
                    loadIrq,
                })),
                currentLoad,
                currentLoadUser,
                currentLoadSystem,
                currentLoadNice,
                currentLoadIdle,
                currentLoadIrq,
            },
        };
    }
    catch (e) {
        harper_logger_ts_1.default.error(`error in getCPUInfo: ${e}`);
        return null;
    }
}
/**
 * Detect system and Node.js memory usage.
 */
async function getMemoryInfo() {
    try {
        const { total, free, used, active, available, reclaimable, swaptotal, swapused, swapfree, writeback, dirty } = await systeminformation_1.default.mem();
        return {
            total,
            free,
            used,
            active,
            available,
            reclaimable,
            swaptotal,
            swapused,
            swapfree,
            writeback,
            dirty,
            ...process.memoryUsage(),
        };
    }
    catch (e) {
        harper_logger_ts_1.default.error(`error in getMemoryInfo: ${e}`);
        return null;
    }
}
async function getHdbPid() {
    try {
        return Number.parseInt(await (0, promises_1.readFile)(node_path_1.default.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), hdbTerms.HDB_PID_FILE), 'utf8'));
    }
    catch (err) {
        if (err.code === hdbTerms.NODE_ERROR_CODES.ENOENT) {
            harper_logger_ts_1.default.warn(`Unable to locate 'hdb.pid' file, try stopping and starting Harper. This could be because Harper is not running.`);
        }
        else {
            throw err;
        }
    }
}
/**
 * Detects the Harper process PID and returns the process info.
 * @returns {Promise<{core: []}>}
 */
async function getHDBProcessInfo() {
    const harperdbProcesses = {
        core: [],
    };
    try {
        const [processes, hdbPid] = await Promise.all([systeminformation_1.default.processes(), getHdbPid()]);
        const proc = processes.list.find((p) => p.pid === hdbPid);
        if (proc) {
            harperdbProcesses.core.push(proc);
        }
    }
    catch (e) {
        harper_logger_ts_1.default.error(`error in getHDBProcessInfo: ${e}`);
    }
    return harperdbProcesses;
}
/**
 * Retrieves disk related info & stats
 * @returns {Promise<DiskInfo>}
 */
async function getDiskInfo() {
    const disk = {};
    const quotaStatus = await (0, storageReclamation_ts_1.getQuotaStatus)();
    if (quotaStatus?.quotaBytes) {
        disk.free_space_basis = 'quota';
        disk.quota_size_bytes = quotaStatus.quotaBytes;
        disk.quota_used_bytes = quotaStatus.usedBytes;
        disk.quota_status_age_seconds = Math.floor((Date.now() - quotaStatus.updatedAt) / 1000);
    }
    else {
        disk.free_space_basis = 'filesystem';
    }
    try {
        if (!env.get(hdbTerms.CONFIG_PARAMS.OPERATIONSAPI_SYSINFO_DISK))
            return disk;
        const [disksIO, fsStats, fsSize] = await Promise.all([systeminformation_1.default.disksIO(), systeminformation_1.default.fsStats(), systeminformation_1.default.fsSize()]);
        const { rIO, wIO, tIO } = disksIO;
        disk.io = { rIO, wIO, tIO };
        const { rx, tx, wx } = fsStats;
        disk.read_write = { rx, tx, wx };
        disk.size = fsSize;
    }
    catch (e) {
        harper_logger_ts_1.default.error(`error in getDiskInfo: ${e}`);
    }
    return disk;
}
/**
 * Detects networking connection information & stats
 * @returns {Promise<{interfaces: [], default_interface: null, stats: [], latency: {}, connections: []}>}
 */
async function getNetworkInfo() {
    const network = {
        default_interface: null,
        latency: {},
        interfaces: [],
        stats: [],
        connections: [],
    };
    try {
        if (!env.get(hdbTerms.CONFIG_PARAMS.OPERATIONSAPI_SYSINFO_NETWORK))
            return network;
        const [defaultInterface, latency, nInterfaces, stats] = await Promise.all([
            systeminformation_1.default.networkInterfaceDefault(),
            systeminformation_1.default.inetChecksite('https://google.com').catch(() => ({})),
            systeminformation_1.default.networkInterfaces(),
            systeminformation_1.default.networkStats(),
        ]);
        network.default_interface = defaultInterface || null;
        network.latency = latency;
        for (const nInterface of nInterfaces) {
            const { iface, ifaceName, default: isDefault, ip4, ip4subnet, ip6, ip6subnet, mac, operstate, type, duplex, speed, } = nInterface;
            network.interfaces.push({
                iface,
                ifaceName,
                default: isDefault,
                ip4,
                ip4subnet,
                ip6,
                ip6subnet,
                mac,
                operstate,
                type,
                duplex,
                speed,
            });
        }
        for (const nStat of stats) {
            const { iface, operstate, rx_bytes, rx_dropped, rx_errors, tx_bytes, tx_dropped, tx_errors } = nStat;
            network.stats.push({ iface, operstate, rx_bytes, rx_dropped, rx_errors, tx_bytes, tx_dropped, tx_errors });
        }
    }
    catch (e) {
        harper_logger_ts_1.default.error(`error in getNetworkInfo: ${e}`);
    }
    return network;
}
/**
 * Detect operating system and Node.js runtime information.
 * @returns {Promise<SystemInfo>}
 */
async function getSystemInformation() {
    if (systemInformationCache !== undefined) {
        return systemInformationCache;
    }
    let systemInfo = {};
    try {
        const [osInfo, versions] = await Promise.all([systeminformation_1.default.osInfo(), systeminformation_1.default.versions('node, npm')]);
        const { platform, distro, release, codename, kernel, arch, hostname, fqdn } = osInfo;
        const { node, npm } = versions;
        systemInfo = {
            platform,
            distro,
            release,
            codename,
            kernel,
            arch,
            hostname,
            fqdn,
            node_version: node,
            npm_version: npm,
        };
        systemInformationCache = systemInfo;
    }
    catch (e) {
        harper_logger_ts_1.default.error(`error in getSystemInformation: ${e}`);
    }
    return systemInfo;
}
function rocksdbGetTableSize(table) {
    const rocksdb = table.primaryStore;
    const stats = rocksdb.getStats();
    const transactionLogSize = rocksdb
        .listLogs()
        .reduce((sum, logName) => sum + rocksdb.useLog(logName).getLogFileSize(), 0);
    return new TableSizeObject_ts_1.TableSizeObject(table.databaseName, table.tableName, stats['rocksdb.estimate-live-data-size'] ?? 0, stats['rocksdb.estimate-num-keys'] ?? 0, transactionLogSize
    // transactionLogRecordCount - currently not supported by `rocksdb-js`
    );
}
/**
 * Retrieves table size information.
 * @returns {TableSizeObject[]}
 */
function getTableSize() {
    const results = [];
    const databases = (0, databases_ts_1.getDatabases)();
    for (const db of Object.values(databases)) {
        for (const table of Object.values(db)) {
            if (table.primaryStore.rootStore instanceof rocksdb_js_1.RocksDatabase) {
                results.push(rocksdbGetTableSize(table));
            }
            else {
                results.push((0, lmdbGetTableSize_ts_1.lmdbGetTableSize)(table));
            }
        }
    }
    return results;
}
const rocksDBDatabaseLevelStats = new Set([
    'blockCacheCapacity',
    'blockCacheDataHit',
    'blockCacheDataMiss',
    'blockCacheFilterHit',
    'blockCacheFilterMiss',
    'blockCacheHit',
    'blockCacheIndexHit',
    'blockCacheIndexMiss',
    'blockCacheMiss',
    'blockCachePinnedUsage',
    'blockCacheUsage',
    'bytesRead',
    'bytesWritten',
    'dbFlushMicros',
    'dbGetMicros',
    'dbSeekMicros',
    'dbWriteMicros',
    'noFileErrors',
    'numberKeysRead',
    'numberKeysWritten',
    'numberReseeksIteration',
    'numRunningFlushes',
    'oldestSnapshotTime',
    'stallMicros',
    'txnOverheadMutexOldCommitMap',
    'txnOverheadMutexPrepare',
    'txnOverheadMutexSnapshot',
]);
// Strips the "rocksdb." prefix and converts kebab-case to camelCase
function toRocksDBCamelCase(key) {
    return key.replace(/^rocksdb\./, '').replace(/[-.]([a-z])/g, (_, c) => c.toUpperCase());
}
function getRocksDBStats(table, dbStats) {
    const stats = table.primaryStore.getStats();
    const tableStats = (dbStats.tables[table.tableName] = {});
    for (const [key, value] of Object.entries(stats)) {
        const name = toRocksDBCamelCase(key);
        if (rocksDBDatabaseLevelStats.has(name)) {
            dbStats[name] = value;
        }
        else {
            tableStats[name] = value;
        }
    }
}
function getLMDBStats(table, dbStats) {
    if (!dbStats.readers) {
        const { root: _root, ...stats } = table.primaryStore.rootStore.getStats();
        Object.assign(dbStats, stats);
        dbStats.readers = table.primaryStore.rootStore
            .readerList?.()
            ?.split(/\n\s+/)
            .slice(1)
            .map((line) => {
            const [pid, thread, txnid] = line.trim().split(' ');
            return { pid, thread, txnid };
        });
        if (table.auditStore) {
            const { treeDepth, treeBranchPageCount, treeLeafPageCount, entryCount, overflowPages } = table.auditStore.getStats();
            dbStats.audit = { treeDepth, treeBranchPageCount, treeLeafPageCount, entryCount, overflowPages };
        }
    }
    const { entryCount, overflowPages, treeBranchPageCount, treeDepth, treeLeafPageCount } = table.primaryStore.getStats();
    dbStats.tables[table.tableName] = { entryCount, overflowPages, treeBranchPageCount, treeDepth, treeLeafPageCount };
}
/**
 * Get RocksDB or LMDB metrics for all databases and tables.
 * @returns {Promise<DatabaseMetrics>}
 */
async function getMetrics() {
    const databaseStats = {};
    const databases = (0, databases_ts_1.getDatabases)();
    for (const [dbName, db] of Object.entries(databases)) {
        const dbStats = { tables: {} };
        databaseStats[dbName] = dbStats;
        for (const [tableName, table] of Object.entries(db)) {
            try {
                if (table.primaryStore.rootStore instanceof rocksdb_js_1.RocksDatabase) {
                    getRocksDBStats(table, dbStats);
                }
                else {
                    getLMDBStats(table, dbStats);
                }
            }
            catch (error) {
                // if a database no longer exists, don't want to throw an error
                harper_logger_ts_1.default.notify(`Error getting stats for table ${tableName}: ${error}`);
            }
        }
    }
    return databaseStats;
}
const attributeMap = {
    system: getSystemInformation,
    time: getTimeInfo,
    cpu: getCPUInfo,
    memory: getMemoryInfo,
    disk: getDiskInfo,
    network: getNetworkInfo,
    harperdb_processes: getHDBProcessInfo,
    table_size: getTableSize,
    metrics: getMetrics,
    threads: manageThreads_js_1.getThreadInfo,
};
/**
 * Retrieves system information for the requested attributes.
 * @param {SystemInformationRequest} systemInfoReq
 * @returns {Promise<SystemInformationResponse>}
 */
async function systemInformation(systemInfoReq) {
    const attributes = Array.isArray(systemInfoReq.attributes) && systemInfoReq.attributes.length > 0
        ? systemInfoReq.attributes
        : Object.keys(attributeMap);
    const response = new SystemInformationResponse();
    await Promise.all(attributes
        .filter((attr) => attr in attributeMap)
        .map(async (attr) => {
        if (attr === 'database_metrics') {
            attr = 'metrics';
        }
        response[attr] = await attributeMap[attr]();
    }));
    return response;
}
//# sourceMappingURL=systemInformation.js.map