From fcc6808054827eb971c4990fcc5012d2d029962f Mon Sep 17 00:00:00 2001 From: Jamie Wong Date: Sat, 21 Jul 2018 19:42:58 -0700 Subject: [PATCH] Optionally read from stdin via cli (#99) Test Plan: - `./cli.js` opens speedscope in browser with no file selected - `./cli.js sample/profiles/Chrome/65/simple-timeline.json` opens profile - `cat sample/profiles/Chrome/65/simple-timeline.json | ./cli.js -` opens profile - `node --prof-process --preprocess -j sample/profiles/node/8.5.0/isolate-0x102802600-v8.log | ./cli.js -` opens profile Fixes #95 --- README.md | 10 ++-- cli.js | 139 +++++++++++++++++++++++++++++++++--------------------- 2 files changed, 87 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 10eaeb7..c9fe46d 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,10 @@ If you record profiling information like so: node --prof /path/to/my/script.js -Then this will generate a bunch of `isolate*.log` files. Process one of them -into usable JSON like so: +Then this will generate one or more `isolate*.log` files. You can open +the resulting profile in speedscope by running the following command: - node --prof-process -preprocess -j isolate*.log > profile.v8log.json - -Then drop the resulting `profile.v8log.json` file into speedscope, or run - - speedscope profile.v8log.json + node --prof-process --preprocess -j isolate*.log | speedscope - ### Instruments.app diff --git a/cli.js b/cli.js index 0c7066d..884a273 100755 --- a/cli.js +++ b/cli.js @@ -2,73 +2,102 @@ const path = require('path') const fs = require('fs') const os = require('os') +const stream = require('stream') + const opn = require('opn') -const helpString = ` -Usage: speedscope [filepath] +const helpString = `Usage: speedscope [filepath] If invoked with no arguments, will open a local copy of speedscope in your default browser. Once open, you can browse for a profile to import. + +If - is used as the filepath, will read from stdin instead. + + cat /path/to/profile | speedscope - ` -if (process.argv.includes('--help') || process.argv.includes('-h')) { - console.log(helpString) - process.exit(0) +function getProfileStream(relPath) { + const absPath = path.resolve(process.cwd(), relPath) + if (relPath === '-') { + // Read from stdin + return process.stdin + } else { + return fs.createReadStream(absPath) + } } -if (process.argv.includes('--version') || process.argv.includes('-v')) { - console.log('v' + require('./package.json').version) - process.exit(0) +function getProfileBuffer(relPath) { + const profileStream = getProfileStream(relPath) + const chunks = [] + return new Promise((resolve, reject) => { + profileStream.pipe( + stream.Writable({ + write(chunk, encoding, callback) { + chunks.push(chunk) + callback() + }, + final() { + resolve(Buffer.concat(chunks)) + }, + }), + ) + profileStream.on('error', ev => reject(ev)) + }) } -if (process.argv.length > 3) { - console.log('At most one argument expected') - console.log(helpString) - process.exit(1) +async function main() { + if (process.argv.includes('--help') || process.argv.includes('-h')) { + console.log(helpString) + return + } + + if (process.argv.includes('--version') || process.argv.includes('-v')) { + console.log('v' + require('./package.json').version) + return + } + + if (process.argv.length > 3) { + throw new Error('At most one argument expected') + } + + let urlToOpen = 'file://' + path.resolve(__dirname, './dist/release/index.html') + + if (process.argv.length === 3) { + const relPath = process.argv[2] + const sourceBuffer = await getProfileBuffer(relPath) + const filename = path.basename(relPath) + const sourceBase64 = sourceBuffer.toString('base64') + const jsSource = `speedscope.loadFileFromBase64(${JSON.stringify(filename)}, ${JSON.stringify( + sourceBase64, + )})` + + const filePrefix = `speedscope-${+new Date()}-${process.pid}` + const jsPath = path.join(os.tmpdir(), `${filePrefix}.js`) + console.log(`Creating temp file ${jsPath}`) + fs.writeFileSync(jsPath, jsSource) + urlToOpen += `#localProfilePath=${jsPath}` + + // For some silly reason, the OS X open command ignores any query parameters or hash parameters + // passed as part of the URL. To get around this weird issue, we'll create a local HTML file + // that just redirects. + const htmlPath = path.join(os.tmpdir(), `${filePrefix}.html`) + console.log(`Creating temp file ${htmlPath}`) + fs.writeFileSync(htmlPath, ``) + + urlToOpen = `file://${htmlPath}` + } + + console.log('Opening', urlToOpen, 'in your default browser') + + await opn(urlToOpen, {wait: false}) } -let urlToOpen = 'file://' + path.resolve(__dirname, './dist/release/index.html') - -if (process.argv.length === 3) { - const absPath = path.resolve(process.cwd(), process.argv[2]) - let sourceBuffer - try { - sourceBuffer = fs.readFileSync(absPath) - } catch (e) { - console.log(e) +main() + .then(() => { + process.exit(0) + }) + .catch(e => { + console.log(e.stack + '\n') console.log(helpString) process.exit(1) - } - const filename = path.basename(absPath) - const sourceBase64 = sourceBuffer.toString('base64') - const jsSource = `speedscope.loadFileFromBase64(${JSON.stringify(filename)}, ${JSON.stringify( - sourceBase64, - )})` - - const filePrefix = `speedscope-${+new Date()}-${process.pid}` - const jsPath = path.join(os.tmpdir(), `${filePrefix}.js`) - console.log(`Creating temp file ${jsPath}`) - fs.writeFileSync(jsPath, jsSource) - urlToOpen += `#localProfilePath=${jsPath}` - - // For some silly reason, the OS X open command ignores any query parameters or hash parameters - // passed as part of the URL. To get around this weird issue, we'll create a local HTML file - // that just redirects. - const htmlPath = path.join(os.tmpdir(), `${filePrefix}.html`) - console.log(`Creating temp file ${htmlPath}`) - fs.writeFileSync(htmlPath, ``) - - urlToOpen = `file://${htmlPath}` -} - -console.log('Opening', urlToOpen, 'in your default browser') - -opn(urlToOpen, {wait: false}).then( - () => { - process.exit(0) - }, - err => { - console.error(err) - console.exit(1) - }, -) + })