在 Linux 下我们通过 top 或者 htop 命令可以看到当前的 CPU 资源利用率,另外在一些监控工具中你可能也遇见过,那么它是如何计算的呢?在 Nodejs 中我们该如何实现?
带着这些疑问,本节会先从 Linux 下的 CPU 利用率进行一个简单讲解做一下前置知识铺垫,之后会深入 Nodejs 源码,去探讨如何获取 CPU 信息及计算 CPU 某时间段的利用率。
开始之前,可以先看一张图,它展示了 Nodejs OS 模块读取系统 CPU 信息的整个过程调用,在下文中也会详细讲解,你会再次看到它。
LINUX 下 CPU 利用率
Linux 下 CPU 的利用率分为用户态(用户模式下执行时间)、系统态(系统内核执行)、空闲态(空闲系统进程执行时间),三者相加为 CPU 执行总时间,关于 CPU 的活动信息我们可以在 /proc/stat 文件查看。
CPU 利用率是指非系统空闲进程 / CPU 总执行时间。
> cat /proc/stat
cpu
2255
34
2290
22625563
6290
127
456
cpu0
1132
34
1441
11311718
3675
127
438
cpu1
1123
0
849
11313845
2614
0
18
intr
114930548
113199788
3
0
5
263
0
4 [... lots more numbers ...]
ctxt
1990473 # 自系统启动以来 CPU 发生的上下文交换次数
btime
1062191376 # 启动到现在为止的时间,单位为秒
processes
2915 # 系统启动以来所创建的任务数目
procs_running
1 # 当前运行队列的任务数目
procs_blocked
0 # 当前被阻塞的任务数目
上面第一行 cpu 表示总的 CPU 使用情况,下面的cpu0、cpu1 是指系统的每个 CPU 核心数运行情况(cpu0 + cpu1 + cpuN = cpu 总的核心数),我们看下第一行的含义。
user:系统启动开始累计到当前时刻,用户态的 CPU 时间(单位:jiffies),不包含 nice 值为负的进程。
nice:系统启动开始累计到当前时刻,nice 值为负的进程所占用的 CPU 时间。
system:系统启动开始累计到当前时刻,核心时间
idle:从系统启动开始累计到当前时刻,除硬盘IO等待时间以外其它等待时间
iowait:从系统启动开始累计到当前时刻,硬盘IO等待时间
irq:从系统启动开始累计到当前时刻,硬中断时间
softirq:从系统启动开始累计到当前时刻,软中断时间
关于 /proc/stat 的介绍,参考这里 http://www.linuxhowtos.org/System/procstat.htm
CPU 某时间段利用率公式
/proc/stat 文件下展示的是系统从启动到当下所累加的总的 CPU 时间,如果要计算 CPU 在某个时间段的利用率,则需要取 t1、t2 两个时间点进行运算。
t1~t2 时间段的 CPU 执行时间:
t1 = (user1 + nice1 + system1 + idle1 + iowait1 + irq1 + softirq1)
t2 = (user2 + nice2 + system2 + idle2 + iowait2 + irq2 + softirq2)
t = t2 - t1
t1~t2 时间段的 CPU 空闲使用时间:
idle = (idle2 - idle1)
t1~t2 时间段的 CPU 空闲率:
idleRate = idle / t;
t1~t2 时间段的 CPU 利用率:
usageRate = 1 - idleRate;
上面我们对 Linux 下 CPU 利用率做一个简单的了解,计算某时间段的 CPU 利用率公式可以先理解下,在下文最后会使用 Nodejs 进行实践。
这块可以扩展下,感兴趣的可以尝试下使用 shell 脚本实现 CPU 利用率的计算。
在 NODEJS 中是如何获取 CPU 信息的?
Nodejs os 模块 cpus() 方法返回一个对象数组,包含每个逻辑 CPU 内核信息。
提个疑问,这些数据具体是怎么获取的?和上面 Linuv 下的 /proc/stat 有关联吗?带着这些疑问只能从源码中一探究竟。
const os = require('os');
1. JS 层
lib 模块是 Node.js 对外暴露的 js 层模块代码,找到 os.js 文件,以下只保留 cpus 相关核心代码,其中 getCPUs 是通过 internalBinding('os') 导入。
internalBinding 就是链接 JS 层与 C++ 层的桥梁。
const {
getCPUs,
getFreeMem,
getLoadAvg,
} = internalBinding('os');
for 循环遍历每个 CPU 核心数据,赋值给变量 ci,遍历过程中 user、nice、sys... 这些数据就很熟悉了,正是我们在 Nodejs 中通过 os.cpus() 拿到的,这些数据都会保存在 result 对象中
遍历结束,通过 uv_free_cpu_info 对 cpu_infos、count 进行回收
最后,设置参数 Array::New(isolate, result.data(), result.size()) 以数组形式返回。
4. OS 操作系统层
4.1 linux-core.c:
在 deps/uv/ 下搜索 uv_cpu_info,会发现它的实现有很多 aix、cygwin.c、darwin.c、freebsd.c、linux-core.c 等等各种系统的,按照名字也可以看出 linux-core.c 似乎就是 Linux 下的实现了,重点也来看下这个的实现。
uv__open_file("/proc/stat") 参数 /proc/stat 这个正是 Linux 下 CPU 信息的位置。
4.2 core.c:
最终找到 uv__open_file() 方法的实现是在 /deps/uv/src/unix/core.c 文件,它以只读和执行后关闭模式获取一个文件的指针。
到这里也就该明白了,Linux 平台下我们使用 Nodejs os 模块的 cpus() 方法最终也是读取的 /proc/stat 文件获取的 CPU 信息。
什么时候该定位到 win 目录下?什么时候定位到 unix 目录下?
这取决于 Libuv 层,在“深入浅出 Nodejs” 一书中有这样一段话:“Node 在编译期间会判断平台条件,选择性编译 unix 目录或是 win 目录下的源文件到目标程序中”,所以这块是在编译时而非运行时来确定的。
5. 一图胜千言
通过对 OS 模块读取 CPU 信息流程梳理,再次展现 Nodejs 的经典架构:
JavaScript -> internalBinding -> C++ -> Libuv -> OS
在 NODEJS 中实践
了解了上面的原理之后在来 Nodejs 中实现,已经再简单不过了,系统层为我们提供了完美的 API 调用。
OS.CPUS() 数据指标
Nodejs os.cpus() 返回的对象数组中有一个 times 字段,包含了 user、nice、sys、idle、irq 几个指标数据,分别代表 CPU 在用户模式、良好模式、系统模式、空闲模式、中断模式下花费的毫秒数。相比 linux 下,直接通过 cat /proc/stat 查看更直观了。
本文先从 Linux 下 CPU 利用率的概念做一个简单的讲解,之后深入 Nodejs OS 模块源码对获取系统 CPU 信息进行了梳理,另一方面也再次呈现了 Nodejs 经典的架构 JavaScript -> internalBinding -> C++ -> Libuv -> OS 这对于梳理其它 API 是通用的,可以做为一定的参考,最后使用 Nodejs 对 CPU 利用率的计算进行了实践。
REFERENCE
http://www.penglixun.com/tech/system/how_to_calc_load_cpu.html
https://blog.csdn.net/htjx99/article/details/42920641
http://www.linuxhowtos.org/System/procstat.htm
https://github.com/Q-Angelo/node/tree/master/deps/uv/src/unix
http://nodejs.cn/api/os.html