GeezerVison is showing github.com — process: add threadCpuUsage · nodejs/node@035da74
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/process.md +19Lines changed: 19 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -4247,6 +4247,25 @@ Thrown: | 4247 | 4247 | [DeprecationWarning: test] { name: 'DeprecationWarning' } | 4248 | 4248 | ``` | 4249 | 4249 |
| | 4250 | +## `process.threadCpuUsage([previousValue])` | | 4251 | +
| | 4252 | +<!-- YAML | | 4253 | +added: REPLACEME | | 4254 | +--> | | 4255 | +
| | 4256 | +* `previousValue` {Object} A previous return value from calling | | 4257 | + `process.cpuUsage()` | | 4258 | +* Returns: {Object} | | 4259 | + * `user` {integer} | | 4260 | + * `system` {integer} | | 4261 | +
| | 4262 | +The `process.threadCpuUsage()` method returns the user and system CPU time usage of | | 4263 | +the current worker thread, in an object with properties `user` and `system`, whose | | 4264 | +values are microsecond values (millionth of a second). | | 4265 | +
| | 4266 | +The result of a previous call to `process.threadCpuUsage()` can be passed as the | | 4267 | +argument to the function, to get a diff reading. | | 4268 | +
| 4250 | 4269 | ## `process.title` | 4251 | 4270 |
| 4252 | 4271 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/bootstrap/node.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -173,6 +173,7 @@ const rawMethods = internalBinding('process_methods'); | 173 | 173 | process.loadEnvFile = wrapped.loadEnvFile; | 174 | 174 | process._rawDebug = wrapped._rawDebug; | 175 | 175 | process.cpuUsage = wrapped.cpuUsage; | | 176 | + process.threadCpuUsage = wrapped.threadCpuUsage; | 176 | 177 | process.resourceUsage = wrapped.resourceUsage; | 177 | 178 | process.memoryUsage = wrapped.memoryUsage; | 178 | 179 | process.constrainedMemory = rawMethods.constrainedMemory; | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/per_thread.js +47Lines changed: 47 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -40,6 +40,7 @@ const { | 40 | 40 | ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, | 41 | 41 | ERR_INVALID_ARG_TYPE, | 42 | 42 | ERR_INVALID_ARG_VALUE, | | 43 | + ERR_OPERATION_FAILED, | 43 | 44 | ERR_OUT_OF_RANGE, | 44 | 45 | ERR_UNKNOWN_SIGNAL, | 45 | 46 | ERR_WORKER_UNSUPPORTED_OPERATION, | @@ -110,6 +111,7 @@ function nop() {} | 110 | 111 | function wrapProcessMethods(binding) { | 111 | 112 | const { | 112 | 113 | cpuUsage: _cpuUsage, | | 114 | + threadCpuUsage: _threadCpuUsage, | 113 | 115 | memoryUsage: _memoryUsage, | 114 | 116 | rss, | 115 | 117 | resourceUsage: _resourceUsage, | @@ -162,6 +164,50 @@ function wrapProcessMethods(binding) { | 162 | 164 | }; | 163 | 165 | } | 164 | 166 |
| | 167 | + const threadCpuValues = new Float64Array(2); | | 168 | + | | 169 | + // Replace the native function with the JS version that calls the native | | 170 | + // function. | | 171 | + function threadCpuUsage(prevValue) { | | 172 | + // If a previous value was passed in, ensure it has the correct shape. | | 173 | + if (prevValue) { | | 174 | + if (!previousValueIsValid(prevValue.user)) { | | 175 | + validateObject(prevValue, 'prevValue'); | | 176 | + | | 177 | + validateNumber(prevValue.user, 'prevValue.user'); | | 178 | + throw new ERR_INVALID_ARG_VALUE.RangeError('prevValue.user', | | 179 | + prevValue.user); | | 180 | + } | | 181 | + | | 182 | + if (!previousValueIsValid(prevValue.system)) { | | 183 | + validateNumber(prevValue.system, 'prevValue.system'); | | 184 | + throw new ERR_INVALID_ARG_VALUE.RangeError('prevValue.system', | | 185 | + prevValue.system); | | 186 | + } | | 187 | + } | | 188 | + | | 189 | + if (process.platform === 'sunos') { | | 190 | + throw new ERR_OPERATION_FAILED('threadCpuUsage is not available on SunOS'); | | 191 | + } | | 192 | + | | 193 | + // Call the native function to get the current values. | | 194 | + _threadCpuUsage(threadCpuValues); | | 195 | + | | 196 | + // If a previous value was passed in, return diff of current from previous. | | 197 | + if (prevValue) { | | 198 | + return { | | 199 | + user: threadCpuValues[0] - prevValue.user, | | 200 | + system: threadCpuValues[1] - prevValue.system, | | 201 | + }; | | 202 | + } | | 203 | + | | 204 | + // If no previous value passed in, return current value. | | 205 | + return { | | 206 | + user: threadCpuValues[0], | | 207 | + system: threadCpuValues[1], | | 208 | + }; | | 209 | + } | | 210 | + | 165 | 211 | // Ensure that a previously passed in value is valid. Currently, the native | 166 | 212 | // implementation always returns numbers <= Number.MAX_SAFE_INTEGER. | 167 | 213 | function previousValueIsValid(num) { | @@ -326,6 +372,7 @@ function wrapProcessMethods(binding) { | 326 | 372 | return { | 327 | 373 | _rawDebug, | 328 | 374 | cpuUsage, | | 375 | + threadCpuUsage, | 329 | 376 | resourceUsage, | 330 | 377 | memoryUsage, | 331 | 378 | kill, | |
Collapse file Copy file name to clipboardExpand all lines: src/node_process_methods.cc +25Lines changed: 25 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -133,6 +133,29 @@ static void CPUUsage(const FunctionCallbackInfo<Value>& args) { | 133 | 133 | fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; | 134 | 134 | } | 135 | 135 |
| | 136 | +// ThreadCPUUsage use libuv's uv_getrusage_thread() this-thread resource usage | | 137 | +// accessor, to access ru_utime (user CPU time used) and ru_stime | | 138 | +// (system CPU time used), which are uv_timeval_t structs | | 139 | +// (long tv_sec, long tv_usec). | | 140 | +// Returns those values as Float64 microseconds in the elements of the array | | 141 | +// passed to the function. | | 142 | +static void ThreadCPUUsage(const FunctionCallbackInfo<Value>& args) { | | 143 | + Environment* env = Environment::GetCurrent(args); | | 144 | + uv_rusage_t rusage; | | 145 | + | | 146 | + // Call libuv to get the values we'll return. | | 147 | + int err = uv_getrusage_thread(&rusage); | | 148 | + if (err) return env->ThrowUVException(err, "uv_getrusage_thread"); | | 149 | + | | 150 | + // Get the double array pointer from the Float64Array argument. | | 151 | + Local<ArrayBuffer> ab = get_fields_array_buffer(args, 0, 2); | | 152 | + double* fields = static_cast<double*>(ab->Data()); | | 153 | + | | 154 | + // Set the Float64Array elements to be user / system values in microseconds. | | 155 | + fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec; | | 156 | + fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; | | 157 | +} | | 158 | + | 136 | 159 | static void Cwd(const FunctionCallbackInfo<Value>& args) { | 137 | 160 | Environment* env = Environment::GetCurrent(args); | 138 | 161 | CHECK(env->has_run_bootstrapping_code()); | @@ -745,6 +768,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, | 745 | 768 | SetMethod(isolate, target, "availableMemory", GetAvailableMemory); | 746 | 769 | SetMethod(isolate, target, "rss", Rss); | 747 | 770 | SetMethod(isolate, target, "cpuUsage", CPUUsage); | | 771 | + SetMethod(isolate, target, "threadCpuUsage", ThreadCPUUsage); | 748 | 772 | SetMethod(isolate, target, "resourceUsage", ResourceUsage); | 749 | 773 |
| 750 | 774 | SetMethod(isolate, target, "_debugEnd", DebugEnd); | @@ -793,6 +817,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { | 793 | 817 | registry->Register(GetAvailableMemory); | 794 | 818 | registry->Register(Rss); | 795 | 819 | registry->Register(CPUUsage); | | 820 | + registry->Register(ThreadCPUUsage); | 796 | 821 | registry->Register(ResourceUsage); | 797 | 822 |
| 798 | 823 | registry->Register(GetActiveRequests); | |
Collapse file Copy file name to clipboard +87Lines changed: 87 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const { isSunOS } = require('../common'); | | 4 | + | | 5 | +const { ok, throws, notStrictEqual } = require('assert'); | | 6 | + | | 7 | +function validateResult(result) { | | 8 | + notStrictEqual(result, null); | | 9 | + | | 10 | + ok(Number.isFinite(result.user)); | | 11 | + ok(Number.isFinite(result.system)); | | 12 | + | | 13 | + ok(result.user >= 0); | | 14 | + ok(result.system >= 0); | | 15 | +} | | 16 | + | | 17 | +// Test that process.threadCpuUsage() works on the main thread | | 18 | +// The if check and the else branch should be removed once SmartOS support is fixed in | | 19 | +// https://github.com/libuv/libuv/issues/4706 | | 20 | +if (!isSunOS) { | | 21 | + const result = process.threadCpuUsage(); | | 22 | + | | 23 | + // Validate the result of calling with no previous value argument. | | 24 | + validateResult(process.threadCpuUsage()); | | 25 | + | | 26 | + // Validate the result of calling with a previous value argument. | | 27 | + validateResult(process.threadCpuUsage(result)); | | 28 | + | | 29 | + // Ensure the results are >= the previous. | | 30 | + let thisUsage; | | 31 | + let lastUsage = process.threadCpuUsage(); | | 32 | + for (let i = 0; i < 10; i++) { | | 33 | + thisUsage = process.threadCpuUsage(); | | 34 | + validateResult(thisUsage); | | 35 | + ok(thisUsage.user >= lastUsage.user); | | 36 | + ok(thisUsage.system >= lastUsage.system); | | 37 | + lastUsage = thisUsage; | | 38 | + } | | 39 | +} else { | | 40 | + throws( | | 41 | + () => process.threadCpuUsage(), | | 42 | + { | | 43 | + code: 'ERR_OPERATION_FAILED', | | 44 | + name: 'Error', | | 45 | + message: 'Operation failed: threadCpuUsage is not available on SunOS' | | 46 | + } | | 47 | + ); | | 48 | +} | | 49 | + | | 50 | +// Test argument validaton | | 51 | +{ | | 52 | + throws( | | 53 | + () => process.threadCpuUsage(123), | | 54 | + { | | 55 | + code: 'ERR_INVALID_ARG_TYPE', | | 56 | + name: 'TypeError', | | 57 | + message: 'The "prevValue" argument must be of type object. Received type number (123)' | | 58 | + } | | 59 | + ); | | 60 | + | | 61 | + throws( | | 62 | + () => process.threadCpuUsage([]), | | 63 | + { | | 64 | + code: 'ERR_INVALID_ARG_TYPE', | | 65 | + name: 'TypeError', | | 66 | + message: 'The "prevValue" argument must be of type object. Received an instance of Array' | | 67 | + } | | 68 | + ); | | 69 | + | | 70 | + throws( | | 71 | + () => process.threadCpuUsage({ user: -123 }), | | 72 | + { | | 73 | + code: 'ERR_INVALID_ARG_VALUE', | | 74 | + name: 'RangeError', | | 75 | + message: "The property 'prevValue.user' is invalid. Received -123" | | 76 | + } | | 77 | + ); | | 78 | + | | 79 | + throws( | | 80 | + () => process.threadCpuUsage({ user: 0, system: 'bar' }), | | 81 | + { | | 82 | + code: 'ERR_INVALID_ARG_TYPE', | | 83 | + name: 'TypeError', | | 84 | + message: "The \"prevValue.system\" property must be of type number. Received type string ('bar')" | | 85 | + } | | 86 | + ); | | 87 | +} |
Collapse file Copy file name to clipboard +91Lines changed: 91 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const { mustCall, platformTimeout, hasCrypto, skip, isSunOS } = require('../common'); | | 4 | + | | 5 | +if (!hasCrypto) { | | 6 | + skip('missing crypto'); | | 7 | +}; | | 8 | + | | 9 | +// This block can be removed once SmartOS support is fixed in | | 10 | +// https://github.com/libuv/libuv/issues/4706 | | 11 | +// The behavior on SunOS is tested in | | 12 | +// test/parallel/test-process-threadCpuUsage-main-thread.js | | 13 | +if (isSunOS) { | | 14 | + skip('Operation not supported yet on SmartOS'); | | 15 | +} | | 16 | + | | 17 | +const { ok } = require('assert'); | | 18 | +const { randomBytes, createHash } = require('crypto'); | | 19 | +const { once } = require('events'); | | 20 | +const { Worker, parentPort, workerData } = require('worker_threads'); | | 21 | + | | 22 | +const FREQUENCIES = [100, 500, 1000]; | | 23 | + | | 24 | +function performLoad() { | | 25 | + const buffer = randomBytes(1e8); | | 26 | + | | 27 | + // Do some work | | 28 | + return setInterval(() => { | | 29 | + createHash('sha256').update(buffer).end(buffer); | | 30 | + }, platformTimeout(workerData?.frequency ?? 100)); | | 31 | +} | | 32 | + | | 33 | +function getUsages() { | | 34 | + return { process: process.cpuUsage(), thread: process.threadCpuUsage() }; | | 35 | +} | | 36 | + | | 37 | +function validateResults(results) { | | 38 | + // This test should have checked that the CPU usage of each thread is greater | | 39 | + // than the previous one, while the process one was not. | | 40 | + // Unfortunately, the real values are not really predictable on the CI so we | | 41 | + // just check that all the values are positive numbers. | | 42 | + for (let i = 0; i < 3; i++) { | | 43 | + ok(typeof results[i].process.user === 'number'); | | 44 | + ok(results[i].process.user >= 0); | | 45 | + | | 46 | + ok(typeof results[i].process.system === 'number'); | | 47 | + ok(results[i].process.system >= 0); | | 48 | + | | 49 | + ok(typeof results[i].thread.user === 'number'); | | 50 | + ok(results[i].thread.user >= 0); | | 51 | + | | 52 | + ok(typeof results[i].thread.system === 'number'); | | 53 | + ok(results[i].thread.system >= 0); | | 54 | + } | | 55 | +} | | 56 | + | | 57 | +// The main thread will spawn three more threads, then after a while it will ask all of them to | | 58 | +// report the thread CPU usage and exit. | | 59 | +if (!workerData?.frequency) { // Do not use isMainThread here otherwise test will not run in --worker mode | | 60 | + const workers = []; | | 61 | + for (const frequency of FREQUENCIES) { | | 62 | + workers.push(new Worker(__filename, { workerData: { frequency } })); | | 63 | + } | | 64 | + | | 65 | + setTimeout(mustCall(async () => { | | 66 | + clearInterval(interval); | | 67 | + | | 68 | + const results = [getUsages()]; | | 69 | + | | 70 | + for (const worker of workers) { | | 71 | + const statusPromise = once(worker, 'message'); | | 72 | + | | 73 | + worker.postMessage('done'); | | 74 | + const [status] = await statusPromise; | | 75 | + results.push(status); | | 76 | + worker.terminate(); | | 77 | + } | | 78 | + | | 79 | + validateResults(results); | | 80 | + }), platformTimeout(5000)); | | 81 | + | | 82 | +} else { | | 83 | + parentPort.on('message', () => { | | 84 | + clearInterval(interval); | | 85 | + parentPort.postMessage(getUsages()); | | 86 | + process.exit(0); | | 87 | + }); | | 88 | +} | | 89 | + | | 90 | +// Perform load on each thread | | 91 | +const interval = performLoad(); |
Collapse file Copy file name to clipboardExpand all lines: typings/globals.d.ts +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -10,6 +10,7 @@ import { FsDirBinding } from './internalBinding/fs_dir'; | 10 | 10 | import { MessagingBinding } from './internalBinding/messaging'; | 11 | 11 | import { OptionsBinding } from './internalBinding/options'; | 12 | 12 | import { OSBinding } from './internalBinding/os'; | | 13 | +import { ProcessBinding } from './internalBinding/process'; | 13 | 14 | import { SerdesBinding } from './internalBinding/serdes'; | 14 | 15 | import { SymbolsBinding } from './internalBinding/symbols'; | 15 | 16 | import { TimersBinding } from './internalBinding/timers'; | @@ -35,6 +36,7 @@ interface InternalBindingMap { | 35 | 36 | modules: ModulesBinding; | 36 | 37 | options: OptionsBinding; | 37 | 38 | os: OSBinding; | | 39 | + process: ProcessBinding; | 38 | 40 | serdes: SerdesBinding; | 39 | 41 | symbols: SymbolsBinding; | 40 | 42 | timers: TimersBinding; | |
Collapse file Copy file name to clipboard +15Lines changed: 15 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +interface CpuUsageValue { | | 2 | + user: number; | | 3 | + system: number; | | 4 | +} | | 5 | + | | 6 | +declare namespace InternalProcessBinding { | | 7 | + interface Process { | | 8 | + cpuUsage(previousValue?: CpuUsageValue): CpuUsageValue; | | 9 | + threadCpuUsage(previousValue?: CpuUsageValue): CpuUsageValue; | | 10 | + } | | 11 | +} | | 12 | + | | 13 | +export interface ProcessBinding { | | 14 | + process: InternalProcessBinding.Process; | | 15 | +} |
|