Support importing partial JSON files (#202)

Partial files are allowed in many specs, e.g. Trace Event Format,
so the viewer should be able to load partial files as well.
This commit is contained in:
Marcin Kolny
2019-02-09 02:08:51 +00:00
committed by Jamie Wong
parent ddc61302e8
commit cfc8fe8f6e
8 changed files with 151 additions and 8 deletions

View File

@@ -44,7 +44,8 @@
"ts-jest": "22.4.6",
"typescript": "2.8.1",
"typescript-eslint-parser": "17.0.1",
"uglify-es": "3.2.2"
"uglify-es": "3.2.2",
"partial-json-parser": "1.0.3"
},
"jest": {
"transform": {

View File

@@ -0,0 +1,7 @@
{
"traceEvents": [
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
{"pid": 0, "tid": 0, "ph": "X", "ts": 7, "tdur": 4},
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14},

View File

@@ -0,0 +1,7 @@
[
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
{"pid": 0, "tid": 0, "ph": "X", "name": "gamma", "ts": 2, "dur": 5, "args": {"detail": "foobar"}},
{"pid": 0, "tid": 0, "ph": "X", "name": "epsilon", "ts": 7, "tdur": 4},
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14},

View File

@@ -258,10 +258,112 @@ Object {
}
`;
exports[`importTraceEvents simple object partial 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "alpha",
"line": undefined,
"name": "alpha",
"selfWeight": 2,
"totalWeight": 14,
},
Frame {
"col": undefined,
"file": undefined,
"key": "beta",
"line": undefined,
"name": "beta",
"selfWeight": 8,
"totalWeight": 12,
},
Frame {
"col": undefined,
"file": undefined,
"key": "(unnamed)",
"line": undefined,
"name": "(unnamed)",
"selfWeight": 4,
"totalWeight": 4,
},
],
"name": "pid 0, tid 0",
"stacks": Array [
"alpha 1.00µs",
"alpha;beta 6.00µs",
"alpha;beta;(unnamed) 4.00µs",
"alpha;beta 2.00µs",
"alpha 1.00µs",
],
}
`;
exports[`importTraceEvents simple object partial: indexToView 1`] = `0`;
exports[`importTraceEvents simple object partial: profileGroup.name 1`] = `"simple-object-partial.json"`;
exports[`importTraceEvents simple object: indexToView 1`] = `0`;
exports[`importTraceEvents simple object: profileGroup.name 1`] = `"simple-object.json"`;
exports[`importTraceEvents simple partial 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "alpha",
"line": undefined,
"name": "alpha",
"selfWeight": 2,
"totalWeight": 14,
},
Frame {
"col": undefined,
"file": undefined,
"key": "beta",
"line": undefined,
"name": "beta",
"selfWeight": 3,
"totalWeight": 12,
},
Frame {
"col": undefined,
"file": undefined,
"key": "gamma {\\"detail\\":\\"foobar\\"}",
"line": undefined,
"name": "gamma {\\"detail\\":\\"foobar\\"}",
"selfWeight": 5,
"totalWeight": 5,
},
Frame {
"col": undefined,
"file": undefined,
"key": "epsilon",
"line": undefined,
"name": "epsilon",
"selfWeight": 4,
"totalWeight": 4,
},
],
"name": "pid 0, tid 0",
"stacks": Array [
"alpha 1.00µs",
"alpha;beta 1.00µs",
"alpha;beta;gamma {\\"detail\\":\\"foobar\\"} 5.00µs",
"alpha;beta;epsilon 4.00µs",
"alpha;beta 2.00µs",
"alpha 1.00µs",
],
}
`;
exports[`importTraceEvents simple partial: indexToView 1`] = `0`;
exports[`importTraceEvents simple partial: profileGroup.name 1`] = `"simple-partial.json"`;
exports[`importTraceEvents simple: indexToView 1`] = `0`;
exports[`importTraceEvents simple: profileGroup.name 1`] = `"simple.json"`;

View File

@@ -1,6 +1,8 @@
import {Profile, ProfileGroup} from '../lib/profile'
import {FileSystemDirectoryEntry} from './file-system-entry'
import {partialParse} from 'partial-json-parser'
import {
importFromChromeCPUProfile,
importFromChromeTimeline,
@@ -71,6 +73,14 @@ function toGroup(profile: Profile | null): ProfileGroup | null {
return {name: profile.getName(), indexToView: 0, profiles: [profile]}
}
function tryImportTraceEvents(parsed: any): ProfileGroup | null {
if (isTraceEventFormatted(parsed)) {
console.log('Importing as Trace Event Format profile')
return importTraceEvents(parsed)
}
return null
}
async function _importProfileGroup(dataSource: ProfileDataSource): Promise<ProfileGroup | null> {
const fileName = await dataSource.name()
@@ -115,6 +125,7 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
// Second pass: Try to guess what file format it is based on structure
let parsed: any
let profileGroup: ProfileGroup | null
try {
parsed = JSON.parse(contents)
} catch (e) {}
@@ -131,9 +142,8 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
console.log('Importing as Chrome CPU Profile')
return toGroup(importFromChromeCPUProfile(parsed))
} else if (isTraceEventFormatted(parsed)) {
console.log('Importing as Trace Event Format profile')
return importTraceEvents(parsed)
} else if ((profileGroup = tryImportTraceEvents(parsed))) {
return profileGroup
} else if ('head' in parsed && 'samples' in parsed && 'timestamps' in parsed) {
console.log('Importing as Chrome CPU Profile (old format)')
return toGroup(importFromOldV8CPUProfile(parsed))
@@ -175,6 +185,17 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
}
}
// Third pass: try parsing partial JSON file, as some of the formats,
// e.g. Trace Event Format, allow partial files.
try {
parsed = partialParse(contents)
} catch {}
if (parsed) {
if ((profileGroup = tryImportTraceEvents(parsed))) {
return profileGroup
}
}
// Unrecognized format
return null
}

1
src/import/partial-json-parser.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'partial-json-parser'

View File

@@ -11,3 +11,11 @@ test('importTraceEvents simple object', async () => {
test('importTraceEvents multiprocess', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/multiprocess.json')
})
test('importTraceEvents simple partial', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial.json')
})
test('importTraceEvents simple object partial', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-object-partial.json')
})

View File

@@ -288,10 +288,6 @@ export function isTraceEventFormatted(
// We're only going to suppor the JSON formatted profiles for now.
// The spec also discusses support for data embedded in ftrace supported data: https://lwn.net/Articles/365835/.
// TODO(jlfwong): The spec also specifies that it's valid for the trace to not contain a terminating `]`.
// That complicates things a bit for us, so let's just ignore that for now until someone writes in with a
// bug report from real data.
return isTraceEventObject(rawProfile) || isTraceEventList(rawProfile)
}