[pve-devel] [PATCH pve-eslint] use worker_threads for linting
Dominik Csapak
d.csapak at proxmox.com
Fri Jul 16 16:18:07 CEST 2021
instead linting all files in the main thread, use worker threads
for that (4 by default) and add the '-t' switch to able to control that
since nodejs always wants a module/script to load for a thread,
give a small script that load the file itself
a basic benchmark of eslint of pve-manager showed some performance
gains:
Benchmark #1: Current
Time (mean ± σ): 6.449 s ± 0.207 s [User: 9.818 s, System: 0.362 s]
Range (min … max): 6.190 s … 6.773 s 10 runs
Benchmark #2: 2Threads
Time (mean ± σ): 4.525 s ± 0.143 s [User: 12.646 s, System: 0.584 s]
Range (min … max): 4.324 s … 4.799 s 10 runs
Benchmark #3: 4Threads
Time (mean ± σ): 3.443 s ± 0.041 s [User: 16.393 s, System: 0.672 s]
Range (min … max): 3.354 s … 3.508 s 10 runs
Benchmark #4: 8Threads
Time (mean ± σ): 2.835 s ± 0.052 s [User: 22.343 s, System: 1.023 s]
Range (min … max): 2.764 s … 2.934 s 10 runs
Summary
'8Threads' ran
1.21 ± 0.03 times faster than '4Threads'
1.60 ± 0.06 times faster than '2Threads'
2.28 ± 0.08 times faster than 'Current'
after 8 threads, there were no real performance benefits since the
overhead to load the eslint js file seems to be the biggest factor.
Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
i recently looked how we could do that, but did not find the docs for
the worker_threads. i stumbled upon it today, and quickly threw this
together. the self loading inline script is a bit of a hack, but the
only way to do it better would be to ship eslint and the worker code as
module, but i did not look into that for now...
src/app.js | 65 ++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 58 insertions(+), 7 deletions(-)
diff --git a/src/app.js b/src/app.js
index 9226234..71a88bc 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,9 +1,18 @@
-(function() {
+(async function() {
'use strict';
const path = require('path');
const color = require('colors');
const program = require('commander');
+const worker = require('worker_threads');
+
+if (!worker.isMainThread) {
+ const data = worker.workerData;
+ const cli = new eslint.CLIEngine(data.cliOptions);
+ const report = cli.executeOnFiles(data.files);
+ worker.parentPort.postMessage(report);
+ process.exit(0);
+}
program
.usage('[options] [<file(s) ...>]')
@@ -11,6 +20,7 @@ program
.option('-e, --extend <configfile>', 'uses <configfile> ontop of default eslint config.')
.option('-f, --fix', 'if set, fixes will be applied.')
.option('-s, --strict', 'if set, also exit uncleanly on warnings')
+ .option('-t, --threads <threads>', 'how many worker_threads should be used (default=4)')
.option('--output-config', 'if set, only output the config as JSON and exit.')
;
@@ -39,6 +49,11 @@ if (!paths.length) {
paths = [process.cwd()];
}
+let threadCount = 4;
+if (program.threads) {
+ threadCount = program.threads;
+}
+
const defaultConfig = {
parserOptions: {
ecmaVersion: 2020,
@@ -280,20 +295,56 @@ if (program.outputConfig) {
process.exit(0);
}
-const cli = new eslint.CLIEngine({
+const cliOptions = {
baseConfig: config,
useEslintrc: true,
fix: !!program.fix,
cwd: process.cwd(),
-});
+};
+
+let lintFiles = async function(files) {
+ return new Promise((resolve, reject) => {
+ const child = new worker.Worker(
+ `
+ const worker = require('worker_threads');
+ let file = worker.workerData.__filename;
+ delete worker.workerData.__filename;
+ require(file);
+ `,
+ {
+ eval: true,
+ workerData: {
+ __filename,
+ cliOptions,
+ files,
+ }
+ }
+ );
+ child.on('message', resolve);
+ child.on('error', reject);
+ child.on('exit', (code) => {
+ if (code !== 0)
+ reject(new Error(`Worker stopped with exit code ${code}`));
+ });
+ });
+};
+
+let promises = [];
+let filesPerThread = Math.round(paths.length / threadCount);
+for (let i = 0; i < (threadCount - 1); i++) {
+ let files = paths.splice(0, filesPerThread);
+ promises.push(lintFiles(files));
+}
+// the remaining paths
+promises.push(lintFiles(paths));
-const report = cli.executeOnFiles(paths);
+let results = (await Promise.all(promises)).map(res => res.results).flat(1);
let exitcode = 0;
let files_err = [], files_warn = [], files_ok = [];
let fixes = 0;
console.log('------------------------------------------------------------');
-report.results.forEach(function(result) {
+results.forEach(function(result) {
let filename = path.relative(process.cwd(), result.filePath);
let msgs = result.messages;
let max_sev = 0;
@@ -345,7 +396,7 @@ report.results.forEach(function(result) {
console.log('------------------------------------------------------------');
});
-if (report.results.length > 1) {
+if (results.length > 1) {
console.log(`${color.bold(files_ok.length + files_err.length)} files:`);
if (files_err.length > 0) {
console.log(color.red(` ${color.bold(files_err.length)} files have Errors`));
@@ -364,7 +415,7 @@ console.log('------------------------------------------------------------');
if (program.fix) {
if (fixes > 0) {
console.log(`Writing ${color.bold(fixes)} fixed files...`);
- eslint.CLIEngine.outputFixes(report);
+ eslint.CLIEngine.outputFixes({ results });
console.log('Done');
} else {
console.log("No fixable Errors/Warnings found.");
--
2.30.2
More information about the pve-devel
mailing list