From a7acd72eda66c3bc25035d5c6a0638d31fad09b8 Mon Sep 17 00:00:00 2001 From: Trevor Norris Date: Wed, 14 Oct 2020 13:57:43 -0600 Subject: [PATCH] worker: add eventLoopUtilization() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow calling eventLoopUtilization() directly on a worker thread: const worker = new Worker('./foo.js'); const elu = worker.performance.eventLoopUtilization(); setTimeout(() => { worker.performance.eventLoopUtilization(elu); }, 10); Add a new performance object on the Worker instance that will hopefully one day hold all the other performance metrics, such as nodeTiming. Include benchmarks and tests. PR-URL: https://github.com/nodejs/node/pull/35664 Backport-PR-URL: https://github.com/nodejs/node/pull/37163 Reviewed-By: Juan José Arboleda Reviewed-By: Anna Henningsen Reviewed-By: Gerhard Stöbich Reviewed-By: James M Snell --- benchmark/worker/bench-eventlooputil.js | 61 +++++++++ doc/api/perf_hooks.md | 8 +- doc/api/worker_threads.md | 62 ++++++++++ lib/internal/worker.js | 56 +++++++++ src/node_worker.cc | 35 ++++++ src/node_worker.h | 2 + test/parallel/test-bootstrap-modules.js | 1 + .../test-performance-eventlooputil.js | 51 ++++---- test/parallel/test-worker-eventlooputil.js | 116 ++++++++++++++++++ 9 files changed, 368 insertions(+), 24 deletions(-) create mode 100644 benchmark/worker/bench-eventlooputil.js create mode 100644 test/parallel/test-worker-eventlooputil.js diff --git a/benchmark/worker/bench-eventlooputil.js b/benchmark/worker/bench-eventlooputil.js new file mode 100644 index 00000000000000..2d59f9f19ed563 --- /dev/null +++ b/benchmark/worker/bench-eventlooputil.js @@ -0,0 +1,61 @@ +'use strict'; + +const common = require('../common.js'); +const { Worker, parentPort } = require('worker_threads'); + +if (process.argv[2] === 'idle cats') { + return parentPort.once('message', () => {}); +} + +const bench = common.createBenchmark(main, { + n: [1e6], + method: [ + 'ELU_simple', + 'ELU_passed', + ], +}); + +function main({ method, n }) { + switch (method) { + case 'ELU_simple': + benchELUSimple(n); + break; + case 'ELU_passed': + benchELUPassed(n); + break; + default: + throw new Error(`Unsupported method ${method}`); + } +} + +function benchELUSimple(n) { + const worker = new Worker(__filename, { argv: ['idle cats'] }); + + spinUntilIdle(worker, () => { + bench.start(); + for (let i = 0; i < n; i++) + worker.performance.eventLoopUtilization(); + bench.end(n); + worker.postMessage('bye'); + }); +} + +function benchELUPassed(n) { + const worker = new Worker(__filename, { argv: ['idle cats'] }); + + spinUntilIdle(worker, () => { + let elu = worker.performance.eventLoopUtilization(); + bench.start(); + for (let i = 0; i < n; i++) + elu = worker.performance.eventLoopUtilization(elu); + bench.end(n); + worker.postMessage('bye'); + }); +} + +function spinUntilIdle(w, cb) { + const t = w.performance.eventLoopUtilization(); + if (t.idle + t.active > 0) + return process.nextTick(cb); + setTimeout(() => spinUntilIdle(w, cb), 1); +} diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index ac7934d81d5f24..bad6f459b06aea 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -72,8 +72,11 @@ added: The `eventLoopUtilization()` method returns an object that contains the cumulative duration of time the event loop has been both idle and active as a high resolution milliseconds timer. The `utilization` value is the calculated -Event Loop Utilization (ELU). If bootstrapping has not yet finished, the -properties have the value of `0`. +Event Loop Utilization (ELU). + +If bootstrapping has not yet finished on the main thread the properties have +the value of `0`. The ELU is immediately available on [Worker threads][] since +bootstrap happens within the event loop. Both `utilization1` and `utilization2` are optional parameters. @@ -762,6 +765,7 @@ require('some-module'); [Performance Timeline]: https://w3c.github.io/performance-timeline/ [User Timing]: https://www.w3.org/TR/user-timing/ [Web Performance APIs]: https://w3c.github.io/perf-timing-primer/ +[Worker threads]: worker_threads.md#worker_threads_worker_threads [`'exit'`]: process.md#process_event_exit [`child_process.spawnSync()`]: child_process.md#child_process_child_process_spawnsync_command_args_options [`process.hrtime()`]: process.md#process_process_hrtime_time diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 7d070dc58fe171..7b7647c2596320 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -787,6 +787,65 @@ If the Worker thread is no longer running, which may occur before the [`'exit'` event][] is emitted, the returned `Promise` will be rejected immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. +### `worker.performance` + + +An object that can be used to query performance information from a worker +instance. Similar to [`perf_hooks.performance`][]. + +#### `performance.eventLoopUtilization([utilization1[, utilization2]])` + + +* `utilization1` {Object} The result of a previous call to + `eventLoopUtilization()`. +* `utilization2` {Object} The result of a previous call to + `eventLoopUtilization()` prior to `utilization1`. +* Returns {Object} + * `idle` {number} + * `active` {number} + * `utilization` {number} + +The same call as [`perf_hooks` `eventLoopUtilization()`][], except the values +of the worker instance are returned. + +One difference is that, unlike the main thread, bootstrapping within a worker +is done within the event loop. So the event loop utilization will be +immediately available once the worker's script begins execution. + +An `idle` time that does not increase does not indicate that the worker is +stuck in bootstrap. The following examples shows how the worker's entire +lifetime will never accumulate any `idle` time, but is still be able to process +messages. + +```js +const { Worker, isMainThread, parentPort } = require('worker_threads'); + +if (isMainThread) { + const worker = new Worker(__filename); + setInterval(() => { + worker.postMessage('hi'); + console.log(worker.performance.eventLoopUtilization()); + }, 100).unref(); + return; +} + +parentPort.on('message', () => console.log('msg')).unref(); +(function r(n) { + if (--n < 0) return; + const t = Date.now(); + while (Date.now() - t < 300); + setImmediate(r, n); +})(10); +``` + +The event loop utilization of a worker is available only after the [`'online'` +event][] emitted, and if called before this, or after the [`'exit'` +event][], then all properties have the value of `0`. + ### `worker.postMessage(value[, transferList])`