"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.memoryReportingInstance = void 0;
const Context = __importStar(require("../p2p/Context"));
const utils = __importStar(require("../utils"));
const CycleCreator = __importStar(require("../p2p/CycleCreator"));
const os_1 = __importDefault(require("os"));
const nestedCounters_1 = require("../utils/nestedCounters");
const process_1 = __importStar(require("process"));
const debugMiddleware_1 = require("../network/debugMiddleware");
const NodeList = __importStar(require("../p2p/NodeList"));
const child_process_1 = require("child_process");
const network_1 = require("../network");
const lib_types_1 = require("@shardeum-foundation/lib-types");
class MemoryReporting {
    constructor(shardus) {
        this.crypto = null;
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        exports.memoryReportingInstance = this;
        this.report = [];
        this.shardus = shardus;
        this.lastCPUTimes = this.getCPUTimes();
    }
    registerEndpoints() {
        Context.network.registerExternalGet('memory', debugMiddleware_1.isDebugModeMiddlewareLow, (req, res) => {
            const toMB = 1 / 1000000;
            const report = process_1.default.memoryUsage();
            res.write(`System Memory Report.  Timestamp: ${(0, network_1.shardusGetTime)()}\n`);
            res.write(`rss: ${(report.rss * toMB).toFixed(2)} MB\n`);
            res.write(`heapTotal: ${(report.heapTotal * toMB).toFixed(2)} MB\n`);
            res.write(`heapUsed: ${(report.heapUsed * toMB).toFixed(2)} MB\n`);
            res.write(`external: ${(report.external * toMB).toFixed(2)} MB\n`);
            res.write(`arrayBuffers: ${(report.arrayBuffers * toMB).toFixed(2)} MB\n\n\n`);
            this.gatherReport();
            this.reportToStream(this.report, res);
            res.end();
        });
        Context.network.registerExternalGet('memory-short', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            // only here to so we can test the rare event counter system
            nestedCounters_1.nestedCountersInstance.countRareEvent('test', 'memory-short');
            const toMB = 1 / 1000000;
            const report = process_1.default.memoryUsage();
            res.write(`System Memory Report.  Timestamp: ${(0, network_1.shardusGetTime)()}\n`);
            res.write(`rss: ${(report.rss * toMB).toFixed(2)} MB\n`);
            res.write(`heapTotal: ${(report.heapTotal * toMB).toFixed(2)} MB\n`);
            res.write(`heapUsed: ${(report.heapUsed * toMB).toFixed(2)} MB\n`);
            res.write(`external: ${(report.external * toMB).toFixed(2)} MB\n`);
            res.write(`arrayBuffers: ${(report.arrayBuffers * toMB).toFixed(2)} MB\n`);
            res.end();
        });
        Context.network.registerExternalGet('nodelist', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            this.report = [];
            this.addNodesToReport();
            res.write('\n');
            this.reportToStream(this.report, res);
            res.write('\n');
            res.end();
        });
        Context.network.registerExternalGet('netstats', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            this.report = [];
            res.write('\n');
            this.addNetStatsToReport();
            this.reportToStream(this.report, res);
            res.write('\n');
            res.end();
        });
        Context.network.registerExternalGet('top', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            // This would work well in linux OS.
            const top = (0, child_process_1.spawn)('top', ['b', '-n', '10', '1']);
            top.stdout.on('data', (dataBuffer) => {
                res.write(dataBuffer);
                top.kill();
                res.end();
            });
            top.on('close', (code) => {
                console.log(`child process exited with code ${code}`);
            });
            top.stderr.on('data', (data) => {
                console.log('top command error', data);
                res.write('top command error');
                top.kill();
                res.end();
            });
        });
        Context.network.registerExternalGet('df', debugMiddleware_1.isDebugModeMiddlewareMultiSig, (req, res) => {
            const df = (0, child_process_1.spawn)('df');
            df.stdout.on('data', (dataBuffer) => {
                res.write(dataBuffer);
                df.kill();
                res.end();
            });
            df.on('close', (code) => {
                console.log(`child process exited with code ${code}`);
            });
            df.stderr.on('data', (data) => {
                console.log('df command error', data);
                res.write('df command error');
                df.kill();
                res.end();
            });
        });
        Context.network.registerExternalGet('memory-gc', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            res.write(`System Memory Report.  Timestamp: ${(0, network_1.shardusGetTime)()}\n`);
            try {
                if (global.gc) {
                    global.gc();
                    res.write('garbage collected!');
                }
                else {
                    res.write('No access to global.gc.  run with node --expose-gc');
                }
            }
            catch (e) {
                res.write('ex:No access to global.gc.  run with node --expose-gc');
            }
            res.end();
        });
        Context.network.registerExternalGet('scaleFactor', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            res.write(`Scale debug  Timestamp: ${(0, network_1.shardusGetTime)()} offset: ${(0, network_1.getNetworkTimeOffset)()} \n`);
            try {
                res.write(`CycleAutoScale.  ${CycleCreator.scaleFactor}`);
            }
            catch (e) {
                res.write(lib_types_1.Utils.safeStringify(e));
            }
            res.end();
        });
        Context.network.registerExternalGet('time-report', debugMiddleware_1.isDebugModeMiddleware, (req, res) => {
            const timeReport = {
                now: Date.now,
                shardusGetTime: (0, network_1.shardusGetTime)(),
                offset: (0, network_1.getNetworkTimeOffset)(),
                NPT: (0, network_1.getLastNTPObject)(),
            };
            res.write(JSON.stringify(timeReport, null, 2));
            res.end();
        });
    }
    addNodesToReport() {
        if (NodeList.activeByIdOrder) {
            const allNodeIds = [];
            for (const node of NodeList.activeByIdOrder) {
                allNodeIds.push(utils.makeShortHash(node.id));
            }
            this.addToReport('P2P', 'Nodelist', `${utils.stringifyReduce(allNodeIds)}`, 1);
        }
    }
    getMemoryStringBasic() {
        const toMB = 1 / 1000000;
        const report = process_1.default.memoryUsage();
        let outStr = `rss: ${(report.rss * toMB).toFixed(2)} MB`;
        //todo integrate this into the main stats tsv
        if (this.shardus && this.shardus.stateManager) {
            const numActiveNodes = NodeList.activeByIdOrder.length;
            const queueCount = this.shardus.stateManager.transactionQueue._transactionQueue.length;
            const archiveQueueCount = this.shardus.stateManager.transactionQueue.archivedQueueEntries.length;
            outStr += ` nds:${numActiveNodes} qCt:${queueCount} aAr:${archiveQueueCount}`;
        }
        outStr += '\n';
        return outStr;
    }
    addToReport(category, subcat, itemKey, count) {
        const obj = { category, subcat, itemKey, count };
        this.report.push(obj);
    }
    reportToStream(report, stream) {
        for (const item of report) {
            const { category, subcat, itemKey, count } = item;
            const countStr = `${count}`;
            stream.write(`${countStr.padStart(10)} ${category} ${subcat} ${itemKey}\n`);
        }
    }
    gatherReport() {
        this.report = [];
        this.gatherStateManagerReport();
        this.systemProcessReport();
        this.addNetStatsToReport();
    }
    gatherStateManagerReport() {
        if (this.shardus && this.shardus.stateManager) {
            if (NodeList.activeByIdOrder) {
                const numActiveNodes = NodeList.activeByIdOrder.length;
                this.addToReport('P2P', 'Nodelist', 'numActiveNodes', numActiveNodes);
            }
            const cacheDbg = this.shardus.stateManager.accountCache.getDebugStats();
            this.addToReport('StateManager', 'AccountsCache', 'workingAccounts', cacheDbg[0]);
            this.addToReport('StateManager', 'AccountsCache', 'mainMap', cacheDbg[1]);
            const queueCount = this.shardus.stateManager.transactionQueue._transactionQueue.length;
            this.addToReport('StateManager', 'TXQueue', 'queueCount', queueCount);
            const pendingQueueCount = this.shardus.stateManager.transactionQueue.pendingTransactionQueue.length;
            this.addToReport('StateManager', 'TXQueue', 'pendingQueueCount', pendingQueueCount);
            const archiveQueueCount = this.shardus.stateManager.transactionQueue.archivedQueueEntries.length;
            this.addToReport('StateManager', 'TXQueue', 'archiveQueueCount', archiveQueueCount);
            const executeQueueLength = this.shardus.stateManager.transactionQueue.getExecuteQueueLength();
            this.addToReport('StateManager', 'TXQueue', 'executeQueueLength', executeQueueLength);
            for (const syncTracker of this.shardus.stateManager.accountSync.syncTrackers) {
                const partition = `${utils.stringifyReduce(syncTracker.range?.low)} - ${utils.stringifyReduce(syncTracker.range?.high)}`;
                this.addToReport('StateManager', 'SyncTracker', `isGlobal:${syncTracker.isGlobalSyncTracker} started:${syncTracker.syncStarted} finished:${syncTracker.syncFinished} partition:${partition}`, 1);
            }
            const inSync = !this.shardus.stateManager.accountPatcher.failedLastTrieSync;
            this.addToReport('Patcher', 'insync', `${inSync}`, 1);
            this.addToReport('Patcher', 'history', lib_types_1.Utils.safeStringify(this.shardus.stateManager.accountPatcher.syncFailHistory), 1);
            this.addToReport('Patcher', 'insync', `${inSync}`, 1);
        }
    }
    getCPUTimes() {
        const cpus = os_1.default.cpus();
        const times = [];
        for (const cpu of cpus) {
            const timeObj = {};
            let total = 0;
            for (const [key, value] of Object.entries(cpu.times)) {
                const time = Number(value);
                total += time;
                // eslint-disable-next-line security/detect-object-injection
                timeObj[key] = value;
            }
            timeObj['total'] = total;
            times.push(timeObj);
        }
        return times;
    }
    cpuPercent() {
        const currentTimes = this.getCPUTimes();
        const deltaTimes = [];
        const percentTimes = [];
        let percentTotal = 0;
        for (let i = 0; i < currentTimes.length; i++) {
            /* eslint-disable security/detect-object-injection */
            const currentTimeEntry = currentTimes[i];
            const lastTimeEntry = this.lastCPUTimes[i];
            const deltaTimeObj = {};
            for (const [key] of Object.entries(currentTimeEntry)) {
                deltaTimeObj[key] = currentTimeEntry[key] - lastTimeEntry[key];
            }
            deltaTimes.push(deltaTimeObj);
            for (const [key] of Object.entries(currentTimeEntry)) {
                percentTimes[key] = deltaTimeObj[key] / deltaTimeObj['total'];
            }
            percentTotal += percentTimes['user'] || 0;
            percentTotal += percentTimes['nice'] || 0;
            percentTotal += percentTimes['sys'] || 0;
            /* eslint-enable security/detect-object-injection */
        }
        this.lastCPUTimes = currentTimes;
        const percentUsed = percentTotal / currentTimes.length;
        return percentUsed;
    }
    roundTo3decimals(num) {
        return Math.round((num + Number.EPSILON) * 1000) / 1000;
    }
    systemProcessReport() {
        this.addToReport('Process', 'CPU', 'cpuPercent', this.roundTo3decimals(this.cpuPercent() * 100));
        const avgCPU = this.shardus.statistics.getAverage('cpuPercent');
        this.addToReport('Process', 'CPU', 'cpuAVGPercent', this.roundTo3decimals(avgCPU * 100));
        const multiStats = this.shardus.statistics.getMultiStatReport('cpuPercent');
        multiStats.allVals.forEach((val, index) => {
            // eslint-disable-next-line security/detect-object-injection
            multiStats.allVals[index] = Math.round(val * 100);
        });
        multiStats.min = this.roundTo3decimals(multiStats.min * 100);
        multiStats.max = this.roundTo3decimals(multiStats.max * 100);
        multiStats.avg = this.roundTo3decimals(multiStats.avg * 100);
        this.addToReport('Process', 'CPU', `cpu: ${lib_types_1.Utils.safeStringify(multiStats)}`, 1);
        const report = (0, process_1.resourceUsage)();
        for (const [key, value] of Object.entries(report)) {
            this.addToReport('Process', 'Details', key, value);
        }
    }
    getShardusNetReport() {
        if (this.shardus == null || this.shardus.network == null || this.shardus.network.sn == null) {
            return null;
        }
        if (this.shardus.network.sn.stats != null) {
            const stats = this.shardus.network.sn.stats();
            return stats;
        }
        return null;
    }
    addNetStatsToReport() {
        const stats = this.getShardusNetReport();
        if (stats != null) {
            this.addToReport('NetStats', 'stats', 'stats', (JSON.stringify(stats, null, 4), 1));
        }
    }
}
exports.default = MemoryReporting;
//# sourceMappingURL=memoryReporting.js.map