Skip to content

Commit 1b9fdba

Browse files
Patrick MuellerFishrock123
Patrick Mueller
authored andcommitted
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: #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]>
1 parent 2c92a1f commit 1b9fdba

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

doc/api/process.md

+23
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,29 @@ the value of `process.config`.*
504504

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

507+
## process.cpuUsage([previousValue])
508+
509+
Returns the user and system CPU time usage of the current process, in an object
510+
with properties `user` and `system`, whose values are microsecond values
511+
(millionth of a second). These values measure time spent in user and
512+
system code respectively, and may end up being greater than actual elapsed time
513+
if multiple CPU cores are performing work for this process.
514+
515+
The result of a previous call to `process.cpuUsage()` can be passed as the
516+
argument to the function, to get a diff reading.
517+
518+
```js
519+
const startUsage = process.cpuUsage();
520+
// { user: 38579, system: 6986 }
521+
522+
// spin the CPU for 500 milliseconds
523+
const now = Date.now();
524+
while (Date.now() - now < 500);
525+
526+
console.log(process.cpuUsage(startUsage));
527+
// { user: 514883, system: 11226 }
528+
```
529+
507530
## process.cwd()
508531

509532
Returns the current working directory of the process.

lib/internal/bootstrap_node.js

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
const _process = NativeModule.require('internal/process');
4848

4949
_process.setup_hrtime();
50+
_process.setup_cpuUsage();
5051
_process.setupConfig(NativeModule._source);
5152
NativeModule.require('internal/process/warning').setup();
5253
NativeModule.require('internal/process/next_tick').setup();

lib/internal/process.js

+52
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function lazyConstants() {
99
return _lazyConstants;
1010
}
1111

12+
exports.setup_cpuUsage = setup_cpuUsage;
1213
exports.setup_hrtime = setup_hrtime;
1314
exports.setupConfig = setupConfig;
1415
exports.setupKillAndExit = setupKillAndExit;
@@ -22,6 +23,57 @@ const assert = process.assert = function(x, msg) {
2223
};
2324

2425

