本文发布于:2021-09-15,最后更新于:2022-08-11,如果内容失效请留言告知。
目录
[隐藏]
在对 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 统计类用于定时收集系统信息。具体的代码参考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | 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; } } |