diff --git a/.gitignore b/.gitignore index a1cb3fd47..f37edbc8f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules .cache dist .idea -coverage +coverage \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index f54ff27f2..000000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -.cache -dist -.idea -coverage -sample diff --git a/README.md b/README.md index 360a78dfc..94c044453 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,13 @@ Given raw profiling data, speedscope allows you to interactively explore the dat Visit https://www.speedscope.app, then either browse to find a profile file or drag-and-drop one onto the page. The profiles are not uploaded anywhere -- the application is totally in-browser. +For offline use, or convenience in the terminal, you can also install speedscope +via npm: + + npm install -g speedscope + +Invoking `speedscope /path/to/profile` will load speedscope in your default browser. + ## Supported file formats ### Chrome diff --git a/application.tsx b/application.tsx index 8b2a35240..9110f3d03 100644 --- a/application.tsx +++ b/application.tsx @@ -525,6 +525,20 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { } return await importProfile(filename, await response.text()) }) + } else if (this.hashParams.localProfilePath) { + // There isn't good cross-browser support for XHR of local files, even from + // other local files. To work around this restriction, we load the local profile + // as a JavaScript file which will invoke a global function. + ;(window as any)['Speedscope'] = { + loadFileFromBase64: (filename: string, base64source: string) => { + const source = atob(base64source) + this.loadProfile(() => importProfile(filename, source)) + }, + } + + const script = document.createElement('script') + script.src = `file:///${this.hashParams.localProfilePath}` + document.head.appendChild(script) } } diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 000000000..a7d506d9b --- /dev/null +++ b/build-release.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Fail on first error +set -e +OUTDIR=`pwd`/dist/release + +# Typecheck +node_modules/.bin/tsc --noEmit + +# Clean out the release directory +rm -rf "$OUTDIR" +mkdir -p "$OUTDIR" + +# Place info about the current commit into the build dir to easily identify releases +date > "$OUTDIR"/release.txt +git rev-parse HEAD >> "$OUTDIR"/release.txt + +# Place a json schema for the file format into the build directory too +node generate-file-format-schema-json.js > "$OUTDIR"/file-format-schema.json + +# Build the compiled assets +node_modules/.bin/parcel build index.html --no-cache --out-dir "$OUTDIR" --public-url "./" --detailed-report \ No newline at end of file diff --git a/canvas-context.ts b/canvas-context.ts index 7c0595411..3901017e1 100644 --- a/canvas-context.ts +++ b/canvas-context.ts @@ -46,6 +46,12 @@ export class CanvasContext { optionalExtensions: ['EXT_disjoint_timer_query'], profile: true, }) + + console.log( + `WebGL initialized. renderer: ${this.gl.limits.renderer}, vendor: ${ + this.gl.limits.vendor + }, version: ${this.gl.limits.version}`, + ) ;(window as any)['CanvasContext'] = this this.rectangleBatchRenderer = new RectangleBatchRenderer(this.gl) this.viewportRectangleRenderer = new ViewportRectangleRenderer(this.gl) diff --git a/cli.js b/cli.js old mode 100644 new mode 100755 index e33731d6a..ef5487dc5 --- a/cli.js +++ b/cli.js @@ -1,2 +1,74 @@ #!/usr/bin/env node -console.log('Coming soon!') +const path = require('path') +const fs = require('fs') +const os = require('os') +const opn = require('opn') + +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 (process.argv.includes('--help') || process.argv.includes('-h')) { + console.log(helpString) + process.exit(0) +} + +if (process.argv.includes('--version') || process.argv.includes('-v')) { + console.log('v' + require('./package.json').version) + process.exit(0) +} + +if (process.argv.length > 3) { + console.log('At most one argument expected') + console.log(helpString) + process.exit(1) +} + +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) + 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) + }, +) diff --git a/deploy.sh b/deploy.sh index 6ad835c86..9496ddbf7 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,28 +10,7 @@ set -e OUTDIR=`pwd`/dist/release echo $OUTDIR -# Typecheck -node_modules/.bin/tsc --noEmit - -# Clean out the release directory -rm -rf "$OUTDIR" -mkdir -p "$OUTDIR" - -# Place info about the current commit into the build dir to easily identify releases -date > "$OUTDIR"/release.txt -git rev-parse HEAD >> "$OUTDIR"/release.txt - -# Place a json schema for the file format into the build directory too -node generate-file-format-schema-json.js > "$OUTDIR"/file-format-schema.json - -# Build the compiled assets -node_modules/.bin/parcel build index.html --no-cache --out-dir "$OUTDIR" --public-url "./" --detailed-report - -# Create an archive with the release contents in it -pushd "$OUTDIR" -rm -rf ../release.zip -zip -r ../release.zip . -popd +./build-release.sh # Create a shallow clone of the repository TMPDIR=`mktemp -d -t speedscope-release` diff --git a/file-format.ts b/file-format.ts index 002f39e68..c1dfd9178 100644 --- a/file-format.ts +++ b/file-format.ts @@ -15,7 +15,7 @@ export function exportProfile(profile: Profile): FileFormat.File { } const file: FileFormat.File = { - version: '0.0.1', + version: require('./package.json').version, $schema: 'https://www.speedscope.app/file-format-schema.json', shared: {frames}, profiles: [eventedProfile], diff --git a/hash-params.ts b/hash-params.ts index 3019b990c..6a6e227e3 100644 --- a/hash-params.ts +++ b/hash-params.ts @@ -1,6 +1,7 @@ export interface HashParams { profileURL?: string title?: string + localProfilePath?: string } export function getHashParams(): HashParams { @@ -17,6 +18,8 @@ export function getHashParams(): HashParams { result.profileURL = decodeURIComponent(value) } else if (key === 'title') { result.title = decodeURIComponent(value) + } else if (key === 'localProfilePath') { + result.localProfilePath = decodeURIComponent(value) } } return result diff --git a/package-lock.json b/package-lock.json index 9a6bc9c1c..2c9ce5a5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4555,8 +4555,7 @@ }, "fsevents": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "resolved": "", "dev": true, "optional": true, "requires": { @@ -4768,8 +4767,7 @@ }, "debug": { "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "resolved": "", "dev": true, "optional": true, "requires": { @@ -5287,8 +5285,7 @@ }, "rc": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", - "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "resolved": "", "dev": true, "optional": true, "requires": { @@ -5400,8 +5397,7 @@ }, "sshpk": { "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", - "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "resolved": "", "dev": true, "optional": true, "requires": { @@ -5447,8 +5443,7 @@ }, "stringstream": { "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "resolved": "", "dev": true, "optional": true }, @@ -5498,8 +5493,7 @@ }, "tough-cookie": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "resolved": "", "dev": true, "optional": true, "requires": { @@ -6595,8 +6589,7 @@ "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, "isarray": { "version": "1.0.0", @@ -8214,7 +8207,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", - "dev": true, "requires": { "is-wsl": "^1.1.0" } diff --git a/package.json b/package.json index 444d19d0f..feeb069f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "speedscope", - "version": "0.0.1", + "version": "0.1.0", "description": "", "main": "index.js", "bin": { @@ -8,6 +8,7 @@ }, "scripts": { "deploy": "./deploy.sh", + "prepack": "./build-release.sh", "prettier": "prettier --write './**/*.ts' './**/*.tsx'", "lint": "eslint './**/*.ts' './**/*.tsx'", "jest": "jest", @@ -15,10 +16,8 @@ "test": "tsc --noEmit && npm run lint && npm run coverage", "serve": "parcel index.html --open --no-autoinstall" }, - "browserslist": [ - "last 2 Chrome versions", - "last 2 Firefox versions" - ], + "files": ["cli.js", "dist/release/**", "!*.map"], + "browserslist": ["last 2 Chrome versions", "last 2 Firefox versions"], "author": "", "license": "MIT", "devDependencies": { @@ -49,16 +48,10 @@ "^.+\\.tsx?$": "ts-jest" }, "testRegex": "\\.test\\.tsx?$", - "collectCoverageFrom": [ - "**/*.{ts,tsx}", - "!**/*.d.{ts,tsx}" - ], - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json" - ] + "collectCoverageFrom": ["**/*.{ts,tsx}", "!**/*.d.{ts,tsx}"], + "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"] + }, + "dependencies": { + "opn": "5.3.0" } } diff --git a/speedscope.tsx b/speedscope.tsx index d482591da..562dbd7f1 100644 --- a/speedscope.tsx +++ b/speedscope.tsx @@ -1,6 +1,8 @@ import {h, render} from 'preact' import {Application} from './application' +console.log(`speedscope v${require('./package.json').version}`) + let app: Application | null = null const retained = (window as any)['__retained__'] as any declare const module: any