|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +const { mustCall, platformTimeout, hasCrypto, skip } = require('../common'); |
| 4 | +const { ok, deepStrictEqual } = require('assert'); |
| 5 | +const { randomBytes, createHash } = require('crypto'); |
| 6 | +const { once } = require('events'); |
| 7 | +const { Worker, isMainThread, parentPort, threadId } = require('worker_threads'); |
| 8 | + |
| 9 | +if (!hasCrypto) { |
| 10 | + skip('missing crypto'); |
| 11 | +}; |
| 12 | + |
| 13 | +function performLoad() { |
| 14 | + const buffer = randomBytes(1e8); |
| 15 | + const index = threadId + 1; |
| 16 | + |
| 17 | + // Do some work |
| 18 | + return setInterval(() => { |
| 19 | + createHash('sha256').update(buffer).end(buffer); |
| 20 | + }, platformTimeout(index ** 2 * 100)); |
| 21 | +} |
| 22 | + |
| 23 | +function getUsages() { |
| 24 | + return { threadId, process: process.cpuUsage(), thread: process.threadCpuUsage() }; |
| 25 | +} |
| 26 | + |
| 27 | +function validateResults(results) { |
| 28 | + for (let i = 0; i < 4; i++) { |
| 29 | + deepStrictEqual(results[i].threadId, i); |
| 30 | + } |
| 31 | + |
| 32 | + for (let i = 0; i < 3; i++) { |
| 33 | + const processDifference = results[i].process.user / results[i + 1].process.user; |
| 34 | + const threadDifference = results[i].thread.user / results[i + 1].thread.user; |
| 35 | + |
| 36 | + // |
| 37 | + // All process CPU usages should be the same. Technically they should have returned the same |
| 38 | + // value but since we measure it at different times they vary a little bit. |
| 39 | + // Let's allow a tolerance of 20% |
| 40 | + // |
| 41 | + ok(processDifference > 0.8); |
| 42 | + ok(processDifference < 1.2); |
| 43 | + |
| 44 | + // |
| 45 | + // Each thread is configured so that the performLoad schedules a new hash with an interval two times bigger of the |
| 46 | + // previous thread. In theory this should give each thread a load about half of the previous one. |
| 47 | + // But since we can't really predict CPU scheduling, we just check a monotonic increasing sequence. |
| 48 | + // |
| 49 | + ok(threadDifference > 1.2); |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | + |
| 54 | +// The main thread will spawn three more threads, then after a while it will ask all of them to |
| 55 | +// report the thread CPU usage and exit. |
| 56 | +if (isMainThread) { |
| 57 | + const workers = []; |
| 58 | + for (let i = 0; i < 3; i++) { |
| 59 | + workers.push(new Worker(__filename)); |
| 60 | + } |
| 61 | + |
| 62 | + setTimeout(mustCall(async () => { |
| 63 | + clearInterval(interval); |
| 64 | + |
| 65 | + const results = [getUsages()]; |
| 66 | + |
| 67 | + for (const worker of workers) { |
| 68 | + const statusPromise = once(worker, 'message'); |
| 69 | + const exitPromise = once(worker, 'exit'); |
| 70 | + |
| 71 | + worker.postMessage('done'); |
| 72 | + const [status] = await statusPromise; |
| 73 | + results.push(status); |
| 74 | + await exitPromise; |
| 75 | + } |
| 76 | + |
| 77 | + validateResults(results); |
| 78 | + }), platformTimeout(5000)); |
| 79 | + |
| 80 | +} else { |
| 81 | + parentPort.on('message', () => { |
| 82 | + clearInterval(interval); |
| 83 | + parentPort.postMessage(getUsages()); |
| 84 | + process.exit(0); |
| 85 | + }); |
| 86 | +} |
| 87 | + |
| 88 | +// Perform load on each thread |
| 89 | +const interval = performLoad(); |
0 commit comments