diff --git a/packages/memory-profiler/README.md b/packages/memory-profiler/README.md index d29f744e..302330ff 100644 --- a/packages/memory-profiler/README.md +++ b/packages/memory-profiler/README.md @@ -22,21 +22,15 @@ enough. The following resources are helpful in understanding it: ## V8 heap snapshot -First, make a heap snapshot with the hidden sdk-cli command `heap-snapshot`. Then, +With your device connected (`connect device`) and FBA loaded (`set-app-package` or `install`), simply run the `heap-snapshot` command. A `.heapsnapshot` file will be produced in the current directory. -``` -$ node lib/cli.js v8 /path/to/fitbit-project/js-heap.bin /path/to/fitbit-project/build/app.fba /path/to/fitbit.heapsnapshot atlas -``` - -(replacing the paths and `atlas` with your device type). - -Then load the file `fitbit.heapsnapshot` in Chrome Dev Tools under the Memory tab. You can also take +Then load the `.heapsnapshot` file in Chrome Dev Tools under the Memory tab. You can also take successive snapshots and load them in Chrome Dev Tools and compare them to see how the heap has -changed. +changed, though note that IDs are not stable currently which limits the usefulness of this. ## Explore in a REPL -If you want to manually explore the heap graph to really dig into things, you can load a repl +If you want to manually explore the heap graph to really dig into things, you can load a REPL with the graph of the jerryscript heap snapshot already loaded. Run: ``` diff --git a/packages/memory-profiler/src/index.ts b/packages/memory-profiler/src/index.ts index ff28ec7e..5218336b 100644 --- a/packages/memory-profiler/src/index.ts +++ b/packages/memory-profiler/src/index.ts @@ -1 +1 @@ -export { generateGraph as convert } from './convert'; +export { generateGraph, generateV8HeapSnapshot } from './convert'; diff --git a/packages/sdk-cli/src/cli.ts b/packages/sdk-cli/src/cli.ts index e2725bf9..0ad26051 100644 --- a/packages/sdk-cli/src/cli.ts +++ b/packages/sdk-cli/src/cli.ts @@ -36,7 +36,7 @@ cli.history('Fitbit-Command-Line-SDK'); cli.use(build); cli.use(buildAndInstall({ hostConnections, appContext })); cli.use(connect({ hostConnections })); -cli.use(heapSnapshot({ hostConnections })); +cli.use(heapSnapshot({ hostConnections, appContext })); cli.use(install({ hostConnections, appContext })); cli.use(screenshot({ hostConnections })); cli.use(setAppPackage({ appContext })); diff --git a/packages/sdk-cli/src/commands/heapSnapshot.ts b/packages/sdk-cli/src/commands/heapSnapshot.ts index 868eedf5..115c9dfb 100644 --- a/packages/sdk-cli/src/commands/heapSnapshot.ts +++ b/packages/sdk-cli/src/commands/heapSnapshot.ts @@ -4,37 +4,51 @@ import dateformat from 'dateformat'; import untildify from 'untildify'; import vorpal from '@moleculer/vorpal'; -import HostConnections from '../models/HostConnections'; +import AppContext from '../models/AppContext'; import captureHeapSnapshot from '../models/captureHeapSnapshot'; +import * as compatibility from '../models/compatibility'; +import HostConnections from '../models/HostConnections'; +import { ComponentSourceMaps } from '@fitbit/app-package'; -type HeapSnapshotArgs = vorpal.Args & { - uuid?: string; -}; - -export default function install(stores: { hostConnections: HostConnections }) { +export default function heapSnapshot(stores: { + hostConnections: HostConnections; + appContext: AppContext; +}) { return (cli: vorpal) => { cli .command( - 'heap-snapshot [path] [uuid]', + 'heap-snapshot [path]', // tslint:disable-next-line:max-line-length - 'Capture a JS heap snapshot from the connected device and write the raw data to a file (experimental)', + 'Capture a JS heap snapshot from the connected device and write the data to a file (experimental)', ) - .hidden() .option('-f, --format ', 'heap snapshot format to request', () => { if (!stores.hostConnections.appHost) return []; - return stores.hostConnections.appHost.host.getHeapSnapshotSupport() - .formats; + return [ + ...stores.hostConnections.appHost.host.getHeapSnapshotSupport() + .formats, + 'v8', + ]; }) - .types({ string: ['f', 'format', 'path', 'uuid'] }) - .action(async (args: HeapSnapshotArgs & { path?: string }) => { + .types({ string: ['f', 'format', 'path'] }) + .action(async (args: vorpal.Args & { path?: string }) => { const { appHost } = stores.hostConnections; if (!appHost) { cli.activeCommand.log('Not connected to a device'); return false; } + const { appPackage } = stores.appContext; + if (!appPackage) { + cli.activeCommand.log( + 'App package not loaded, use `install` or `set-app-package` commands first', + ); + return false; + } + const { supported, formats } = appHost.host.getHeapSnapshotSupport(); + const snapshotFormats = ['v8', ...formats]; + if (!supported) { cli.activeCommand.log( 'Device does not support capturing JS heap snapshots', @@ -43,15 +57,14 @@ export default function install(stores: { hostConnections: HostConnections }) { } let { format } = args.options; - if (!format) { - if (formats.length === 0) { + if (snapshotFormats.length === 0) { cli.activeCommand.log( 'Device does not support any heap snapshot formats', ); return false; } - if (formats.length === 1) { + if (snapshotFormats.length === 1) { format = formats[0]; cli.activeCommand.log( `Requesting a JS heap snapshot in ${JSON.stringify( @@ -65,20 +78,39 @@ export default function install(stores: { hostConnections: HostConnections }) { name: 'format', message: 'Which format would you like the JS heap snapshot to be in?', - choices: formats, + choices: snapshotFormats, }) ).format; } } + const extension = format === 'v8' ? 'heapsnapshot' : 'bin'; const destPath = path.resolve( args.path ? untildify(args.path) - : dateformat('"js-heap." yyyy-mm-dd.H.MM.ss."bin"'), + : dateformat(`"js-heap." yyyy-mm-dd.H.MM.ss."${extension}"`), ); + let sourceMaps: ComponentSourceMaps | undefined; + if ( + appPackage.sourceMaps !== undefined && + appPackage.sourceMaps.device + ) { + const deviceFamily = compatibility.findCompatibleAppComponent( + appPackage, + appHost.host.info, + ); + sourceMaps = appPackage.sourceMaps.device[deviceFamily]; + } + try { - await captureHeapSnapshot(appHost.host, format, destPath, args.uuid); + await captureHeapSnapshot( + appHost.host, + format, + destPath, + appPackage.uuid, + sourceMaps, + ); cli.activeCommand.log(`JS heap snapshot saved to ${destPath}`); return true; } catch (ex) { diff --git a/packages/sdk-cli/src/models/captureHeapSnapshot.ts b/packages/sdk-cli/src/models/captureHeapSnapshot.ts index a260f62b..44251e95 100644 --- a/packages/sdk-cli/src/models/captureHeapSnapshot.ts +++ b/packages/sdk-cli/src/models/captureHeapSnapshot.ts @@ -1,4 +1,6 @@ +import { ComponentSourceMaps } from '@fitbit/app-package'; import { RemoteHost } from '@fitbit/fdb-debugger'; +import { generateGraph, generateV8HeapSnapshot } from '@fitbit/memory-profiler'; import fsExtra from 'fs-extra'; export default async function captureHeapSnapshot( @@ -6,11 +8,26 @@ export default async function captureHeapSnapshot( format: string, destPath: string, uuid?: string, + sourceMaps?: ComponentSourceMaps, ) { + let v8Requested = false; + let actualFormat = format; + + if (format === 'v8') { + v8Requested = true; + actualFormat = 'jerryscript-1'; + } + if (!host.getHeapSnapshotSupport().supported) { throw new Error('Connected device does not support heap snapshots'); } - const snapshot = await host.captureHeapSnapshot(format, uuid); - return fsExtra.writeFile(destPath, snapshot); + const snapshot = await host.captureHeapSnapshot(actualFormat, uuid); + if (!v8Requested) { + return fsExtra.writeFile(destPath, snapshot); + } + + const graph = await generateGraph(snapshot, actualFormat, sourceMaps || {}); + const v8Snapshot = generateV8HeapSnapshot(graph); + return fsExtra.writeFile(destPath, JSON.stringify(v8Snapshot)); }