首页 > 编程语言 >深入 NODEJS 源码探究 CPU 信息的获取与利用率计算

深入 NODEJS 源码探究 CPU 信息的获取与利用率计算

时间:2023-04-22 15:55:59浏览次数:31  
标签:info node NODEJS uv CPU 源码 os cpu

在 Linux 下我们通过 top 或者 htop 命令可以看到当前的 CPU 资源利用率,另外在一些监控工具中你可能也遇见过,那么它是如何计算的呢?在 Nodejs 中我们该如何实现?

带着这些疑问,本节会先从 Linux 下的 CPU 利用率进行一个简单讲解做一下前置知识铺垫,之后会深入 Nodejs 源码,去探讨如何获取 CPU 信息及计算 CPU 某时间段的利用率。

开始之前,可以先看一张图,它展示了 Nodejs OS 模块读取系统 CPU 信息的整个过程调用,在下文中也会详细讲解,你会再次看到它。

LINUX 下 CPU 利用率

Linux 下 CPU 的利用率分为用户态(用户模式下执行时间)、系统态(系统内核执行)、空闲态(空闲系统进程执行时间),三者相加为 CPU 执行总时间,关于 CPU 的活动信息我们可以在 /proc/stat 文件查看。

CPU 利用率是指非系统空闲进程 / CPU 总执行时间。

  1.  > cat /proc/stat
  2.  cpu  2255 34 2290 22625563 6290 127 456
  3.  cpu0 1132 34 1441 11311718 3675 127 438
  4.  cpu1 1123 0 849 11313845 2614 0 18
  5.  intr 114930548 113199788 3 0 5 263 0 4 [... lots more numbers ...]
  6.  ctxt 1990473 # 自系统启动以来 CPU 发生的上下文交换次数
  7.  btime 1062191376 # 启动到现在为止的时间,单位为秒
  8.  processes 2915 # 系统启动以来所创建的任务数目
  9.  procs_running 1 # 当前运行队列的任务数目
  10.  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 执行时间:

  1.  t1 = (user1 + nice1 + system1 + idle1 + iowait1 + irq1 + softirq1)
  2.  t2 = (user2 + nice2 + system2 + idle2 + iowait2 + irq2 + softirq2) 
  3.  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 有关联吗?带着这些疑问只能从源码中一探究竟。

  1.  const os = require('os');
  2.   
  3.  os.cpus();

1. JS 层

lib 模块是 Node.js 对外暴露的 js 层模块代码,找到 os.js 文件,以下只保留 cpus 相关核心代码,其中 getCPUs 是通过 internalBinding('os') 导入。

internalBinding 就是链接 JS 层与 C++ 层的桥梁。

  1.  // https://github.com/Q-Angelo/node/blob/master/lib/os.js#L41
  2.  const {
  3.    getCPUs,
  4.    getFreeMem,
  5.    getLoadAvg,
  6.    ...
  7.  } = internalBinding('os');
  8.    
  9. // https://github.com/Q-Angelo/node/blob/master/lib/os.js#L92
  10. function cpus() {
  11.   // [] is a bugfix for a regression introduced in 51cea61
  12.   const data = getCPUs() || [];
  13.   const result = [];
  14.    let i = 0;
  15.    while (i < data.length) {
  16.      result.push({
  17.        model: data[i++],
  18.        speed: data[i++],
  19.        times: {
  20.         user: data[i++],
  21.          nice: data[i++],
  22.          sys: data[i++],
  23.          idle: data[i++],
  24.          irq: data[i++]
  25.        }
  26.      });
  27.    }
  28.    return result;
  29.  }
  30.    
  31.  // https://github.com/Q-Angelo/node/blob/master/lib/os.js#L266
  32.  module.exports = {
  33.    cpus,
  34.    ...
  35.  };

2. C++ 层

2.1 Initialize:

