Skip to content

Commit

Permalink
process: add process.cpuUsage() - implementation, doc, tests
Browse files Browse the repository at this point in the history
Add process.cpuUsage() method that returns the user and system
CPU time usage of the current process

PR-URL: #6157
Reviewed-By: Robert Lindstaedt <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Trevor Norris <[email protected]>
Reviewed-By: Santiago Gimeno <[email protected]>
  • Loading branch information
Patrick Mueller authored and jasnell committed Apr 29, 2016
1 parent 706778a commit 52cb410
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 0 deletions.
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);
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);

0 comments on commit 52cb410

Please sign in to comment.