/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as os from "os";
import * as process from "process";
import { Logger } from "@itwin/core-bentley";
import { BackendLoggerCategory } from "./BackendLoggerCategory";
import { IModelHost } from "./IModelHost";
import { IModelNative } from "./internal/NativePlatform";
// cspell:ignore ppid elap
const loggerCategory = BackendLoggerCategory.DevTools;
/** Utility to format the JSON created by DevTools.stats() to include the appropriate units
 * @internal
 */
export class DevToolsStatsFormatter {
    static _megaByteProps = ["totalmem", "freemem", "rss", "heapTotal", "heapUsed", "external"];
    static _percentProps = ["user", "nice", "sys", "idle", "irq", "cpuUsage"];
    static _mHzProps = ["speed"];
    static _secondsProps = ["uptime"];
    /** Replacer that includes units - can be used with JSON.stringify()  */
    static _replacer = (key, value) => {
        if (DevToolsStatsFormatter._megaByteProps.includes(key))
            return `${value.toFixed()} MB`;
        if (DevToolsStatsFormatter._percentProps.includes(key))
            return `${value.toFixed()}%`;
        if (DevToolsStatsFormatter._mHzProps.includes(key))
            return `${value.toString()} MHz`;
        if (DevToolsStatsFormatter._secondsProps.includes(key))
            return `${value.toFixed()} secs`;
        return value;
    };
    /** Converts the input stats to another JSON object with the appropriate units setup for various fields */
    static toFormattedJson(stats) {
        // Serialize the stats to a string with a replacer that sets up units during the serialization
        const statsStr = JSON.stringify(stats, DevToolsStatsFormatter._replacer);
        // Deserialize back to JSON
        return JSON.parse(statsStr);
    }
}
/**
 * Internal diagnostic utility
 * @internal
 */
export class DevTools {
    /** Receives a ping and returns true */
    static ping() {
        Logger.logInfo(loggerCategory, "Received ping at backend");
        return true;
    }
    static hrtimeToMS(hrtime) {
        return hrtime[0] * 1000 + hrtime[1] / 1000000;
    }
    static bytesToMegaBytes(bytes) {
        const megaBytes = bytes / Math.pow(1024, 2);
        return Math.round(megaBytes * 100) / 100;
    }
    static evaluateCpuUsage() {
        const NUMBER_OF_CPUS = os.cpus().length;
        const startTime = process.hrtime();
        const startUsage = process.cpuUsage();
        // spin the CPU for 500 milliseconds
        const now = Date.now();
        while (Date.now() - now < 500)
            ;
        const elapTime = process.hrtime(startTime);
        const elapUsage = process.cpuUsage(startUsage);
        const elapTimeMS = this.hrtimeToMS(elapTime);
        const elapUserMS = elapUsage.user / 1000; // microseconds to milliseconds
        const elapSystMS = elapUsage.system / 1000;
        const cpuPercent = Math.round((100 * (elapUserMS + elapSystMS) / elapTimeMS / NUMBER_OF_CPUS));
        return cpuPercent;
    }
    static evaluateCpus() {
        // Create a clone
        const srcCpus = os.cpus();
        const cpus = new Array(srcCpus.length);
        let ii = 0;
        for (const srcCpu of srcCpus)
            cpus[ii++] = { ...srcCpu };
        // Evaluate cpu usage as percentages
        for (const cpu of Object.values(cpus)) {
            const total = Object.values(cpu.times).reduce((_total, currValue) => _total += currValue, 0);
            const cpuTimes = cpu.times;
            for (const type of Object.keys(cpuTimes)) {
                const cpuPercent = Math.round(100 * cpuTimes[type] / total);
                cpuTimes[type] = cpuPercent;
            }
        }
        return cpus;
    }
    static evaluateMemoryUsage() {
        // Create a clone
        const memUsage = { ...process.memoryUsage() };
        const memUsageObj = memUsage;
        // Evaluate memory usage as mega bytes
        for (const type of Object.keys(memUsageObj)) {
            memUsageObj[type] = this.bytesToMegaBytes(memUsageObj[type]);
        }
        return memUsage;
    }
    static evaluateProcessStats() {
        return {
            uptime: process.uptime(),
            pid: process.pid,
            ppid: process.ppid,
            memoryUsage: this.evaluateMemoryUsage(),
        };
    }
    static evaluateOsStats() {
        return {
            platform: os.platform(),
            hostname: os.hostname(),
            totalmem: this.bytesToMegaBytes(os.totalmem()),
            freemem: this.bytesToMegaBytes(os.freemem()),
            uptime: os.uptime(),
            cpus: this.evaluateCpus(),
            cpuUsage: this.evaluateCpuUsage(),
        };
    }
    /** Returns JSON object with backend statistics */
    static stats() {
        try {
            const stats = {
                os: this.evaluateOsStats(),
                process: this.evaluateProcessStats(),
            };
            return stats;
        }
        catch (error) {
            Logger.logError(loggerCategory, "Could not fetch stats at backend");
            throw error;
        }
    }
    /** Sets up a log level at the backend and returns the old log level */
    static setLogLevel(inLoggerCategory, newLevel) {
        const oldLevel = Logger.getLevel(inLoggerCategory);
        Logger.logInfo(loggerCategory, `Setting log level`, () => ({ loggerCategory: inLoggerCategory, oldLevel, newLevel }));
        Logger.setLevel(inLoggerCategory, newLevel);
        IModelNative.platform.clearLogLevelCache();
        return oldLevel;
    }
    /** Obtains the backend application and iTwin.js Core versions */
    static async versions() {
        const availableRpcs = [];
        return {
            application: IModelHost.applicationVersion,
            iTwinJs: require("../../package.json").version, // eslint-disable-line @typescript-eslint/no-require-imports
            availableRpcs, // filled in on the frontend
        };
    }
}
//# sourceMappingURL=DevTools.js.map