From eaa709bb8e15cf0dfa0e88a684dfe6ba22b447ff Mon Sep 17 00:00:00 2001 From: Rafael Silva Date: Mon, 7 Feb 2022 10:16:03 -0300 Subject: [PATCH] feat: add stop delay arg (#331) * feat: add stop-delay option * doc: stop-delay option documentation * chore: upgrade minimum v12 version * tests: improve test to stop-delay --- .github/workflows/ci.yml | 2 +- README.md | 3 +- bin.js | 25 ++++++++-- docs/clinic-bubbleprof.txt | 1 + docs/clinic-doctor.txt | 1 + docs/clinic-flame.txt | 1 + docs/clinic-heap-profiler.txt | 1 + package.json | 2 +- test/cli-bubbleprof-stop-delay.test.js | 40 ++++++++++++++++ test/cli-doctor-stop-delay.test.js | 59 ++++++++++++++++++++++++ test/cli-flame-stop-delay.test.js | 40 ++++++++++++++++ test/cli-heapprofiler-stop-delay.test.js | 40 ++++++++++++++++ 12 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 test/cli-bubbleprof-stop-delay.test.js create mode 100644 test/cli-doctor-stop-delay.test.js create mode 100644 test/cli-flame-stop-delay.test.js create mode 100644 test/cli-heapprofiler-stop-delay.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0696c1c..e380146 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - node-version: [12.13.0, 12.x, 14.x, 16.x, 17.x] + node-version: [12.x, 14.x, 16.x, 17.x] runs-on: ${{matrix.os}} steps: diff --git a/README.md b/README.md index 9a6b622..c455e9d 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ To avoid that behavior you can either quote ("--", '--') or escape (`--`) the do ## Supported Node.js versions Clinic.js relies heavily on Node.js core instrumentation available in later versions. -Currently the supported Node.js versions are `>= 12.13.0`. +Currently the supported Node.js versions are `>= 12.22.7`. ## Examples and Demos @@ -123,6 +123,7 @@ clinic heapprofiler --help --on-port Run a script when the server starts listening on a port. --autocannon Run the autocannon benchmarking tool when the server starts listening on a port. --dest Destination for the collect data (default .). +--stop-delay Add a delay to close the process when a job is done through either `autocannon` or `on-port` flag (milliseconds) ``` ## Programmable Interfaces diff --git a/bin.js b/bin.js index 6fa02c8..0f9973f 100755 --- a/bin.js +++ b/bin.js @@ -76,7 +76,8 @@ const result = commist() 'visualize-only', 'sample-interval', 'on-port', - 'dest' + 'dest', + 'stop-delay' ], default: { 'sample-interval': '10', @@ -117,7 +118,8 @@ const result = commist() ], string: [ 'visualize-only', - 'dest' + 'dest', + 'stop-delay' ], default: { open: true, @@ -158,7 +160,8 @@ const result = commist() ], string: [ 'visualize-only', - 'dest' + 'dest', + 'stop-delay' ], default: { open: true, @@ -198,7 +201,8 @@ const result = commist() ], string: [ 'visualize-only', - 'dest' + 'dest', + 'stop-delay' ], default: { open: true, @@ -322,6 +326,8 @@ async function runTool (toolName, Tool, version, args, uiOptions) { kernelTracing: args['kernel-tracing'] }) + const stopDelayMs = parseInt(args['stop-delay']) + const spinner = ora({ text: 'Analysing data', color: uiOptions.color, @@ -337,7 +343,16 @@ async function runTool (toolName, Tool, version, args, uiOptions) { tool.on('port', function (port, proc, cb) { process.env.PORT = port // inline the PORT env to make it easier for cross platform usage - execspawn(envString(onPort, { PORT: port }), { stdio: 'inherit' }).on('exit', cb) + execspawn(envString(onPort, { PORT: port }), { stdio: 'inherit' }) + .on('exit', () => { + if (stopDelayMs) { + tool.emit('status', 'Waiting to close the process') + if (spinner.isEnabled && !spinner.isSpinning) spinner.start() + setTimeout(() => cb(), stopDelayMs) + } else { + cb() + } + }) }) tool.on('analysing', function (message = 'Analysing data') { diff --git a/docs/clinic-bubbleprof.txt b/docs/clinic-bubbleprof.txt index 270fe77..7ccac01 100644 --- a/docs/clinic-bubbleprof.txt +++ b/docs/clinic-bubbleprof.txt @@ -41,3 +41,4 @@ --autocannon Run the autocannon benchmarking tool when the server starts listening on a port. --open Boolean to enable or disable your report opening in your web browser. --dest Destination for the collected data (default .clinic/). + --stop-delay Add a delay to close the process when a job is done through either `autocannon` or `on-port` flag (milliseconds) diff --git a/docs/clinic-doctor.txt b/docs/clinic-doctor.txt index be4f6e9..e7b33de 100644 --- a/docs/clinic-doctor.txt +++ b/docs/clinic-doctor.txt @@ -44,3 +44,4 @@ --autocannon Run the autocannon benchmarking tool when the server starts listening on a port. --open Boolean to enable or disable your report opening in your web browser. --dest Destination for the collected data (default .clinic/). + --stop-delay Add a delay to close the process when a job is done through either `autocannon` or `on-port` flag (milliseconds) diff --git a/docs/clinic-flame.txt b/docs/clinic-flame.txt index 3f02987..1b7aab2 100644 --- a/docs/clinic-flame.txt +++ b/docs/clinic-flame.txt @@ -50,3 +50,4 @@ --open Boolean to enable or disable your report opening in your web browser. --dest Destination for the collected data (default .clinic/). --kernel-tracing Profile application using linux_perf (linux only). + --stop-delay Add a delay to close the process when a job is done through either `autocannon` or `on-port` flag (milliseconds) diff --git a/docs/clinic-heap-profiler.txt b/docs/clinic-heap-profiler.txt index fd8a870..4d7e5a4 100644 --- a/docs/clinic-heap-profiler.txt +++ b/docs/clinic-heap-profiler.txt @@ -43,3 +43,4 @@ --autocannon Run the autocannon benchmarking tool when the server starts listening on a port. --open Boolean to enable or disable your report opening in your web browser. --dest Destination for the collected data (default .clinic/). + --stop-delay Add a delay to close the process when a job is done through either `autocannon` or `on-port` flag (milliseconds) diff --git a/package.json b/package.json index 1af72b2..0d4be43 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "clinicjs/node-clinic", "version": "11.0.0", "engines": { - "node": ">= 12.13.0" + "node": ">= 12.22.7" }, "bin": { "clinic": "bin.js" diff --git a/test/cli-bubbleprof-stop-delay.test.js b/test/cli-bubbleprof-stop-delay.test.js new file mode 100644 index 0000000..0e7a5fa --- /dev/null +++ b/test/cli-bubbleprof-stop-delay.test.js @@ -0,0 +1,40 @@ +'use strict' + +const fs = require('fs') +const url = require('url') +const async = require('async') +const path = require('path') +const test = require('tap').test +const cli = require('./cli.js') + +test('clinic bubbleprof --stop-delay --on-port - no issues', function (t) { + cli({}, [ + 'clinic', 'bubbleprof', '--no-open', '--stop-delay', '500', '--on-port', 'node -e "setTimeout(() => {}, 0)"', + '--', 'node', '-e', ` + const http = require('http') + + http.createServer((req, res) => res.end('ok')).listen(0) + ` + ], function (err, stdout, stderr, tempdir) { + t.error(err) + + const dirname = stdout.match(/(\.clinic[/\\]\d+.clinic-bubbleprof)/)[1] + const fullpath = url.pathToFileURL(fs.realpathSync(path.resolve(tempdir, dirname))) + t.equal(stdout.split('\n')[0], 'Waiting to close the process') + t.equal(stdout.split('\n')[1], 'Analysing data') + t.equal(stdout.split('\n')[2], `Generated HTML file is ${fullpath}.html`) + + // check that files exists + async.parallel({ + sourceData (done) { + fs.access(path.resolve(tempdir, dirname), done) + }, + htmlFile (done) { + fs.access(path.resolve(tempdir, dirname + '.html'), done) + } + }, function (err) { + t.error(err) + t.end() + }) + }) +}) diff --git a/test/cli-doctor-stop-delay.test.js b/test/cli-doctor-stop-delay.test.js new file mode 100644 index 0000000..2978791 --- /dev/null +++ b/test/cli-doctor-stop-delay.test.js @@ -0,0 +1,59 @@ +'use strict' + +const fs = require('fs') +const url = require('url') +const async = require('async') +const path = require('path') +const test = require('tap').test +const cli = require('./cli.js') + +test('clinic doctor --stop-delay --on-port - no issues', function (t) { + cli({}, [ + 'clinic', 'doctor', '--no-open', '--stop-delay', '500', '--on-port', 'node -e "setTimeout(() => {}, 0)"', + '--', 'node', '-e', ` + const http = require('http') + + http.createServer((req, res) => res.end('ok')).listen(0) + ` + ], function (err, stdout, stderr, tempdir) { + t.error(err) + + const dirname = stdout.match(/(\.clinic[/\\]\d+.clinic-doctor)/)[1] + const fullpath = url.pathToFileURL(fs.realpathSync(path.resolve(tempdir, dirname))) + t.equal(stdout.split('\n')[0], 'Waiting to close the process') + t.equal(stdout.split('\n')[1], 'Analysing data') + t.equal(stdout.split('\n')[2], `Generated HTML file is ${fullpath}.html`) + + // check that files exists + async.parallel({ + sourceData (done) { + fs.access(path.resolve(tempdir, dirname), done) + }, + htmlFile (done) { + fs.access(path.resolve(tempdir, dirname + '.html'), done) + } + }, function (err) { + t.error(err) + t.end() + }) + }) +}) + +test('clinic doctor --stop-delay --on-port - exceeding timeout', function (t) { + const onPortDuration = 500 + setTimeout(() => { + t.pass('timeout should be called before t.fail') + t.end() + process.exit(0) + }, onPortDuration + 500) + cli({}, [ + 'clinic', 'doctor', '--no-open', '--stop-delay', '2000', '--on-port', `node -e "setTimeout(() => {}, ${onPortDuration})"`, + '--', 'node', '-e', ` + const http = require('http') + + http.createServer((req, res) => res.end('ok')).listen(0) + ` + ], function () { + t.fail('it should not be called once timeout is reached') + }) +}) diff --git a/test/cli-flame-stop-delay.test.js b/test/cli-flame-stop-delay.test.js new file mode 100644 index 0000000..7910941 --- /dev/null +++ b/test/cli-flame-stop-delay.test.js @@ -0,0 +1,40 @@ +'use strict' + +const fs = require('fs') +const url = require('url') +const async = require('async') +const path = require('path') +const test = require('tap').test +const cli = require('./cli.js') + +test('clinic flame --stop-delay --on-port - no issues', function (t) { + cli({}, [ + 'clinic', 'flame', '--no-open', '--stop-delay', '500', '--on-port', 'node -e "setTimeout(() => {}, 0)"', + '--', 'node', '-e', ` + const http = require('http') + + http.createServer((req, res) => res.end('ok')).listen(0) + ` + ], function (err, stdout, stderr, tempdir) { + t.error(err) + + const dirname = stdout.match(/(\.clinic[/\\]\d+.clinic-flame)/)[1] + const fullpath = url.pathToFileURL(fs.realpathSync(path.resolve(tempdir, dirname))) + t.equal(stdout.split('\n')[0], 'Waiting to close the process') + t.equal(stdout.split('\n')[1], 'Analysing data') + t.equal(stdout.split('\n')[2], `Generated HTML file is ${fullpath}.html`) + + // check that files exists + async.parallel({ + sourceData (done) { + fs.access(path.resolve(tempdir, dirname), done) + }, + htmlFile (done) { + fs.access(path.resolve(tempdir, dirname + '.html'), done) + } + }, function (err) { + t.error(err) + t.end() + }) + }) +}) diff --git a/test/cli-heapprofiler-stop-delay.test.js b/test/cli-heapprofiler-stop-delay.test.js new file mode 100644 index 0000000..66115cd --- /dev/null +++ b/test/cli-heapprofiler-stop-delay.test.js @@ -0,0 +1,40 @@ +'use strict' + +const fs = require('fs') +const url = require('url') +const async = require('async') +const path = require('path') +const test = require('tap').test +const cli = require('./cli.js') + +test('clinic heapprofiler --stop-delay --on-port - no issues', function (t) { + cli({}, [ + 'clinic', 'heapprofiler', '--no-open', '--stop-delay', '500', '--on-port', 'node -e "setTimeout(() => {}, 0)"', + '--', 'node', '-e', ` + const http = require('http') + + http.createServer((req, res) => res.end('ok')).listen(0) + ` + ], function (err, stdout, stderr, tempdir) { + t.error(err) + + const dirname = stdout.match(/(\.clinic[/\\]\d+.clinic-heapprofile)/)[1] + const fullpath = url.pathToFileURL(fs.realpathSync(path.resolve(tempdir, dirname))) + t.equal(stdout.split('\n')[0], 'Waiting to close the process') + t.equal(stdout.split('\n')[1], 'Analysing data') + t.equal(stdout.split('\n')[2], `Generated HTML file is ${fullpath}.html`) + + // check that files exists + async.parallel({ + sourceData (done) { + fs.access(path.resolve(tempdir, dirname), done) + }, + htmlFile (done) { + fs.access(path.resolve(tempdir, dirname + '.html'), done) + } + }, function (err) { + t.error(err) + t.end() + }) + }) +})