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