-
Notifications
You must be signed in to change notification settings - Fork 29.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf_hooks: add idleTime and event loop util
Use uv_metrics_idle_time() to return a high resolution millisecond timer of the amount of time the event loop has been idle since it was initialized. Include performance.eventLoopUtilization() API to handle the math of calculating the idle and active times. This has been added to prevent accidental miscalculations of the event loop utilization. Such as not taking into consideration offsetting nodeTiming.loopStart or timing differences when being called from a Worker thread. PR-URL: #34938 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Adrian Estrada <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
- Loading branch information
1 parent
b117467
commit 2c409a2
Showing
8 changed files
with
279 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
'use strict'; | ||
|
||
const common = require('../common.js'); | ||
const assert = require('assert').ok; | ||
const { performance } = require('perf_hooks'); | ||
const { nodeTiming, eventLoopUtilization } = performance; | ||
|
||
const bench = common.createBenchmark(main, { | ||
n: [1e6], | ||
method: [ | ||
'idleTime', | ||
'ELU_simple', | ||
'ELU_passed', | ||
], | ||
}); | ||
|
||
function main({ method, n }) { | ||
switch (method) { | ||
case 'idleTime': | ||
benchIdleTime(n); | ||
break; | ||
case 'ELU_simple': | ||
benchELUSimple(n); | ||
break; | ||
case 'ELU_passed': | ||
benchELUPassed(n); | ||
break; | ||
default: | ||
throw new Error(`Unsupported method ${method}`); | ||
} | ||
} | ||
|
||
function benchIdleTime(n) { | ||
bench.start(); | ||
for (let i = 0; i < n; i++) | ||
nodeTiming.idleTime; | ||
bench.end(n); | ||
} | ||
|
||
function benchELUSimple(n) { | ||
// Need to put this in setImmediate or will always return 0. | ||
setImmediate(() => { | ||
const elu = eventLoopUtilization(); | ||
assert(elu.active + elu.idle > 0); | ||
|
||
bench.start(); | ||
for (let i = 0; i < n; i++) | ||
eventLoopUtilization(); | ||
bench.end(n); | ||
}); | ||
} | ||
|
||
function benchELUPassed(n) { | ||
// Need to put this in setImmediate or will always return 0. | ||
setImmediate(() => { | ||
let elu = eventLoopUtilization(); | ||
assert(elu.active + elu.idle > 0); | ||
|
||
bench.start(); | ||
for (let i = 0; i < n; i++) | ||
elu = eventLoopUtilization(elu); | ||
bench.end(n); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
'use strict'; | ||
|
||
require('../common'); | ||
|
||
const TIMEOUT = 50; | ||
|
||
const assert = require('assert'); | ||
const { performance } = require('perf_hooks'); | ||
const { Worker, parentPort } = require('worker_threads'); | ||
|
||
const { nodeTiming, eventLoopUtilization } = performance; | ||
const elu = eventLoopUtilization(); | ||
|
||
// Take into account whether this test was started as a Worker. | ||
if (nodeTiming.loopStart === -1) { | ||
assert.strictEqual(nodeTiming.idleTime, 0); | ||
assert.deepStrictEqual(elu, { idle: 0, active: 0, utilization: 0 }); | ||
assert.deepStrictEqual(eventLoopUtilization(elu), | ||
{ idle: 0, active: 0, utilization: 0 }); | ||
assert.deepStrictEqual(eventLoopUtilization(elu, eventLoopUtilization()), | ||
{ idle: 0, active: 0, utilization: 0 }); | ||
} | ||
|
||
// Place in setTimeout() to make sure there is some idle time, but not going to | ||
// assert this since it could make the test flaky. | ||
setTimeout(() => { | ||
const t = Date.now(); | ||
const elu1 = eventLoopUtilization(); | ||
|
||
while (Date.now() - t < 50) { } | ||
|
||
const elu2 = eventLoopUtilization(); | ||
const elu3 = eventLoopUtilization(elu1); | ||
const elu4 = eventLoopUtilization(elu2, elu1); | ||
|
||
assert.strictEqual(elu3.idle, 0); | ||
assert.strictEqual(elu4.idle, 0); | ||
assert.strictEqual(elu3.utilization, 1); | ||
assert.strictEqual(elu4.utilization, 1); | ||
assert.strictEqual(elu2.active - elu1.active, elu4.active); | ||
assert.ok(elu2.active > elu3.active); | ||
assert.ok(elu2.active > elu4.active); | ||
assert.ok(elu3.active > elu4.active); | ||
|
||
setTimeout(runIdleTimeTest, TIMEOUT); | ||
}, 5); | ||
|
||
function runIdleTimeTest() { | ||
const idleTime = nodeTiming.idleTime; | ||
const elu1 = eventLoopUtilization(); | ||
const sum = elu1.idle + elu1.active; | ||
|
||
assert.ok(sum >= elu1.idle && sum >= elu1.active, | ||
`idle: ${elu1.idle} active: ${elu1.active} sum: ${sum}`); | ||
assert.strictEqual(elu1.idle, idleTime); | ||
assert.strictEqual(elu1.utilization, elu1.active / sum); | ||
|
||
setTimeout(runCalcTest, TIMEOUT, elu1); | ||
} | ||
|
||
function runCalcTest(elu1) { | ||
const now = performance.now(); | ||
const elu2 = eventLoopUtilization(); | ||
const elu3 = eventLoopUtilization(elu2, elu1); | ||
const active_delta = elu2.active - elu1.active; | ||
const idle_delta = elu2.idle - elu1.idle; | ||
|
||
assert.ok(elu2.idle >= 0); | ||
assert.ok(elu2.active >= 0); | ||
assert.ok(elu3.idle >= 0); | ||
assert.ok(elu3.active >= 0); | ||
assert.ok(elu2.idle + elu2.active > elu1.idle + elu2.active); | ||
assert.ok(elu2.idle + elu2.active >= now - nodeTiming.loopStart); | ||
assert.strictEqual(elu3.active, elu2.active - elu1.active); | ||
assert.strictEqual(elu3.idle, elu2.idle - elu1.idle); | ||
assert.strictEqual(elu3.utilization, | ||
active_delta / (idle_delta + active_delta)); | ||
|
||
setImmediate(runWorkerTest); | ||
} | ||
|
||
function runWorkerTest() { | ||
// Use argv to detect whether we're running as a Worker called by this test | ||
// vs. this test also being called as a Worker. | ||
if (process.argv[2] === 'iamalive') { | ||
parentPort.postMessage(JSON.stringify(eventLoopUtilization())); | ||
return; | ||
} | ||
|
||
const elu1 = eventLoopUtilization(); | ||
const worker = new Worker(__filename, { argv: [ 'iamalive' ] }); | ||
|
||
worker.on('message', (msg) => { | ||
const elu2 = eventLoopUtilization(elu1); | ||
const data = JSON.parse(msg); | ||
|
||
assert.ok(elu2.active + elu2.idle > data.active + data.idle); | ||
}); | ||
} |