Skip to content

Commit 151cca6

Browse files
Patrick Muellerjasnell
Patrick Mueller
authored andcommitted
process: add process.cpuUsage() - implementation, doc, tests
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>
1 parent b302358 commit 151cca6

File tree

5 files changed

+208
-0
lines changed

5 files changed

+208
-0
lines changed

doc/api/process.md

+23
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,29 @@ added: v0.7.2
421421

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

424+
## process.cpuUsage([previousValue])
425+
426+
Returns the user and system CPU time usage of the current process, in an object
427+
with properties `user` and `system`, whose values are microsecond values
428+
(millionth of a second). These values measure time spent in user and
429+
system code respectively, and may end up being greater than actual elapsed time
430+
if multiple CPU cores are performing work for this process.
431+
432+
The result of a previous call to `process.cpuUsage()` can be passed as the
433+
argument to the function, to get a diff reading.
434+
435+
```js
436+
const startUsage = process.cpuUsage();
437+
// { user: 38579, system: 6986 }
438+
439+
// spin the CPU for 500 milliseconds
440+
const now = Date.now();
441+
while (Date.now() - now < 500);
442+
443+
console.log(process.cpuUsage(startUsage));
444+
// { user: 514883, system: 11226 }
445+
```
446+
424447
## process.cwd()
425448
<!-- YAML
426449
added: v0.1.8

src/node.cc

+35
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ using v8::Boolean;
102102
using v8::Context;
103103
using v8::EscapableHandleScope;
104104
using v8::Exception;
105+
using v8::Float64Array;
105106
using v8::Function;
106107
using v8::FunctionCallbackInfo;
107108
using v8::FunctionTemplate;
@@ -2203,6 +2204,38 @@ void Hrtime(const FunctionCallbackInfo<Value>& args) {
22032204
args.GetReturnValue().Set(tuple);
22042205
}
22052206

2207+
// Microseconds in a second, as a float, used in CPUUsage() below
2208+
#define MICROS_PER_SEC 1e6
2209+
2210+
// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor,
2211+
// to access ru_utime (user CPU time used) and ru_stime (system CPU time used),
2212+
// which are uv_timeval_t structs (long tv_sec, long tv_usec).
2213+
// Returns those values as Float64 microseconds in the elements of the array
2214+
// passed to the function.
2215+
void CPUUsage(const FunctionCallbackInfo<Value>& args) {
2216+
uv_rusage_t rusage;
2217+
2218+
// Call libuv to get the values we'll return.
2219+
int err = uv_getrusage(&rusage);
2220+
if (err) {
2221+
// On error, return the strerror version of the error code.
2222+
Local<String> errmsg = OneByteString(args.GetIsolate(), uv_strerror(err));
2223+
args.GetReturnValue().Set(errmsg);
2224+
return;
2225+
}
2226+
2227+
// Get the double array pointer from the Float64Array argument.
2228+
CHECK(args[0]->IsFloat64Array());
2229+
Local<Float64Array> array = args[0].As<Float64Array>();
2230+
CHECK_EQ(array->Length(), 2);
2231+
Local<ArrayBuffer> ab = array->Buffer();
2232+
double* fields = static_cast<double*>(ab->GetContents().Data());
2233+
2234+
// Set the Float64Array elements to be user / system values in microseconds.
2235+
fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec;
2236+
fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec;
2237+
}
2238+
22062239
extern "C" void node_module_register(void* m) {
22072240
struct node_module* mp = reinterpret_cast<struct node_module*>(m);
22082241

@@ -3153,6 +3186,8 @@ void SetupProcessObject(Environment* env,
31533186

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

3189+
env->SetMethod(process, "cpuUsage", CPUUsage);
3190+
31563191
env->SetMethod(process, "dlopen", DLOpen);
31573192

31583193
env->SetMethod(process, "uptime", Uptime);

src/node.js

+53
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
startup.processStdio();
3939
startup.processKillAndExit();
4040
startup.processSignalHandlers();
41+
startup.processCpuUsage();
4142

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

290+
startup.processCpuUsage = function() {
291+
// Get the native function, which will be replaced with a JS version.
292+
const _cpuUsage = process.cpuUsage;
293+
294+
// Create the argument array that will be passed to the native function.
295+
const cpuValues = new Float64Array(2);
296+
297+
// Replace the native function with the JS version that calls the native
298+
// function.
299+
process.cpuUsage = function cpuUsage(prevValue) {
300+
// If a previous value was passed in, ensure it has the correct shape.
301+
if (prevValue) {
302+
if (!previousValueIsValid(prevValue.user)) {
303+
throw new TypeError('value of user property of argument is invalid');
304+
}
305+
306+
if (!previousValueIsValid(prevValue.system)) {
307+
throw new TypeError(
308+
'value of system property of argument is invalid');
309+
}
310+
}
311+
312+
// Call the native function to get the current values.
313+
const errmsg = _cpuUsage(cpuValues);
314+
if (errmsg) {
315+
throw new Error('unable to obtain CPU usage: ' + errmsg);
316+
}
317+
318+
// If a previous value was passed in,
319+
// return diff of current from previous.
320+
if (prevValue) return {
321+
user: cpuValues[0] - prevValue.user,
322+
system: cpuValues[1] - prevValue.system
323+
};
324+
325+
// If no previous value passed in, return current value.
326+
return {
327+
user: cpuValues[0],
328+
system: cpuValues[1]
329+
};
330+
331+
// Ensure that a previously passed in value is valid. Currently, the
332+
// native implementation always returns
333+
// numbers <= Number.MAX_SAFE_INTEGER.
334+
function previousValueIsValid(num) {
335+
return Number.isFinite(num) &&
336+
num <= Number.MAX_SAFE_INTEGER &&
337+
num >= 0;
338+
}
339+
};
340+
};
341+
289342
var addPendingUnhandledRejection;
290343
var hasBeenNotifiedProperty = new WeakMap();
291344
startup.processNextTick = function() {
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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() {
46+
process.cpuUsage({
47+
user: Number.POSITIVE_INFINITY,
48+
system: 4
49+
});
50+
});
51+
assert.throws(function() {
52+
process.cpuUsage({
53+
user: 5,
54+
system: Number.NEGATIVE_INFINITY
55+
});
56+
});
57+
58+
// Ensure that the return value is the expected shape.
59+
function validateResult(result) {
60+
assert.notEqual(result, null);
61+
62+
assert(Number.isFinite(result.user));
63+
assert(Number.isFinite(result.system));
64+
65+
assert(result.user >= 0);
66+
assert(result.system >= 0);
67+
}

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)