这几天和小伙伴在研究怎么用nodejs来监控机器的硬件信息,其中有项是要计算CPU的剩余idle信息,第一时间想到用top
命令, 能够直接获取当前机器的硬件信息。本着好奇查了下top命令计算CPU idle的原理,具体能够参见这里。简单总结就是经过查询/proc/stat
文件获取每一个核的信息,而后经过计算得出总的剩余idle。既然是经过查询/proc/stat
来获取的信息,那我是否是能够手动执行下cat /proc/stat
命令一探究竟,然并卵,item提示没有此文件(mac os系统)。索性我登陆线上又执行了一遍。因而看到了以下信息:node
果真获取到了每一个CPU核心的信息。咱们知道nodejs中os
模块有个os.cpus()
api也能够获取一样的信息,在mac上具体输出以下:linux
能够看到node输出的信息可读性高许多。那么有几个问题来了:c++
nodejs 是怎么提供跨平台api获取到包括mac os & linux等系统机器的CPU信息呢macos
命令cat /proc/stat
输出的第一行CPU总的信息为何在nodejs输出中没有体现呢windows
其中第一个问题能够详细的描述为: 虽然咱们知道nodejs是经过libuv这个库(用C实现)实现跨平台的,那么咱们仍是想看看js是怎么C通讯的和在不一样的平台是怎么获取CPU信息的。带着这些问题我打开了以前下好的nodejs源码,打算一探究竟。api
在进入具体的os.cpus()
api阅读以前,咱们先简单介绍下nodejs的几个重要的目录:数组
. ├── AUTHORS ├── BSDmakefile //bsd平台makefile文件 ├── LICENSE ├── Makefile //linux平台makefile文件 ├── common.gypi ├── config.gypi ├── config.mk ├── configure ├── deps //nodejs的依赖 ├── lib //nodejs的js核心模块 ├── node -> out/Release/node ├── node.gyp //node-gyp构建编译任务的配置文件 ├── src //nodejs的c++内建模块 ├── test ├── tools └── vcbuild.bat //win平台makefile文件
其中lib
目录是咱们nodejs对外暴露的js模块源码,这部分熟悉nodejs同窗应该很亲切。 咱们知道有些模块好比http & OS模块是经过js封装了C++的实现方式对外提供的api。而这部分的C++的代码就放在src
目录下。咱们还知道nodejs实际上是基于V8引擎运行和libuv实现跨平台的,对于这部分的依赖是放在deps
目录中的,而其余带makefile
字样为名字的文件大都是针对不一样的平台的编译文件,而组织这些编译任务的是node-gyp工具,其配置文件对应就是node.gyp
文件。函数
有了上面的基本的知识以后咱们能够首先打开lib目录找到os.js
文件。果真在代码里面找到了这样的代码:工具
'use strict'; const binding = process.binding('os'); const internalUtil = require('internal/util'); const isWindows = process.platform === 'win32'; exports.hostname = binding.getHostname; exports.loadavg = binding.getLoadAvg; exports.uptime = binding.getUptime; exports.freemem = binding.getFreeMem; exports.totalmem = binding.getTotalMem; exports.cpus = binding.getCPUs; exports.type = binding.getOSType; exports.release = binding.getOSRelease; exports.networkInterfaces = binding.getInterfaceAddresses; exports.homedir = binding.getHomeDirectory;
能够很清楚的知道os.cpus()
api只经过binding
对象获取的,而binding
对象又是经过上面的process.binding('os')
导入的。通过一番查证,这个process.binding就是js调用C++代码的关键所在。结合这两句能够明确的知道这个方法就是直接经过C++内建模块直接导出的一个api。学习
经过src目录找到node_os.cc模块,观察文件最后发现有个初始化的函数具体以下:
void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context) { Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "getHostname", GetHostname); env->SetMethod(target, "getLoadAvg", GetLoadAvg); env->SetMethod(target, "getUptime", GetUptime); env->SetMethod(target, "getTotalMem", GetTotalMemory); env->SetMethod(target, "getFreeMem", GetFreeMemory); env->SetMethod(target, "getCPUs", GetCPUInfo); env->SetMethod(target, "getOSType", GetOSType); env->SetMethod(target, "getOSRelease", GetOSRelease); env->SetMethod(target, "getInterfaceAddresses", GetInterfaceAddresses); env->SetMethod(target, "getHomeDirectory", GetHomeDirectory); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "isBigEndian"), Boolean::New(env->isolate(), IsBigEndian())); }
说明getCPUs
函数其实就是GetCPUInfo函数,因而就能够愉快的找到GetCPUInfo函数看看了。
static void GetCPUInfo(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); uv_cpu_info_t* cpu_infos; int count, i; int err = uv_cpu_info(&cpu_infos, &count); if (err) return; Local<Array> cpus = Array::New(env->isolate()); for (i = 0; i < count; i++) { uv_cpu_info_t* ci = cpu_infos + i; Local<Object> times_info = Object::New(env->isolate()); times_info->Set(env->user_string(), Number::New(env->isolate(), ci->cpu_times.user)); times_info->Set(env->nice_string(), Number::New(env->isolate(), ci->cpu_times.nice)); times_info->Set(env->sys_string(), Number::New(env->isolate(), ci->cpu_times.sys)); times_info->Set(env->idle_string(), Number::New(env->isolate(), ci->cpu_times.idle)); times_info->Set(env->irq_string(), Number::New(env->isolate(), ci->cpu_times.irq)); Local<Object> cpu_info = Object::New(env->isolate()); cpu_info->Set(env->model_string(), OneByteString(env->isolate(), ci->model)); cpu_info->Set(env->speed_string(), Number::New(env->isolate(), ci->speed)); cpu_info->Set(env->times_string(), times_info); (*cpus)->Set(i, cpu_info); } uv_free_cpu_info(cpu_infos, count); args.GetReturnValue().Set(cpus); }
能够看到这个函数的大概意思是
先经过调用uv_cpu_info以指针的形式传入参数,获取到全部的cpu的信息,并判断错误码,有错误直接退出
建立一个新的数组cpus。
经过遍历循环(count应该就是第一步获取到的cpu的核数)每一个核心的信息,存储在ci对象中。
循环中建立了一个times_info对象存储每一个cpu核心的times信息(包括user, sys, nice, idle等)。
而且还建立一个cpu_info对象来存储ci的model信息,speed信息和上一步中的times_info信息。
而后把cpu_info放入到数组cpus中。
最后释放cpu_infos 和count对象。而且把数组经过设置到参数的形式返回出去。
其实看到这里os.cpu()
这个api的面目已经差很少了,而且对应到开头在node RLPE环境中执行输出的结果也能够跟这里一一对应上了。最后问题就落在第一步中的 uv_cpu_info
函数上了,这个函数是全部cpu信息的来源。那么这个函数在哪里呢?
经过搜索能够查到这个uv_cpu_info
函数来自deps/uv/
目录中,而且存在多份定义,在sunos.c, netbsd.c linux-core.c, freebsd.c,darwin.c, utils.c
中都存在定义,想必这就是libuv的真实面目了吧,针对不一样的平台实现了统一的api。而后被nodejs的C++内建模块调用,最后经过js模块暴露一个简单的os.cpu()
api。看到这里应该对刚开始的第一个问题有一个答案了。那么这么多xxxbsd又是啥呢?查了下原来是unix的不一样的发行版本,而sunos应该是原来SUN公司搞的那个系统,而linux系分支应该是没有疑问的是linux-core了。而utils.c发现是windows的实现, 而darwin.c应该mac os的实现,其实跟xxxbsd实现很类似。那咱们先看下最好理解的linux-core.c文件:
int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { // *** some code....**** err = read_models(numcpus, ci); if (err == 0) err = read_times(numcpus, ci); // *** some code....**** }
能够看到read_times函数获取的cpu times信息。因而找到read_times
函数以下:
static int read_times(unsigned int numcpus, uv_cpu_info_t* ci) { // *** some code....**** fp = fopen("/proc/stat", "r"); if (fp == NULL) return -errno; if (!fgets(buf, sizeof(buf), fp)) abort(); num = 0; while (fgets(buf, sizeof(buf), fp)) { if (num >= numcpus) break; if (strncmp(buf, "cpu", 3)) break; // *** some code....**** ts.user = clock_ticks * user; ts.nice = clock_ticks * nice; ts.sys = clock_ticks * sys; ts.idle = clock_ticks * idle; ts.irq = clock_ticks * irq; ci[num++].cpu_times = ts; } fclose(fp); assert(num == numcpus); return 0; }
看到了fp = fopen("/proc/stat", "r");
这句是否是豁然开朗,这不就是咱们在开头说的top命令实现原理查看的文件么,nodejs在linux平台的实现也是经过读这个文件获取的cpu信息的呢。经过while循环逐行获取文件信息,注意到if (strncmp(buf, "cpu", 3))
这句代码,经过函数名能够猜出这个一个字符串比较的函数,通过查证果真是吧当前在buf中的字符串的前三个字符跟字符串'cpu'比较若是相等救你直接break
跳过这同样,这就回答了咱们前面提到的第二个问题。因此跳过了/proc/stat文件的第一行,而直接获取了每一个核的单独信息。至此就是完整的os.cpus()
api的linux实现了。
再看darwin.c
中的uv_cpu_info函数实现,能够看到并无经过/proc/stat
文件来实现(macos也不存在这个文件)。而是经过系统调用(sysctlbyname, host_processor_info)的形式来实现的。
int uv_cpu_info(uv_cpu_info_t** cpu_infos, int* count) { // *** some code....**** size = sizeof(model); if (sysctlbyname("machdep.cpu.brand_string", &model, &size, NULL, 0) && sysctlbyname("hw.model", &model, &size, NULL, 0)) { return -errno; } size = sizeof(cpuspeed); if (sysctlbyname("hw.cpufrequency", &cpuspeed, &size, NULL, 0)) return -errno; if (host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numcpus, (processor_info_array_t*)&info, &msg_type) != KERN_SUCCESS) { return -EINVAL; /* FIXME(bnoordhuis) Translate error. */ } // *** some code....**** }
到这里其实就大概能够看到 os.cpus()
实现的全貌了。
虽然这个 os.cpus()
api很简单,可是它的实现确是nodejs实现的一个典型的例子。典型在哪里呢,能够看下面图:
能够看到咱们业务代码经过require导入nodejs核心的js模块,核心js模块经过process.binding
的方式导入C++内建模块。而C++内建模块在处理有平台的兼容性的功能时又是经过libuv来实现的。libuv其实就是针对不一样平台实现功能后提供的统一的api封装给上层调用,具体调用哪一个平台的api这个应该是在编译nodejs的时候就决定的,不是在运行时判断的,这个流程适用于nodejs中不少地方。初次探索nodejs源码,有疏漏之处恳请指出,记录于此,也以便以后更深刻的学习。
原文地址:http://blog.fexnotes.com/2016/01/18/nodejs-source-intro/