C++ 层代码位于 src 目录下,这一块属于内建模块,是给 JS 层(lib 目录下)提供的 API,在 src/node_os.cc 文件中有一个 Initialize 初始化操作,getCPUs 对应的则是 GetCPUInfo 方法,接下来我们就要看这个方法的实现。

  1.   // https://github.com/Q-Angelo/node/blob/master/src/node_os.cc#L390
  2.   void Initialize(Local<Object> target,
  3.                   Local<Value> unused,
  4.                   Local<Context> context,
  5.                   void* priv) {
  6.     Environment* env = Environment::GetCurrent(context);
  7.     env->SetMethod(target, "getCPUs", GetCPUInfo);
  8.     ...
  9.     target->Set(env->context(),
  10.                 FIXED_ONE_BYTE_STRING(env->isolate(), "isBigEndian"),
  11.                 Boolean::New(env->isolate(), IsBigEndian())).Check();
  12.   }

2.2 GetCPUInfo 实现:

  • 核心是在 uv_cpu_info 方法通过指针的形式传入 &cpu_infos、&count 两个参数拿到 cpu 的信息和个数 count

  • for 循环遍历每个 CPU 核心数据,赋值给变量 ci,遍历过程中 user、nice、sys... 这些数据就很熟悉了,正是我们在 Nodejs 中通过 os.cpus() 拿到的,这些数据都会保存在 result 对象中

  • 遍历结束,通过 uv_free_cpu_info 对 cpu_infos、count 进行回收

  • 最后,设置参数 Array::New(isolate, result.data(), result.size()) 以数组形式返回。

  1.   // https://github.com/Q-Angelo/node/blob/master/src/node_os.cc#L113
  2.   static void GetCPUInfo(const FunctionCallbackInfo<Value>& args) {
  3.     Environment* env = Environment::GetCurrent(args);
  4.     Isolate* isolate = env->isolate();
  5.    
  6.     uv_cpu_info_t* cpu_infos;
  7.     int count;
  8.    
  9.     int err = uv_cpu_info(&cpu_infos, &count);
  10.     if (err)
  11.       return;
  12.    
  13.     // It's faster to create an array packed with all the data and
  14.     // assemble them into objects in JS than to call Object::Set() repeatedly
  15.     // The array is in the format
  16.     // [model, speed, (5 entries of cpu_times), model2, speed2, ...]
  17.     std::vector<Local<Value>> result(count * 7);
  18.     for (int i = 0, j = 0; i < count; i++) {
  19.       uv_cpu_info_t* ci = cpu_infos + i;
  20.       result[j++] = OneByteString(isolate, ci->model);
  21.       result[j++] = Number::New(isolate, ci->speed);
  22.       result[j++] = Number::New(isolate, ci->cpu_times.user);
  23.       result[j++] = Number::New(isolate, ci->cpu_times.nice);
  24.       result[j++] = Number::New(isolate, ci->cpu_times.sys);
  25.       result[j++] = Number::New(isolate, ci->cpu_times.idle);
  26.       result[j++] = Number::New(isolate, ci->cpu_times.irq);
  27.     }
  28.    
  29.     uv_free_cpu_info(cpu_infos, count);
  30.     args.GetReturnValue().Set(Array::New(isolate, result.data(), result.size()));
  31.   }

3. LIBUV 层

经过上面 C++ 内建模块的分析,其中一个重要的方法 uv_cpu_info 是用来获取数据源,现在就要找它啦

3.1 node_os.cc:

内建模块 node_os.cc 引用了头文件 env-inl.h

  1.   // https://github.com/Q-Angelo/node/blob/master/src/node_os.cc#L22
  2.   #include "env-inl.h"
  3.    
  4.   ...

3.2 env-inl.h:

env-inl.h 处又引用了 uv.h

  1.   // https://github.com/Q-Angelo/node/blob/master/src/env-inl.h#L31
  2.   #include "uv.h"

3.3 uv.h:

.h(头文件)包含了类里面成员和方法的声明,它不包含具体的实现,声明找到了,下面找下它的具体实现。

除了我们要找的 uv_cpu_info,此处还声明了 uv_free_cpu_info 方法,与之对应主要用来做回收,上文 C++ 层在数据遍历结束就使用的这个方法对参数 cpu_infos、count 进行了回收。

  1.   /* https://github.com/Q-Angelo/node/blob/master/deps/uv/include/uv.h#L1190 */
  2.   UV_EXTERN int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count);
  3.   UV_EXTERN void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count);

