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

本文发布于:2021-09-15,最后更新于:2022-08-11,如果内容失效请留言告知。
目录
[隐藏]

在对 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 统计类用于定时收集系统信息。具体的代码参考如下:

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;
}
}

7. 相关参考

点赞 (1)

发表回复

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

Captcha Code