mirror of https://github.com/nodejs/node.git
process: add threadCpuUsage
PR-URL: https://github.com/nodejs/node/pull/56467 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
ccf496cff9
commit
c6ddfa52fb
|
@ -4204,6 +4204,25 @@ Thrown:
|
||||||
[DeprecationWarning: test] { name: 'DeprecationWarning' }
|
[DeprecationWarning: test] { name: 'DeprecationWarning' }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `process.threadCpuUsage([previousValue])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `previousValue` {Object} A previous return value from calling
|
||||||
|
`process.cpuUsage()`
|
||||||
|
* Returns: {Object}
|
||||||
|
* `user` {integer}
|
||||||
|
* `system` {integer}
|
||||||
|
|
||||||
|
The `process.threadCpuUsage()` method returns the user and system CPU time usage of
|
||||||
|
the current worker thread, in an object with properties `user` and `system`, whose
|
||||||
|
values are microsecond values (millionth of a second).
|
||||||
|
|
||||||
|
The result of a previous call to `process.threadCpuUsage()` can be passed as the
|
||||||
|
argument to the function, to get a diff reading.
|
||||||
|
|
||||||
## `process.title`
|
## `process.title`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
|
|
@ -172,6 +172,7 @@ const rawMethods = internalBinding('process_methods');
|
||||||
process.loadEnvFile = wrapped.loadEnvFile;
|
process.loadEnvFile = wrapped.loadEnvFile;
|
||||||
process._rawDebug = wrapped._rawDebug;
|
process._rawDebug = wrapped._rawDebug;
|
||||||
process.cpuUsage = wrapped.cpuUsage;
|
process.cpuUsage = wrapped.cpuUsage;
|
||||||
|
process.threadCpuUsage = wrapped.threadCpuUsage;
|
||||||
process.resourceUsage = wrapped.resourceUsage;
|
process.resourceUsage = wrapped.resourceUsage;
|
||||||
process.memoryUsage = wrapped.memoryUsage;
|
process.memoryUsage = wrapped.memoryUsage;
|
||||||
process.constrainedMemory = rawMethods.constrainedMemory;
|
process.constrainedMemory = rawMethods.constrainedMemory;
|
||||||
|
|
|
@ -36,6 +36,7 @@ const {
|
||||||
codes: {
|
codes: {
|
||||||
ERR_INVALID_ARG_TYPE,
|
ERR_INVALID_ARG_TYPE,
|
||||||
ERR_INVALID_ARG_VALUE,
|
ERR_INVALID_ARG_VALUE,
|
||||||
|
ERR_OPERATION_FAILED,
|
||||||
ERR_OUT_OF_RANGE,
|
ERR_OUT_OF_RANGE,
|
||||||
ERR_UNKNOWN_SIGNAL,
|
ERR_UNKNOWN_SIGNAL,
|
||||||
},
|
},
|
||||||
|
@ -97,6 +98,7 @@ function nop() {}
|
||||||
function wrapProcessMethods(binding) {
|
function wrapProcessMethods(binding) {
|
||||||
const {
|
const {
|
||||||
cpuUsage: _cpuUsage,
|
cpuUsage: _cpuUsage,
|
||||||
|
threadCpuUsage: _threadCpuUsage,
|
||||||
memoryUsage: _memoryUsage,
|
memoryUsage: _memoryUsage,
|
||||||
rss,
|
rss,
|
||||||
resourceUsage: _resourceUsage,
|
resourceUsage: _resourceUsage,
|
||||||
|
@ -148,6 +150,50 @@ function wrapProcessMethods(binding) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const threadCpuValues = new Float64Array(2);
|
||||||
|
|
||||||
|
// Replace the native function with the JS version that calls the native
|
||||||
|
// function.
|
||||||
|
function threadCpuUsage(prevValue) {
|
||||||
|
// If a previous value was passed in, ensure it has the correct shape.
|
||||||
|
if (prevValue) {
|
||||||
|
if (!previousValueIsValid(prevValue.user)) {
|
||||||
|
validateObject(prevValue, 'prevValue');
|
||||||
|
|
||||||
|
validateNumber(prevValue.user, 'prevValue.user');
|
||||||
|
throw new ERR_INVALID_ARG_VALUE.RangeError('prevValue.user',
|
||||||
|
prevValue.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previousValueIsValid(prevValue.system)) {
|
||||||
|
validateNumber(prevValue.system, 'prevValue.system');
|
||||||
|
throw new ERR_INVALID_ARG_VALUE.RangeError('prevValue.system',
|
||||||
|
prevValue.system);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'sunos') {
|
||||||
|
throw new ERR_OPERATION_FAILED('threadCpuUsage is not available on SunOS');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the native function to get the current values.
|
||||||
|
_threadCpuUsage(threadCpuValues);
|
||||||
|
|
||||||
|
// If a previous value was passed in, return diff of current from previous.
|
||||||
|
if (prevValue) {
|
||||||
|
return {
|
||||||
|
user: threadCpuValues[0] - prevValue.user,
|
||||||
|
system: threadCpuValues[1] - prevValue.system,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no previous value passed in, return current value.
|
||||||
|
return {
|
||||||
|
user: threadCpuValues[0],
|
||||||
|
system: threadCpuValues[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that a previously passed in value is valid. Currently, the native
|
// Ensure that a previously passed in value is valid. Currently, the native
|
||||||
// implementation always returns numbers <= Number.MAX_SAFE_INTEGER.
|
// implementation always returns numbers <= Number.MAX_SAFE_INTEGER.
|
||||||
function previousValueIsValid(num) {
|
function previousValueIsValid(num) {
|
||||||
|
@ -263,6 +309,7 @@ function wrapProcessMethods(binding) {
|
||||||
return {
|
return {
|
||||||
_rawDebug,
|
_rawDebug,
|
||||||
cpuUsage,
|
cpuUsage,
|
||||||
|
threadCpuUsage,
|
||||||
resourceUsage,
|
resourceUsage,
|
||||||
memoryUsage,
|
memoryUsage,
|
||||||
kill,
|
kill,
|
||||||
|
|
|
@ -130,6 +130,29 @@ static void CPUUsage(const FunctionCallbackInfo<Value>& args) {
|
||||||
fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec;
|
fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ThreadCPUUsage use libuv's uv_getrusage_thread() this-thread 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.
|
||||||
|
static void ThreadCPUUsage(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
uv_rusage_t rusage;
|
||||||
|
|
||||||
|
// Call libuv to get the values we'll return.
|
||||||
|
int err = uv_getrusage_thread(&rusage);
|
||||||
|
if (err) return env->ThrowUVException(err, "uv_getrusage_thread");
|
||||||
|
|
||||||
|
// Get the double array pointer from the Float64Array argument.
|
||||||
|
Local<ArrayBuffer> ab = get_fields_array_buffer(args, 0, 2);
|
||||||
|
double* fields = static_cast<double*>(ab->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;
|
||||||
|
}
|
||||||
|
|
||||||
static void Cwd(const FunctionCallbackInfo<Value>& args) {
|
static void Cwd(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
CHECK(env->has_run_bootstrapping_code());
|
CHECK(env->has_run_bootstrapping_code());
|
||||||
|
@ -651,6 +674,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
|
||||||
SetMethod(isolate, target, "availableMemory", GetAvailableMemory);
|
SetMethod(isolate, target, "availableMemory", GetAvailableMemory);
|
||||||
SetMethod(isolate, target, "rss", Rss);
|
SetMethod(isolate, target, "rss", Rss);
|
||||||
SetMethod(isolate, target, "cpuUsage", CPUUsage);
|
SetMethod(isolate, target, "cpuUsage", CPUUsage);
|
||||||
|
SetMethod(isolate, target, "threadCpuUsage", ThreadCPUUsage);
|
||||||
SetMethod(isolate, target, "resourceUsage", ResourceUsage);
|
SetMethod(isolate, target, "resourceUsage", ResourceUsage);
|
||||||
|
|
||||||
SetMethod(isolate, target, "_debugEnd", DebugEnd);
|
SetMethod(isolate, target, "_debugEnd", DebugEnd);
|
||||||
|
@ -695,6 +719,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||||
registry->Register(GetAvailableMemory);
|
registry->Register(GetAvailableMemory);
|
||||||
registry->Register(Rss);
|
registry->Register(Rss);
|
||||||
registry->Register(CPUUsage);
|
registry->Register(CPUUsage);
|
||||||
|
registry->Register(ThreadCPUUsage);
|
||||||
registry->Register(ResourceUsage);
|
registry->Register(ResourceUsage);
|
||||||
|
|
||||||
registry->Register(GetActiveRequests);
|
registry->Register(GetActiveRequests);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { isSunOS } = require('../common');
|
||||||
|
|
||||||
|
const { ok, throws, notStrictEqual } = require('assert');
|
||||||
|
|
||||||
|
function validateResult(result) {
|
||||||
|
notStrictEqual(result, null);
|
||||||
|
|
||||||
|
ok(Number.isFinite(result.user));
|
||||||
|
ok(Number.isFinite(result.system));
|
||||||
|
|
||||||
|
ok(result.user >= 0);
|
||||||
|
ok(result.system >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that process.threadCpuUsage() works on the main thread
|
||||||
|
// The if check and the else branch should be removed once SmartOS support is fixed in
|
||||||
|
// https://github.com/libuv/libuv/issues/4706
|
||||||
|
if (!isSunOS) {
|
||||||
|
const result = process.threadCpuUsage();
|
||||||
|
|
||||||
|
// Validate the result of calling with no previous value argument.
|
||||||
|
validateResult(process.threadCpuUsage());
|
||||||
|
|
||||||
|
// Validate the result of calling with a previous value argument.
|
||||||
|
validateResult(process.threadCpuUsage(result));
|
||||||
|
|
||||||
|
// Ensure the results are >= the previous.
|
||||||
|
let thisUsage;
|
||||||
|
let lastUsage = process.threadCpuUsage();
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
thisUsage = process.threadCpuUsage();
|
||||||
|
validateResult(thisUsage);
|
||||||
|
ok(thisUsage.user >= lastUsage.user);
|
||||||
|
ok(thisUsage.system >= lastUsage.system);
|
||||||
|
lastUsage = thisUsage;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throws(
|
||||||
|
() => process.threadCpuUsage(),
|
||||||
|
{
|
||||||
|
code: 'ERR_OPERATION_FAILED',
|
||||||
|
name: 'Error',
|
||||||
|
message: 'Operation failed: threadCpuUsage is not available on SunOS'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test argument validaton
|
||||||
|
{
|
||||||
|
throws(
|
||||||
|
() => process.threadCpuUsage(123),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
name: 'TypeError',
|
||||||
|
message: 'The "prevValue" argument must be of type object. Received type number (123)'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throws(
|
||||||
|
() => process.threadCpuUsage([]),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
name: 'TypeError',
|
||||||
|
message: 'The "prevValue" argument must be of type object. Received an instance of Array'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throws(
|
||||||
|
() => process.threadCpuUsage({ user: -123 }),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_VALUE',
|
||||||
|
name: 'RangeError',
|
||||||
|
message: "The property 'prevValue.user' is invalid. Received -123"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throws(
|
||||||
|
() => process.threadCpuUsage({ user: 0, system: 'bar' }),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
name: 'TypeError',
|
||||||
|
message: "The \"prevValue.system\" property must be of type number. Received type string ('bar')"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { mustCall, platformTimeout, hasCrypto, skip, isSunOS } = require('../common');
|
||||||
|
|
||||||
|
if (!hasCrypto) {
|
||||||
|
skip('missing crypto');
|
||||||
|
};
|
||||||
|
|
||||||
|
// This block can be removed once SmartOS support is fixed in
|
||||||
|
// https://github.com/libuv/libuv/issues/4706
|
||||||
|
// The behavior on SunOS is tested in
|
||||||
|
// test/parallel/test-process-threadCpuUsage-main-thread.js
|
||||||
|
if (isSunOS) {
|
||||||
|
skip('Operation not supported yet on SmartOS');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ok } = require('assert');
|
||||||
|
const { randomBytes, createHash } = require('crypto');
|
||||||
|
const { once } = require('events');
|
||||||
|
const { Worker, parentPort, workerData } = require('worker_threads');
|
||||||
|
|
||||||
|
const FREQUENCIES = [100, 500, 1000];
|
||||||
|
|
||||||
|
function performLoad() {
|
||||||
|
const buffer = randomBytes(1e8);
|
||||||
|
|
||||||
|
// Do some work
|
||||||
|
return setInterval(() => {
|
||||||
|
createHash('sha256').update(buffer).end(buffer);
|
||||||
|
}, platformTimeout(workerData?.frequency ?? 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUsages() {
|
||||||
|
return { process: process.cpuUsage(), thread: process.threadCpuUsage() };
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateResults(results) {
|
||||||
|
// This test should have checked that the CPU usage of each thread is greater
|
||||||
|
// than the previous one, while the process one was not.
|
||||||
|
// Unfortunately, the real values are not really predictable on the CI so we
|
||||||
|
// just check that all the values are positive numbers.
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
ok(typeof results[i].process.user === 'number');
|
||||||
|
ok(results[i].process.user >= 0);
|
||||||
|
|
||||||
|
ok(typeof results[i].process.system === 'number');
|
||||||
|
ok(results[i].process.system >= 0);
|
||||||
|
|
||||||
|
ok(typeof results[i].thread.user === 'number');
|
||||||
|
ok(results[i].thread.user >= 0);
|
||||||
|
|
||||||
|
ok(typeof results[i].thread.system === 'number');
|
||||||
|
ok(results[i].thread.system >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main thread will spawn three more threads, then after a while it will ask all of them to
|
||||||
|
// report the thread CPU usage and exit.
|
||||||
|
if (!workerData?.frequency) { // Do not use isMainThread here otherwise test will not run in --worker mode
|
||||||
|
const workers = [];
|
||||||
|
for (const frequency of FREQUENCIES) {
|
||||||
|
workers.push(new Worker(__filename, { workerData: { frequency } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(mustCall(async () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
|
||||||
|
const results = [getUsages()];
|
||||||
|
|
||||||
|
for (const worker of workers) {
|
||||||
|
const statusPromise = once(worker, 'message');
|
||||||
|
|
||||||
|
worker.postMessage('done');
|
||||||
|
const [status] = await statusPromise;
|
||||||
|
results.push(status);
|
||||||
|
worker.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
validateResults(results);
|
||||||
|
}), platformTimeout(5000));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
parentPort.on('message', () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
parentPort.postMessage(getUsages());
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform load on each thread
|
||||||
|
const interval = performLoad();
|
|
@ -9,6 +9,7 @@ import { FsDirBinding } from './internalBinding/fs_dir';
|
||||||
import { MessagingBinding } from './internalBinding/messaging';
|
import { MessagingBinding } from './internalBinding/messaging';
|
||||||
import { OptionsBinding } from './internalBinding/options';
|
import { OptionsBinding } from './internalBinding/options';
|
||||||
import { OSBinding } from './internalBinding/os';
|
import { OSBinding } from './internalBinding/os';
|
||||||
|
import { ProcessBinding } from './internalBinding/process';
|
||||||
import { SerdesBinding } from './internalBinding/serdes';
|
import { SerdesBinding } from './internalBinding/serdes';
|
||||||
import { SymbolsBinding } from './internalBinding/symbols';
|
import { SymbolsBinding } from './internalBinding/symbols';
|
||||||
import { TimersBinding } from './internalBinding/timers';
|
import { TimersBinding } from './internalBinding/timers';
|
||||||
|
@ -34,6 +35,7 @@ interface InternalBindingMap {
|
||||||
modules: ModulesBinding;
|
modules: ModulesBinding;
|
||||||
options: OptionsBinding;
|
options: OptionsBinding;
|
||||||
os: OSBinding;
|
os: OSBinding;
|
||||||
|
process: ProcessBinding;
|
||||||
serdes: SerdesBinding;
|
serdes: SerdesBinding;
|
||||||
symbols: SymbolsBinding;
|
symbols: SymbolsBinding;
|
||||||
timers: TimersBinding;
|
timers: TimersBinding;
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
interface CpuUsageValue {
|
||||||
|
user: number;
|
||||||
|
system: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace InternalProcessBinding {
|
||||||
|
interface Process {
|
||||||
|
cpuUsage(previousValue?: CpuUsageValue): CpuUsageValue;
|
||||||
|
threadCpuUsage(previousValue?: CpuUsageValue): CpuUsageValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessBinding {
|
||||||
|
process: InternalProcessBinding.Process;
|
||||||
|
}
|
Loading…
Reference in New Issue