Electron 应用中的系统信息收集与性能监测

目录
[隐藏]

在对 Electron 应用的问题进行分析时,相关的系统运行信息有时也是必不可少的,所以自然就有了系统基础信息收集和运行性能监测的需求。

Electron 中的原生能力基于 Node.js,所以 Electron 应用的系统数据收集大多数是基于 Node.js 提供的能力实现。在 Node.js 应用中,主要可以通过 processos 模块获取各种系统相关的信息,其中又可分为静态基础信息和动态变化的运行时信息。

下面对数据统计分析可能用到的信息进行简单的介绍。

1. Node.js 中的 process 模块提供的信息

  • process.pid 当前进程的 PID
  • process.ppid 当前进程的父进程的 PID
  • process.platform 返回标识运行 Node.js 进程的操作系统平台的字符串
  • process.cwd() 返回应用进程的当前工作目录
  • process.arch 其编译 Node.js 二进制文件的操作系统 CPU 架构。
  • process.argv 返回数组,其中包含启动 Node.js 进程时传入的命令行参数。
  • process.versions 返回 Node.js 及其依赖项的版本信息
  • process.env 应用运行环境的环境变量
  • process.cpuUsage([previousValue]) 在具有属性 user 和 system 的对象中返回当前进程的用户和系统 CPU 时间使用情况,其值为微秒值
  • process.memoryUsage() 返回描述 Node.js 进程的内存使用量(以字节为单位)的对象
    • .heapTotal.heapUsed 指的是 V8 的内存使用量。
    • .external 指的是绑定到 V8 管理的 JavaScript 对象的 C++ 对象的内存使用量。
    • .rss 常驻集大小,是进程在主内存设备(即总分配内存的子集)中占用的空间量,包括所有 C++ 和 JavaScript 对象和代码。
    • .arrayBuffers 是指为 ArrayBuffer 和 SharedArrayBuffer 分配的内存,包括所有 Node.js Buffer。 这也包含在 external 值中。 当 Node.js 被用作嵌入式库时,此值可能为 0,因为在这种情况下可能不会跟踪 ArrayBuffer 的分配。
  • process.resourceUsage() 返回当前进程的资源使用情况
    • Node.js@12.6+ / Electron@7.x+ 才会有
  • process.uptime() 返回当前 Node.js 进程已经运行的秒数

2. Electron 中的 process 模块扩展的信息

  • process.resourcesPath 资源目录的路径。可以统计应用被安装或启动运行的位置
  • process.type 当前进程的类型。可以是: browserrendererworker
  • process.getCPUUsage() 返回当前进程的 CPU 使用率
  • process.getHeapStatistics() 返回包含 V8 堆统计的对象。所有数据值以 KB 为单位
  • process.getSystemMemoryInfo() 返回一个对象,提供整个系统的内存使用统计。统计值单位为 KB
  • process.getProcessMemoryInfo() 返回一个对象,提供当前进程的内存使用统计。统计值单位为 KB
  • process.getBlinkMemoryInfo()
    • 返回带有Blink内存信息的对象。 可以用于调试渲染/DOM相关内存问题。所有值都以KB为单位
    • Electron@7+ 版本增加的方法

3. electron 提供的信息统计方法

4. Node.js 中的 os 模块提供的信息

  • os.platform() 操作系统平台的字符串标识,同 process.platform
  • os.hostname() 主机名
  • os.homedir() 用户主目录
  • os.type() 操作系统内核名
  • os.endianness() 返回标识为其编译 Node.js 二进制文件的 CPU 的字节序的字符串。大端序 BE、小端序 LE
  • os.cpus() 返回包含有关每个逻辑 CPU 内核的信息的对象数组。
  • os.uptime() 以秒为单位返回系统正常运行时间。同 process.uptime()
  • os.totalmem() 内存总量。同 process.getSystemMemoryInfo().total
  • os.freemem() 空闲内存大小。同 process.getSystemMemoryInfo().free
  • os.loadavg() 1、5 和 15 分钟的平均负载。windows 始终为 0
  • os.release() 以字符串形式返回操作系统的版本编号
  • os.tmpdir() 以字符串形式返回操作系统默认的临时文件的目录
  • os.networkInterfaces() 已分配网址的网卡信息
  • os.userInfo([options]) 返回有关当前有效用户的信息

5. 数据收集与分析

基于以上各 API 提供的能力,结合在实际测试中的实践经验,我们得出一些经验总结如下。

数据分类。我们将统计收集的信息可以分为静态基础信息和动态信息。静态基础信息即不会变化的数据,只需在初始化时统计并记录一次即可。静态信息可以用于分析具体的应用运行环境状态。动态信息即每次获取都可能会变化的数据。对应用的性能分析主要基于动态信息的定时监测与收集。

数据格式化。收集到的原始数据不够直观,可用于数据统计,但在直接显示上则需要做一定的预格式化处理。主要包括时间消耗、内存大小、CPU使用率三类数据,设计对应的格式化辅助函数,在取得数据后进行预处理即可。具体实现见下文代码示例。

