Skip to content
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

process.cpuUsage() - implementation, doc, tests #6157

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,29 @@ the value of `process.config`.*

If `process.connected` is `false`, it is no longer possible to send messages.

## process.cpuUsage([previousValue])

Returns the user and system CPU time usage of the current process, in an object
with properties `user` and `system`, whose values are microsecond values
(millionth of a second). These values measure time spent in user and
system code respectively, and may end up being greater than actual elapsed time
if multiple CPU cores are performing work for this process.

The result of a previous call to `process.cpuUsage()` can be passed as the
argument to the function, to get a diff reading.

```js
const startUsage = process.cpuUsage();
// { user: 38579, system: 6986 }

// spin the CPU for 500 milliseconds
const now = Date.now();
while (Date.now() - now < 500);

console.log(process.cpuUsage(startUsage));
// { user: 514883, system: 11226 }
```

## process.cwd()

Returns the current working directory of the process.
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
const _process = NativeModule.require('internal/process');

_process.setup_hrtime();
_process.setup_cpuUsage();
_process.setupConfig(NativeModule._source);
NativeModule.require('internal/process/warning').setup();
NativeModule.require('internal/process/next_tick').setup();
Expand Down
52 changes: 52 additions & 0 deletions lib/internal/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function lazyConstants() {
return _lazyConstants;
}

exports.setup_cpuUsage = setup_cpuUsage;
exports.setup_hrtime = setup_hrtime;
exports.setupConfig = setupConfig;
exports.setupKillAndExit = setupKillAndExit;
Expand All @@ -22,6 +23,57 @@ const assert = process.assert = function(x, msg) {
};


// Set up the process.cpuUsage() function.
function setup_cpuUsage() {
// Get the native function, which will be replaced with a JS version.
const _cpuUsage = process.cpuUsage;

// Create the argument array that will be passed to the native function.
const cpuValues = new Float64Array(2);

// Replace the native function with the JS version that calls the native
// function.
process.cpuUsage = function cpuUsage(prevValue) {
// If a previous value was passed in, ensure it has the correct shape.
if (prevValue) {
if (!previousValueIsValid(prevValue.user)) {
throw new TypeError('value of user property of argument is invalid');
}

if (!previousValueIsValid(prevValue.system)) {
throw new TypeError('value of system property of argument is invalid');
}
}

// Call the native function to get the current values.
const errmsg = _cpuUsage(cpuValues);
if (errmsg) {
throw new Error('unable to obtain CPU usage: ' + errmsg);
}

// If a previous value was passed in, return diff of current from previous.
if (prevValue) return {
user: cpuValues[0] - prevValue.user,
system: cpuValues[1] - prevValue.system
};

// If no previous value passed in, return current value.
return {
user: cpuValues[0],
system: cpuValues[1]
};

// Ensure that a previously passed in value is valid. Currently, the native
// implementation always returns numbers <= Number.MAX_SAFE_INTEGER.
function previousValueIsValid(num) {
return Number.isFinite(num) &&
num <= Number.MAX_SAFE_INTEGER &&
num >= 0;
}
};
}


