目录
[隐藏]
在对 Electron 应用的问题进行分析时,相关的系统运行信息有时也是必不可少的,所以自然就有了系统基础信息收集和运行性能监测的需求。
Electron 中的原生能力基于 Node.js,所以 Electron 应用的系统数据收集大多数是基于 Node.js 提供的能力实现。在 Node.js 应用中,主要可以通过 process
和 os
模块获取各种系统相关的信息,其中又可分为静态基础信息和动态变化的运行时信息。
下面对数据统计分析可能用到的信息进行简单的介绍。
1. Node.js 中的 process
模块提供的信息
process.pid
当前进程的 PIDprocess.ppid
当前进程的父进程的 PIDprocess.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
当前进程的类型。可以是:browser
、renderer
、worker
- process.getCPUUsage() 返回当前进程的 CPU 使用率
- process.getHeapStatistics() 返回包含 V8 堆统计的对象。所有数据值以
KB
为单位 - process.getSystemMemoryInfo() 返回一个对象,提供整个系统的内存使用统计。统计值单位为
KB
- process.getProcessMemoryInfo() 返回一个对象,提供当前进程的内存使用统计。统计值单位为
KB
- process.getBlinkMemoryInfo()
- 返回带有Blink内存信息的对象。 可以用于调试渲染/DOM相关内存问题。所有值都以KB为单位
- 在
Electron@7+
版本增加的方法
3. electron
提供的信息统计方法
- electron.app.getAppMetrics()
- 返回 ProcessMetric[]: 包含所有与应用相关的进程的内存和CPU的使用统计的 ProcessMetric 对象的数组。
- 返回值在
electron@7+
才新增memory
字段,包含所有子进程的内存使用统计
- electron.screen.getAllDisplays() 返回一个窗口数组Display[],表示当前可用的显示器窗口。
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 始终为 0os.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; } }