各 API 提供的数据很多,在基于应用性能的数据收集与分析上,动态监测的数据主要为各进程的 CPU 使用率和内存使用量。绝大部分数据在主进程收集即可。

对于动态变化的数据,需按一定策略间隔性的收集,然后可以导出到针对性的分析平台,画出动态变化的曲线来观测分析。

6. Node.js 系统信息统计的实现代码参考

综合以上的信息收集与分析,简单实现了一个 SysInfoStats 统计类用于定时收集系统信息。具体的代码参考如下:

import os from 'os';
import { app, screen } from 'electron';

/** 将秒转换为日期 */
function formatSeconds(seconds: number) {
    seconds = seconds | 0;

    const day = (seconds / (3600 * 24)) | 0;
    const hours = ((seconds - day * 3600) / 3600) | 0;
    const minutes = ((seconds - day * 3600 * 24 - hours * 3600) / 60) | 0;
    const second = seconds % 60;

    return [day, hours, minutes, second].map(d => String(d).padStart(2, '0')).join(':');
}

function formatMem(mem: number) {
    if (mem > 1 << 30) return (mem / (1 << 30)).toFixed(2) + 'G';
    if (mem > 1 << 20) return (mem / (1 << 20)).toFixed(2) + 'M';
    if (mem > 1 << 10) return (mem / (1 << 10)).toFixed(2) + 'KB';
    return mem + 'B';
}

function formatMemForObject<t>(obj: T, unit = 1): Record<keyof T, string> {
    const d = {} as Record</keyof><keyof T, string> ;

    Object.keys(obj).forEach(k => {
        d[k] = formatMem(Number(obj[k]) * unit);
    });
    return d;
}

class SysInfoStats {
    /** 基础信息,不会变化的 */
    public baseInfo = {
        /** 当前进程的 PID */
        pid: process.pid,
        /** 当前进程的父进程的 PID */
        ppid: process.ppid,
        /** 程序启动位置 */
        cwd: process.cwd(),
        /** 主机名 */
        hostname: os.hostname(),
        /** 用户主目录 */
        homedir: os.homedir(),
        /** 系统架构 */
        arch: process.arch,
        /** 系统平台 Win32 */
        platform: process.platform || os.platform(),
        /** 操作系统内核名 Windows_NT */
        type: os.type(),
        /** 系统版本号 */
        release: os.release(),
        /** electron 相关的版本信息 */
        versions: process.versions,
        /** 字节序 */
        endianness: os.endianness(),
        /** 网卡信息 */
        get networkInterfaces() {
            return os.networkInterfaces();
        },
        /** 显示器信息 **/
        get allDisplays() {
            return screen.getAllDisplays();
        },
        /** 命令行参数 */
        argv: process.argv,
        /** 环境变量 */
        env: process.env,
    };
    /** 实时动态变更的信息 */
    public dynamicInfo = {
        // /** 内存总量,同 sysMemoryInfo.total */
        // totalmem: os.totalmem(),
        // /** 空闲内存,同 sysMemoryInfo.free */
        // freemem: os.freemem(),
        /** 系统的正常运行时间(以秒为单位) */
        get uptime() {
            return process.uptime();
        },
        /** 1、5 和 15 分钟的平均负载。windows 始终为 0 */
        get loadavg() {
            return os.loadavg();
        },
        /** memory usage statistics about the current process */
        processMemoryInfo: null as Electron.ProcessMemoryInfo,
        /** memory usage statistics about the entire system. */
        get sysMemoryInfo() {
            return formatMemForObject(process.getSystemMemoryInfo(), 1024);
        },
        /** V8 heap statistics. KB */
        get heapStatistics() {
            const r = process.getHeapStatistics();
            return formatMemForObject(r, 1024);
        },
        /** 内存使用量(以字节为单位) */
        get memoryUsage() {
            const r = process.memoryUsage();
            return formatMemForObject(r);
        },
        /** cpu 使用率 */
        get getCPUUsage() {
            return process.getCPUUsage();
        },
        /** 各子进程 cpu 占用率 */
        getAppMetrics() {
            const appMetrics = app.getAppMetrics();
            return appMetrics.map(item => {
                return {
                    pid: item.pid,
                    type: item.type,
                    percentCPUUsage: Number(item.cpu.percentCPUUsage).toFixed(2) + '%',
                    // electron@7+ 才有 item.memory 信息
                    // memory: formatMemForObject(item.memory, 1024),
                }
            });
        },
        /** cpu 信息 */
        get cpus() {
            return os.cpus();
        },
        /** 资源占用情况 -- electron@7.x+ 才有 */
        // get resourceUsage() {
        //    return process.resourceUsage();
        // },
    };

