Compare commits

...
26 Commits
Author SHA1 Message Date
Jamie Wong 707462e9cf 1.5.3 2020-01-16 00:09:07 -08:00
Jamie Wong 375040e892 Bump dependency versions to unbreak build (#253)
I ended up in a horrible peer dependency hell and apparently needed to bump the versions of quicktype, typescript, ts-jest, *and* jest to get out of it. But I think I got out of it!

Local builds and deployment builds both seem to work after these changes.
2020-01-15 23:32:14 -08:00
Jamie Wong 5ae9abcf1d Trace event: Prevent event re-ordering from generating incorrect flamegraphs (#252)
The code to import trace formatted events intentionally re-orders events in order to make it easier at flamegraph construction time to order the pushes and pops of frames.

It turns out that this re-ordering results in incorrect flamegraphs being generated as shown in #251.

This PR fixes this by avoiding re-ordering in situations where it isn't necessary.
2020-01-15 22:03:23 -08:00
miso11 bdd9301c59 make tooltip width wider (#239)
Issue #191

It shouldn't be ellipsized, or at least it should be to the right, because I think it's more interesting the file name and function name than the system path where it is found.

Make max width bigger

Before: 
![before](https://user-images.githubusercontent.com/52132927/67621727-ff6dda80-f812-11e9-8c10-533542fe0302.png)

After:

![after](https://user-images.githubusercontent.com/52132927/67621730-04cb2500-f813-11e9-8d36-80e8c58a529e.png)
2019-10-28 11:36:15 -07:00
Jamie Wong cc9750923a Update README.md 2019-10-16 00:15:06 -07:00
Jonathan Chan c3074b7343 1.5.2 2019-10-10 18:27:28 -07:00
Jonathan Chan b15a08b3ff Support newer Emscripten .symbols with hex escapes (#233)
Apparently Emscripten now generates `.symbols` files where names are not mangled using Clang's mangling scheme, but rather hex-escaped! So 'a\20b' means 'a b'. Currently we can't import these symbol maps into Speedscope because a regex rejects them, and they look weird because we don't unescape.
2019-10-10 14:31:34 -07:00
Jamie Wong 68683aa054 Add pyspeedscope & flamescope to README 2019-10-06 15:10:00 -07:00
Jamie Wong eb0e1ce731 Add py-spy to README 2019-10-06 14:59:17 -07:00
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
Jamie Wong ddc61302e8 1.4.1 2019-01-22 21:53:09 -08:00
Jamie Wong 864c065053 Fix importing of Trace Event Format files with no ts field on M events (#198)
The spec for the Trace Event Format technically requires that all entries have "ts" values, and they do in the profiles recorded using chrome://tracing. We don't actually use those values in the case of "M" (metadata) events, however, and they're semantically meaningless as far as I can tell, so let's stop requiring them.

This allows the files that @aras-p provided in #77 to import successfully.

Fixes #77
2019-01-22 21:51:23 -08:00
Jamie Wong 7cca1a76bc 1.4.0 2019-01-22 12:03:31 -08:00
Jamie Wong 8c574d1c92 Support basic import of profiles in the "Trace Event Format" (#197)
This PR implements basic import of profiles from the "Trace Event Format", which is used by `chrome://tracing`, but also which many other tools target as a convenient event tracing format. The spec can be found here: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.xqopa5m0e28f.

The standard supports a broad set of events, some of which don't yet have any practical way to visualize them in speedscope. This PR implements support for the `B`, `E`, and `X` events, as well as gathering process and thread names via some of the `M` events.

This work was motivated by a generous donation to /dev/color by @aras-p: https://github.com/jlfwong/speedscope/issues/77#issuecomment-455077014

Fixes #77
2019-01-21 20:49:25 -08:00
Jamie Wong fbc8946caa Update CHANGELOG.md 2018-12-04 12:05:59 -08:00
vmarchaud 8cddf3fe81 Import v8 cpu profile (old format) (#177)
As said on #170, i added the support for the old format used by https://github.com/hyj1991/v8-profiler-node8 (which is currently used for pm2.io).
2018-12-04 12:05:19 -08:00
Jamie Wong c15ca263d3 Update CHANGELOG.md 2018-12-03 19:29:25 -08:00
35 changed files with 15443 additions and 5059 deletions
+49 -2
View File
@@ -1,11 +1,58 @@
## Unreleased
## [1.5.3] - 2020-01-16
### Fixed
- Bump dependency versions to unbreak build [#253] (by @jlfwong, with changes from @Archerlly's #215)
- Trace event: Prevent event re-ordering from generating incorrect flam
egraphs (#252, with changes from @hwajaywang's #249)
- Make tooltip width wider [#239] (by @miso11)
## [1.5.2] - 2019-10-10
### Fixed
* Fix emscripten remapping when symbols are hex-escaped, like `a\20b` [#233] (by @jyc)
## [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
* Fix importing of Trace Event Format files with no ts field on M events [#198] (by @jlfwong)
## [1.4.0] - 2019-01-22
### Added
* Import v8 cpu profile (old format) [#177] (by @vmarchaud)
* Import basic "Trace Event Format" profiles [#197] (by @jlfwong)
## [1.3.2] - 2018-12-03
* Fixed import of multithreaded Chrome profiles [#19] (by @jlfwong)
### Fixed
* Fixed import of multithreaded Chrome profiles [#19] (by @jlfwong)
## [1.3.1] - 2018-11-08
### Fixed
+6
View File
@@ -40,11 +40,17 @@ speedscope is designed to ingest profiles from a variety of different profilers
- Ruby
- [Importing from stackprof](https://github.com/jlfwong/speedscope/wiki/Importing-from-stackprof-(ruby))
- [Importing from rbspy](https://github.com/jlfwong/speedscope/wiki/Importing-from-rbspy-(ruby))
- Python
- [Importing from py-spy](https://github.com/jlfwong/speedscope/wiki/Importing-from-py-spy-(python))
- [pyspeedscope](https://github.com/windelbouwman/pyspeedscope)
- Go
- [Importing from pprof](https://github.com/jlfwong/speedscope/wiki/Importing-from-pprof-(go))
- Rust
- [flamescope](https://github.com/coolreader18/flamescope)
- Native code
- [Importing from Instruments.app](https://github.com/jlfwong/speedscope/wiki/Importing-from-Instruments.app) (macOS)
- [Importing from `perf`](https://github.com/jlfwong/speedscope/wiki/Importing-from-perf-(linux)) (linux)
- [Importing from .NET Core](https://github.com/jlfwong/speedscope/wiki/Importing-from-.NET-Core)
- [Importing from GHC (Haskell)](https://github.com/jlfwong/speedscope/wiki/Importing-from-Haskell)
- [Importing from custom sources](https://github.com/jlfwong/speedscope/wiki/Importing-from-custom-sources)
+4208 -5012
View File
File diff suppressed because it is too large Load Diff
+25 -9
View File
@@ -1,6 +1,6 @@
{
"name": "speedscope",
"version": "1.3.2",
"version": "1.5.3",
"description": "",
"repository": "jlfwong/speedscope",
"main": "index.js",
@@ -17,8 +17,15 @@
"test": "tsc --noEmit && npm run lint && npm run coverage",
"serve": "parcel assets/index.html --open --no-autoinstall"
},
"files": ["bin/cli.js", "dist/release/**", "!*.map"],
"browserslist": ["last 2 Chrome versions", "last 2 Firefox versions"],
"files": [
"bin/cli.js",
"dist/release/**",
"!*.map"
],
"browserslist": [
"last 2 Chrome versions",
"last 2 Firefox versions"
],
"author": "",
"license": "MIT",
"devDependencies": {
@@ -30,7 +37,7 @@
"coveralls": "3.0.1",
"eslint": "4.19.1",
"eslint-plugin-prettier": "2.6.0",
"jest": "23.0.1",
"jest": "24.3.0",
"jsverify": "0.8.3",
"jszip": "3.1.5",
"pako": "1.0.6",
@@ -39,10 +46,10 @@
"preact-redux": "jlfwong/preact-redux#a56dcc4",
"prettier": "1.12.0",
"protobufjs": "6.8.8",
"quicktype": "15.0.45",
"quicktype": "15.0.209",
"redux": "^4.0.0",
"ts-jest": "22.4.6",
"typescript": "2.8.1",
"ts-jest": "24.3.0",
"typescript": "3.2.4",
"typescript-eslint-parser": "17.0.1",
"uglify-es": "3.2.2"
},
@@ -51,8 +58,17 @@
"^.+\\.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"
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"head":{"functionName":"(root)","url":"","lineNumber":-1,"bailoutReason":"","id":1,"scriptId":0,"hitCount":0,"children":[{"functionName":"","url":"","lineNumber":0,"callUID":1,"bailoutReason":"","id":2,"scriptId":0,"hitCount":0,"children":[{"functionName":"a","url":"","lineNumber":0,"callUID":2,"bailoutReason":"","id":3,"scriptId":0,"hitCount":0,"children":[{"functionName":"b","url":"","lineNumber":5,"callUID":3,"bailoutReason":"","id":4,"scriptId":0,"hitCount":0,"children":[{"functionName":"d","url":"","lineNumber":13,"callUID":4,"bailoutReason":"","id":5,"scriptId":0,"hitCount":14,"children":[]}]},{"functionName":"c","url":"","lineNumber":9,"callUID":3,"bailoutReason":"","id":6,"scriptId":0,"hitCount":0,"children":[{"functionName":"d","url":"","lineNumber":13,"callUID":6,"bailoutReason":"","id":7,"scriptId":0,"hitCount":14,"children":[]}]}]}]}]},"startTime":163140,"endTime":163140,"samples":[2,5,5,5,5,5,5,5,5,5,5,5,5,5,5,7,7,7,7,7,7,7,7,7,7,7,7,7,7],"timestamps":[163140599286,163140610861,163140611921,163140612966,163140614236,163140615507,163140616694,163140617968,163140619238,163140620258,163140621507,163140622765,163140624037,163140625303,163140626378,163140627649,163140628923,163140630191,163140631457,163140632746,163140634032,163140635304,163140636440,163140637716,163140638990,163140640255,163140641520,163140642791,163140644063,163140645206]}
@@ -0,0 +1,22 @@
[
{"pid": 0, "tid": 0, "ph": "M", "name": "process_name", "args": {"name": "p0"}},
{"pid": 0, "tid": 0, "ph": "M", "name": "thread_name", "args": {"name": "p0t0"}},
{"pid": 0, "tid": 0, "ph": "X", "name": "alpha", "ts": 0, "dur": 1},
{"pid": 0, "tid": 1, "ph": "M", "name": "thread_name", "args": {"name": "p0t1"}},
{"pid": 0, "tid": 1, "ph": "X", "name": "beta", "ts": 0, "dur": 1},
{"pid": 1, "tid": 0, "ph": "M", "name": "process_name", "args": {"name": "p1"}},
{"pid": 1, "tid": 0, "ph": "M", "name": "thread_name", "args": {"name": "p1t0"}},
{"pid": 1, "tid": 0, "ph": "X", "name": "gamma", "ts": 0, "dur": 1},
{"pid": 1, "tid": 1, "ph": "M", "name": "thread_name", "args": {"name": "p1t1"}},
{"pid": 1, "tid": 1, "ph": "X", "name": "delta", "ts": 0, "dur": 1},
{"pid": 2, "tid": 0, "ph": "M", "name": "thread_name", "args": {"name": "p2t0"}},
{"pid": 2, "tid": 0, "ph": "X", "name": "epsilon", "ts": 0, "dur": 1},
{"pid": 2, "tid": 1, "ph": "M", "name": "thread_name", "args": {"name": "p2t1"}},
{"pid": 2, "tid": 1, "ph": "X", "name": "phi", "ts": 0, "dur": 1},
{"pid": 3, "tid": 0, "ph": "M", "name": "process_name", "args": {"name": "p3"}},
{"pid": 3, "tid": 0, "ph": "X", "name": "zeta", "ts": 0, "dur": 1},
{"pid": 3, "tid": 1, "ph": "X", "name": "eta", "ts": 0, "dur": 1}
]
@@ -0,0 +1,16 @@
[
{"tid": 1, "ph": "B", "pid": 0, "name": "A", "ts": 0},
{"tid": 1, "ph": "B", "pid": 0, "name": "B", "ts": 0},
{"tid": 1, "ph": "B", "pid": 0, "name": "C", "ts": 0},
{"tid": 1, "ph": "E", "pid": 0, "name": "C", "ts": 1},
{"tid": 1, "ph": "E", "pid": 0, "name": "B", "ts": 2},
{"tid": 1, "ph": "E", "pid": 0, "name": "A", "ts": 3},
{"tid": 1, "ph": "B", "pid": 0, "name": "A", "ts": 4},
{"tid": 1, "ph": "B", "pid": 0, "name": "B", "ts": 5},
{"tid": 1, "ph": "B", "pid": 0, "name": "C", "ts": 6},
{"tid": 1, "ph": "E", "pid": 0, "name": "C", "ts": 7},
{"tid": 1, "ph": "E", "pid": 0, "name": "B", "ts": 7},
{"tid": 1, "ph": "E", "pid": 0, "name": "A", "ts": 7},
{"tid": 1, "ph": "B", "pid": 0, "name": "X", "ts": 7},
{"tid": 1, "ph": "E", "pid": 0, "name": "X", "ts": 10}
]
@@ -0,0 +1,9 @@
{
"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}
]
}
@@ -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}
+8
View File
@@ -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": "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}
]
@@ -0,0 +1,60 @@
/*
* This script is used to reconstruct an cpu profile from chrome with an old format
* still used in nodejs with v8-profiler
*/
const fs = require('fs')
const data = JSON.parse(fs.readFileSync('../../profiles/Chrome/65/simple.cpuprofile').toString())
const _convertTimeDeltas = (profile) => {
if (!profile.timeDeltas) return null
let lastTimeUsec = profile.startTime
const timestamps = new Array(profile.timeDeltas.length + 1)
for (let i = 0; i < profile.timeDeltas.length; ++i) {
timestamps[i] = lastTimeUsec
lastTimeUsec += profile.timeDeltas[i]
}
timestamps[profile.timeDeltas.length] = lastTimeUsec
return timestamps
}
const reformatNode = node => {
if (!node.children) node.children = []
node.children = node.children.map(childID => {
if (typeof childID !== 'number') return childID
const childNode = data.nodes.find(node => node.id === childID)
if (typeof childNode !== 'object') return null
childNode.callUID = node.id
return childNode
})
return {
functionName: node.callFrame.functionName,
url: node.callFrame.url,
lineNumber: node.callFrame.lineNumber,
callUID: node.callUID,
bailoutReason: '',
id: node.id,
scriptId: 0,
hitCount: node.hitCount,
children: node.children.map(reformatNode)
}
}
// reformat then only keep the root as top level node
const nodes = data.nodes
.map(reformatNode)
.filter(node => node.functionName === '(root)')[0]
// since it can be undefined, create an array so execution still works
if (!data.timeDeltas) {
data.timeDeltas = []
}
fs.writeFileSync('./new.cpuprofile', JSON.stringify({
head: nodes,
startTime: Math.floor(data.startTime / 1000000),
endTime: Math.floor(data.endTime / 1000000),
samples: data.samples,
timestamps: _convertTimeDeltas(data)
}))
+8 -2
View File
@@ -455,7 +455,8 @@ export namespace WebGL {
widthInAppUnits: number,
heightInAppUnits: number,
) {
const bounds = this._gl.canvas.getBoundingClientRect()
let canvas = this._gl.canvas as HTMLCanvasElement
const bounds = canvas.getBoundingClientRect()
if (
this._width === widthInAppUnits &&
@@ -467,7 +468,6 @@ export namespace WebGL {
return
}
let canvas = this._gl.canvas
let style = canvas.style
canvas.width = widthInPixels
canvas.height = heightInPixels
@@ -1086,11 +1086,17 @@ export namespace WebGL {
_compileShader(gl: WebGLRenderingContext, type: GLenum, source: string) {
let shader = gl.createShader(type)
if (!shader) {
throw new Error('Failed to create shader')
}
gl.shaderSource(shader, source)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(`${gl.getShaderInfoLog(shader)}`)
}
if (!this._program) {
throw new Error('Tried to attach shader before program was created')
}
gl.attachShader(this._program, shader)
}
@@ -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 [
@@ -1357,3 +1759,66 @@ exports[`importFromChromeTimeline Workers Chrome 70: profileGroup.name 1`] = `"w
exports[`importFromChromeTimeline: indexToView 1`] = `0`;
exports[`importFromChromeTimeline: profileGroup.name 1`] = `"simple-timeline.json"`;
exports[`importFromV8Profiler Node 10 1`] = `
Object {
"frames": Array [
Frame {
"col": 0,
"file": "",
"key": "(anonymous)::0:0",
"line": 0,
"name": "(anonymous)",
"selfWeight": 11575,
"totalWeight": 44777,
},
Frame {
"col": 0,
"file": "",
"key": "a::0:0",
"line": 0,
"name": "a",
"selfWeight": 0,
"totalWeight": 33202,
},
Frame {
"col": 0,
"file": "",
"key": "b::5:0",
"line": 5,
"name": "b",
"selfWeight": 0,
"totalWeight": 16788,
},
Frame {
"col": 0,
"file": "",
"key": "d::13:0",
"line": 13,
"name": "d",
"selfWeight": 33202,
"totalWeight": 33202,
},
Frame {
"col": 0,
"file": "",
"key": "c::9:0",
"line": 9,
"name": "c",
"selfWeight": 0,
"totalWeight": 16414,
},
],
"name": "example.cpuprofile",
"stacks": Array [
" 599.29ms",
"(anonymous) 11.57ms",
"(anonymous);a;b;d 16.79ms",
"(anonymous);a;c;d 16.41ms",
],
}
`;
exports[`importFromV8Profiler Node 10: indexToView 1`] = `0`;
exports[`importFromV8Profiler Node 10: profileGroup.name 1`] = `"example.cpuprofile"`;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,526 @@
// 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 event re-ordering 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "A",
"line": undefined,
"name": "A",
"selfWeight": 1,
"totalWeight": 10,
},
Frame {
"col": undefined,
"file": undefined,
"key": "C",
"line": undefined,
"name": "C",
"selfWeight": 3,
"totalWeight": 10,
},
Frame {
"col": undefined,
"file": undefined,
"key": "B",
"line": undefined,
"name": "B",
"selfWeight": 3,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": undefined,
"key": "X",
"line": undefined,
"name": "X",
"selfWeight": 3,
"totalWeight": 3,
},
],
"name": "pid 0, tid 1",
"stacks": Array [
"A;C;B 2.00µs",
"A;C 2.00µs",
"A;C;A 1.00µs",
"A;C;A;B 1.00µs",
"A;C;A;B;C 1.00µs",
"A;C;X 3.00µs",
],
}
`;
exports[`importTraceEvents event re-ordering: indexToView 1`] = `0`;
exports[`importTraceEvents event re-ordering: profileGroup.name 1`] = `"must-retain-original-order.json"`;
exports[`importTraceEvents multiprocess 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "alpha",
"line": undefined,
"name": "alpha",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p0 (pid 0), p0t0 (tid 0)",
"stacks": Array [
"alpha 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 2`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "beta",
"line": undefined,
"name": "beta",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p0 (pid 0), p0t1 (tid 1)",
"stacks": Array [
"beta 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 3`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "gamma",
"line": undefined,
"name": "gamma",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p1 (pid 1), p1t0 (tid 0)",
"stacks": Array [
"gamma 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 4`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "delta",
"line": undefined,
"name": "delta",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p1 (pid 1), p1t1 (tid 1)",
"stacks": Array [
"delta 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 5`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "epsilon",
"line": undefined,
"name": "epsilon",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p2t0 (pid 2, tid 0)",
"stacks": Array [
"epsilon 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 6`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "phi",
"line": undefined,
"name": "phi",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p2t1 (pid 2, tid 1)",
"stacks": Array [
"phi 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 7`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "zeta",
"line": undefined,
"name": "zeta",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p3 (pid 3, tid 0)",
"stacks": Array [
"zeta 1.00µs",
],
}
`;
exports[`importTraceEvents multiprocess 8`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "eta",
"line": undefined,
"name": "eta",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "p3 (pid 3, tid 1)",
"stacks": Array [
"eta 1.00µs",
],
}
`;
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 [
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 object 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: indexToView 1`] = `0`;
exports[`importTraceEvents simple object: profileGroup.name 1`] = `"simple-object.json"`;
exports[`importTraceEvents simple: indexToView 1`] = `0`;
exports[`importTraceEvents simple: profileGroup.name 1`] = `"simple.json"`;
+8
View File
@@ -12,6 +12,14 @@ test('importFromChromeTimeline Chrome 69', async () => {
await checkProfileSnapshot('./sample/profiles/Chrome/69/simple.json')
})
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')
})
+16 -14
View File
@@ -1,6 +1,7 @@
import {Profile, FrameInfo, CallTreeProfileBuilder, ProfileGroup} from '../lib/profile'
import {getOrInsert, lastOf, sortBy, itForEach} from '../lib/utils'
import {TimeFormatter} from '../lib/value-formatters'
import {chromeTreeToNodes, OldCPUProfile} from './v8cpuFormatter'
// See: https://github.com/v8/v8/blob/master/src/inspector/js_protocol.json
@@ -31,7 +32,7 @@ interface CPUProfileCallFrame {
url: string
}
interface CPUProfileNode {
export interface CPUProfileNode {
callFrame: CPUProfileCallFrame
hitCount: number
id: number
@@ -40,7 +41,7 @@ interface CPUProfileNode {
parent?: CPUProfileNode
}
interface CPUProfile {
export interface CPUProfile {
startTime: number
endTime: number
nodes: CPUProfileNode[]
@@ -78,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: [],
@@ -109,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) {
@@ -130,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}`)
}
}
}
@@ -312,3 +310,7 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
profile.setValueFormatter(new TimeFormatter('microseconds'))
return profile.build()
}
export function importFromOldV8CPUProfile(content: OldCPUProfile): Profile {
return importFromChromeCPUProfile(chromeTreeToNodes(content))
}
+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)
})
+37 -8
View File
@@ -1,7 +1,12 @@
import {Profile, ProfileGroup} from '../lib/profile'
import {FileSystemDirectoryEntry} from './file-system-entry'
import {importFromChromeCPUProfile, importFromChromeTimeline, isChromeTimeline} from './chrome'
import {
importFromChromeCPUProfile,
importFromChromeTimeline,
isChromeTimeline,
importFromOldV8CPUProfile,
} from './chrome'
import {importFromStackprof} from './stackprof'
import {importFromInstrumentsDeepCopy, importFromInstrumentsTrace} from './instruments'
import {importFromBGFlameGraph} from './bg-flamegraph'
@@ -14,6 +19,7 @@ import {ProfileDataSource, TextProfileDataSource, MaybeCompressedDataReader} fro
import {importAsPprofProfile} from './pprof'
import {decodeBase64} from '../lib/utils'
import {importFromChromeHeapProfile} from './v8heapalloc'
import {isTraceEventFormatted, importTraceEvents} from './trace-event'
export async function importProfileGroupFromText(
fileName: string,
@@ -65,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()
@@ -84,9 +110,6 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
if (fileName.endsWith('.speedscope.json')) {
console.log('Importing as speedscope json file')
return importSpeedscopeProfiles(JSON.parse(contents))
} else if (fileName.endsWith('.cpuprofile')) {
console.log('Importing as Chrome CPU Profile')
return toGroup(importFromChromeCPUProfile(JSON.parse(contents)))
} else if (fileName.endsWith('.chrome.json') || /Profile-\d{8}T\d{6}/.exec(fileName)) {
console.log('Importing as Chrome Timeline')
return importFromChromeTimeline(JSON.parse(contents), fileName)
@@ -113,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') {
@@ -123,12 +146,18 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
console.log('Importing as Firefox profile')
return toGroup(importFromFirefox(parsed))
} else if (isChromeTimeline(parsed)) {
console.log('Importing as Chrome CPU Profile')
console.log('Importing as Chrome Timeline')
return importFromChromeTimeline(parsed, fileName)
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
console.log('Importing as Chrome Timeline')
console.log('Importing as Chrome CPU Profile')
return toGroup(importFromChromeCPUProfile(parsed))
} else if ('mode' in parsed && 'frames' in parsed) {
} else if (isTraceEventFormatted(parsed)) {
console.log('Importing as Trace Event Format profile')
return importTraceEvents(parsed)
} 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 && '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')
})
})
+9 -3
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]
@@ -630,7 +633,7 @@ export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any {
////////////////////////////////////////////////////////////////////////////////
export function decodeUTF8(bytes: Uint8Array): string {
let text = String.fromCharCode.apply(String, bytes)
let text = String.fromCharCode.apply(String, Array.from(bytes))
if (text.slice(-1) === '\0') text = text.slice(0, -1) // Remove a single trailing null character if present
return decodeURIComponent(escape(text))
}
@@ -737,7 +740,10 @@ function paternMatchObjectiveC(
// Replace NSString with a string
case 'NSString':
case 'NSMutableString':
return decodeUTF8(value['NS.bytes'])
if (value['NS.string']) return value['NS.string']
if (value['NS.bytes']) return decodeUTF8(value['NS.bytes'])
console.warn(`Unexpected ${name} format: `, value)
return null
// Replace NSArray with an Array
case 'NSArray':
+1 -1
View File
@@ -1,6 +1,6 @@
import {checkProfileSnapshot} from '../lib/test-utils'
describe('importFromLinuxPerf', async () => {
describe('importFromLinuxPerf', () => {
test('simple.linux-perf.txt', async () => {
await checkProfileSnapshot('./sample/profiles/linux-perf/simple.linux-perf.txt')
})
+33
View File
@@ -0,0 +1,33 @@
import {checkProfileSnapshot} from '../lib/test-utils'
test('importTraceEvents simple', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple.json')
})
test('importTraceEvents simple object', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-object.json')
})
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')
})
test('importTraceEvents event re-ordering', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/must-retain-original-order.json')
})
+323
View File
@@ -0,0 +1,323 @@
import {sortBy, zeroPad, lastOf} from '../lib/utils'
import {ProfileGroup, CallTreeProfileBuilder, FrameInfo} from '../lib/profile'
import {TimeFormatter} from '../lib/value-formatters'
// This file concerns import from the "Trace Event Format", authored by Google
// and used for Google's own chrome://trace.
//
// The file format is extremely general, and we only support the parts of it
// that logically map onto speedscope's visualization capabilities.
// Specifically, we only support the "B", "E", and "X" event types. Everything
// else is ignored. We do, however, support import of profiles that are
// multi-process/multi-threaded. Each process is split into a separate profile.
//
// Note that Chrome Developer Tools uses this format as well, but all the
// relevant data used in those profiles is stored in events with the name
// "CpuProfile", "Profile", or "ProfileChunk". If we detect those, we prioritize
// importing the profile as a Chrome Developer Tools profile. Otherwise,
// we try to import it as a "Trace Event Format" file.
//
// Spec: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
interface TraceEvent {
// The process ID for the process that output this event.
pid: number
// The thread ID for the thread that output this event.
tid: number
// The event type. This is a single character which changes depending on the type of event being output. The valid values are listed in the table below. We will discuss each phase type below.
ph: string
// The tracing clock timestamp of the event. The timestamps are provided at microsecond granularity.
ts: number
// The thread clock timestamp of the event. The timestamps are provided at microsecond granularity.
tts?: number
// The name of the event, as displayed in Trace Viewer
name?: string
// The event categories. This is a comma separated list of categories for the event. The categories can be used to hide events in the Trace Viewer UI.
cat?: string
// Any arguments provided for the event. Some of the event types have required argument fields, otherwise, you can put any information you wish in here. The arguments are displayed in Trace Viewer when you view an event in the analysis section.
args: any
// A fixed color name to associate with the event. If provided, cname must be one of the names listed in trace-viewer's base color scheme's reserved color names list
cname?: string
}
interface BTraceEvent extends TraceEvent {
ph: 'B'
}
interface ETraceEvent extends TraceEvent {
ph: 'E'
}
interface XTraceEvent extends TraceEvent {
ph: 'X'
dur?: number
tdur?: number
}
// The trace format supports a number of event types that we ignore.
type ImportableTraceEvent = BTraceEvent | ETraceEvent | XTraceEvent
type DurationEvent = BTraceEvent | ETraceEvent
function filterIgnoredEventTypes(events: TraceEvent[]): ImportableTraceEvent[] {
const ret: ImportableTraceEvent[] = []
for (let ev of events) {
switch (ev.ph) {
case 'B':
case 'E':
case 'X':
ret.push(ev as ImportableTraceEvent)
}
}
return ret
}
function convertToDurationEvents(events: ImportableTraceEvent[]): DurationEvent[] {
const ret: DurationEvent[] = []
for (let ev of events) {
switch (ev.ph) {
case 'B':
ret.push(ev)
break
case 'E':
ret.push(ev)
break
case 'X':
let dur: number | null = null
if (ev.dur != null) dur = ev.dur
else if (ev.tdur != null) dur = ev.tdur
if (dur == null) {
console.warn('Found a complete event (X) with no duration. Skipping: ', ev)
continue
}
ret.push({...ev, ph: 'B'} as BTraceEvent)
ret.push({...ev, ph: 'E', ts: ev.ts + dur} as ETraceEvent)
break
default:
const _exhaustiveCheck: never = ev
return _exhaustiveCheck
}
}
return ret
}
function getProcessNamesByPid(events: TraceEvent[]): Map<number, string> {
const processNamesByPid = new Map<number, string>()
for (let ev of events) {
if (ev.ph === 'M' && ev.name === 'process_name' && ev.args && ev.args.name) {
processNamesByPid.set(ev.pid, ev.args.name)
}
}
return processNamesByPid
}
function getThreadNamesByPidTid(events: TraceEvent[]): Map<string, string> {
const threadNameByPidTid = new Map<string, string>()
for (let ev of events) {
if (ev.ph === 'M' && ev.name === 'thread_name' && ev.args && ev.args.name) {
const key = `${ev.pid}:${ev.tid}`
threadNameByPidTid.set(key, ev.args.name)
}
}
return threadNameByPidTid
}
function keyForEvent(event: TraceEvent): string {
let name = `${event.name || '(unnamed)'}`
if (event.args) {
name += ` ${JSON.stringify(event.args)}`
}
return name
}
type TraceEventProfileState = {profile: CallTreeProfileBuilder; eventStack: BTraceEvent[]}
function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
const stateByPidTid = new Map<string, TraceEventProfileState>()
const importableEvents = filterIgnoredEventTypes(events)
const durationEvents = convertToDurationEvents(importableEvents)
const processNamesByPid = getProcessNamesByPid(events)
const threadNamesByPidTid = getThreadNamesByPidTid(events)
durationEvents.sort((a, b) => {
if (a.ts < b.ts) return -1
if (a.ts > b.ts) return 1
if (a.pid < b.pid) return -1
if (a.pid > b.pid) return 1
if (a.tid < b.tid) return -1
if (a.tid > b.tid) return 1
// We have to be careful with events that have the same timestamp
// and the same pid/tid
const aKey = keyForEvent(a)
const bKey = keyForEvent(b)
if (aKey === bKey) {
// If the two elements have the same key, we need to process the begin
// event before the end event. This will be a zero-duration event.
if (a.ph === 'B' && b.ph === 'E') return -1
if (a.ph === 'E' && b.ph === 'B') return 1
} else {
// If the two elements have *different* keys, we want to process
// the end of an event before the beginning of the event to prevent
// out-of-order push/pops from the call-stack.
if (a.ph === 'B' && b.ph === 'E') return 1
if (a.ph === 'E' && b.ph === 'B') return -1
}
// In all other cases, retain the original sort order.
return 0
})
if (durationEvents.length > 0) {
const firstTs = durationEvents[0].ts
for (let ev of durationEvents) {
ev.ts -= firstTs
}
}
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 state = stateByPidTid.get(pidTid)
if (state != null) return state
let profile = new CallTreeProfileBuilder()
state = {profile, eventStack: []}
profile.setValueFormatter(new TimeFormatter('microseconds'))
stateByPidTid.set(pidTid, state)
const processName = processNamesByPid.get(pid)
const threadName = threadNamesByPidTid.get(`${pid}:${tid}`)
if (processName != null && threadName != null) {
profile.setName(`${processName} (pid ${pid}), ${threadName} (tid ${tid})`)
} else if (processName != null) {
profile.setName(`${processName} (pid ${pid}, tid ${tid})`)
} else if (threadName != null) {
profile.setName(`${threadName} (pid ${pid}, tid ${tid})`)
} else {
profile.setName(`pid ${pid}, tid ${tid}`)
}
return state
}
for (let ev of durationEvents) {
const {profile, eventStack} = getOrCreateProfileState(ev.pid, ev.tid)
const key = keyForEvent(ev)
const frameInfo: FrameInfo = {
key: key,
name: key,
}
switch (ev.ph) {
case 'B':
eventStack.push(ev)
profile.enterFrame(frameInfo, ev.ts)
break
case 'E':
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:
const _exhaustiveCheck: never = ev
return _exhaustiveCheck
}
}
// For now, we just sort processes by pid & tid.
// 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(stateByPidTid.entries())
sortBy(profilePairs, p => p[0])
return {name: '', indexToView: 0, profiles: profilePairs.map(p => p[1].profile)}
}
function isTraceEventList(maybeEventList: any): maybeEventList is TraceEvent[] {
if (!Array.isArray(maybeEventList)) return false
if (maybeEventList.length === 0) return false
// Both ph and ts should be provided for every event. In theory, many other
// fields are mandatory, but without these fields, we won't usefully be able
// to import the data, so we'll rely upon these.
for (let el of maybeEventList) {
if (!('ph' in el)) {
return false
}
switch (el.ph) {
case 'B':
case 'E':
case 'X':
// All B, E, and X events must have a timestamp specified, otherwise we
// won't be able to import correctly.
if (!('ts' in el)) {
return false
}
case 'M':
// It's explicitly okay for "M" (metadata) events not to specify a "ts"
// field, since usually there is no logical timestamp for them to have
break
}
}
return true
}
function isTraceEventObject(
maybeTraceEventObject: any,
): maybeTraceEventObject is {traceEvents: TraceEvent[]} {
if (!('traceEvents' in maybeTraceEventObject)) return false
return isTraceEventList(maybeTraceEventObject['traceEvents'])
}
export function isTraceEventFormatted(
rawProfile: any,
): rawProfile is {traceEvents: TraceEvent[]} | TraceEvent[] {
// 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/.
return isTraceEventObject(rawProfile) || isTraceEventList(rawProfile)
}
export function importTraceEvents(
rawProfile: {traceEvents: TraceEvent[]} | TraceEvent[],
): ProfileGroup {
if (isTraceEventObject(rawProfile)) {
return eventListToProfileGroup(rawProfile.traceEvents)
} else if (isTraceEventList(rawProfile)) {
return eventListToProfileGroup(rawProfile)
} else {
const _exhaustiveCheck: never = rawProfile
return _exhaustiveCheck
}
}
+71
View File
@@ -0,0 +1,71 @@
import {CPUProfile, CPUProfileNode} from './chrome'
/**
* This importer handles an old format used by the C++ API of V8. This format is still used by v8-profiler-node8.
* There are two differences between the two formats:
* - Nodes are a tree in the old format and a flat array in the new format
* - Weights are timestamps in the old format and deltas in the new format.
*
* For more information, see https://github.com/hyj1991/v8-profiler-node8
*/
interface OldCPUProfileNode {
functionName: string
lineNumber: number
scriptId: string
url: string
hitCount: number
bailoutReason: string
id: number
children: OldCPUProfileNode[]
}
export interface OldCPUProfile {
startTime: number
endTime: number
head: OldCPUProfileNode
samples: number[]
timestamps: number[]
}
function treeToArray(root: OldCPUProfileNode): CPUProfileNode[] {
const nodes: CPUProfileNode[] = []
function visit(node: OldCPUProfileNode) {
nodes.push({
id: node.id,
callFrame: {
columnNumber: 0,
functionName: node.functionName,
lineNumber: node.lineNumber,
scriptId: node.scriptId,
url: node.url,
},
hitCount: node.hitCount,
children: node.children.map(child => child.id),
})
node.children.forEach(visit)
}
visit(root)
return nodes
}
function timestampsToDeltas(timestamps: number[], startTime: number): number[] {
return timestamps.map((timestamp, index) => {
const lastTimestamp = index === 0 ? startTime * 1000000 : timestamps[index - 1]
return timestamp - lastTimestamp
})
}
/**
* Convert the old tree-based format to the new flat-array based format
*/
export function chromeTreeToNodes(content: OldCPUProfile): CPUProfile {
// Note that both startTime and endTime are now in microseconds
return {
samples: content.samples,
startTime: content.startTime * 1000000,
endTime: content.endTime * 1000000,
nodes: treeToArray(content.head),
timeDeltas: timestampsToDeltas(content.timestamps, content.startTime),
}
}
+8
View File
@@ -41,6 +41,10 @@ test('importEmscriptenSymbolMap', () => {
'1:B',
'2:C',
'3:D-D',
'4:a\\20b',
'5:a\\2',
'6:a\\3z',
'7:a\\20b\\20c',
].join('\n'),
),
).toEqual(
@@ -49,6 +53,10 @@ test('importEmscriptenSymbolMap', () => {
['wasm-function[1]', 'B'],
['wasm-function[2]', 'C'],
['wasm-function[3]', 'D-D'],
['wasm-function[4]', 'a b'],
['wasm-function[5]', 'a\\2'],
['wasm-function[6]', 'a\\3z'],
['wasm-function[7]', 'a b c'],
]),
)
+13 -3
View File
@@ -1,5 +1,15 @@
type EmscriptenSymbolMap = Map<string, string>
// Returns `input` with hex escapes expanded (e.g. `\20` becomes ` `.)
//
// NOTE: This will fail to ignore escaped backslahes (e.g. `\\20`).
function unescapeHex(input: string): string {
return input.replace(/\\([a-fA-F0-9]{2})/g, (_match, group) => {
const scalar = parseInt(group, 16)
return String.fromCharCode(scalar)
})
}
// This imports symbol maps generated by emscripten using the "--emit-symbol-map" flag.
// It allows you to visualize a profile captured in a release build as long as you also
// have the associated symbol map. To do this, first drop the profile into speedscope
@@ -14,21 +24,21 @@ export function importEmscriptenSymbolMap(contents: string): EmscriptenSymbolMap
if (!lines.length) return null
const map: EmscriptenSymbolMap = new Map()
const intRegex = /^(\d+):([\$\w-]+)$/
const intRegex = /^(\d+):(.+)$/
const idRegex = /^([\$\w]+):([\$\w-]+)$/
for (const line of lines) {
// Match lines like "103:__ZN8tinyxml210XMLCommentD0Ev"
const intMatch = intRegex.exec(line)
if (intMatch) {
map.set(`wasm-function[${intMatch[1]}]`, intMatch[2])
map.set(`wasm-function[${intMatch[1]}]`, unescapeHex(intMatch[2]))
continue
}
// Match lines like "u6:__ZN8tinyxml210XMLCommentD0Ev"
const idMatch = idRegex.exec(line)
if (idMatch) {
map.set(idMatch[1], idMatch[2])
map.set(idMatch[1], unescapeHex(idMatch[2]))
continue
}
+1 -1
View File
@@ -1,6 +1,6 @@
import {checkProfileSnapshot} from './test-utils'
describe('importSpeedscopeProfiles', async () => {
describe('importSpeedscopeProfiles', () => {
test('0.0.1 evented profile', async () => {
await checkProfileSnapshot('./sample/profiles/speedscope/0.0.1/simple.speedscope.json')
})
+12 -3
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) {
@@ -398,6 +402,8 @@ export class Application extends StatelessComponent<ApplicationProps> {
this.props.setDragActive(false)
ev.preventDefault()
if (!ev.dataTransfer) return
const firstItem = ev.dataTransfer.items[0]
if ('webkitGetAsEntry' in firstItem) {
const webkitEntry: FileSystemDirectoryEntry = firstItem.webkitGetAsEntry()
@@ -486,7 +492,9 @@ export class Application extends StatelessComponent<ApplicationProps> {
ev.preventDefault()
ev.stopPropagation()
const pasted = (ev as ClipboardEvent).clipboardData.getData('text')
const clipboardData = (ev as ClipboardEvent).clipboardData
if (!clipboardData) return
const pasted = clipboardData.getData('text')
this.loadProfile(async () => {
return await importProfilesFromText('From Clipboard', pasted)
})
@@ -607,7 +615,8 @@ export class Application extends StatelessComponent<ApplicationProps> {
href="https://github.com/jlfwong/speedscope/issues"
>
report any issues on GitHub
</a>.
</a>
.
</p>
</div>
</div>
+1 -1
View File
@@ -27,7 +27,7 @@ export enum Colors {
export enum Sizes {
MINIMAP_HEIGHT = 100,
DETAIL_VIEW_HEIGHT = 150,
TOOLTIP_WIDTH_MAX = 300,
TOOLTIP_WIDTH_MAX = 900,
TOOLTIP_HEIGHT_MAX = 80,
SEPARATOR_HEIGHT = 2,
FRAME_HEIGHT = 20,