共计 8613 个字符,预计需要花费 22 分钟才能阅读完成。
提醒:本文最后更新于2025-07-07 14:37,文中所关联的信息可能已发生改变,请知悉!
在对 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
提供的信息统计方法
-
- 返回 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 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. 相关参考