Skip to content

Commit

Permalink
process: add process.cpuUsage() - implementation, doc, tests
Browse files Browse the repository at this point in the history
Backport to v4.x

Original commit message:
  Add process.cpuUsage() method that returns the user and system
  CPU time usage of the current process

  PR-URL: #6157
  Reviewed-By: Robert Lindstaedt <robert.lindstaedt@gmail.com>
  Reviewed-By: James M Snell <jasnell@gmail.com>
  Reviewed-By: Trevor Norris <trev.norris@gmail.com>
  Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>

PR-URL: #10796
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ali Ijaz Sheikh <ofrobots@google.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
  • Loading branch information
Patrick Mueller authored and MylesBorins committed Jan 24, 2017
1 parent 3eee6d4 commit bf3a67c
Show file tree
Hide file tree
Showing 5 changed files with 208 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 @@ -421,6 +421,29 @@ added: v0.7.2

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()
<!-- YAML
added: v0.1.8
Expand Down
35 changes: 35 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,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 @@ -2203,6 +2204,38 @@ void Hrtime(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(tuple);
}

// 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 @@ -3153,6 +3186,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
53 changes: 53 additions & 0 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
startup.processStdio();
startup.processKillAndExit();
startup.processSignalHandlers();
startup.processCpuUsage();

// Do not initialize channel in debugger agent, it deletes env variable
// and the main thread won't see it.
Expand Down Expand Up @@ -286,6 +287,58 @@
});
};

startup.processCpuUsage = function() {
// 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;
}
};
};

var addPendingUnhandledRejection;
var hasBeenNotifiedProperty = new WeakMap();
startup.processNextTick = function() {
Expand Down
67 changes: 67 additions & 0 deletions test/parallel/test-process-cpuUsage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'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 bf3a67c

Please sign in to comment.