Libuv 层只是对下层操作系统的一种封装,下面来看操作系统层的实现。

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 信息的位置。

  1.   // https://github.com/Q-Angelo/node/blob/master/deps/uv/src/unix/linux-core.c#L610
  2.    
  3.   int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) {
  4.     unsigned int numcpus;
  5.     uv_cpu_info_t* ci;
  6.     int err;
  7.     FILE* statfile_fp;
  8.    
  9.     *cpu_infos = NULL;
  10.     *count = 0;
  11.    
  12.     statfile_fp = uv__open_file("/proc/stat");
  13.     ...
  14.   }

4.2 core.c:

最终找到 uv__open_file() 方法的实现是在 /deps/uv/src/unix/core.c 文件,它以只读和执行后关闭模式获取一个文件的指针。

到这里也就该明白了,Linux 平台下我们使用 Nodejs os 模块的 cpus() 方法最终也是读取的 /proc/stat 文件获取的 CPU 信息。

  1.   // https://github.com/Q-Angelo/node/blob/master/deps/uv/src/unix/core.c#L455
  2.   /* get a file pointer to a file in read-only and close-on-exec mode */
  3.   FILE* uv__open_file(const char* path) {
  4.     int fd;
  5.     FILE* fp;
  6.    
  7.     fd = uv__open_cloexec(path, O_RDONLY);
  8.     if (fd < 0)
  9.       return NULL;
  10.    
  11.      fp = fdopen(fd, "r");
  12.      if (fp == NULL)
  13.        uv__close(fd);
  14.    
  15.      return fp;
  16.   }