    constructor() {
        this.statsBase();
        // this.statsDynamic();
        this.interval();
    }
    /** 打印并写入日志 */
    private printLog(...args) {
        logger.log('[sysstats]', ...args);
    }
    private intervalTimer: NodeJS.Timeout;
    /**
     * 执行定时统计
     * @param intervalTime 定时器时间,默认为 5 分钟。小于 1000 表示停止
     */
    public interval(intervalTime = 5 * 60 * 1000) {
        clearTimeout(this.intervalTimer);

        const cInterval = Number((process.env as HippoEnv).HIPPO_SYSSTATS_INTERVAL);
        if (cInterval) intervalTime = cInterval;

        if (intervalTime > 1000) {
            this.intervalTimer = setInterval(() => this.statsDynamic(), intervalTime);
        }
    }

    /** 基本信息收集与打印 */
    public statsBase() {
        const msgList: string[] = [];
        const { baseInfo } = this;

        msgList.push(`\ncwd: ${baseInfo.cwd}`);
        // msgList.push(`Node.js 版本:${baseInfo.versions.node}`);
        msgList.push(`cpu架构:${baseInfo.arch}`);

        msgList.push('操作系统内核:' + baseInfo.type);
        msgList.push('平台:' + baseInfo.platform);
        msgList.push('主机名:' + baseInfo.hostname);
        msgList.push('主目录:' + baseInfo.homedir);

        const networkInterfaces = baseInfo.networkInterfaces;
        msgList.push('*****网卡信息*******');
        for (const key in networkInterfaces) {
            const list = networkInterfaces[key];
            msgList.push(`${key}:`);
            list.forEach((obj, idx) => {
                msgList.push(`[${idx}][${obj.cidr}]:`);
                msgList.push(`IP: ${obj.address}`);
                msgList.push(`NETMASK: ${obj.netmask}`);
                msgList.push(`MAC: ${obj.mac}`);
                msgList.push(`family: ${obj.family}`);
                // msgList.push(`远程访问:${obj.internal ? '否' : '是'}\n`);
            });
        }

        this.printLog('[base][overview]', msgList.join('\n'));
        this.printLog('[base][json]\n', this.baseInfo);
        return this.baseInfo;
    }
    /**
     * 实时系统信息收集与打印
     * @param printBase 是否打印固定不变的基础信息。构造函数内默认执行过一次,默认 false
     * @returns
     */
    public async statsDynamic(label = '', printBase = false) {
        if (printBase) this.statsBase();

        const sysInfo = this.dynamicInfo;
        const msgList: string[] = [];

        msgList.push('\n运行时长:' + formatSeconds(sysInfo.uptime));

        // const sysMemoryInfo = sysInfo.sysMemoryInfo;
        // msgList.push('内存大小:' + sysMemoryInfo.total);
        // msgList.push('空闲内存:' + formatMem(sysMemoryInfo.free));

        // electron@7+ 才有
        // msgList.push('userCPUTime:' + formatSeconds(sysInfo.resourceUsage.userCPUTime / (1000 * 1000)));
        // msgList.push('systemCPUTime:' + formatSeconds(sysInfo.resourceUsage.systemCPUTime / (1000 * 1000)));

        // 内存用量
        const memoryUsage = sysInfo.memoryUsage;
        msgList.push('*****memoryUsage*******');
        msgList.push(`V8 内存总量:\t ${memoryUsage.heapTotal}`);
        msgList.push(`V8 使用量:\t ${memoryUsage.heapUsed}`);
        msgList.push(`C++对象内存:\t ${memoryUsage.external}`);
        msgList.push(`主内存设备占用:\t ${memoryUsage.rss}`);

        // cpu
        const cpus = sysInfo.cpus;
        msgList.push('*****cpu信息*******');
        cpus.forEach((cpu, idx, _arr) => {
            const times = cpu.times;
            const useageRate = `${((1 - times.idle / (times.idle + times.user + times.nice + times.sys + times.irq)) * 100).toFixed(2)}%`;
            msgList.push([`cpu-${idx}:`, `使用率:${useageRate}`, `型号:${cpu.model}`, `频率:${cpu.speed}MHz`].join('  '));
        });

        sysInfo.processMemoryInfo = await this.getProcessMemoryInfo();

        this.printLog('[dynamicInfo][overview]' + label, msgList.join('\n'));
        this.printLog('[dynamicInfo][json]\n', sysInfo);

        return msgList;
    }
    /** 获取当前进程的内存占用信息 */
    public async getProcessMemoryInfo() {
        const processMemoryInfo = await process.getProcessMemoryInfo();

        Object.keys(processMemoryInfo).forEach(k => {
            processMemoryInfo[k] = formatMem(processMemoryInfo[k] * 1024);
        });
        this.dynamicInfo.processMemoryInfo = processMemoryInfo;

        return processMemoryInfo;
    }
    /** 获取 ip 和 mac 地址(可远程访问的) */
    public getIPAndMac() {
        const result = [] as { ip: string; mac: string; family: "IPv4" | "IPv6" }[];

        Object.keys(this.baseInfo.networkInterfaces).forEach(key => {
            const item = this.baseInfo.networkInterfaces[key];
            item.forEach(d => {
                if (!d.internal) {
                    result.push({ ip: d.address, mac: d.mac, family: d.family });
                }
            });
        });

        return result;
    }
}

7. 相关参考

点赞 (1)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code