function setup_hrtime() {
const _hrtime = process.hrtime;
const hrValues = new Uint32Array(3);
Expand Down
35 changes: 35 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ using v8::Boolean;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::Float64Array;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
Expand Down Expand Up @@ -2220,6 +2221,38 @@ void Hrtime(const FunctionCallbackInfo<Value>& args) {
fields[2] = t % NANOS_PER_SEC;
}

// Microseconds in a second, as a float, used in CPUUsage() below
#define MICROS_PER_SEC 1e6

// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor,
// to access ru_utime (user CPU time used) and ru_stime (system CPU time used),
// which are uv_timeval_t structs (long tv_sec, long tv_usec).
// Returns those values as Float64 microseconds in the elements of the array
// passed to the function.
void CPUUsage(const FunctionCallbackInfo<Value>& args) {
uv_rusage_t rusage;

// Call libuv to get the values we'll return.
int err = uv_getrusage(&rusage);
if (err) {
// On error, return the strerror version of the error code.
Local<String> errmsg = OneByteString(args.GetIsolate(), uv_strerror(err));
args.GetReturnValue().Set(errmsg);
return;
}

// Get the double array pointer from the Float64Array argument.
CHECK(args[0]->IsFloat64Array());
Local<Float64Array> array = args[0].As<Float64Array>();
CHECK_EQ(array->Length(), 2);
Local<ArrayBuffer> ab = array->Buffer();
double* fields = static_cast<double*>(ab->GetContents().Data());

// Set the Float64Array elements to be user / system values in microseconds.
fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec;
fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec;
}

extern "C" void node_module_register(void* m) {
struct node_module* mp = reinterpret_cast<struct node_module*>(m);

Expand Down Expand Up @@ -3212,6 +3245,8 @@ void SetupProcessObject(Environment* env,

env->SetMethod(process, "hrtime", Hrtime);

env->SetMethod(process, "cpuUsage", CPUUsage);

env->SetMethod(process, "dlopen", DLOpen);

env->SetMethod(process, "uptime", Uptime);
Expand Down
63 changes: 63 additions & 0 deletions test/parallel/test-process-cpuUsage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';
require('../common');
const assert = require('assert');

const result = process.cpuUsage();

// Validate the result of calling with no previous value argument.
validateResult(result);

// Validate the result of calling with a previous value argument.
validateResult(process.cpuUsage(result));

// Ensure the results are >= the previous.
let thisUsage;
let lastUsage = process.cpuUsage();
for (let i = 0; i < 10; i++) {
thisUsage = process.cpuUsage();
validateResult(thisUsage);
assert(thisUsage.user >= lastUsage.user);
assert(thisUsage.system >= lastUsage.system);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it ever possible for thisUsage.user == lastUsage.user?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, why not? Assuming a fast enough processor that can run this code faster than a microsecond. In fact, the cpuUsage() call with no argument (returning cpu usage since process start) could in theory return 0's.

Will ensure tests and comments match that assumption.

lastUsage = thisUsage;
}

// Ensure that the diffs are >= 0.
let startUsage;
let diffUsage;
for (let i = 0; i < 10; i++) {
startUsage = process.cpuUsage();
diffUsage = process.cpuUsage(startUsage);
validateResult(startUsage);
validateResult(diffUsage);
assert(diffUsage.user >= 0);
assert(diffUsage.system >= 0);
}

// Ensure that an invalid shape for the previous value argument throws an error.
assert.throws(function() { process.cpuUsage(1); });
assert.throws(function() { process.cpuUsage({}); });
assert.throws(function() { process.cpuUsage({ user: 'a' }); });
assert.throws(function() { process.cpuUsage({ system: 'b' }); });
assert.throws(function() { process.cpuUsage({ user: null, system: 'c' }); });
assert.throws(function() { process.cpuUsage({ user: 'd', system: null }); });
assert.throws(function() { process.cpuUsage({ user: -1, system: 2 }); });
assert.throws(function() { process.cpuUsage({ user: 3, system: -2 }); });
assert.throws(function() { process.cpuUsage({
user: Number.POSITIVE_INFINITY,
system: 4
});});
assert.throws(function() { process.cpuUsage({
user: 5,
system: Number.NEGATIVE_INFINITY
});});

// Ensure that the return value is the expected shape.
function validateResult(result) {
assert.notEqual(result, null);

assert(Number.isFinite(result.user));
assert(Number.isFinite(result.system));

assert(result.user >= 0);
assert(result.system >= 0);
}
30 changes: 30 additions & 0 deletions test/pummel/test-process-cpuUsage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';
require('../common');
const assert = require('assert');

const start = process.cpuUsage();

// Run a busy-loop for specified # of milliseconds.
const RUN_FOR_MS = 500;

// Define slop factor for checking maximum expected diff values.
const SLOP_FACTOR = 2;

// Run a busy loop.
const now = Date.now();
while (Date.now() - now < RUN_FOR_MS);

// Get a diff reading from when we started.
const diff = process.cpuUsage(start);

const MICROSECONDS_PER_SECOND = 1000 * 1000;

// Diff usages should be >= 0, <= ~RUN_FOR_MS millis.
// Let's be generous with the slop factor, defined above, in case other things
// are happening on this CPU. The <= check may be invalid if the node process
// is making use of multiple CPUs, in which case, just remove it.
assert(diff.user >= 0);
assert(diff.user <= SLOP_FACTOR * RUN_FOR_MS * MICROSECONDS_PER_SECOND);

assert(diff.system >= 0);
assert(diff.system <= SLOP_FACTOR * RUN_FOR_MS * MICROSECONDS_PER_SECOND);