From 1b9fdba04e350ef534a70a1f17d9604285f52100 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 5 Apr 2016 09:17:48 -0400 Subject: [PATCH] process: add process.cpuUsage() - implementation, doc, tests Add process.cpuUsage() method that returns the user and system CPU time usage of the current process PR-URL: https://github.com/nodejs/node/pull/6157 Reviewed-By: Robert Lindstaedt Reviewed-By: James M Snell Reviewed-By: Trevor Norris Reviewed-By: Santiago Gimeno --- doc/api/process.md | 23 ++++++++++ lib/internal/bootstrap_node.js | 1 + lib/internal/process.js | 52 +++++++++++++++++++++ src/node.cc | 35 ++++++++++++++ test/parallel/test-process-cpuUsage.js | 63 ++++++++++++++++++++++++++ test/pummel/test-process-cpuUsage.js | 30 ++++++++++++ 6 files changed, 204 insertions(+) create mode 100644 test/parallel/test-process-cpuUsage.js create mode 100644 test/pummel/test-process-cpuUsage.js diff --git a/doc/api/process.md b/doc/api/process.md index bdadaaea88c164..ed4944a97b6610 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -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. diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index 195dfbc0fb89d6..a4324a68e36e8c 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -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(); diff --git a/lib/internal/process.js b/lib/internal/process.js index 17ca5bc326c08a..881d91b11e9a73 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -9,6 +9,7 @@ function lazyConstants() { return _lazyConstants; } +exports.setup_cpuUsage = setup_cpuUsage; exports.setup_hrtime = setup_hrtime; exports.setupConfig = setupConfig; exports.setupKillAndExit = setupKillAndExit; @@ -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); diff --git a/src/node.cc b/src/node.cc index 2cfb2866b898bf..081750ee9995a9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -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; @@ -2220,6 +2221,38 @@ void Hrtime(const FunctionCallbackInfo& 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& 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 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 array = args[0].As(); + CHECK_EQ(array->Length(), 2); + Local ab = array->Buffer(); + double* fields = static_cast(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(m); @@ -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); diff --git a/test/parallel/test-process-cpuUsage.js b/test/parallel/test-process-cpuUsage.js new file mode 100644 index 00000000000000..d0ac2a18fcc329 --- /dev/null +++ b/test/parallel/test-process-cpuUsage.js @@ -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); +} diff --git a/test/pummel/test-process-cpuUsage.js b/test/pummel/test-process-cpuUsage.js new file mode 100644 index 00000000000000..20b2fadef9fae0 --- /dev/null +++ b/test/pummel/test-process-cpuUsage.js @@ -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);