When analyzing problems with Electron applications, relevant system operation information is sometimes essential, so naturally there is a need for system base information collection and operation performance monitoring.

The native capabilities in Electron are based on Node.js, so most of the system data collection for Electron applications is based on the capabilities provided by Node.js. In Node.js applications, various system-related information can be obtained mainly through the process and os modules, which can be divided into static basic information and dynamically changing runtime information.

The following is a brief introduction to the information that may be used for statistical analysis of data.

Information provided by the process module in Node.js

  • process.pid The PID of the current process
  • process.ppid The PID of the current process’s parent process
  • process.platform Returns a string identifying the OS platform on which the Node.js process is running
  • process.cwd() Returns the current working directory of the application process
  • process.arch The CPU architecture of the operating system on which the Node.js binary was compiled.
  • process.argv Returns the array containing the command line arguments passed in when starting the Node.js process.
  • process.versions returns the version information for Node.js and its dependencies
  • process.env The environment variable for the application runtime environment.
  • process.cpuUsage([previousValue]) in object with attributes user and system Returns the user and system CPU time usage of the current process in microseconds
  • process.memoryUsage() Returns an object describing the memory usage (in bytes) of the Node.js process
    • .heapTotal and .heapUsed refer to the memory usage of V8.
    • .external refers to the memory usage of C++ objects bound to JavaScript objects managed by V8.
    • .rss resident set size, which is the amount of space occupied by the process in the main memory device (i.e., a subset of the total allocated memory), including all C++ and JavaScript objects and code.
    • .arrayBuffers is the memory allocated for ArrayBuffer and SharedArrayBuffer, including all Node.js Buffer. This is also included in the external value. This value may be 0 when Node.js is used as an embedded library, as ArrayBuffer allocations may not be tracked in this case.
  • process.resourceUsage() Returns the resource usage of the current process
  • process.uptime() Returns the number of seconds the current Node.js process has been running

Information about the process module extension in Electron

  • process.resourcesPath The path to the resource directory. You can count the location where the application was installed or started running
  • process.type The type of the current process. Can be: browser, renderer, worker
  • process.getCPUUsage() Returns the CPU usage of the current process.
  • process.getHeapStatistics() Returns an object containing V8 heap statistics. All data values are in KB units
  • process.getSystemMemoryInfo() Returns an object that provides memory usage statistics for the entire system. The statistics are in KB
  • process.getProcessMemoryInfo() Returns an object that provides memory usage statistics for the current process. The statistics are in KB
  • process.getBlinkMemoryInfo()
    • Returns an object with Blink memory information. Can be used to debug rendering/DOM related memory issues. All values are in KB
    • Method added in Electron@7+ version

Statistical methods of information provided by electron

  • electron.app.getAppMetrics()
    • Returns ProcessMetric[]: an array of ProcessMetric objects containing memory and CPU usage statistics for all processes associated with the application.
    • New memory field added only to electron@7+, contains memory usage statistics for all sub-processes
  • electron.screen.getAllDisplays() returns an array of windows Display[], representing the currently available display windows .

Information provided by the os module in Node.js

  • os.platform() String identifier for the operating system platform, same as process.platform
  • os.hostname() hostname
  • os.homedir() user home directory
  • os.type() operating system kernel name
  • os.endianness() Returns a string identifying the byte order of the CPU for which the Node.js binaries were compiled. Big Ending Order BE, Little Ending Order LE.
  • os.cpus() Returns an array of objects containing information about each logical CPU kernel.
  • os.uptime() Returns the system uptime in seconds. Same as process.uptime()
  • os.totalmem() Total amount of memory. Same as process.getSystemMemoryInfo().total
  • os.freemem() The size of free memory. Same as process.getSystemMemoryInfo().free
  • os.loadavg() Average load for 1, 5 and 15 minutes. windows is always 0
  • os.release() Returns the version number of the operating system as a string
  • os.tmpdir() Returns the default directory of the operating system’s temporary files as a string.
  • os.networkInterfaces() NIC information for the assigned URL
  • os.userInfo([options]) Returns information about the currently active user

Data collection and analysis

Based on the capabilities provided by the above APIs, combined with practical experience in actual testing, we have come up with some experiences summarized below.

Data classification. We can classify the information collected by statistics into static basic information and dynamic information. Static basic information, i.e. data that does not change, can be counted and recorded only once during initialization. Static information can be used to analyze the state of specific application operating environment. Dynamic information is the data that may change every time it is acquired. The performance analysis of the application is mainly based on the regular monitoring and collection of dynamic information.

Data formatting. The raw data collected is not intuitive enough to be used for data statistics, but some pre-formatting is required for direct display. Mainly including time consumption, memory size, CPU utilization three types of data, design the corresponding formatting auxiliary functions, after obtaining data for pre-processing can be. See the code examples below for details.

The APIs provide a lot of data. In the data collection and analysis based on application performance, the data that are dynamically monitored are mainly the CPU usage and memory usage of each process. Most of the data can be collected in the main process.

For the dynamically changing data, it needs to be collected at intervals according to certain strategies, and then it can be exported to the targeted analysis platform to draw the dynamically changing curves for observation and analysis.

Node.js system information statistics implementation code reference

The above information collection and analysis, a simple implementation of SysInfoStats statistics class for the regular collection of system information. The specific code reference is as follows.

  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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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;
    }
}