-
Notifications
You must be signed in to change notification settings - Fork 21
Add option to collect cpu usage from processes #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
#include <nan.h> | ||
#include "process.h" | ||
#include "worker.h" | ||
#include <cmath> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this being used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it imports isnan |
||
|
||
void GetProcessList(const Nan::FunctionCallbackInfo<v8::Value>& args) { | ||
ProcessInfo process_info[1024]; | ||
|
@@ -32,9 +33,66 @@ void GetProcessList(const Nan::FunctionCallbackInfo<v8::Value>& args) { | |
Nan::AsyncQueueWorker(worker); | ||
} | ||
|
||
void GetProcessCpuUsage(const Nan::FunctionCallbackInfo<v8::Value>& args) { | ||
if (args.Length() < 2) { | ||
Nan::ThrowTypeError("GetProcessCpuUsage expects two arguments."); | ||
return; | ||
} | ||
|
||
if (!args[0]->IsArray()) { | ||
Nan::ThrowTypeError("The first argument of GetProcessCpuUsage, callback, must be an array."); | ||
return; | ||
} | ||
|
||
if (!args[1]->IsFunction()) { | ||
Nan::ThrowTypeError("The second argument of GetProcessCpuUsage, flags, must be a function."); | ||
return; | ||
} | ||
|
||
// Read the ProcessTreeNode JS object | ||
v8::Local<v8::Array> processes = v8::Local<v8::Array>::Cast(args[0]); | ||
uint32_t count = processes->Length(); | ||
Cpu* cpu_info = new Cpu[count]; | ||
|
||
// Read pid from each array and populate data structure to calculate CPU, take first sample of counters | ||
for (uint32_t i = 0; i < count; i++) { | ||
v8::Local<v8::Object> process = processes->Get(Nan::New<v8::Integer>(i))->ToObject(); | ||
DWORD pid = (DWORD)(process->Get(Nan::New("pid").ToLocalChecked()))->NumberValue(); | ||
cpu_info[i].pid = pid; | ||
GetCpuUsage(cpu_info, &i, true); | ||
} | ||
|
||
// Sleep for one second | ||
Sleep(1000); | ||
|
||
// Sample counters again and complete CPU usage calculation | ||
for (uint32_t i = 0; i < count; i++) { | ||
GetCpuUsage(cpu_info, &i, false); | ||
} | ||
|
||
Nan::Callback *callback = new Nan::Callback(v8::Local<v8::Function>::Cast(args[1])); | ||
|
||
v8::Local<v8::Array> result = Nan::New<v8::Array>(count); | ||
for (uint32_t i = 0; i < count; i++) { | ||
v8::Local<v8::Object> object = processes->Get(Nan::New<v8::Integer>(i))->ToObject(); | ||
|
||
if (!std::isnan(cpu_info[i].cpu)) { | ||
Nan::Set(object, Nan::New<v8::String>("cpu").ToLocalChecked(), | ||
Nan::New<v8::Number>(cpu_info[i].cpu)); | ||
} | ||
|
||
Nan::Set(result, i, Nan::New<v8::Value>(object)); | ||
} | ||
|
||
v8::Local<v8::Value> argv[] = { result }; | ||
callback->Call(1, argv); | ||
} | ||
|
||
void Init(v8::Local<v8::Object> exports) { | ||
exports->Set(Nan::New("getProcessList").ToLocalChecked(), | ||
Nan::New<v8::FunctionTemplate>(GetProcessList)->GetFunction()); | ||
exports->Set(Nan::New("getProcessCpuUsage").ToLocalChecked(), | ||
Nan::New<v8::FunctionTemplate>(GetProcessCpuUsage)->GetFunction()); | ||
} | ||
|
||
NODE_MODULE(hello, Init) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
|
||
#include <tlhelp32.h> | ||
#include <psapi.h> | ||
#include <limits> | ||
|
||
void GetRawProcessList(ProcessInfo process_info[1024], uint32_t* process_count, DWORD* process_data_flags) { | ||
*process_count = 0; | ||
|
@@ -31,6 +32,7 @@ void GetRawProcessList(ProcessInfo process_info[1024], uint32_t* process_count, | |
} | ||
} while (*process_count < 1024 && Process32Next(snapshot_handle, &process_entry)); | ||
} | ||
|
||
CloseHandle(snapshot_handle); | ||
} | ||
|
||
|
@@ -48,6 +50,49 @@ void GetProcessMemoryUsage(ProcessInfo process_info[1024], uint32_t* process_cou | |
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) { | ||
process_info[*process_count].memory = (DWORD)pmc.WorkingSetSize; | ||
} | ||
|
||
} | ||
|
||
// Per documentation, it is not recommended to add or subtract values from the FILETIME | ||
// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows. | ||
// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead. | ||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx | ||
ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) { | ||
ULARGE_INTEGER kt, ut; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment to explain function |
||
kt.LowPart = (*kernelTime).dwLowDateTime; | ||
kt.HighPart = (*kernelTime).dwHighDateTime; | ||
|
||
ut.LowPart = (*userTime).dwLowDateTime; | ||
ut.HighPart = (*userTime).dwHighDateTime; | ||
|
||
return kt.QuadPart + ut.QuadPart; | ||
} | ||
|
||
void GetCpuUsage(Cpu* cpu_info, uint32_t* process_index, BOOL first_pass) { | ||
DWORD pid = cpu_info[*process_index].pid; | ||
HANDLE hProcess; | ||
|
||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid); | ||
|
||
if (hProcess == NULL) { | ||
return; | ||
} | ||
|
||
FILETIME creationTime, exitTime, kernelTime, userTime; | ||
FILETIME sysIdleTime, sysKernelTime, sysUserTime; | ||
if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may want to handle when the if condition is false (cpu = undefined?) |
||
&& GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) { | ||
if (first_pass) { | ||
cpu_info[*process_index].initialProcRunTime = GetTotalTime(&kernelTime, &userTime); | ||
cpu_info[*process_index].initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime); | ||
} else { | ||
ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime); | ||
ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime); | ||
|
||
cpu_info[*process_index].cpu = 100.0 * (endProcTime - cpu_info[*process_index].initialProcRunTime) / (endSysTime - cpu_info[*process_index].initialSystemTime); | ||
} | ||
} else { | ||
cpu_info[*process_index].cpu = std::numeric_limits<double>::quiet_NaN(); | ||
} | ||
|
||
CloseHandle(hProcess); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should refactor this to share code with getProcessTree, I think we need to pass in a queue and a filter function