什么时候该定位到 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 查看更直观了。

  1.   [
  2.     {
  3.       model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',
  4.       speed: 2926,
  5.       times: {
  6.         user: 252020,
  7.         nice: 0,
  8.         sys: 30340,
  9.         idle: 1070356870,
  10.         irq: 0
  11.       }
  12.    }
  13.    ...

NODEJS 中编码实践

定义方法 _getCPUInfo 用来获取系统 CPU 信息。

方法 getCPUUsage 提供了 CPU 利用率的 “实时” 监控,这个 “实时” 不是绝对的实时,总会有时差的,我们下面实现中默认设置的 1 秒钟,可通过 Options.ms 进行调整。

  1.   const os = require('os');
  2.   const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  3.    
  4.   class OSUtils {
  5.     constructor() {
  6.       this.cpuUsageMSDefault = 1000; // CPU 利用率默认时间段
  7.     }
  8.    
  9.     /**
  10.      * 获取某时间段 CPU 利用率
  11.      * @param { Number } Options.ms [时间段,默认是 1000ms,即 1 秒钟]
  12.      * @param { Boolean } Options.percentage [true(以百分比结果返回)|false] 
  13.      * @returns { Promise }
  14.      */
  15.     async getCPUUsage(options={}) {
  16.       const that = this;
  17.       let { cpuUsageMS, percentage } = options;
  18.       cpuUsageMS = cpuUsageMS || that.cpuUsageMSDefault;
  19.       const t1 = that._getCPUInfo(); // t1 时间点 CPU 信息
  20.    
  21.       await sleep(cpuUsageMS);
  22.    
  23.       const t2 = that._getCPUInfo(); // t2 时间点 CPU 信息
  24.       const idle = t2.idle - t1.idle;
  25.       const total = t2.total - t1.total;
  26.       let usage = 1 - idle / total;
  27.    
  28.       if (percentage) usage = (usage * 100.0).toFixed(2) + "%";
  29.    
  30.       return usage;
  31.     }
  32.    
  33.     /**
  34.      * 获取 CPU 信息
  35.      * @returns { Object } CPU 信息
  36.      */
  37.     _getCPUInfo() {
  38.       const cpus = os.cpus();
  39.       let user = 0, nice = 0, sys = 0, idle = 0, irq = 0, total = 0;
  40.    
  41.       for (let cpu in cpus) {
  42.         const times = cpus[cpu].times;
  43.         user += times.user;
  44.         nice += times.nice;
  45.         sys += times.sys;
  46.         idle += times.idle;
  47.         irq += times.irq;
  48.       }
  49.    
  50.       total += user + nice + sys + idle + irq;
  51.    
  52.       return {
  53.         user,
  54.         sys,
  55.         idle,
  56.         total,
  57.       }
  58.     }
  59.   }

使用方式如下所示:

  1.   const cpuUsage = await osUtils.getCPUUsage({ percentage: true });
  2.   console.log('CPU 利用率:', cpuUsage) // CPU 利用率:13.72%

总结

本文先从 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

标签:info,node,NODEJS,uv,CPU,源码,os,cpu
From: https://www.cnblogs.com/wish123/p/17343237.html

相关文章

  • UE5新功能StateTree源码解析
    StateTree是一种UE5中新增的通用分层状态机,其组合了行为树中的选择器(Selectors)与状态机中的状态(States)和过渡(Transitions)。用户可以创建非常高效、保持灵活且井然有序的逻辑。StateTree包含以树结构布局的状态。状态选择可以在树中的任意位置触发。相比行为树,其组织方式更......
  • 04:基础入门-WEB源码拓展
    前言:WEB源码在安全测试中是非常重要的信息来源,可以用来代码审计漏洞也可以用来做信息突破口,其中WEB源码有很多技术需要简明分析。比如:获取某ASP源码后可以采用默认数据库下载为突破,获取某其他脚本源码漏洞可以进行代码审计挖掘或分析其业务逻辑等,总之源码的获取将为后期的安全......
  • Visual Studio Code开发常用的工具栏选项,查看源码技巧以及【vscode常用的快捷键】
    一、开发常用的工具栏选项1、当前打开的文件快速在左侧资源树中定位:其实打开了当前的文件已经有在左侧资源树木定位了,只是颜色比较浅2、打开太多文件的时候,可以关闭3、设置查看当前类或文件的结构OUTLINE相当于idea查看当前类或接口的结构Structure二、查看源码技巧:(1)Ctr+鼠标......
  • Qt编写网络摄像头推流(4路1080P主码流只占用0.2%CPU/极低延时极速响应)
    一、前言说明将从网络摄像头拉流过来的视频流重新推流出去,是目前一个很常规的做法,尤其是推流到流媒体服务中心,这样流媒体服务中心就把散落在各个区域的监控摄像头集中起来统一管理,同时提供对外一致的访问接口。很多时候不仅仅是几个摄像头,很可能是几百个上千个,所以对推流程序也是......
  • 用nodejs生成带页码和目录的word
    varofficegen=require('officegen');varfs=require('fs');varpath=require('path');vardocx=officegen('docx');docx.on('finalize',function(written){console.log('FinishtocreateWord......
  • Vue3快速上手+俩种创建方式+主要源码讲解
    一.Vue3快速上手2020年9月19日凌晨,尤雨溪正式发布了Vue.js3.0版本,代号:OnePiece。此框架新的主要版本提供了更好的性能、更小的捆绑包体积、更好的TypeScript集成、用于处溪理大规模用例的新API,并为框架未来的长期迭代奠定了坚实的基础。3.0版本的开发周期长达两年多,期间......
  • boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续)
    boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续)书接上回项目源码仓库github项目源码仓库giteeboot-admin是一款采用前后端分离模式、基于SpringCloud微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功......
  • cesium源码编译调试及调用全过程
    完整记录一次cesium源码从下载、打包、调用、调试的全过程。本文使用软件或API版本:VSCodeNode:12.18.3cesium版本:1.94总体步骤:下载源码执行npminstall和npmstart启动web服务打包源码(打包前可以先将申请到的cesium的token更改到ion.js文件中的默认值中)运行测试html页面......
  • 动态线程池DynamicTP源码分析
    一、简述dynamic-tp是一个轻量级的动态线程池插件,它是一个基于配置中心的动态线程池,线程池的参数可以通过配置中心配置进行动态的修改,目前支持的配置中心有Apollo,Nacos和Zookeeper,同时dynamic-tp支持线程池的监控和报警,具体特性如下:基于Spring框架,现只支持SpringBoot项目使用,......
  • 关于互助游戏系统开发项目方案讲解(成熟源码)
    区块链是一种去中心化的分布式账本技术,是比币实现的技术基础。区块链数据是分散在网络中的各个节点上,每个节点都有完整的数据副本,通过算法的共识来保证数据的一致性和可信性搭建lovei130908。functiontryMul(uint256a,uint256b)internalpurereturns(bool,uint......