Compare commits

..
10 Commits
Author SHA1 Message Date
Jamie Wong bd546ca893 1.5.1 2019-06-06 00:07:14 -07:00
Jamie Wong 0c1e477f35 Support import from trace event format event when there are too many "E" events. (#222)
Fixes #221
2019-06-06 00:03:08 -07:00
Jamie Wong 30ca6291ca 1.5.0 2019-02-17 18:38:17 -08:00
Jamie Wong 66a9e5d1cf Support importing unterminated JSON in simple cases (#208)
This PR introduces support for importing JSON based profiles that are missing a terminating `]` (and possibly have an extraneous `,`).

This is similar to #202, but takes a much more targeted and simple approach.

I'm confident that this approach is sufficient because this is exactly what `chrome://tracing` does: https://github.com/catapult-project/catapult/blob/27e047e0494df162022be6aa8a8862742a270232/tracing/tracing/extras/importer/trace_event_importer.html#L197-L208

Fixes #204
2019-02-17 18:32:26 -08:00
Jamie Wong a2022a07a2 Fix crash when importing from stackprof without raw_timestamp_deltas (#207)
Fixes #200
2019-02-17 18:30:02 -08:00
Jamie Wong b7806a1c5f Alert instead of crash when successfully importing a file containing no profiles (#205)
Fixes #169
2019-02-17 17:46:24 -08:00
Jamie Wong 7f19a13012 Support importing multithreaded profiles from Chrome 66 (#206)
In #194, I added code to support import of multithreaded profiles from Chrome 70. I'm now doing some profiling work on an older version of Android chrome, and it seems like the profile objects don't yet have `id` properties. Instead, we should try using the `pid/tid` pair to identify profiles when the `id` field is absent.

This was tested against a profile import from Android Chrome 66.
2019-02-17 17:46:09 -08:00
Archerlly abd74be9fa add default instruments selected run number (#203)
this's will lack `com.apple.xray.owner.template` in instruments archive data where run instruments with command line.
like:
1. run`instruments -t Template.tracetemplate -D demo.trace -l 10000 -w  test.app`
2. drag `demo.trace` into `https://www.speedscope.app`
3. alert `Unrecognized format! See documentation about supported formats`
2019-02-17 17:45:51 -08:00
Jamie Wong c706bdfe04 Revert "Support importing partial JSON files (#202)"
This reverts commit cfc8fe8f6e.
2019-02-08 18:33:30 -08:00
Marcin Kolny cfc8fe8f6e 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.
2019-02-08 18:08:51 -08:00
20 changed files with 10220 additions and 34 deletions
+19
View File
@@ -1,5 +1,24 @@
## Unreleased
## [1.5.1] - 2019-06-06
### Fixed
* Fixed import of trace event files which contain unmatched "E" events (#222) (by @jlfwong)
## [1.5.0] - 2019-02-17
### Added
* Support importing unterminated JSON in simple cases (#208) (by @jlfwong)
### Fixed
* Fix crash when importing from stackpro without raw_timestamp_deltas (#207) (by @jlfwong)
* Alert instead of crash when importing a file containing no profiles (#205) (by @jlfwong)
* Fixed import of multithreaded profiles from Chrome 66 (#206) (by @jlfwong)
* Fixed import of instruments trace files with missing run number (#203) (by @Archerlly)
## [1.4.1] - 2019-01-22
### Fixed
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "speedscope",
"version": "1.4.1",
"version": "1.5.1",
"description": "",
"repository": "jlfwong/speedscope",
"main": "index.js",
File diff suppressed because one or more lines are too long
@@ -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},
@@ -0,0 +1,13 @@
[
{"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},
@@ -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}
@@ -0,0 +1,8 @@
[
{"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": "E", "name": "beta", "ts": 13},
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14},
{"pid": 0, "tid": 0, "ph": "E", "name": "gamma", "ts": 5},
{"pid": 0, "tid": 0, "ph": "E", "name": "delta", "ts": 5}
]
@@ -515,6 +515,408 @@ exports[`importFromChromeTimeline Chrome 69: indexToView 1`] = `0`;
exports[`importFromChromeTimeline Chrome 69: profileGroup.name 1`] = `"simple.json"`;
exports[`importFromChromeTimeline Workers Chrome 66 1`] = `
Object {
"frames": Array [
Frame {
"col": -1,
"file": "",
"key": "(program)::-1:-1",
"line": -1,
"name": "(program)",
"selfWeight": 819120,
"totalWeight": 819120,
},
Frame {
"col": 20,
"file": "http://172.30.3.176:7777/worker.js",
"key": "onmessage:http://172.30.3.176:7777/worker.js:14:20",
"line": 14,
"name": "onmessage",
"selfWeight": 639,
"totalWeight": 857719,
},
Frame {
"col": 14,
"file": "http://172.30.3.176:7777/worker.js",
"key": "alpha:http://172.30.3.176:7777/worker.js:8:14",
"line": 8,
"name": "alpha",
"selfWeight": 19976,
"totalWeight": 835149,
},
Frame {
"col": 14,
"file": "http://172.30.3.176:7777/worker.js",
"key": "gamma:http://172.30.3.176:7777/worker.js:0:14",
"line": 0,
"name": "gamma",
"selfWeight": 22197,
"totalWeight": 30000,
},
Frame {
"col": -1,
"file": "",
"key": "postMessage::-1:-1",
"line": -1,
"name": "postMessage",
"selfWeight": 1278,
"totalWeight": 21931,
},
],
"name": "worker.json - DedicatedWorker thread",
"stacks": Array [
" 9.07ms",
"(program) 5.49ms",
"onmessage;alpha;gamma 3.69ms",
"onmessage;postMessage 212.00µs",
"onmessage;postMessage;(program) 3.97ms",
"onmessage;alpha;gamma 3.82ms",
"onmessage;postMessage 414.00µs",
"onmessage;postMessage;(program) 3.71ms",
"onmessage;alpha;gamma 1.13ms",
"onmessage 188.00µs",
"onmessage;alpha;gamma 2.48ms",
"onmessage;alpha;gamma;(program) 3.89ms",
"onmessage;alpha;gamma 373.00µs",
"onmessage;alpha 184.00µs",
"onmessage;alpha;gamma 3.36ms",
"onmessage;alpha;gamma;(program) 3.91ms",
"onmessage;alpha;gamma 3.94ms",
"onmessage;postMessage 205.00µs",
"onmessage;postMessage;(program) 3.91ms",
"onmessage 451.00µs",
"onmessage;alpha;gamma 566.00µs",
"onmessage;alpha 194.00µs",
"onmessage;alpha;gamma 2.09ms",
"onmessage;alpha 189.00µs",
"onmessage;alpha;gamma 746.00µs",
"onmessage;postMessage 211.00µs",
"onmessage;postMessage;(program) 4.00ms",
"onmessage;alpha 4.80ms",
"onmessage;alpha;(program) 5.28ms",
"onmessage;alpha 4.70ms",
"onmessage;postMessage 236.00µs",
"onmessage;postMessage;(program) 5.06ms",
"onmessage;alpha 5.07ms",
"onmessage;alpha;(program) 4.81ms",
"onmessage;alpha 4.84ms",
"onmessage;alpha;(program) 775.08ms",
],
}
`;
exports[`importFromChromeTimeline Workers Chrome 66 2`] = `
Object {
"frames": Array [
Frame {
"col": -1,
"file": "",
"key": "(program)::-1:-1",
"line": -1,
"name": "(program)",
"selfWeight": 106601,
"totalWeight": 108131,
},
Frame {
"col": 8,
"file": "",
"key": "(anonymous)::247:8",
"line": 247,
"name": "(anonymous)",
"selfWeight": 551,
"totalWeight": 2766,
},
Frame {
"col": -1,
"file": "",
"key": "addEventListener::-1:-1",
"line": -1,
"name": "addEventListener",
"selfWeight": 178,
"totalWeight": 2215,
},
Frame {
"col": 17,
"file": "",
"key": "onLoaded::866:17",
"line": 866,
"name": "onLoaded",
"selfWeight": 372,
"totalWeight": 5158,
},
Frame {
"col": 0,
"file": "",
"key": "(anonymous)::0:0",
"line": 0,
"name": "(anonymous)",
"selfWeight": 0,
"totalWeight": 25649,
},
Frame {
"col": 17,
"file": "",
"key": "dispatch::845:17",
"line": 845,
"name": "dispatch",
"selfWeight": 1103,
"totalWeight": 25649,
},
Frame {
"col": 20,
"file": "",
"key": "setPlatform::839:20",
"line": 839,
"name": "setPlatform",
"selfWeight": 0,
"totalWeight": 902,
},
Frame {
"col": -1,
"file": "",
"key": "add::-1:-1",
"line": -1,
"name": "add",
"selfWeight": 521,
"totalWeight": 902,
},
Frame {
"col": 14,
"file": "",
"key": "reset::433:14",
"line": 433,
"name": "reset",
"selfWeight": 911,
"totalWeight": 2438,
},
Frame {
"col": -1,
"file": "",
"key": "getContext::-1:-1",
"line": -1,
"name": "getContext",
"selfWeight": 598,
"totalWeight": 598,
},
Frame {
"col": 20,
"file": "",
"key": "resetCanvas::423:20",
"line": 423,
"name": "resetCanvas",
"selfWeight": 370,
"totalWeight": 553,
},
Frame {
"col": -1,
"file": "",
"key": "scale::-1:-1",
"line": -1,
"name": "scale",
"selfWeight": 183,
"totalWeight": 183,
},
Frame {
"col": 8,
"file": "http://172.30.3.176:7777/worker.html",
"key": "(anonymous):http://172.30.3.176:7777/worker.html:0:8",
"line": 0,
"name": "(anonymous)",
"selfWeight": 0,
"totalWeight": 2591,
},
Frame {
"col": -1,
"file": "",
"key": "Worker::-1:-1",
"line": -1,
"name": "Worker",
"selfWeight": 873,
"totalWeight": 2591,
},
Frame {
"col": 29,
"file": "http://172.30.3.176:7777/worker.html",
"key": "worker.onmessage:http://172.30.3.176:7777/worker.html:19:29",
"line": 19,
"name": "worker.onmessage",
"selfWeight": 1221,
"totalWeight": 60859,
},
Frame {
"col": 16,
"file": "http://172.30.3.176:7777/worker.html",
"key": "apple:http://172.30.3.176:7777/worker.html:9:16",
"line": 9,
"name": "apple",
"selfWeight": 5474,
"totalWeight": 58863,
},
Frame {
"col": 17,
"file": "http://172.30.3.176:7777/worker.html",
"key": "banana:http://172.30.3.176:7777/worker.html:1:17",
"line": 1,
"name": "banana",
"selfWeight": 50635,
"totalWeight": 53816,
},
Frame {
"col": -1,
"file": "",
"key": "postMessage::-1:-1",
"line": -1,
"name": "postMessage",
"selfWeight": 348,
"totalWeight": 348,
},
Frame {
"col": -1,
"file": "",
"key": "(garbage collector)::-1:-1",
"line": -1,
"name": "(garbage collector)",
"selfWeight": 1530,
"totalWeight": 1530,
},
],
"name": "worker.json - CrRendererMain",
"stacks": Array [
" 28.66ms",
"(program) 315.00µs",
" 108.00µs",
"(program) 433.00µs",
" 116.00µs",
"(program) 436.00µs",
" 368.00µs",
"(program) 108.00µs",
" 30.76ms",
"(program) 1.48ms",
" 167.00µs",
"(program) 1.68ms",
" 6.96ms",
"(program) 2.03ms",
" 4.64ms",
"(program) 3.86ms",
" 14.05ms",
"(program) 15.35ms",
"(anonymous) 551.00µs",
"(anonymous);addEventListener 178.00µs",
"(anonymous);addEventListener;(program) 2.04ms",
"onLoaded 372.00µs",
"onLoaded;(program) 4.79ms",
"(anonymous);dispatch;setPlatform;add 521.00µs",
"(anonymous);dispatch;setPlatform;add;(program) 381.00µs",
"(anonymous);dispatch 927.00µs",
"(anonymous);dispatch;reset 185.00µs",
"(anonymous);dispatch;reset;getContext 598.00µs",
"(anonymous);dispatch;reset 131.00µs",
"(anonymous);dispatch;reset;resetCanvas 370.00µs",
"(anonymous);dispatch;reset;resetCanvas;scale 183.00µs",
"(anonymous);dispatch;reset 595.00µs",
"(anonymous);dispatch;reset;(program) 376.00µs",
"(anonymous);dispatch 176.00µs",
"(anonymous);dispatch;(program) 21.21ms",
" 606.00µs",
"(program) 11.20ms",
" 296.00µs",
"(program) 1.90ms",
"(anonymous);Worker 873.00µs",
"(anonymous);Worker;(program) 1.72ms",
" 670.00µs",
"(program) 28.45ms",
" 353.00µs",
"(program) 187.00µs",
" 185.00µs",
"(program) 4.29ms",
" 607.00µs",
"(program) 192.00µs",
" 407.00µs",
"(program) 215.00µs",
" 28.98ms",
"(program) 770.00µs",
"worker.onmessage;apple 185.00µs",
"worker.onmessage;apple;banana 180.00µs",
"worker.onmessage;apple 367.00µs",
"worker.onmessage;apple;banana 182.00µs",
"worker.onmessage;apple 183.00µs",
"worker.onmessage;apple;banana 546.00µs",
"worker.onmessage;apple 365.00µs",
"worker.onmessage;apple;banana 182.00µs",
"worker.onmessage;apple 365.00µs",
"worker.onmessage;apple;banana 188.00µs",
"worker.onmessage;apple 912.00µs",
"worker.onmessage;apple;banana 189.00µs",
"worker.onmessage;apple 2.51ms",
"worker.onmessage;apple;banana 8.48ms",
"worker.onmessage;postMessage 170.00µs",
" 3.95ms",
"worker.onmessage;apple;banana 433.00µs",
"worker.onmessage 187.00µs",
"worker.onmessage;apple;banana 3.30ms",
" 3.90ms",
"(program) 187.00µs",
"worker.onmessage;apple;banana 1.47ms",
"worker.onmessage;apple 187.00µs",
"worker.onmessage;apple;banana 2.05ms",
" 4.01ms",
"worker.onmessage;apple;banana 3.77ms",
"worker.onmessage;postMessage 178.00µs",
" 3.87ms",
"worker.onmessage;apple;banana 3.74ms",
"worker.onmessage 233.00µs",
" 3.88ms",
"(program) 187.00µs",
"worker.onmessage;apple;banana 744.00µs",
"worker.onmessage;apple 196.00µs",
"worker.onmessage;apple;banana 2.08ms",
"worker.onmessage 188.00µs",
"worker.onmessage;apple;banana 582.00µs",
" 4.46ms",
"(program) 187.00µs",
"worker.onmessage;apple;banana 3.84ms",
"worker.onmessage;apple;banana;(program) 278.00µs",
" 4.76ms",
"worker.onmessage;apple;banana 5.01ms",
"worker.onmessage;apple;banana;(program) 214.00µs",
" 4.67ms",
"(program) 193.00µs",
"worker.onmessage;apple;banana 2.44ms",
"worker.onmessage 186.00µs",
"worker.onmessage;apple;banana 1.02ms",
"worker.onmessage;banana 209.00µs",
"worker.onmessage;apple;banana 207.00µs",
"worker.onmessage;apple 207.00µs",
"worker.onmessage;apple;banana 634.00µs",
"worker.onmessage;apple;banana;(program) 237.00µs",
" 4.92ms",
"worker.onmessage;apple;banana 3.05ms",
"worker.onmessage;banana 218.00µs",
"worker.onmessage;apple;banana 876.00µs",
"worker.onmessage 219.00µs",
"worker.onmessage;apple;banana 219.00µs",
"worker.onmessage;apple;banana;(program) 706.00µs",
" 4.40ms",
"worker.onmessage;apple;banana 2.71ms",
"worker.onmessage 208.00µs",
"worker.onmessage;apple;banana 2.08ms",
"worker.onmessage;apple;banana;(program) 216.00µs",
"worker.onmessage;apple;banana;(program);(garbage collector) 1.53ms",
" 469.53ms",
"(program) 382.00µs",
" 262.13ms",
"(program) 200.00µs",
" 4.51ms",
"(program) 221.00µs",
],
}
`;
exports[`importFromChromeTimeline Workers Chrome 66: indexToView 1`] = `1`;
exports[`importFromChromeTimeline Workers Chrome 66: profileGroup.name 1`] = `"worker.json"`;
exports[`importFromChromeTimeline Workers Chrome 70 1`] = `
Object {
"frames": Array [
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`importTraceEvents bad E events 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": 12,
"totalWeight": 12,
},
],
"name": "pid 0, tid 0",
"stacks": Array [
"alpha 1.00µs",
"alpha;beta 12.00µs",
"alpha 1.00µs",
],
}
`;
exports[`importTraceEvents bad E events: indexToView 1`] = `0`;
exports[`importTraceEvents bad E events: profileGroup.name 1`] = `"too-many-end-events.json"`;
exports[`importTraceEvents multiprocess 1`] = `
Object {
"frames": Array [
@@ -164,6 +199,174 @@ exports[`importTraceEvents multiprocess: indexToView 1`] = `0`;
exports[`importTraceEvents multiprocess: profileGroup.name 1`] = `"multiprocess.json"`;
exports[`importTraceEvents partial json import 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 partial json import trailing comma 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 partial json import trailing comma: indexToView 1`] = `0`;
exports[`importTraceEvents partial json import trailing comma: profileGroup.name 1`] = `"simple-partial-trailing-comma.json"`;
exports[`importTraceEvents partial json import whitespace padding 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 partial json import whitespace padding: indexToView 1`] = `0`;
exports[`importTraceEvents partial json import whitespace padding: profileGroup.name 1`] = `"simple-partial-whitespace.json"`;
exports[`importTraceEvents partial json import: indexToView 1`] = `0`;
exports[`importTraceEvents partial json import: profileGroup.name 1`] = `"simple-partial.json"`;
exports[`importTraceEvents simple 1`] = `
Object {
"frames": Array [
+4
View File
@@ -16,6 +16,10 @@ test('importFromV8Profiler Node 10', async () => {
await checkProfileSnapshot('./sample/profiles/node/10.11.0/example.cpuprofile')
})
test('importFromChromeTimeline Workers Chrome 66', async () => {
await checkProfileSnapshot('./sample/profiles/Chrome/66/worker.json')
})
test('importFromChromeTimeline Workers Chrome 70', async () => {
await checkProfileSnapshot('./sample/profiles/Chrome/70/worker.json')
})
+9 -12
View File
@@ -79,19 +79,17 @@ export function importFromChromeTimeline(events: TimelineEvent[], fileName: stri
// that they are.
sortBy(events, e => e.ts)
const DEFAULT_ID = '(default id)'
for (let event of events) {
if (event.name === 'CpuProfile') {
cpuProfileByID.set(event.id || DEFAULT_ID, event.args.data.cpuProfile as CPUProfile)
if (event.id) {
pidTidById.set(event.id, `${event.pid}:${event.tid}`)
}
const pidTid = `${event.pid}:${event.tid}`
const id = event.id || pidTid
cpuProfileByID.set(id, event.args.data.cpuProfile as CPUProfile)
pidTidById.set(id, pidTid)
}
if (event.name === 'Profile') {
cpuProfileByID.set(event.id || DEFAULT_ID, {
const pidTid = `${event.pid}:${event.tid}`
cpuProfileByID.set(event.id || pidTid, {
startTime: 0,
endTime: 0,
nodes: [],
@@ -110,7 +108,8 @@ export function importFromChromeTimeline(events: TimelineEvent[], fileName: stri
}
if (event.name === 'ProfileChunk') {
const cpuProfile = cpuProfileByID.get(event.id || DEFAULT_ID)
const pidTid = `${event.pid}:${event.tid}`
const cpuProfile = cpuProfileByID.get(event.id || pidTid)
if (cpuProfile) {
const chunk = event.args.data
if (chunk.cpuProfile) {
@@ -131,9 +130,7 @@ export function importFromChromeTimeline(events: TimelineEvent[], fileName: stri
cpuProfile.endTime = chunk.endTime
}
} else {
console.warn(
`Ignoring ProfileChunk for undeclared Profile with id ${event.id || DEFAULT_ID}`,
)
console.warn(`Ignoring ProfileChunk for undeclared Profile with id ${event.id || pidTid}`)
}
}
}
+4
View File
@@ -5,4 +5,8 @@ test('importProfileGroup', async () => {
expect(await importProfileGroupFromText('unknown', '')).toBe(null)
expect(await importProfileGroupFromText('unknown', 'Hello world')).toBe(null)
expect(await importProfileGroupFromText('unknown', 'Hello\n\nWorld')).toBe(null)
// Importing from a version of stackprof which was missing raw_timestamp_deltas should return null
const oldStackprof = `{"version":1.2,"mode":"wall","interval":1000,"samples":0,"gc_samples":0,"missed_samples":0,"frames":{}}`
expect(await importProfileGroupFromText('unknown', oldStackprof)).toBe(null)
})
+22 -2
View File
@@ -71,6 +71,26 @@ function toGroup(profile: Profile | null): ProfileGroup | null {
return {name: profile.getName(), indexToView: 0, profiles: [profile]}
}
function fixUpJSON(content: string): string {
// This code is similar to the code from here:
// https://github.com/catapult-project/catapult/blob/27e047e0494df162022be6aa8a8862742a270232/tracing/tracing/extras/importer/trace_event_importer.html#L197-L208
//
// If the event data begins with a [, then we know it should end with a ]. The
// reason we check for this is because some tracing implementations cannot
// guarantee that a ']' gets written to the trace file. So, we are forgiving
// and if this is obviously the case, we fix it up before throwing the string
// at JSON.parse.
//
content = content.trim()
if (content[0] === '[') {
content = content.replace(/,\s*$/, '')
if (content[content.length - 1] !== ']') {
content += ']'
}
}
return content
}
async function _importProfileGroup(dataSource: ProfileDataSource): Promise<ProfileGroup | null> {
const fileName = await dataSource.name()
@@ -116,7 +136,7 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
// Second pass: Try to guess what file format it is based on structure
let parsed: any
try {
parsed = JSON.parse(contents)
parsed = JSON.parse(fixUpJSON(contents))
} catch (e) {}
if (parsed) {
if (parsed['$schema'] === 'https://www.speedscope.app/file-format-schema.json') {
@@ -137,7 +157,7 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
} else if ('head' in parsed && 'samples' in parsed && 'timestamps' in parsed) {
console.log('Importing as Chrome CPU Profile (old format)')
return toGroup(importFromOldV8CPUProfile(parsed))
} else if ('mode' in parsed && 'frames' in parsed) {
} else if ('mode' in parsed && 'frames' in parsed && 'raw_timestamp_deltas' in parsed) {
console.log('Importing as stackprof profile')
return toGroup(importFromStackprof(parsed))
} else if ('code' in parsed && 'functions' in parsed && 'ticks' in parsed) {
+3
View File
@@ -98,4 +98,7 @@ describe('importFromInstrumentsTrace', () => {
test('Instruments 9.3.1', async () => {
await importFromTrace('./sample/profiles/Instruments/9.3.1/simple-time-profile.trace.zip')
})
test('Instruments 10.0', async () => {
await importFromTrace('./sample/profiles/Instruments/10.0/simple-time-profile.trace.zip')
})
})
+4 -1
View File
@@ -361,7 +361,10 @@ async function readFormTemplate(tree: TraceDirectoryTree): Promise<FormTemplateD
const archive = readInstrumentsKeyedArchive(await readAsArrayBuffer(formTemplate))
const version = archive['com.apple.xray.owner.template.version']
const selectedRunNumber = archive['com.apple.xray.owner.template'].get('_selectedRunNumber')
let selectedRunNumber = 1
if ('com.apple.xray.owner.template' in archive) {
selectedRunNumber = archive['com.apple.xray.owner.template'].get('_selectedRunNumber')
}
let instrument = archive['$1']
if ('stubInfoByUUID' in archive) {
instrument = Array.from(archive['stubInfoByUUID'].keys())[0]
+16
View File
@@ -11,3 +11,19 @@ test('importTraceEvents simple object', async () => {
test('importTraceEvents multiprocess', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/multiprocess.json')
})
test('importTraceEvents partial json import', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial.json')
})
test('importTraceEvents partial json import trailing comma', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial-trailing-comma.json')
})
test('importTraceEvents partial json import whitespace padding', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial-whitespace.json')
})
test('importTraceEvents bad E events', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/too-many-end-events.json')
})
+28 -17
View File
@@ -1,4 +1,4 @@
import {sortBy, zeroPad} from '../lib/utils'
import {sortBy, zeroPad, lastOf} from '../lib/utils'
import {ProfileGroup, CallTreeProfileBuilder, FrameInfo} from '../lib/profile'
import {TimeFormatter} from '../lib/value-formatters'
@@ -143,8 +143,10 @@ function keyForEvent(event: TraceEvent): string {
return name
}
type TraceEventProfileState = {profile: CallTreeProfileBuilder; eventStack: BTraceEvent[]}
function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
const profileByPidTid = new Map<string, CallTreeProfileBuilder>()
const stateByPidTid = new Map<string, TraceEventProfileState>()
const importableEvents = filterIgnoredEventTypes(events)
const durationEvents = convertToDurationEvents(importableEvents)
@@ -185,15 +187,16 @@ function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
}
}
function getOrCreateProfile(pid: number, tid: number) {
function getOrCreateProfileState(pid: number, tid: number): TraceEventProfileState {
// We zero-pad the PID and TID to make sorting them by pid/tid pair later easier.
const pidTid = `${zeroPad('' + pid, 10)}:${zeroPad('' + tid, 10)}`
let profile = profileByPidTid.get(pidTid)
if (profile != null) return profile
profile = new CallTreeProfileBuilder()
let state = stateByPidTid.get(pidTid)
if (state != null) return state
let profile = new CallTreeProfileBuilder()
state = {profile, eventStack: []}
profile.setValueFormatter(new TimeFormatter('microseconds'))
profileByPidTid.set(pidTid, profile)
stateByPidTid.set(pidTid, state)
const processName = processNamesByPid.get(pid)
const threadName = threadNamesByPidTid.get(`${pid}:${tid}`)
@@ -208,11 +211,11 @@ function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
profile.setName(`pid ${pid}, tid ${tid}`)
}
return profile
return state
}
for (let ev of durationEvents) {
const profile = getOrCreateProfile(ev.pid, ev.tid)
const {profile, eventStack} = getOrCreateProfileState(ev.pid, ev.tid)
const key = keyForEvent(ev)
const frameInfo: FrameInfo = {
key: key,
@@ -220,11 +223,23 @@ function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
}
switch (ev.ph) {
case 'B':
eventStack.push(ev)
profile.enterFrame(frameInfo, ev.ts)
break
case 'E':
profile.leaveFrame(frameInfo, ev.ts)
const lastEvent = lastOf(eventStack)
if (lastEvent != null && lastEvent.name === ev.name) {
profile.leaveFrame(frameInfo, ev.ts)
eventStack.pop()
} else {
console.warn(
'Event discarded because it did not match top-of-stack. Discarded event:',
ev,
'Top of stack:',
lastEvent,
)
}
break
default:
@@ -237,10 +252,10 @@ function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
// TODO: The standard specifies that metadata events with the name
// "process_sort_index" and "thread_sort_index" can be used to influence the
// order, but for simplicity we'll ignore that until someone complains :)
const profilePairs = Array.from(profileByPidTid.entries())
const profilePairs = Array.from(stateByPidTid.entries())
sortBy(profilePairs, p => p[0])
return {name: '', indexToView: 0, profiles: profilePairs.map(p => p[1])}
return {name: '', indexToView: 0, profiles: profilePairs.map(p => p[1].profile)}
}
function isTraceEventList(maybeEventList: any): maybeEventList is TraceEvent[] {
@@ -285,13 +300,9 @@ function isTraceEventObject(
export function isTraceEventFormatted(
rawProfile: any,
): rawProfile is {traceEvents: TraceEvent[]} | TraceEvent[] {
// We're only going to suppor the JSON formatted profiles for now.
// We're only going to support 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)
}
+5 -1
View File
@@ -310,11 +310,15 @@ export class Application extends StatelessComponent<ApplicationProps> {
return
}
// TODO(jlfwong): Make these into nicer overlays
if (profileGroup == null) {
// TODO(jlfwong): Make this a nicer overlay
alert('Unrecognized format! See documentation about supported formats.')
this.props.setLoading(false)
return
} else if (profileGroup.profiles.length === 0) {
alert("Successfully imported profile, but it's empty!")
this.props.setLoading(false)
return
}
if (this.props.hashParams.title) {