26+
// Set up the process.cpuUsage() function.
27+
function setup_cpuUsage() {
28+
// Get the native function, which will be replaced with a JS version.
29+
const _cpuUsage = process.cpuUsage;
30+
31+
// Create the argument array that will be passed to the native function.
32+
const cpuValues = new Float64Array(2);
33+
34+
// Replace the native function with the JS version that calls the native
35+
// function.
36+
process.cpuUsage = function cpuUsage(prevValue) {
37+
// If a previous value was passed in, ensure it has the correct shape.
38+
if (prevValue) {
39+
if (!previousValueIsValid(prevValue.user)) {
40+
throw new TypeError('value of user property of argument is invalid');
41+
}
42+
43+
if (!previousValueIsValid(prevValue.system)) {
44+
throw new TypeError('value of system property of argument is invalid');
45+
}
46+
}
47+
48+
// Call the native function to get the current values.
49+
const errmsg = _cpuUsage(cpuValues);
50+
if (errmsg) {
51+
throw new Error('unable to obtain CPU usage: ' + errmsg);
52+
}
53+
54+
// If a previous value was passed in, return diff of current from previous.
55+
if (prevValue) return {
56+
user: cpuValues[0] - prevValue.user,
57+
system: cpuValues[1] - prevValue.system
58+
};
59+
60+
// If no previous value passed in, return current value.
61+
return {
62+
user: cpuValues[0],
63+
system: cpuValues[1]
64+
};
65+
66+
// Ensure that a previously passed in value is valid. Currently, the native
67+
// implementation always returns numbers <= Number.MAX_SAFE_INTEGER.
68+
function previousValueIsValid(num) {
69+
return Number.isFinite(num) &&
70+
num <= Number.MAX_SAFE_INTEGER &&
71+
num >= 0;
72+
}
73+
};
74+
}
75+
76+
2577
function setup_hrtime() {
2678
const _hrtime = process.hrtime;
2779
const hrValues = new Uint32Array(3);

src/node.cc

+35
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ using v8::Boolean;
106106
using v8::Context;
107107
using v8::EscapableHandleScope;
108108
using v8::Exception;
109+
using v8::Float64Array;
109110
using v8::Function;
110111
using v8::FunctionCallbackInfo;
111112
using v8::FunctionTemplate;
@@ -2220,6 +2221,38 @@ void Hrtime(const FunctionCallbackInfo<Value>& args) {
22202221
fields[2] = t % NANOS_PER_SEC;
22212222
}
22222223

2224+
// Microseconds in a second, as a float, used in CPUUsage() below
2225+
#define MICROS_PER_SEC 1e6
2226+
2227+
// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor,
2228+
// to access ru_utime (user CPU time used) and ru_stime (system CPU time used),
2229+
// which are uv_timeval_t structs (long tv_sec, long tv_usec).
2230+
// Returns those values as Float64 microseconds in the elements of the array
2231+
// passed to the function.
2232+
void CPUUsage(const FunctionCallbackInfo<Value>& args) {
2233+
uv_rusage_t rusage;
2234+
2235+
// Call libuv to get the values we'll return.
2236+
int err = uv_getrusage(&rusage);
2237+
if (err) {
2238+
// On error, return the strerror version of the error code.
2239+
Local<String> errmsg = OneByteString(args.GetIsolate(), uv_strerror(err));
2240+
args.GetReturnValue().Set(errmsg);
2241+
return;
2242+
}
2243+
2244+
// Get the double array pointer from the Float64Array argument.
2245+
CHECK(args[0]->IsFloat64Array());
2246+
Local<Float64Array> array = args[0].As<Float64Array>();
2247+
CHECK_EQ(array->Length(), 2);
2248+
Local<ArrayBuffer> ab = array->Buffer();
2249+
double* fields = static_cast<double*>(ab->GetContents().Data());
2250+
2251+
// Set the Float64Array elements to be user / system values in microseconds.
2252+
fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec;
2253+
fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec;
2254+
}
2255+
22232256
extern "C" void node_module_register(void* m) {
22242257
struct node_module* mp = reinterpret_cast<struct node_module*>(m);
22252258

@@ -3212,6 +3245,8 @@ void SetupProcessObject(Environment* env,
32123245

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

3248+
env->SetMethod(process, "cpuUsage", CPUUsage);
3249+
32153250
env->SetMethod(process, "dlopen", DLOpen);
32163251

32173252
env->SetMethod(process, "uptime", Uptime);
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
5+
const result = process.cpuUsage();
6+
7+
// Validate the result of calling with no previous value argument.
8+
validateResult(result);
9+
10+
// Validate the result of calling with a previous value argument.
11+
validateResult(process.cpuUsage(result));
12+
13+
// Ensure the results are >= the previous.
14+
let thisUsage;
15+
let lastUsage = process.cpuUsage();
16+
for (let i = 0; i < 10; i++) {
17+
thisUsage = process.cpuUsage();
18+
validateResult(thisUsage);
19+
assert(thisUsage.user >= lastUsage.user);
20+
assert(thisUsage.system >= lastUsage.system);
21+
lastUsage = thisUsage;
22+
}
23+
24+
// Ensure that the diffs are >= 0.
25+
let startUsage;
26+
let diffUsage;
27+
for (let i = 0; i < 10; i++) {
28+
startUsage = process.cpuUsage();
29+
diffUsage = process.cpuUsage(startUsage);
30+
validateResult(startUsage);
31+
validateResult(diffUsage);
32+
assert(diffUsage.user >= 0);
33+
assert(diffUsage.system >= 0);
34+
}
35+
36+
// Ensure that an invalid shape for the previous value argument throws an error.
37+
assert.throws(function() { process.cpuUsage(1); });
38+
assert.throws(function() { process.cpuUsage({}); });
39+
assert.throws(function() { process.cpuUsage({ user: 'a' }); });
40+
assert.throws(function() { process.cpuUsage({ system: 'b' }); });
41+
assert.throws(function() { process.cpuUsage({ user: null, system: 'c' }); });
42+
assert.throws(function() { process.cpuUsage({ user: 'd', system: null }); });
43+
assert.throws(function() { process.cpuUsage({ user: -1, system: 2 }); });
44+
assert.throws(function() { process.cpuUsage({ user: 3, system: -2 }); });
45+
assert.throws(function() { process.cpuUsage({
46+
user: Number.POSITIVE_INFINITY,
47+
system: 4
48+
});});
49+
assert.throws(function() { process.cpuUsage({
50+
user: 5,
51+
system: Number.NEGATIVE_INFINITY
52+
});});
53+
54+
// Ensure that the return value is the expected shape.
55+
function validateResult(result) {
56+
assert.notEqual(result, null);
57+
58+
assert(Number.isFinite(result.user));
59+
assert(Number.isFinite(result.system));
60+
61+
assert(result.user >= 0);
62+
assert(result.system >= 0);
63+
}

test/pummel/test-process-cpuUsage.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
5+
const start = process.cpuUsage();
6+
7+
// Run a busy-loop for specified # of milliseconds.
8+
const RUN_FOR_MS = 500;
9+
10+
// Define slop factor for checking maximum expected diff values.
11+
const SLOP_FACTOR = 2;
12+
13+
// Run a busy loop.
14+
const now = Date.now();
15+
while (Date.now() - now < RUN_FOR_MS);
16+
17+
// Get a diff reading from when we started.
18+
const diff = process.cpuUsage(start);
19+
20+
const MICROSECONDS_PER_SECOND = 1000 * 1000;
21+
22+
// Diff usages should be >= 0, <= ~RUN_FOR_MS millis.
23+
// Let's be generous with the slop factor, defined above, in case other things
24+
// are happening on this CPU. The <= check may be invalid if the node process
25+
// is making use of multiple CPUs, in which case, just remove it.
26+
assert(diff.user >= 0);
27+
assert(diff.user <= SLOP_FACTOR * RUN_FOR_MS * MICROSECONDS_PER_SECOND);
28+
29+
assert(diff.system >= 0);
30+
assert(diff.system <= SLOP_FACTOR * RUN_FOR_MS * MICROSECONDS_PER_SECOND);

0 commit comments

Comments
 (0)