這幾天和小夥伴在研究怎麼用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/