Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d0a3a2c59 | ||
|
|
3f205ec3e9 | ||
|
|
c70171836c | ||
|
|
ee0c2c5025 | ||
|
|
3ba60a424d | ||
|
|
c9ba143eb6 | ||
|
|
802fc2d358 | ||
|
|
b910a2069b | ||
|
|
a1f9755f9c | ||
|
|
2686a3ccc0 | ||
|
|
789f296c9c | ||
|
|
64e290c9fc | ||
|
|
a0eba8d434 | ||
|
|
281d9f9033 | ||
|
|
44a1f520fe | ||
|
|
b6190362b4 | ||
|
|
a09f27d816 | ||
|
|
944a6cb126 | ||
|
|
828beb7ccf |
@@ -1,3 +1,31 @@
|
||||
## Unreleased
|
||||
|
||||
## [1.1.0] - 2018-09-26
|
||||
|
||||
### Added
|
||||
|
||||
* Add go tool pprof import support [#165]
|
||||
|
||||
## [1.0.4] - 2018-09-12
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix import from Chrome < 69 when there are multiple profiles [#161]
|
||||
|
||||
## [1.0.3] - 2018-09-10
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix import for Chrome 69, support leading idle time before first call [#160]
|
||||
|
||||
## [1.0.2] - 2018-09-04
|
||||
|
||||
### Fixed
|
||||
|
||||
* Allow optional CR before LF when probing collapsed stacks files [#154]
|
||||
* Fix import for Firefox 63 [#156]
|
||||
* Change time formatting for minutes from 1.50min to 1:30 [#153]
|
||||
|
||||
## [1.0.1] - 2018-08-23
|
||||
|
||||
* Fixed an issue where flamegraph bounds were not always being cleared correctly, leading to visual artifacts [#150]
|
||||
|
||||
+11
-6
@@ -44,7 +44,7 @@ If everything looks good, proceed to "Prepare the release".
|
||||
2. Update CHANGELOG.md to indicate the changes that were made as part of this release
|
||||
3. Commit the changes with the version name as the commit message, e.g. `git commit -m 0.6.0`
|
||||
4. `git tag` the release. We use tags like `v0.6.0`, e.g. `git tag v0.6.0`
|
||||
5. `git push --tags`
|
||||
5. `git push && git push --tags`
|
||||
|
||||
## Publish to npm
|
||||
|
||||
@@ -59,13 +59,16 @@ Try `speedscope sample/profiles/stackcollapse/simple.txt`, which should immediat
|
||||
|
||||
## Deploying the website
|
||||
|
||||
This step must follow the "Publish to npm" step, since it uses assets from
|
||||
the npm publish.
|
||||
|
||||
https://www.speedscope.app/ is hosted on GitHub pages, and is published via pushing
|
||||
to the `gh-pages` branch. The `gh-pages` branch has totally different contents than
|
||||
other branches of this repository: https://github.com/jlfwong/speedscope/tree/gh-pages.
|
||||
|
||||
It's populated by a deploy script which is invoked by running `npm run deploy` script.
|
||||
This will do a build of the static resources, and boot a local server for you to test
|
||||
the compiled assets. Please do not skip the manual testing in this step.
|
||||
It's populated by a deploy script which is invoked by running `npm run deploy` script. This populate a directory with assets pulled from npm, and
|
||||
boot a local server for you to test the compiled assets. Please do not skip
|
||||
the manual testing in this step.
|
||||
|
||||
If everything looks good, you should be able to hit Ctrl+C, and you should see this prompt:
|
||||
|
||||
@@ -80,8 +83,10 @@ which includes the version, the date, and the commit of the deploy.
|
||||
|
||||
## Upload a release to GitHub
|
||||
|
||||
To make a zipfile suitable for uploading to GitHub as a release, run prepare-zip-file.sh. Note that this step must follow the "Publish to npm" step,
|
||||
since it uses assets from the npm publish.
|
||||
This step must follow the "Publish to npm" step, since it uses assets from
|
||||
the npm publish.
|
||||
|
||||
To make a zipfile suitable for uploading to GitHub as a release, run `scripts/prepare-zip-file.sh`.
|
||||
|
||||
Once that's done, you should have a zip file in `dist/release/`
|
||||
|
||||
|
||||
Generated
+2622
-2528
File diff suppressed because it is too large
Load Diff
+22
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "speedscope",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"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": {
|
||||
@@ -38,6 +45,7 @@
|
||||
"preact": "8.2.7",
|
||||
"preact-redux": "jlfwong/preact-redux#a56dcc4",
|
||||
"prettier": "1.12.0",
|
||||
"protobufjs": "6.8.8",
|
||||
"quicktype": "15.0.45",
|
||||
"redux": "^4.0.0",
|
||||
"ts-jest": "22.4.6",
|
||||
@@ -50,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 it is too large
Load Diff
Vendored
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +1,81 @@
|
||||
{"version":"0.0.1","$schema":"https://www.speedscope.app/file-format-schema.json","shared":{"frames":[{"name":"a"},{"name":"b"},{"name":"c"},{"name":"d"}]},"profiles":[{"type":"evented","name":"simple.txt","unit":"none","startValue":0,"endValue":14,"events":[{"type":"O","frame":0,"at":0},{"type":"O","frame":1,"at":0},{"type":"O","frame":2,"at":0},{"type":"C","frame":2,"at":2},{"type":"O","frame":3,"at":2},{"type":"C","frame":3,"at":6},{"type":"O","frame":2,"at":6},{"type":"C","frame":2,"at":9},{"type":"C","frame":1,"at":14},{"type":"C","frame":0,"at":14}]}]}
|
||||
{
|
||||
"$schema": "https://www.speedscope.app/file-format-schema.json",
|
||||
"profiles": [
|
||||
{
|
||||
"endValue": 14,
|
||||
"events": [
|
||||
{
|
||||
"at": 0,
|
||||
"frame": 0,
|
||||
"type": "O"
|
||||
},
|
||||
{
|
||||
"at": 0,
|
||||
"frame": 1,
|
||||
"type": "O"
|
||||
},
|
||||
{
|
||||
"at": 0,
|
||||
"frame": 2,
|
||||
"type": "O"
|
||||
},
|
||||
{
|
||||
"at": 2,
|
||||
"frame": 2,
|
||||
"type": "C"
|
||||
},
|
||||
{
|
||||
"at": 2,
|
||||
"frame": 3,
|
||||
"type": "O"
|
||||
},
|
||||
{
|
||||
"at": 6,
|
||||
"frame": 3,
|
||||
"type": "C"
|
||||
},
|
||||
{
|
||||
"at": 6,
|
||||
"frame": 2,
|
||||
"type": "O"
|
||||
},
|
||||
{
|
||||
"at": 9,
|
||||
"frame": 2,
|
||||
"type": "C"
|
||||
},
|
||||
{
|
||||
"at": 14,
|
||||
"frame": 1,
|
||||
"type": "C"
|
||||
},
|
||||
{
|
||||
"at": 14,
|
||||
"frame": 0,
|
||||
"type": "C"
|
||||
}
|
||||
],
|
||||
"name": "simple.txt",
|
||||
"startValue": 0,
|
||||
"type": "evented",
|
||||
"unit": "none"
|
||||
}
|
||||
],
|
||||
"shared": {
|
||||
"frames": [
|
||||
{
|
||||
"name": "a"
|
||||
},
|
||||
{
|
||||
"name": "b"
|
||||
},
|
||||
{
|
||||
"name": "c"
|
||||
},
|
||||
{
|
||||
"name": "d"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "0.0.1"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
a;b;c 1
|
||||
a;b;c 1
|
||||
a;b;d 4
|
||||
a;b;c 3
|
||||
a;b 5
|
||||
@@ -0,0 +1,2 @@
|
||||
simple
|
||||
server
|
||||
@@ -0,0 +1,15 @@
|
||||
.PHONY: all clean linux-perf
|
||||
|
||||
all: simple server
|
||||
|
||||
clean:
|
||||
rm -f simple server
|
||||
|
||||
simple: simple.go
|
||||
go build $<
|
||||
|
||||
server: server.go
|
||||
go build $<
|
||||
|
||||
simple.prof: simple
|
||||
./$< -cpuprofile=$@
|
||||
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
import _ "net/http/pprof"
|
||||
|
||||
// See https://golang.org/pkg/net/http/pprof/ for details
|
||||
|
||||
func alpha() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
}
|
||||
|
||||
func beta() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
}
|
||||
|
||||
func delta() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
alpha()
|
||||
beta()
|
||||
}
|
||||
|
||||
func gamma() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
// we need a webserver to get the pprof webserver
|
||||
go func() {
|
||||
log.Println(http.ListenAndServe("localhost:6060", nil))
|
||||
}()
|
||||
fmt.Println("hello world")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go leakyFunction(wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func leakyFunction(wg sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
s := make([]string, 3)
|
||||
for i:= 0; i < 10000000; i++{
|
||||
alpha()
|
||||
beta()
|
||||
delta()
|
||||
gamma()
|
||||
s = append(s, "magical pandas")
|
||||
if (i % 100000) == 0 {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import "flag"
|
||||
import "runtime/pprof"
|
||||
import "os"
|
||||
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
|
||||
func alpha() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
}
|
||||
|
||||
func beta() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
}
|
||||
|
||||
func delta() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
alpha()
|
||||
beta()
|
||||
}
|
||||
|
||||
func gamma() {
|
||||
z := 3
|
||||
for i := 0; i < 100000; i++ {
|
||||
z *= 3
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *cpuprofile != "" {
|
||||
f, _ := os.Create(*cpuprofile)
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
alpha()
|
||||
beta()
|
||||
delta()
|
||||
gamma()
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,4 @@ function gamma() {
|
||||
return prod
|
||||
}
|
||||
|
||||
console.profile('simple')
|
||||
alpha()
|
||||
setTimeout(() => {
|
||||
console.profileEnd('simple')
|
||||
}, 0)
|
||||
|
||||
@@ -7,6 +7,9 @@ OUTDIR=`pwd`/dist/release
|
||||
# Typecheck
|
||||
node_modules/.bin/tsc --noEmit
|
||||
|
||||
# Run unit tests
|
||||
npm run jest
|
||||
|
||||
# Clean out the release directory
|
||||
rm -rf "$OUTDIR"
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
+12
-13
@@ -1,26 +1,28 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# type check, do a release build, then do a shallow clone of the
|
||||
# repository into a temporary directory and copy the release build
|
||||
# artifacts into there to commit & push to the gh-pages branch
|
||||
# Do a shallow clone of the repository into a temporary directory and copy the
|
||||
# artifacts pulled from npm into the shallow clone to commit & push to the
|
||||
# gh-pages branch.
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
OUTDIR=`pwd`/dist/release
|
||||
echo $OUTDIR
|
||||
SRCDIR=`pwd`
|
||||
OUTDIR=`mktemp -d -t speedscope-unpacked`
|
||||
|
||||
./scripts/build-release.sh
|
||||
# Untar the package
|
||||
pushd "$OUTDIR"
|
||||
PACKEDNAME=`npm pack speedscope | tail -n1`
|
||||
tar -xvvf "$PACKEDNAME"
|
||||
|
||||
# Create a shallow clone of the repository
|
||||
TMPDIR=`mktemp -d -t speedscope-release`
|
||||
echo "Entering $TMPDIR"
|
||||
TMPDIR=`mktemp -d -t speedscope-deploy`
|
||||
pushd "$TMPDIR"
|
||||
git clone --depth 1 git@github.com:jlfwong/speedscope.git -b gh-pages
|
||||
|
||||
# Copy the build artifacts into the shallow clone
|
||||
pushd speedscope
|
||||
rm -rf *
|
||||
cp -R "$OUTDIR"/* .
|
||||
cp -R "$OUTDIR"/package/dist/release/** .
|
||||
|
||||
# Set the CNAME record
|
||||
echo www.speedscope.app > CNAME
|
||||
@@ -35,17 +37,14 @@ function ctrl_c() {
|
||||
if [[ $REPLY =~ ^yes$ ]]
|
||||
then
|
||||
git add --all
|
||||
git commit -m 'Release'
|
||||
git commit -m "Deploy $PACKEDNAME"
|
||||
git push origin HEAD:gh-pages
|
||||
popd
|
||||
rm -rf "$TMPDIR"
|
||||
exit 0
|
||||
else
|
||||
set +x
|
||||
echo "Aborting release."
|
||||
set -x
|
||||
|
||||
popd
|
||||
rm -rf "$TMPDIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Prepare a mock installation of speedscope to test it before the actual npm
|
||||
# publish
|
||||
# Create a zip file containing a standalone copy of speedscope
|
||||
# based on the contents of the package published to npm
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
|
||||
@@ -50,6 +50,60 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromBGFlameGraph with CRLF 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "a",
|
||||
"line": undefined,
|
||||
"name": "a",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "b",
|
||||
"line": undefined,
|
||||
"name": "b",
|
||||
"selfWeight": 5,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "c",
|
||||
"line": undefined,
|
||||
"name": "c",
|
||||
"selfWeight": 5,
|
||||
"totalWeight": 5,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "d",
|
||||
"line": undefined,
|
||||
"name": "d",
|
||||
"selfWeight": 4,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
],
|
||||
"name": "simple-crlf.txt",
|
||||
"stacks": Array [
|
||||
"a;b;c 2",
|
||||
"a;b;d 4",
|
||||
"a;b;c 3",
|
||||
"a;b 5",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromBGFlameGraph with CRLF: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromBGFlameGraph with CRLF: profileGroup.name 1`] = `"simple-crlf.txt"`;
|
||||
|
||||
exports[`importFromBGFlameGraph: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromBGFlameGraph: profileGroup.name 1`] = `"simple.txt"`;
|
||||
|
||||
@@ -9,8 +9,8 @@ Object {
|
||||
"key": "(anonymous)::0:0",
|
||||
"line": 0,
|
||||
"name": "(anonymous)",
|
||||
"selfWeight": 11575,
|
||||
"totalWeight": 45920,
|
||||
"selfWeight": 1060,
|
||||
"totalWeight": 34345,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -19,7 +19,7 @@ Object {
|
||||
"line": 0,
|
||||
"name": "a",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 34345,
|
||||
"totalWeight": 33285,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -28,7 +28,7 @@ Object {
|
||||
"line": 5,
|
||||
"name": "b",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 16788,
|
||||
"totalWeight": 17002,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -36,8 +36,8 @@ Object {
|
||||
"key": "d::13:10",
|
||||
"line": 13,
|
||||
"name": "d",
|
||||
"selfWeight": 34345,
|
||||
"totalWeight": 34345,
|
||||
"selfWeight": 33285,
|
||||
"totalWeight": 33285,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -46,14 +46,15 @@ Object {
|
||||
"line": 9,
|
||||
"name": "c",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 17557,
|
||||
"totalWeight": 16283,
|
||||
},
|
||||
],
|
||||
"name": "simple.cpuprofile",
|
||||
"stacks": Array [
|
||||
"(anonymous) 11.57ms",
|
||||
"(anonymous);a;b;d 16.79ms",
|
||||
"(anonymous);a;c;d 17.56ms",
|
||||
" 11.57ms",
|
||||
"(anonymous) 1.06ms",
|
||||
"(anonymous);a;b;d 17.00ms",
|
||||
"(anonymous);a;c;d 16.28ms",
|
||||
],
|
||||
}
|
||||
`;
|
||||
@@ -71,8 +72,8 @@ Object {
|
||||
"key": "(program)::-1:-1",
|
||||
"line": -1,
|
||||
"name": "(program)",
|
||||
"selfWeight": 12244,
|
||||
"totalWeight": 12244,
|
||||
"selfWeight": 2299,
|
||||
"totalWeight": 2299,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -81,7 +82,7 @@ Object {
|
||||
"line": 0,
|
||||
"name": "a",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 33184,
|
||||
"totalWeight": 33186,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -89,8 +90,8 @@ Object {
|
||||
"key": "c:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:13:10",
|
||||
"line": 13,
|
||||
"name": "c",
|
||||
"selfWeight": 16197,
|
||||
"totalWeight": 16197,
|
||||
"selfWeight": 16275,
|
||||
"totalWeight": 16275,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
@@ -98,170 +99,422 @@ Object {
|
||||
"key": "b:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:7:10",
|
||||
"line": 7,
|
||||
"name": "b",
|
||||
"selfWeight": 16987,
|
||||
"totalWeight": 16987,
|
||||
"selfWeight": 16911,
|
||||
"totalWeight": 16911,
|
||||
},
|
||||
],
|
||||
"name": "simple-timeline.json",
|
||||
"stacks": Array [
|
||||
"(program) 10.16ms",
|
||||
" 1.99ms",
|
||||
"(program) 926.00µs",
|
||||
" 85.95ms",
|
||||
"(program) 133.00µs",
|
||||
" 10.06ms",
|
||||
"(program) 202.00µs",
|
||||
" 1.97ms",
|
||||
"(program) 1.04ms",
|
||||
" 85.89ms",
|
||||
"(program) 132.00µs",
|
||||
" 8.40ms",
|
||||
"(program) 134.00µs",
|
||||
"a;c 257.00µs",
|
||||
"a;b 534.00µs",
|
||||
"a;c 513.00µs",
|
||||
"a;c 273.00µs",
|
||||
"a;b 513.00µs",
|
||||
"a;c 257.00µs",
|
||||
"a;b 518.00µs",
|
||||
"a;c 514.00µs",
|
||||
"a;b 261.00µs",
|
||||
"a;c 512.00µs",
|
||||
"a;b 513.00µs",
|
||||
"a;c 262.00µs",
|
||||
"a;b 514.00µs",
|
||||
"a;c 517.00µs",
|
||||
"a;b 258.00µs",
|
||||
"a;b 261.00µs",
|
||||
"a;c 515.00µs",
|
||||
"a;b 273.00µs",
|
||||
"a;c 134.00µs",
|
||||
"a;b 146.00µs",
|
||||
"a;c 291.00µs",
|
||||
"a;b 256.00µs",
|
||||
"a;c 145.00µs",
|
||||
"a;b 134.00µs",
|
||||
"a;c 292.00µs",
|
||||
"a;b 272.00µs",
|
||||
"a;c 129.00µs",
|
||||
"a;b 145.00µs",
|
||||
"a;c 265.00µs",
|
||||
"a;b 142.00µs",
|
||||
"a;c 141.00µs",
|
||||
"a;b 139.00µs",
|
||||
"a;c 397.00µs",
|
||||
"a;b 126.00µs",
|
||||
"a;c 131.00µs",
|
||||
"a;b 126.00µs",
|
||||
"a;c 521.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 261.00µs",
|
||||
"a;b 127.00µs",
|
||||
"a;c 130.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 136.00µs",
|
||||
"a;b 272.00µs",
|
||||
"a;c 146.00µs",
|
||||
"a;b 661.00µs",
|
||||
"a;c 113.00µs",
|
||||
"a;b 135.00µs",
|
||||
"a;c 585.00µs",
|
||||
"a;b 294.00µs",
|
||||
"a;c 274.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 127.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 247.00µs",
|
||||
"a;b 526.00µs",
|
||||
"a;c 125.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 131.00µs",
|
||||
"a;b 253.00µs",
|
||||
"a;c 135.00µs",
|
||||
"a;b 123.00µs",
|
||||
"a;c 128.00µs",
|
||||
"a;b 257.00µs",
|
||||
"a;c 137.00µs",
|
||||
"a;b 256.00µs",
|
||||
"a;c 411.00µs",
|
||||
"a;b 892.00µs",
|
||||
"a;c 146.00µs",
|
||||
"a;b 148.00µs",
|
||||
"a;c 309.00µs",
|
||||
"a;b 140.00µs",
|
||||
"a;c 151.00µs",
|
||||
"a;b 458.00µs",
|
||||
"a;c 156.00µs",
|
||||
"a;b 170.00µs",
|
||||
"a;c 273.00µs",
|
||||
"a;b 141.00µs",
|
||||
"a;c 139.00µs",
|
||||
"a;b 290.00µs",
|
||||
"a;c 115.00µs",
|
||||
"a;b 281.00µs",
|
||||
"a;c 270.00µs",
|
||||
"a;b 283.00µs",
|
||||
"a;c 144.00µs",
|
||||
"a;b 146.00µs",
|
||||
"a;c 410.00µs",
|
||||
"a;b 146.00µs",
|
||||
"a;c 147.00µs",
|
||||
"a;b 312.00µs",
|
||||
"a;c 146.00µs",
|
||||
"a;b 907.00µs",
|
||||
"a;c 231.00µs",
|
||||
"a;b 138.00µs",
|
||||
"a;c 280.00µs",
|
||||
"a;b 255.00µs",
|
||||
"a;c 257.00µs",
|
||||
"a;b 130.00µs",
|
||||
"a;c 255.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 526.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 384.00µs",
|
||||
"a;b 398.00µs",
|
||||
"a;c 260.00µs",
|
||||
"a;b 664.00µs",
|
||||
"a;c 393.00µs",
|
||||
"a;b 131.00µs",
|
||||
"a;c 126.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 520.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 259.00µs",
|
||||
"a;b 130.00µs",
|
||||
"a;c 128.00µs",
|
||||
"a;b 136.00µs",
|
||||
"a;c 137.00µs",
|
||||
"a;b 281.00µs",
|
||||
"a;c 150.00µs",
|
||||
"a;b 624.00µs",
|
||||
"a;c 135.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 604.00µs",
|
||||
"a;b 292.00µs",
|
||||
"a;c 257.00µs",
|
||||
"a;b 127.00µs",
|
||||
"a;c 128.00µs",
|
||||
"a;b 118.00µs",
|
||||
"a;c 256.00µs",
|
||||
"a;b 524.00µs",
|
||||
"a;c 129.00µs",
|
||||
"a;b 131.00µs",
|
||||
"a;c 125.00µs",
|
||||
"a;b 263.00µs",
|
||||
"a;c 123.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 129.00µs",
|
||||
"a;b 265.00µs",
|
||||
"a;c 128.00µs",
|
||||
"a;b 280.00µs",
|
||||
"a;c 366.00µs",
|
||||
"a;b 931.00µs",
|
||||
"a;c 148.00µs",
|
||||
"a;b 147.00µs",
|
||||
"a;c 156.00µs",
|
||||
"a;b 137.00µs",
|
||||
"a;c 302.00µs",
|
||||
"a;b 151.00µs",
|
||||
"a;c 158.00µs",
|
||||
"a;b 456.00µs",
|
||||
"a;c 170.00µs",
|
||||
"a;b 139.00µs",
|
||||
"a;c 145.00µs",
|
||||
"a;b 260.00µs",
|
||||
"a;c 141.00µs",
|
||||
"a;b 280.00µs",
|
||||
"a;c 268.00µs",
|
||||
"a;b 289.00µs",
|
||||
"a;c 146.00µs",
|
||||
"a;b 120.00µs",
|
||||
"a;c 436.00µs",
|
||||
"a;b 147.00µs",
|
||||
"a;c 164.00µs",
|
||||
"a;b 294.00µs",
|
||||
"a;c 147.00µs",
|
||||
"a;b 856.00µs",
|
||||
"a;c 273.00µs",
|
||||
"a;b 139.00µs",
|
||||
"a;c 269.00µs",
|
||||
"a;b 256.00µs",
|
||||
"a;c 258.00µs",
|
||||
"a;b 128.00µs",
|
||||
"a;c 255.00µs",
|
||||
"a;b 140.00µs",
|
||||
"a;c 141.00µs",
|
||||
"a;c 514.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 395.00µs",
|
||||
"a;b 389.00µs",
|
||||
"a;c 257.00µs",
|
||||
"a;b 686.00µs",
|
||||
"a;c 147.00µs",
|
||||
"a;b 156.00µs",
|
||||
"a;c 137.00µs",
|
||||
"a;b 141.00µs",
|
||||
"a;c 140.00µs",
|
||||
"a;b 140.00µs",
|
||||
"a;c 139.00µs",
|
||||
"a;b 129.00µs",
|
||||
"a;c 269.00µs",
|
||||
"a;b 293.00µs",
|
||||
"a;c 148.00µs",
|
||||
"a;b 293.00µs",
|
||||
"a;c 292.00µs",
|
||||
"a;b 296.00µs",
|
||||
"a;c 147.00µs",
|
||||
"a;b 140.00µs",
|
||||
"a;c 139.00µs",
|
||||
"a;b 139.00µs",
|
||||
"a;c 511.00µs",
|
||||
"a;b 515.00µs",
|
||||
"a;c 386.00µs",
|
||||
"a;b 514.00µs",
|
||||
"a;c 272.00µs",
|
||||
"a;b 242.00µs",
|
||||
"a;c 128.00µs",
|
||||
"a;b 134.00µs",
|
||||
"a;c 112.00µs",
|
||||
"a;b 288.00µs",
|
||||
"a;c 451.00µs",
|
||||
"a;b 138.00µs",
|
||||
"a;c 292.00µs",
|
||||
"a;b 145.00µs",
|
||||
"a;c 259.00µs",
|
||||
"a;b 148.00µs",
|
||||
"a;c 293.00µs",
|
||||
"a;b 141.00µs",
|
||||
"a;c 141.00µs",
|
||||
"a;b 140.00µs",
|
||||
"a;c 140.00µs",
|
||||
" 61.61ms",
|
||||
"(program) 118.00µs",
|
||||
" 134.00µs",
|
||||
"(program) 128.00µs",
|
||||
" 16.40ms",
|
||||
"(program) 125.00µs",
|
||||
" 16.64ms",
|
||||
"(program) 129.00µs",
|
||||
" 16.72ms",
|
||||
"(program) 130.00µs",
|
||||
" 294.15ms",
|
||||
"(program) 129.00µs",
|
||||
" 7.68ms",
|
||||
"a;b 139.00µs",
|
||||
"a;c 129.00µs",
|
||||
"a;b 149.00µs",
|
||||
"a;c 267.00µs",
|
||||
"a;b 294.00µs",
|
||||
"a;c 145.00µs",
|
||||
"a;b 294.00µs",
|
||||
"a;c 294.00µs",
|
||||
"a;b 295.00µs",
|
||||
"a;c 140.00µs",
|
||||
"a;b 139.00µs",
|
||||
"a;c 139.00µs",
|
||||
"a;b 127.00µs",
|
||||
"a;c 513.00µs",
|
||||
"a;b 516.00µs",
|
||||
"a;c 385.00µs",
|
||||
"a;b 520.00µs",
|
||||
"a;c 252.00µs",
|
||||
"a;b 255.00µs",
|
||||
"a;c 134.00µs",
|
||||
"a;b 112.00µs",
|
||||
"a;c 140.00µs",
|
||||
"a;b 296.00µs",
|
||||
"a;c 441.00µs",
|
||||
"a;b 145.00µs",
|
||||
"a;c 292.00µs",
|
||||
"a;b 130.00µs",
|
||||
"a;c 277.00µs",
|
||||
"a;b 147.00µs",
|
||||
"a;c 286.00µs",
|
||||
"a;b 140.00µs",
|
||||
"a;c 136.00µs",
|
||||
" 61.59ms",
|
||||
"(program) 134.00µs",
|
||||
" 128.00µs",
|
||||
"(program) 141.00µs",
|
||||
" 16.39ms",
|
||||
"(program) 145.00µs",
|
||||
" 16.63ms",
|
||||
"(program) 131.00µs",
|
||||
" 16.71ms",
|
||||
"(program) 131.00µs",
|
||||
" 294.15ms",
|
||||
"(program) 109.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromChromeTimeline Chrome 69 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": 0,
|
||||
"file": "extensions::SafeBuiltins",
|
||||
"key": "(anonymous):extensions::SafeBuiltins:0:0",
|
||||
"line": 0,
|
||||
"name": "(anonymous)",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 3188,
|
||||
},
|
||||
Frame {
|
||||
"col": 9,
|
||||
"file": "extensions::SafeBuiltins",
|
||||
"key": "(anonymous):extensions::SafeBuiltins:0:9",
|
||||
"line": 0,
|
||||
"name": "(anonymous)",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 3188,
|
||||
},
|
||||
Frame {
|
||||
"col": 20,
|
||||
"file": "extensions::SafeBuiltins",
|
||||
"key": "saveBuiltin:extensions::SafeBuiltins:24:20",
|
||||
"line": 24,
|
||||
"name": "saveBuiltin",
|
||||
"selfWeight": 302,
|
||||
"totalWeight": 3188,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "(program):undefined:undefined:undefined",
|
||||
"line": undefined,
|
||||
"name": "(program)",
|
||||
"selfWeight": 6862,
|
||||
"totalWeight": 6862,
|
||||
},
|
||||
Frame {
|
||||
"col": 0,
|
||||
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "(anonymous):file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:0:0",
|
||||
"line": 0,
|
||||
"name": "(anonymous)",
|
||||
"selfWeight": 136,
|
||||
"totalWeight": 37217,
|
||||
},
|
||||
Frame {
|
||||
"col": 14,
|
||||
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "alpha:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:0:14",
|
||||
"line": 0,
|
||||
"name": "alpha",
|
||||
"selfWeight": 873,
|
||||
"totalWeight": 37081,
|
||||
},
|
||||
Frame {
|
||||
"col": 13,
|
||||
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "beta:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:7:13",
|
||||
"line": 7,
|
||||
"name": "beta",
|
||||
"selfWeight": 7552,
|
||||
"totalWeight": 17562,
|
||||
},
|
||||
Frame {
|
||||
"col": 14,
|
||||
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "gamma:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:19:14",
|
||||
"line": 19,
|
||||
"name": "gamma",
|
||||
"selfWeight": 20018,
|
||||
"totalWeight": 20018,
|
||||
},
|
||||
Frame {
|
||||
"col": 14,
|
||||
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "delta:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:13:14",
|
||||
"line": 13,
|
||||
"name": "delta",
|
||||
"selfWeight": 7179,
|
||||
"totalWeight": 18646,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "(garbage collector):undefined:undefined:undefined",
|
||||
"line": undefined,
|
||||
"name": "(garbage collector)",
|
||||
"selfWeight": 646,
|
||||
"totalWeight": 778,
|
||||
},
|
||||
],
|
||||
"name": "simple.json",
|
||||
"stacks": Array [
|
||||
" 6.85ms",
|
||||
"(anonymous);(anonymous);saveBuiltin 302.00µs",
|
||||
"(anonymous);(anonymous);saveBuiltin;(program) 2.89ms",
|
||||
" 230.00µs",
|
||||
"(program) 828.00µs",
|
||||
" 110.00µs",
|
||||
"(program) 118.00µs",
|
||||
" 323.00µs",
|
||||
"(program) 123.00µs",
|
||||
" 139.00µs",
|
||||
"(program) 400.00µs",
|
||||
"(anonymous) 136.00µs",
|
||||
"(anonymous);alpha;beta 108.00µs",
|
||||
"(anonymous);alpha;beta;gamma 268.00µs",
|
||||
"(anonymous);alpha 137.00µs",
|
||||
"(anonymous);alpha;delta 139.00µs",
|
||||
"(anonymous);alpha;delta;gamma 261.00µs",
|
||||
"(anonymous);alpha;beta;gamma 964.00µs",
|
||||
"(anonymous);alpha;delta;gamma 268.00µs",
|
||||
"(anonymous);alpha;beta;gamma 265.00µs",
|
||||
"(anonymous);alpha;delta;gamma 582.00µs",
|
||||
"(anonymous);alpha;beta;gamma 135.00µs",
|
||||
"(anonymous);alpha;delta;gamma 147.00µs",
|
||||
"(anonymous);alpha;beta;gamma 163.00µs",
|
||||
"(anonymous);alpha;delta;gamma 133.00µs",
|
||||
"(anonymous);alpha;beta;gamma 144.00µs",
|
||||
"(anonymous);alpha;delta;gamma 276.00µs",
|
||||
"(anonymous);alpha;delta 136.00µs",
|
||||
"(anonymous);alpha;beta;gamma 144.00µs",
|
||||
"(anonymous);alpha;delta;gamma 431.00µs",
|
||||
"(anonymous);alpha;beta;gamma 419.00µs",
|
||||
"(anonymous);alpha;delta;gamma 134.00µs",
|
||||
"(anonymous);alpha;beta;gamma 136.00µs",
|
||||
"(anonymous);alpha;delta;gamma 144.00µs",
|
||||
"(anonymous);alpha;beta;gamma 269.00µs",
|
||||
"(anonymous);alpha;delta;gamma 160.00µs",
|
||||
"(anonymous);alpha;beta;gamma 141.00µs",
|
||||
"(anonymous);alpha;delta;gamma 292.00µs",
|
||||
"(anonymous);alpha;beta;gamma 583.00µs",
|
||||
"(anonymous);alpha;delta;gamma 149.00µs",
|
||||
"(anonymous);alpha;beta;gamma 139.00µs",
|
||||
"(anonymous);alpha;delta;gamma 133.00µs",
|
||||
"(anonymous);alpha;beta;gamma 386.00µs",
|
||||
"(anonymous);alpha;delta;gamma 385.00µs",
|
||||
"(anonymous);alpha;beta;gamma 391.00µs",
|
||||
"(anonymous);alpha;delta;gamma 261.00µs",
|
||||
"(anonymous);alpha;beta;gamma 131.00µs",
|
||||
"(anonymous);alpha;delta;gamma 132.00µs",
|
||||
"(anonymous);alpha 144.00µs",
|
||||
"(anonymous);alpha;delta;gamma 134.00µs",
|
||||
"(anonymous);alpha;beta;gamma 146.00µs",
|
||||
"(anonymous);alpha;delta;gamma 119.00µs",
|
||||
"(anonymous);alpha;beta;gamma 120.00µs",
|
||||
"(anonymous);alpha;delta;gamma 134.00µs",
|
||||
"(anonymous);alpha;beta;gamma 1.47ms",
|
||||
"(anonymous);alpha;delta;gamma 148.00µs",
|
||||
"(anonymous);alpha;beta;gamma 147.00µs",
|
||||
"(anonymous);alpha;delta;gamma 146.00µs",
|
||||
"(anonymous);alpha;beta;gamma 269.00µs",
|
||||
"(anonymous);alpha;delta;gamma 138.00µs",
|
||||
"(anonymous);alpha;beta;gamma 509.00µs",
|
||||
"(anonymous);alpha;delta;gamma 1.63ms",
|
||||
"(anonymous);alpha;beta;gamma 432.00µs",
|
||||
"(anonymous);alpha;delta;gamma 129.00µs",
|
||||
"(anonymous);alpha;beta;gamma 139.00µs",
|
||||
"(anonymous);alpha;delta;gamma 389.00µs",
|
||||
"(anonymous);alpha;beta 133.00µs",
|
||||
"(anonymous);alpha;beta;gamma 133.00µs",
|
||||
"(anonymous);alpha;delta;gamma 803.00µs",
|
||||
"(anonymous);alpha;beta;gamma 283.00µs",
|
||||
"(anonymous);alpha;delta;gamma 152.00µs",
|
||||
"(anonymous);alpha;beta;gamma 135.00µs",
|
||||
"(anonymous);alpha;delta;gamma 132.00µs",
|
||||
"(anonymous);alpha;beta;gamma 134.00µs",
|
||||
"(anonymous);alpha;delta;gamma 154.00µs",
|
||||
"(anonymous);alpha;beta;gamma 145.00µs",
|
||||
"(anonymous);alpha;delta;gamma 516.00µs",
|
||||
"(anonymous);alpha;delta 128.00µs",
|
||||
"(anonymous);alpha;delta;gamma 138.00µs",
|
||||
"(anonymous);alpha;beta;gamma 139.00µs",
|
||||
"(anonymous);alpha;delta;gamma 253.00µs",
|
||||
"(anonymous);alpha;beta;gamma 145.00µs",
|
||||
"(anonymous);alpha;delta;gamma 450.00µs",
|
||||
"(anonymous);alpha;beta;gamma 142.00µs",
|
||||
"(anonymous);alpha;delta;gamma 125.00µs",
|
||||
"(anonymous);alpha;beta;gamma 289.00µs",
|
||||
"(anonymous);alpha;delta;gamma 293.00µs",
|
||||
"(anonymous);alpha;beta;gamma 147.00µs",
|
||||
"(anonymous);alpha 443.00µs",
|
||||
"(anonymous);alpha;delta;gamma 133.00µs",
|
||||
"(anonymous);alpha;beta;gamma 409.00µs",
|
||||
"(anonymous);alpha;beta 385.00µs",
|
||||
"(anonymous);alpha;delta 659.00µs",
|
||||
"(anonymous);alpha;beta 253.00µs",
|
||||
"(anonymous);alpha;delta 257.00µs",
|
||||
"(anonymous);alpha;beta 256.00µs",
|
||||
"(anonymous);alpha;delta 129.00µs",
|
||||
"(anonymous);alpha;beta 260.00µs",
|
||||
"(anonymous);alpha;delta 255.00µs",
|
||||
"(anonymous);alpha;beta 129.00µs",
|
||||
"(anonymous);alpha;delta 140.00µs",
|
||||
"(anonymous);alpha;beta 136.00µs",
|
||||
"(anonymous);alpha;delta 134.00µs",
|
||||
"(anonymous);alpha;beta 283.00µs",
|
||||
"(anonymous);alpha;delta 253.00µs",
|
||||
"(anonymous);alpha;beta 397.00µs",
|
||||
"(anonymous);alpha;delta 401.00µs",
|
||||
"(anonymous);alpha;beta 267.00µs",
|
||||
"(anonymous);alpha;delta 283.00µs",
|
||||
"(anonymous);alpha;beta 137.00µs",
|
||||
"(anonymous);alpha;delta 134.00µs",
|
||||
"(anonymous);alpha;beta 403.00µs",
|
||||
"(anonymous);alpha;delta 135.00µs",
|
||||
"(anonymous);alpha;beta 658.00µs",
|
||||
"(anonymous);alpha;delta 117.00µs",
|
||||
"(anonymous);alpha;beta 257.00µs",
|
||||
"(anonymous);alpha;delta 134.00µs",
|
||||
"(anonymous);alpha;beta 143.00µs",
|
||||
"(anonymous);alpha;delta 260.00µs",
|
||||
"(anonymous);alpha 149.00µs",
|
||||
"(anonymous);alpha;delta 146.00µs",
|
||||
"(anonymous);alpha;beta 129.00µs",
|
||||
"(anonymous);alpha;delta 131.00µs",
|
||||
"(anonymous);alpha;beta 130.00µs",
|
||||
"(anonymous);alpha;delta 665.00µs",
|
||||
"(anonymous);alpha;beta 415.00µs",
|
||||
"(anonymous);alpha;delta 264.00µs",
|
||||
"(anonymous);alpha;beta 527.00µs",
|
||||
"(anonymous);alpha;delta 140.00µs",
|
||||
"(anonymous);alpha;beta 269.00µs",
|
||||
"(anonymous);alpha;delta 135.00µs",
|
||||
"(anonymous);alpha;beta 386.00µs",
|
||||
"(anonymous);alpha;delta 559.00µs",
|
||||
"(anonymous);alpha;beta 131.00µs",
|
||||
"(anonymous);alpha;delta 140.00µs",
|
||||
"(anonymous);alpha;beta 129.00µs",
|
||||
"(anonymous);alpha;delta 400.00µs",
|
||||
"(anonymous);alpha;beta 134.00µs",
|
||||
"(anonymous);alpha;delta 634.00µs",
|
||||
"(anonymous);alpha;beta 1.10ms",
|
||||
"(anonymous);alpha;delta 271.00µs",
|
||||
"(anonymous);alpha;delta;(program) 1.46ms",
|
||||
" 430.00µs",
|
||||
"(program) 781.00µs",
|
||||
" 273.00µs",
|
||||
"(garbage collector) 646.00µs",
|
||||
"(garbage collector);(program) 132.00µs",
|
||||
" 26.36ms",
|
||||
"(program) 135.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromChromeTimeline Chrome 69: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromChromeTimeline Chrome 69: profileGroup.name 1`] = `"simple.json"`;
|
||||
|
||||
exports[`importFromChromeTimeline: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromChromeTimeline: profileGroup.name 1`] = `"simple-timeline.json"`;
|
||||
|
||||
@@ -42,6 +42,7 @@ Object {
|
||||
],
|
||||
"name": "simple-firefox.json",
|
||||
"stacks": Array [
|
||||
" 2.04ms",
|
||||
"a;b;d 989.53µs",
|
||||
"a;c;d 1.02ms",
|
||||
"a;b;d 982.04µs",
|
||||
@@ -59,6 +60,71 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromFirefox ignore self-hosted 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:1",
|
||||
"key": "alpha (http://localhost:8000/simple.js:1:14)",
|
||||
"line": 14,
|
||||
"name": "alpha",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 26.983816999942064,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:14",
|
||||
"key": "delta (http://localhost:8000/simple.js:14:14)",
|
||||
"line": 14,
|
||||
"name": "delta",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 11.946324001066387,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:20",
|
||||
"key": "gamma (http://localhost:8000/simple.js:20:14)",
|
||||
"line": 14,
|
||||
"name": "gamma",
|
||||
"selfWeight": 26.983816999942064,
|
||||
"totalWeight": 26.983816999942064,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:8",
|
||||
"key": "beta (http://localhost:8000/simple.js:8:13)",
|
||||
"line": 13,
|
||||
"name": "beta",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 15.037492998875678,
|
||||
},
|
||||
],
|
||||
"name": "simple-firefox.json",
|
||||
"stacks": Array [
|
||||
" 4.61ms",
|
||||
"alpha;delta;gamma 999.57µs",
|
||||
"alpha;beta;gamma 2.01ms",
|
||||
"alpha;delta;gamma 1.00ms",
|
||||
"alpha;beta;gamma 995.68µs",
|
||||
"alpha;delta;gamma 996.27µs",
|
||||
"alpha;beta;gamma 4.01ms",
|
||||
"alpha;delta;gamma 2.02ms",
|
||||
"alpha;beta;gamma 959.44µs",
|
||||
"alpha;delta;gamma 2.01ms",
|
||||
"alpha;beta;gamma 4.01ms",
|
||||
"alpha;delta;gamma 959.28µs",
|
||||
"alpha;beta;gamma 2.03ms",
|
||||
"alpha;delta;gamma 3.96ms",
|
||||
"alpha;beta;gamma 1.02ms",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromFirefox ignore self-hosted: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromFirefox ignore self-hosted: profileGroup.name 1`] = `"simple-firefox.json"`;
|
||||
|
||||
exports[`importFromFirefox recursion 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
@@ -110,6 +176,7 @@ Object {
|
||||
],
|
||||
"name": "recursion.json",
|
||||
"stacks": Array [
|
||||
" 1.71ms",
|
||||
"main;alpha;beta;alpha;beta;alpha;beta;alpha;delta;gamma 998.89µs",
|
||||
"main 1.19ms",
|
||||
"main;alpha;beta;alpha;delta;gamma 1.83ms",
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`importAsPprofProfile 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.main:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.main",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 136,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/Users/jlfwong/code/speedscope/sample/programs/go/simple.go",
|
||||
"key": "main.main:/Users/jlfwong/code/speedscope/sample/programs/go/simple.go:0",
|
||||
"line": 0,
|
||||
"name": "main.main",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 136,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/Users/jlfwong/code/speedscope/sample/programs/go/simple.go",
|
||||
"key": "main.delta:/Users/jlfwong/code/speedscope/sample/programs/go/simple.go:0",
|
||||
"line": 0,
|
||||
"name": "main.delta",
|
||||
"selfWeight": 22,
|
||||
"totalWeight": 58,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/Users/jlfwong/code/speedscope/sample/programs/go/simple.go",
|
||||
"key": "main.beta:/Users/jlfwong/code/speedscope/sample/programs/go/simple.go:0",
|
||||
"line": 0,
|
||||
"name": "main.beta",
|
||||
"selfWeight": 39,
|
||||
"totalWeight": 39,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/Users/jlfwong/code/speedscope/sample/programs/go/simple.go",
|
||||
"key": "main.alpha:/Users/jlfwong/code/speedscope/sample/programs/go/simple.go:0",
|
||||
"line": 0,
|
||||
"name": "main.alpha",
|
||||
"selfWeight": 48,
|
||||
"totalWeight": 48,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/Users/jlfwong/code/speedscope/sample/programs/go/simple.go",
|
||||
"key": "main.gamma:/Users/jlfwong/code/speedscope/sample/programs/go/simple.go:0",
|
||||
"line": 0,
|
||||
"name": "main.gamma",
|
||||
"selfWeight": 27,
|
||||
"totalWeight": 27,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/asm_amd64.s",
|
||||
"key": "runtime.morestack:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/asm_amd64.s:0",
|
||||
"line": 0,
|
||||
"name": "runtime.morestack",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 11,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/stack.go",
|
||||
"key": "runtime.newstack:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/stack.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.newstack",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 11,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/duff_amd64.s",
|
||||
"key": "runtime.duffcopy:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/duff_amd64.s:0",
|
||||
"line": 0,
|
||||
"name": "runtime.duffcopy",
|
||||
"selfWeight": 11,
|
||||
"totalWeight": 11,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/time.go",
|
||||
"key": "runtime.timerproc:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/time.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.timerproc",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/time.go",
|
||||
"key": "runtime.goroutineReady:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/time.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.goroutineReady",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.goready:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.goready",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/asm_amd64.s",
|
||||
"key": "runtime.systemstack:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/asm_amd64.s:0",
|
||||
"line": 0,
|
||||
"name": "runtime.systemstack",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.goready.func1:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.goready.func1",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.ready:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.ready",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.wakep:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.wakep",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.startm:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.startm",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/lock_sema.go",
|
||||
"key": "runtime.notewakeup:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/lock_sema.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.notewakeup",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/os_darwin.go",
|
||||
"key": "runtime.semawakeup:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/os_darwin.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.semawakeup",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/os_darwin.go",
|
||||
"key": "runtime.mach_semrelease:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/os_darwin.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.mach_semrelease",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/sys_darwin_amd64.s",
|
||||
"key": "runtime.mach_semaphore_signal:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/sys_darwin_amd64.s:0",
|
||||
"line": 0,
|
||||
"name": "runtime.mach_semaphore_signal",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.mstart:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.mstart",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 2,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.mstart1:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.mstart1",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 2,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go",
|
||||
"key": "runtime.sysmon:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/proc.go:0",
|
||||
"line": 0,
|
||||
"name": "runtime.sysmon",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 2,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "/usr/local/Cellar/go/1.10.1/libexec/src/runtime/sys_darwin_amd64.s",
|
||||
"key": "runtime.usleep:/usr/local/Cellar/go/1.10.1/libexec/src/runtime/sys_darwin_amd64.s:0",
|
||||
"line": 0,
|
||||
"name": "runtime.usleep",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 2,
|
||||
},
|
||||
],
|
||||
"name": "simple.prof",
|
||||
"stacks": Array [
|
||||
"runtime.main;main.main;main.delta;main.beta 14",
|
||||
"runtime.main;main.main;main.alpha 26",
|
||||
"runtime.main;main.main;main.gamma 27",
|
||||
"runtime.morestack;runtime.newstack;runtime.duffcopy 11",
|
||||
"runtime.main;main.main;main.beta 25",
|
||||
"runtime.main;main.main;main.delta 22",
|
||||
"runtime.main;main.main;main.delta;main.alpha 22",
|
||||
"runtime.timerproc;runtime.goroutineReady;runtime.goready;runtime.systemstack;runtime.goready.func1;runtime.ready;runtime.wakep;runtime.startm;runtime.notewakeup;runtime.semawakeup;runtime.mach_semrelease;runtime.mach_semaphore_signal 1",
|
||||
"runtime.mstart;runtime.mstart1;runtime.sysmon;runtime.usleep 2",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importAsPprofProfile: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importAsPprofProfile: profileGroup.name 1`] = `"simple.prof"`;
|
||||
@@ -3,3 +3,7 @@ import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
test('importFromBGFlameGraph', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/stackcollapse/simple.txt')
|
||||
})
|
||||
|
||||
test('importFromBGFlameGraph with CRLF', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/stackcollapse/simple-crlf.txt')
|
||||
})
|
||||
|
||||
@@ -7,3 +7,7 @@ test('importFromChromeCPUProfile', async () => {
|
||||
test('importFromChromeTimeline', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Chrome/65/simple-timeline.json')
|
||||
})
|
||||
|
||||
test('importFromChromeTimeline Chrome 69', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Chrome/69/simple.json')
|
||||
})
|
||||
|
||||
+102
-25
@@ -1,7 +1,9 @@
|
||||
import {Profile, FrameInfo, CallTreeProfileBuilder} from '../lib/profile'
|
||||
import {getOrInsert, lastOf} from '../lib/utils'
|
||||
import {getOrInsert, lastOf, sortBy} from '../lib/utils'
|
||||
import {TimeFormatter} from '../lib/value-formatters'
|
||||
|
||||
// See: https://github.com/v8/v8/blob/master/src/inspector/js_protocol.json
|
||||
|
||||
interface TimelineEvent {
|
||||
pid: number
|
||||
tid: number
|
||||
@@ -45,16 +47,77 @@ interface CPUProfile {
|
||||
timeDeltas: number[]
|
||||
}
|
||||
|
||||
export function isChromeTimeline(rawProfile: any): boolean {
|
||||
if (!Array.isArray(rawProfile)) return false
|
||||
if (rawProfile.length < 1) return false
|
||||
const first = rawProfile[0]
|
||||
if (!('pid' in first && 'tid' in first && 'ph' in first && 'cat' in first)) return false
|
||||
if (
|
||||
!rawProfile.find(
|
||||
e => e.name === 'CpuProfile' || e.name === 'Profile' || e.name === 'ProfileChunk',
|
||||
)
|
||||
)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
export function importFromChromeTimeline(events: TimelineEvent[]): Profile {
|
||||
// It seems like sometimes Chrome timeline files contain multiple CpuProfiles?
|
||||
// For now, choose the first one in the list.
|
||||
|
||||
let cpuProfile: CPUProfile | null = null
|
||||
|
||||
// The events aren't necessarily recorded in chronological order. Sort them so
|
||||
// that they are.
|
||||
sortBy(events, e => e.ts)
|
||||
|
||||
for (let event of events) {
|
||||
if (event.name == 'CpuProfile') {
|
||||
const chromeProfile = event.args.data.cpuProfile as CPUProfile
|
||||
return importFromChromeCPUProfile(chromeProfile)
|
||||
cpuProfile = event.args.data.cpuProfile as CPUProfile
|
||||
}
|
||||
|
||||
if (event.name == 'Profile') {
|
||||
cpuProfile = {
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
nodes: [],
|
||||
samples: [],
|
||||
timeDeltas: [],
|
||||
...event.args.data,
|
||||
}
|
||||
}
|
||||
|
||||
if (event.name == 'ProfileChunk') {
|
||||
if (cpuProfile) {
|
||||
const chunk = event.args.data
|
||||
if (chunk.cpuProfile) {
|
||||
if (chunk.cpuProfile.nodes) {
|
||||
cpuProfile.nodes = cpuProfile.nodes.concat(chunk.cpuProfile.nodes)
|
||||
}
|
||||
if (chunk.cpuProfile.samples) {
|
||||
cpuProfile.samples = cpuProfile.samples.concat(chunk.cpuProfile.samples)
|
||||
}
|
||||
}
|
||||
if (chunk.timeDeltas) {
|
||||
cpuProfile.timeDeltas = cpuProfile.timeDeltas.concat(chunk.timeDeltas)
|
||||
}
|
||||
if (chunk.startTime != null) {
|
||||
cpuProfile.startTime = chunk.startTime
|
||||
}
|
||||
if (chunk.endTime != null) {
|
||||
cpuProfile.endTime = chunk.endTime
|
||||
}
|
||||
} else {
|
||||
console.log('Ignoring ProfileChunk when no Profile is active')
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error('Could not find CPU profile in Timeline')
|
||||
|
||||
if (cpuProfile) {
|
||||
return importFromChromeCPUProfile(cpuProfile as CPUProfile)
|
||||
} else {
|
||||
throw new Error('Could not find CPU profile in Timeline')
|
||||
}
|
||||
}
|
||||
|
||||
const callFrameToFrameInfo = new Map<CPUProfileCallFrame, FrameInfo>()
|
||||
@@ -74,7 +137,14 @@ function frameInfoForCallFrame(callFrame: CPUProfileCallFrame) {
|
||||
})
|
||||
}
|
||||
|
||||
function shouldIgnoreFunction(functionName: string) {
|
||||
function shouldIgnoreFunction(callFrame: CPUProfileCallFrame) {
|
||||
const {functionName, url} = callFrame
|
||||
if (url === 'native dummy.js') {
|
||||
// I'm not really sure what this is about, but this seems to be used
|
||||
// as a way of avoiding edge cases in V8's implementation.
|
||||
// See: https://github.com/v8/v8/blob/b8626ca4/tools/js2c.py#L419-L424
|
||||
return true
|
||||
}
|
||||
return functionName === '(root)' || functionName === '(idle)'
|
||||
}
|
||||
|
||||
@@ -90,6 +160,10 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
nodeById.set(node.id, node)
|
||||
}
|
||||
for (let node of chromeProfile.nodes) {
|
||||
if (typeof node.parent === 'number') {
|
||||
node.parent = nodeById.get(node.parent)
|
||||
}
|
||||
|
||||
if (!node.children) continue
|
||||
for (let childId of node.children) {
|
||||
const child = nodeById.get(childId)
|
||||
@@ -99,9 +173,12 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
}
|
||||
|
||||
const samples: number[] = []
|
||||
const timeDeltas: number[] = []
|
||||
const sampleTimes: number[] = []
|
||||
|
||||
// The first delta is relative to the profile startTime.
|
||||
// Ref: https://github.com/v8/v8/blob/44bd8fd7/src/inspector/js_protocol.json#L1485
|
||||
let elapsed = chromeProfile.timeDeltas[0]
|
||||
|
||||
let elapsed = 0
|
||||
let lastNodeId = NaN
|
||||
|
||||
// The chrome CPU profile format doesn't collapse identical samples. We'll do that
|
||||
@@ -110,29 +187,30 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
const nodeId = chromeProfile.samples[i]
|
||||
if (nodeId != lastNodeId) {
|
||||
samples.push(nodeId)
|
||||
timeDeltas.push(elapsed)
|
||||
elapsed = 0
|
||||
sampleTimes.push(elapsed)
|
||||
}
|
||||
|
||||
let timeDelta = chromeProfile.timeDeltas[i]
|
||||
if (timeDelta < 0) {
|
||||
console.warn('Substituting zero for unexpected time delta:', timeDelta, 'at index', i)
|
||||
timeDelta = 0
|
||||
}
|
||||
if (i === chromeProfile.samples.length - 1) {
|
||||
if (!isNaN(lastNodeId)) {
|
||||
samples.push(lastNodeId)
|
||||
sampleTimes.push(elapsed)
|
||||
}
|
||||
} else {
|
||||
let timeDelta = chromeProfile.timeDeltas[i + 1]
|
||||
if (timeDelta < 0) {
|
||||
console.warn('Substituting zero for unexpected time delta:', timeDelta, 'at index', i)
|
||||
timeDelta = 0
|
||||
}
|
||||
|
||||
elapsed += timeDelta
|
||||
lastNodeId = nodeId
|
||||
}
|
||||
if (!isNaN(lastNodeId)) {
|
||||
samples.push(lastNodeId)
|
||||
timeDeltas.push(elapsed)
|
||||
elapsed += timeDelta
|
||||
lastNodeId = nodeId
|
||||
}
|
||||
}
|
||||
|
||||
let prevStack: CPUProfileNode[] = []
|
||||
|
||||
let value = 0
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
const timeDelta = timeDeltas[i + 1] || 0
|
||||
const value = sampleTimes[i]
|
||||
const nodeId = samples[i]
|
||||
let stackTop = nodeById.get(nodeId)
|
||||
if (!stackTop) continue
|
||||
@@ -161,7 +239,7 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
const toOpen: CPUProfileNode[] = []
|
||||
for (
|
||||
let node: CPUProfileNode | null = stackTop;
|
||||
node && node != lca && !shouldIgnoreFunction(node.callFrame.functionName);
|
||||
node && node != lca && !shouldIgnoreFunction(node.callFrame);
|
||||
// Place Chrome internal functions on top of the previous call stack
|
||||
node = shouldPlaceOnTopOfPreviousStack(node.callFrame.functionName)
|
||||
? lastOf(prevStack)
|
||||
@@ -176,12 +254,11 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
}
|
||||
|
||||
prevStack = prevStack.concat(toOpen)
|
||||
value += timeDelta
|
||||
}
|
||||
|
||||
// Close frames that are open at the end of the trace
|
||||
for (let i = prevStack.length - 1; i >= 0; i--) {
|
||||
profile.leaveFrame(frameInfoForCallFrame(prevStack[i].callFrame), value)
|
||||
profile.leaveFrame(frameInfoForCallFrame(prevStack[i].callFrame), lastOf(sampleTimes)!)
|
||||
}
|
||||
|
||||
profile.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
|
||||
@@ -7,3 +7,7 @@ test('importFromFirefox', async () => {
|
||||
test('importFromFirefox recursion', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Firefox/61/recursion.json')
|
||||
})
|
||||
|
||||
test('importFromFirefox ignore self-hosted', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Firefox/63/simple-firefox.json')
|
||||
})
|
||||
|
||||
@@ -180,7 +180,11 @@ export function importFromFirefox(firefoxProfile: FirefoxProfile): Profile {
|
||||
|
||||
if (!match) return null
|
||||
|
||||
if (match[2].startsWith('resource:') || match[2] === 'self-hosted') {
|
||||
if (
|
||||
match[2].startsWith('resource:') ||
|
||||
match[2] === 'self-hosted' ||
|
||||
match[2].startsWith('self-hosted:')
|
||||
) {
|
||||
// Ignore Firefox-internals stuff
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {importProfileGroup} from '.'
|
||||
import {importProfileGroupFromText} from '.'
|
||||
|
||||
test('importProfileGroup', async () => {
|
||||
// Importing garbage should return null
|
||||
expect(await importProfileGroup('unknown', '')).toBe(null)
|
||||
expect(await importProfileGroup('unknown', 'Hello world')).toBe(null)
|
||||
expect(await importProfileGroup('unknown', 'Hello\n\nWorld')).toBe(null)
|
||||
expect(await importProfileGroupFromText('unknown', '')).toBe(null)
|
||||
expect(await importProfileGroupFromText('unknown', 'Hello world')).toBe(null)
|
||||
expect(await importProfileGroupFromText('unknown', 'Hello\n\nWorld')).toBe(null)
|
||||
})
|
||||
|
||||
+49
-9
@@ -1,7 +1,7 @@
|
||||
import {Profile, ProfileGroup} from '../lib/profile'
|
||||
import {FileSystemDirectoryEntry} from './file-system-entry'
|
||||
|
||||
import {importFromChromeCPUProfile, importFromChromeTimeline} from './chrome'
|
||||
import {importFromChromeCPUProfile, importFromChromeTimeline, isChromeTimeline} from './chrome'
|
||||
import {importFromStackprof} from './stackprof'
|
||||
import {importFromInstrumentsDeepCopy, importFromInstrumentsTrace} from './instruments'
|
||||
import {importFromBGFlameGraph} from './bg-flamegraph'
|
||||
@@ -9,12 +9,41 @@ import {importFromFirefox} from './firefox'
|
||||
import {importSpeedscopeProfiles} from '../lib/file-format'
|
||||
import {importFromV8ProfLog} from './v8proflog'
|
||||
import {importFromLinuxPerf} from './linux-tools-perf'
|
||||
import {ProfileDataSource, TextProfileDataSource, MaybeCompressedDataReader} from './utils'
|
||||
import {importAsPprofProfile} from './pprof'
|
||||
import {decodeBase64} from '../lib/utils'
|
||||
|
||||
export async function importProfileGroup(
|
||||
export async function importProfileGroupFromText(
|
||||
fileName: string,
|
||||
contents: string,
|
||||
): Promise<ProfileGroup | null> {
|
||||
const profileGroup = await _importProfileGroup(fileName, contents)
|
||||
return await importProfileGroup(new TextProfileDataSource(fileName, contents))
|
||||
}
|
||||
|
||||
export async function importProfileGroupFromBase64(
|
||||
fileName: string,
|
||||
b64contents: string,
|
||||
): Promise<ProfileGroup | null> {
|
||||
return await importProfileGroup(
|
||||
MaybeCompressedDataReader.fromArrayBuffer(fileName, decodeBase64(b64contents).buffer),
|
||||
)
|
||||
}
|
||||
|
||||
export async function importProfilesFromFile(file: File): Promise<ProfileGroup | null> {
|
||||
return importProfileGroup(MaybeCompressedDataReader.fromFile(file))
|
||||
}
|
||||
|
||||
export async function importProfilesFromArrayBuffer(
|
||||
fileName: string,
|
||||
buffer: ArrayBuffer,
|
||||
): Promise<ProfileGroup | null> {
|
||||
return importProfileGroup(MaybeCompressedDataReader.fromArrayBuffer(fileName, buffer))
|
||||
}
|
||||
|
||||
async function importProfileGroup(dataSource: ProfileDataSource): Promise<ProfileGroup | null> {
|
||||
const fileName = await dataSource.name()
|
||||
|
||||
const profileGroup = await _importProfileGroup(dataSource)
|
||||
if (profileGroup) {
|
||||
if (!profileGroup.name) {
|
||||
profileGroup.name = fileName
|
||||
@@ -34,10 +63,21 @@ function toGroup(profile: Profile | null): ProfileGroup | null {
|
||||
return {name: profile.getName(), indexToView: 0, profiles: [profile]}
|
||||
}
|
||||
|
||||
async function _importProfileGroup(
|
||||
fileName: string,
|
||||
contents: string,
|
||||
): Promise<ProfileGroup | null> {
|
||||
async function _importProfileGroup(dataSource: ProfileDataSource): Promise<ProfileGroup | null> {
|
||||
const fileName = await dataSource.name()
|
||||
|
||||
const buffer = await dataSource.readAsArrayBuffer()
|
||||
|
||||
{
|
||||
const profile = importAsPprofProfile(buffer)
|
||||
if (profile) {
|
||||
console.log('Importing as protobuf encoded pprof file')
|
||||
return toGroup(profile)
|
||||
}
|
||||
}
|
||||
|
||||
const contents = await dataSource.readAsText()
|
||||
|
||||
// First pass: Check known file format names to infer the file type
|
||||
if (fileName.endsWith('.speedscope.json')) {
|
||||
console.log('Importing as speedscope json file')
|
||||
@@ -77,7 +117,7 @@ async function _importProfileGroup(
|
||||
} else if (parsed['systemHost'] && parsed['systemHost']['name'] == 'Firefox') {
|
||||
console.log('Importing as Firefox profile')
|
||||
return toGroup(importFromFirefox(parsed))
|
||||
} else if (Array.isArray(parsed) && parsed[parsed.length - 1].name === 'CpuProfile') {
|
||||
} else if (isChromeTimeline(parsed)) {
|
||||
console.log('Importing as Chrome CPU Profile')
|
||||
return toGroup(importFromChromeTimeline(parsed))
|
||||
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
|
||||
@@ -103,7 +143,7 @@ async function _importProfileGroup(
|
||||
// If every line ends with a space followed by a number, it's probably
|
||||
// the collapsed stack format.
|
||||
const lineCount = contents.split(/\n/).length
|
||||
if (lineCount >= 1 && lineCount === contents.split(/ \d+\n/).length) {
|
||||
if (lineCount >= 1 && lineCount === contents.split(/ \d+\r?\n/).length) {
|
||||
console.log('Importing as collapsed stack format')
|
||||
return toGroup(importFromBGFlameGraph(contents))
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
ProfileGroup,
|
||||
} from '../lib/profile'
|
||||
import {sortBy, getOrThrow, getOrInsert, lastOf, getOrElse, zeroPad} from '../lib/utils'
|
||||
import * as pako from 'pako'
|
||||
import {ByteFormatter, TimeFormatter} from '../lib/value-formatters'
|
||||
import {FileSystemDirectoryEntry, FileSystemEntry, FileSystemFileEntry} from './file-system-entry'
|
||||
import {MaybeCompressedDataReader} from './utils'
|
||||
|
||||
function parseTSV<T>(contents: string): T[] {
|
||||
const lines = contents.split('\n').map(l => l.split('\t'))
|
||||
@@ -183,56 +183,12 @@ async function extractDirectoryTree(entry: FileSystemDirectoryEntry): Promise<Tr
|
||||
return node
|
||||
}
|
||||
|
||||
class MaybeCompressedFileReader {
|
||||
private fileData: Promise<ArrayBuffer>
|
||||
|
||||
constructor(file: File) {
|
||||
this.fileData = new Promise(resolve => {
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener('loadend', () => {
|
||||
if (!(reader.result instanceof ArrayBuffer)) {
|
||||
throw new Error('Expected reader.result to be an instance of ArrayBuffer')
|
||||
}
|
||||
resolve(reader.result)
|
||||
})
|
||||
reader.readAsArrayBuffer(file)
|
||||
})
|
||||
}
|
||||
|
||||
private async getUncompressed(): Promise<ArrayBuffer> {
|
||||
const fileData = await this.fileData
|
||||
try {
|
||||
const result = pako.inflate(new Uint8Array(fileData)).buffer
|
||||
return result
|
||||
} catch (e) {
|
||||
return fileData
|
||||
}
|
||||
}
|
||||
|
||||
async readAsArrayBuffer(): Promise<ArrayBuffer> {
|
||||
return await this.getUncompressed()
|
||||
}
|
||||
|
||||
async readAsText(): Promise<string> {
|
||||
const buffer = await this.getUncompressed()
|
||||
let ret: string = ''
|
||||
|
||||
// JavaScript strings are UTF-16 encoded, but this data is coming
|
||||
// from a UTF-8 encoded file.
|
||||
const array = new Uint8Array(buffer)
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
ret += String.fromCharCode(array[i])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
function readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
||||
return new MaybeCompressedFileReader(file).readAsArrayBuffer()
|
||||
return MaybeCompressedDataReader.fromFile(file).readAsArrayBuffer()
|
||||
}
|
||||
|
||||
function readAsText(file: File): Promise<string> {
|
||||
return new MaybeCompressedFileReader(file).readAsText()
|
||||
return MaybeCompressedDataReader.fromFile(file).readAsText()
|
||||
}
|
||||
|
||||
function getCoreDirForRun(tree: TraceDirectoryTree, selectedRun: number): TraceDirectoryTree {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
|
||||
test('importAsPprofProfile', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/pprof/simple.prof')
|
||||
})
|
||||
@@ -0,0 +1,134 @@
|
||||
import {perftools} from './profile.proto.js'
|
||||
import {FrameInfo, StackListProfileBuilder, Profile} from '../lib/profile'
|
||||
import {lastOf} from '../lib/utils'
|
||||
import {TimeFormatter, ByteFormatter} from '../lib/value-formatters'
|
||||
|
||||
interface SampleType {
|
||||
type: string
|
||||
unit: string
|
||||
}
|
||||
|
||||
export function importAsPprofProfile(rawProfile: ArrayBuffer): Profile | null {
|
||||
if (rawProfile.byteLength === 0) return null
|
||||
|
||||
let protoProfile: perftools.profiles.Profile
|
||||
try {
|
||||
protoProfile = perftools.profiles.Profile.decode(new Uint8Array(rawProfile))
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
|
||||
function i32(n: number | Long): number {
|
||||
return typeof n === 'number' ? n : (n as Long).low
|
||||
}
|
||||
|
||||
function stringVal(key: number | Long): string | null {
|
||||
return protoProfile.stringTable[i32(key)] || null
|
||||
}
|
||||
|
||||
const frameInfoByFunctionID = new Map<number, FrameInfo>()
|
||||
|
||||
function frameInfoForFunction(f: perftools.profiles.IFunction): FrameInfo | null {
|
||||
const {name, filename, startLine} = f
|
||||
|
||||
const nameString = (name != null && stringVal(name)) || '(unknown)'
|
||||
const fileNameString = filename != null ? stringVal(filename) : null
|
||||
const line = startLine != null ? +startLine : null
|
||||
|
||||
const key = `${nameString}:${fileNameString}:${line}`
|
||||
|
||||
const frameInfo: FrameInfo = {
|
||||
key,
|
||||
name: nameString,
|
||||
}
|
||||
|
||||
if (fileNameString != null) {
|
||||
frameInfo.file = fileNameString
|
||||
}
|
||||
|
||||
if (line != null) {
|
||||
frameInfo.line = line
|
||||
}
|
||||
|
||||
return frameInfo
|
||||
}
|
||||
|
||||
for (let f of protoProfile.function) {
|
||||
if (f.id) {
|
||||
const frameInfo = frameInfoForFunction(f)
|
||||
if (frameInfo != null) {
|
||||
frameInfoByFunctionID.set(i32(f.id), frameInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function frameInfoForLocation(location: perftools.profiles.ILocation): FrameInfo | null {
|
||||
const {line} = location
|
||||
if (line == null) return null
|
||||
|
||||
// From a comment on profile.proto:
|
||||
//
|
||||
// Multiple line indicates this location has inlined functions,
|
||||
// where the last entry represents the caller into which the
|
||||
// preceding entries were inlined.
|
||||
//
|
||||
// E.g., if memcpy() is inlined into printf:
|
||||
// line[0].function_name == "memcpy"
|
||||
// line[1].function_name == "printf"
|
||||
//
|
||||
// Let's just take the last line then
|
||||
const lastLine = lastOf(line)
|
||||
if (lastLine == null) return null
|
||||
|
||||
if (lastLine.functionId) {
|
||||
return frameInfoByFunctionID.get(i32(lastLine.functionId)) || null
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const frameByLocationID = new Map<number, FrameInfo>()
|
||||
|
||||
for (let l of protoProfile.location) {
|
||||
if (l.id != null) {
|
||||
const frameInfo = frameInfoForLocation(l)
|
||||
if (frameInfo) {
|
||||
frameByLocationID.set(i32(l.id), frameInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sampleTypes: SampleType[] = protoProfile.sampleType.map(type => ({
|
||||
type: (type.type && stringVal(type.type)) || 'samples',
|
||||
unit: (type.unit && stringVal(type.unit)) || 'count',
|
||||
}))
|
||||
|
||||
const sampleTypeIndex = protoProfile.defaultSampleType
|
||||
? +protoProfile.defaultSampleType
|
||||
: sampleTypes.length - 1
|
||||
const sampleType = sampleTypes[sampleTypeIndex]
|
||||
|
||||
const profileBuilder = new StackListProfileBuilder()
|
||||
|
||||
switch (sampleType.unit) {
|
||||
case 'nanoseconds':
|
||||
case 'microseconds':
|
||||
case 'milliseconds':
|
||||
case 'seconds':
|
||||
profileBuilder.setValueFormatter(new TimeFormatter(sampleType.unit))
|
||||
break
|
||||
|
||||
case 'bytes':
|
||||
profileBuilder.setValueFormatter(new ByteFormatter())
|
||||
break
|
||||
}
|
||||
|
||||
for (let s of protoProfile.sample) {
|
||||
const stack = s.locationId ? s.locationId.map(l => frameByLocationID.get(i32(l))) : []
|
||||
stack.reverse()
|
||||
const value = s.value![sampleTypeIndex]
|
||||
profileBuilder.appendSampleWithWeight(stack.filter(f => f != null) as FrameInfo[], +value)
|
||||
}
|
||||
|
||||
return profileBuilder.build()
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// THIS FILE WAS IMPORTED FROM AN EXTERNAL SOURCE. DO NOT MODIFY THIS FILE
|
||||
// MANUALLY.
|
||||
//
|
||||
// Original from: https://github.com/google/pprof/blob/e027b50/proto/profile.proto
|
||||
//
|
||||
// This file is licensed under the Apache License 2.0
|
||||
// (https://github.com/google/pprof/blob/e027b5/LICENSE)
|
||||
|
||||
// Profile is a common stacktrace profile format.
|
||||
//
|
||||
// Measurements represented with this format should follow the
|
||||
// following conventions:
|
||||
//
|
||||
// - Consumers should treat unset optional fields as if they had been
|
||||
// set with their default value.
|
||||
//
|
||||
// - When possible, measurements should be stored in "unsampled" form
|
||||
// that is most useful to humans. There should be enough
|
||||
// information present to determine the original sampled values.
|
||||
//
|
||||
// - On-disk, the serialized proto must be gzip-compressed.
|
||||
//
|
||||
// - The profile is represented as a set of samples, where each sample
|
||||
// references a sequence of locations, and where each location belongs
|
||||
// to a mapping.
|
||||
// - There is a N->1 relationship from sample.location_id entries to
|
||||
// locations. For every sample.location_id entry there must be a
|
||||
// unique Location with that id.
|
||||
// - There is an optional N->1 relationship from locations to
|
||||
// mappings. For every nonzero Location.mapping_id there must be a
|
||||
// unique Mapping with that id.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package perftools.profiles;
|
||||
|
||||
option java_package = "com.google.perftools.profiles";
|
||||
option java_outer_classname = "ProfileProto";
|
||||
|
||||
message Profile {
|
||||
// A description of the samples associated with each Sample.value.
|
||||
// For a cpu profile this might be:
|
||||
// [["cpu","nanoseconds"]] or [["wall","seconds"]] or [["syscall","count"]]
|
||||
// For a heap profile, this might be:
|
||||
// [["allocations","count"], ["space","bytes"]],
|
||||
// If one of the values represents the number of events represented
|
||||
// by the sample, by convention it should be at index 0 and use
|
||||
// sample_type.unit == "count".
|
||||
repeated ValueType sample_type = 1;
|
||||
// The set of samples recorded in this profile.
|
||||
repeated Sample sample = 2;
|
||||
// Mapping from address ranges to the image/binary/library mapped
|
||||
// into that address range. mapping[0] will be the main binary.
|
||||
repeated Mapping mapping = 3;
|
||||
// Useful program location
|
||||
repeated Location location = 4;
|
||||
// Functions referenced by locations
|
||||
repeated Function function = 5;
|
||||
// A common table for strings referenced by various messages.
|
||||
// string_table[0] must always be "".
|
||||
repeated string string_table = 6;
|
||||
// frames with Function.function_name fully matching the following
|
||||
// regexp will be dropped from the samples, along with their successors.
|
||||
int64 drop_frames = 7; // Index into string table.
|
||||
// frames with Function.function_name fully matching the following
|
||||
// regexp will be kept, even if it matches drop_functions.
|
||||
int64 keep_frames = 8; // Index into string table.
|
||||
|
||||
// The following fields are informational, do not affect
|
||||
// interpretation of results.
|
||||
|
||||
// Time of collection (UTC) represented as nanoseconds past the epoch.
|
||||
int64 time_nanos = 9;
|
||||
// Duration of the profile, if a duration makes sense.
|
||||
int64 duration_nanos = 10;
|
||||
// The kind of events between sampled ocurrences.
|
||||
// e.g [ "cpu","cycles" ] or [ "heap","bytes" ]
|
||||
ValueType period_type = 11;
|
||||
// The number of events between sampled occurrences.
|
||||
int64 period = 12;
|
||||
// Freeform text associated to the profile.
|
||||
repeated int64 comment = 13; // Indices into string table.
|
||||
// Index into the string table of the type of the preferred sample
|
||||
// value. If unset, clients should default to the last sample value.
|
||||
int64 default_sample_type = 14;
|
||||
}
|
||||
|
||||
// ValueType describes the semantics and measurement units of a value.
|
||||
message ValueType {
|
||||
int64 type = 1; // Index into string table.
|
||||
int64 unit = 2; // Index into string table.
|
||||
}
|
||||
|
||||
// Each Sample records values encountered in some program
|
||||
// context. The program context is typically a stack trace, perhaps
|
||||
// augmented with auxiliary information like the thread-id, some
|
||||
// indicator of a higher level request being handled etc.
|
||||
message Sample {
|
||||
// The ids recorded here correspond to a Profile.location.id.
|
||||
// The leaf is at location_id[0].
|
||||
repeated uint64 location_id = 1;
|
||||
// The type and unit of each value is defined by the corresponding
|
||||
// entry in Profile.sample_type. All samples must have the same
|
||||
// number of values, the same as the length of Profile.sample_type.
|
||||
// When aggregating multiple samples into a single sample, the
|
||||
// result has a list of values that is the elemntwise sum of the
|
||||
// lists of the originals.
|
||||
repeated int64 value = 2;
|
||||
// label includes additional context for this sample. It can include
|
||||
// things like a thread id, allocation size, etc
|
||||
repeated Label label = 3;
|
||||
}
|
||||
|
||||
message Label {
|
||||
int64 key = 1; // Index into string table
|
||||
|
||||
// At most one of the following must be present
|
||||
int64 str = 2; // Index into string table
|
||||
int64 num = 3;
|
||||
|
||||
// Should only be present when num is present.
|
||||
// Specifies the units of num.
|
||||
// Use arbitrary string (for example, "requests") as a custom count unit.
|
||||
// If no unit is specified, consumer may apply heuristic to deduce the unit.
|
||||
// Consumers may also interpret units like "bytes" and "kilobytes" as memory
|
||||
// units and units like "seconds" and "nanoseconds" as time units,
|
||||
// and apply appropriate unit conversions to these.
|
||||
int64 num_unit = 4; // Index into string table
|
||||
}
|
||||
|
||||
message Mapping {
|
||||
// Unique nonzero id for the mapping.
|
||||
uint64 id = 1;
|
||||
// Address at which the binary (or DLL) is loaded into memory.
|
||||
uint64 memory_start = 2;
|
||||
// The limit of the address range occupied by this mapping.
|
||||
uint64 memory_limit = 3;
|
||||
// Offset in the binary that corresponds to the first mapped address.
|
||||
uint64 file_offset = 4;
|
||||
// The object this entry is loaded from. This can be a filename on
|
||||
// disk for the main binary and shared libraries, or virtual
|
||||
// abstractions like "[vdso]".
|
||||
int64 filename = 5; // Index into string table
|
||||
// A string that uniquely identifies a particular program version
|
||||
// with high probability. E.g., for binaries generated by GNU tools,
|
||||
// it could be the contents of the .note.gnu.build-id field.
|
||||
int64 build_id = 6; // Index into string table
|
||||
|
||||
// The following fields indicate the resolution of symbolic info.
|
||||
bool has_functions = 7;
|
||||
bool has_filenames = 8;
|
||||
bool has_line_numbers = 9;
|
||||
bool has_inline_frames = 10;
|
||||
}
|
||||
|
||||
// Describes function and line table debug information.
|
||||
message Location {
|
||||
// Unique nonzero id for the location. A profile could use
|
||||
// instruction addresses or any integer sequence as ids.
|
||||
uint64 id = 1;
|
||||
// The id of the corresponding profile.Mapping for this location.
|
||||
// It can be unset if the mapping is unknown or not applicable for
|
||||
// this profile type.
|
||||
uint64 mapping_id = 2;
|
||||
// The instruction address for this location, if available. It
|
||||
// should be within [Mapping.memory_start...Mapping.memory_limit]
|
||||
// for the corresponding mapping. A non-leaf address may be in the
|
||||
// middle of a call instruction. It is up to display tools to find
|
||||
// the beginning of the instruction if necessary.
|
||||
uint64 address = 3;
|
||||
// Multiple line indicates this location has inlined functions,
|
||||
// where the last entry represents the caller into which the
|
||||
// preceding entries were inlined.
|
||||
//
|
||||
// E.g., if memcpy() is inlined into printf:
|
||||
// line[0].function_name == "memcpy"
|
||||
// line[1].function_name == "printf"
|
||||
repeated Line line = 4;
|
||||
// Provides an indication that multiple symbols map to this location's
|
||||
// address, for example due to identical code folding by the linker. In that
|
||||
// case the line information above represents one of the multiple
|
||||
// symbols. This field must be recomputed when the symbolization state of the
|
||||
// profile changes.
|
||||
bool is_folded = 5;
|
||||
}
|
||||
|
||||
message Line {
|
||||
// The id of the corresponding profile.Function for this line.
|
||||
uint64 function_id = 1;
|
||||
// Line number in source code.
|
||||
int64 line = 2;
|
||||
}
|
||||
|
||||
message Function {
|
||||
// Unique nonzero id for the function.
|
||||
uint64 id = 1;
|
||||
// Name of the function, in human-readable form if available.
|
||||
int64 name = 2; // Index into string table
|
||||
// Name of the function, as identified by the system.
|
||||
// For instance, it can be a C++ mangled name.
|
||||
int64 system_name = 3; // Index into string table
|
||||
// Source file containing the function.
|
||||
int64 filename = 4; // Index into string table
|
||||
// Line number in source file.
|
||||
int64 start_line = 5;
|
||||
}
|
||||
Vendored
+1047
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
import * as pako from 'pako'
|
||||
|
||||
export interface ProfileDataSource {
|
||||
name(): Promise<string>
|
||||
readAsArrayBuffer(): Promise<ArrayBuffer>
|
||||
readAsText(): Promise<string>
|
||||
}
|
||||
|
||||
export class TextProfileDataSource implements ProfileDataSource {
|
||||
constructor(private fileName: string, private contents: string) {}
|
||||
async name() {
|
||||
return this.fileName
|
||||
}
|
||||
async readAsArrayBuffer() {
|
||||
// JavaScript strings are UTF-16 encoded, but if this string is
|
||||
// constructed based on
|
||||
|
||||
// TODO(jlfwong): Might want to make this construct an array
|
||||
// buffer based on the text
|
||||
return new ArrayBuffer(0)
|
||||
}
|
||||
async readAsText() {
|
||||
return this.contents
|
||||
}
|
||||
}
|
||||
|
||||
export class MaybeCompressedDataReader implements ProfileDataSource {
|
||||
private uncompressedData: Promise<ArrayBuffer>
|
||||
|
||||
constructor(
|
||||
private namePromise: Promise<string>,
|
||||
maybeCompressedDataPromise: Promise<ArrayBuffer>,
|
||||
) {
|
||||
this.uncompressedData = maybeCompressedDataPromise.then(async (fileData: ArrayBuffer) => {
|
||||
try {
|
||||
const result = pako.inflate(new Uint8Array(fileData)).buffer
|
||||
return result
|
||||
} catch (e) {
|
||||
return fileData
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async name(): Promise<string> {
|
||||
return await this.namePromise
|
||||
}
|
||||
|
||||
async readAsArrayBuffer(): Promise<ArrayBuffer> {
|
||||
return await this.uncompressedData
|
||||
}
|
||||
|
||||
async readAsText(): Promise<string> {
|
||||
const buffer = await this.readAsArrayBuffer()
|
||||
let ret: string = ''
|
||||
|
||||
// JavaScript strings are UTF-16 encoded, but we're reading data
|
||||
// from disk that we're going to asusme is UTF-8 encoded.
|
||||
const array = new Uint8Array(buffer)
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
ret += String.fromCharCode(array[i])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
static fromFile(file: File): MaybeCompressedDataReader {
|
||||
const maybeCompressedDataPromise: Promise<ArrayBuffer> = new Promise(resolve => {
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener('loadend', () => {
|
||||
if (!(reader.result instanceof ArrayBuffer)) {
|
||||
throw new Error('Expected reader.result to be an instance of ArrayBuffer')
|
||||
}
|
||||
resolve(reader.result)
|
||||
})
|
||||
reader.readAsArrayBuffer(file)
|
||||
})
|
||||
|
||||
return new MaybeCompressedDataReader(Promise.resolve(file.name), maybeCompressedDataPromise)
|
||||
}
|
||||
|
||||
static fromArrayBuffer(name: string, buffer: ArrayBuffer): MaybeCompressedDataReader {
|
||||
return new MaybeCompressedDataReader(Promise.resolve(name), Promise.resolve(buffer))
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -521,10 +521,9 @@ export class CallTreeProfileBuilder extends Profile {
|
||||
private framesInStack = new Map<Frame, number>()
|
||||
private stack: Frame[] = []
|
||||
|
||||
private lastValue: number | null = null
|
||||
private lastValue: number = 0
|
||||
private addWeightsToFrames(value: number) {
|
||||
if (this.lastValue == null) this.lastValue = value
|
||||
const delta = value - this.lastValue!
|
||||
const delta = value - this.lastValue
|
||||
for (let frame of this.framesInStack.keys()) {
|
||||
frame.addToTotalWeight(delta)
|
||||
}
|
||||
@@ -534,7 +533,7 @@ export class CallTreeProfileBuilder extends Profile {
|
||||
}
|
||||
}
|
||||
private addWeightsToNodes(value: number, stack: CallTreeNode[]) {
|
||||
const delta = value - this.lastValue!
|
||||
const delta = value - this.lastValue
|
||||
for (let node of stack) {
|
||||
node.addToTotalWeight(delta)
|
||||
}
|
||||
@@ -552,14 +551,15 @@ export class CallTreeProfileBuilder extends Profile {
|
||||
|
||||
if (prevTop) {
|
||||
if (useAppendOrder) {
|
||||
const delta = value - this.lastValue!
|
||||
const delta = value - this.lastValue
|
||||
if (delta > 0) {
|
||||
this.samples.push(prevTop)
|
||||
this.weights.push(value - this.lastValue!)
|
||||
this.weights.push(value - this.lastValue)
|
||||
} else if (delta < 0) {
|
||||
throw new Error(
|
||||
`Samples must be provided in increasing order of cumulative value. Last sample was ${this
|
||||
.lastValue!}, this sample was ${value}`,
|
||||
`Samples must be provided in increasing order of cumulative value. Last sample was ${
|
||||
this.lastValue
|
||||
}, this sample was ${value}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import {Profile, CallTreeNode, Frame} from './profile'
|
||||
import {importProfileGroup} from '../import'
|
||||
import {exportProfileGroup, importSpeedscopeProfiles} from './file-format'
|
||||
import {importProfilesFromArrayBuffer} from '../import'
|
||||
|
||||
interface DumpedProfile {
|
||||
name: string
|
||||
@@ -47,9 +47,10 @@ export function dumpProfile(profile: Profile): any {
|
||||
}
|
||||
|
||||
export async function checkProfileSnapshot(filepath: string) {
|
||||
const input = fs.readFileSync(filepath, 'utf8')
|
||||
const buffer = fs.readFileSync(filepath)
|
||||
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
||||
|
||||
const profileGroup = await importProfileGroup(path.basename(filepath), input)
|
||||
const profileGroup = await importProfilesFromArrayBuffer(path.basename(filepath), arrayBuffer)
|
||||
if (profileGroup) {
|
||||
expect(profileGroup.name).toMatchSnapshot('profileGroup.name')
|
||||
expect(profileGroup.indexToView).toMatchSnapshot('indexToView')
|
||||
@@ -62,7 +63,7 @@ export async function checkProfileSnapshot(filepath: string) {
|
||||
return
|
||||
}
|
||||
|
||||
const profilesWithoutFilename = await importProfileGroup('unknown', input)
|
||||
const profilesWithoutFilename = await importProfilesFromArrayBuffer('unknown', arrayBuffer)
|
||||
if (profilesWithoutFilename) {
|
||||
expect(profilesWithoutFilename.profiles.length).toEqual(profileGroup.profiles.length)
|
||||
profilesWithoutFilename.name = profileGroup.name
|
||||
|
||||
@@ -13,8 +13,11 @@ import {
|
||||
memoizeByReference,
|
||||
memoizeByShallowEquality,
|
||||
objectsHaveShallowEquality,
|
||||
decodeBase64,
|
||||
} from './utils'
|
||||
|
||||
import * as jsc from 'jsverify'
|
||||
|
||||
test('sortBy', () => {
|
||||
const ls = ['a3', 'b2', 'c1', 'd4']
|
||||
sortBy(ls, k => k.charCodeAt(1))
|
||||
@@ -173,3 +176,19 @@ test('objectsHaveShallowEquality', () => {
|
||||
expect(objectsHaveShallowEquality([1], [1, 2])).toBe(false)
|
||||
expect(objectsHaveShallowEquality([1, 2], [1])).toBe(false)
|
||||
})
|
||||
|
||||
test('decodeBase64', () => {
|
||||
jsc.assertForall(jsc.array(jsc.uint8), byteArray => {
|
||||
let binaryString = ''
|
||||
for (let byte of byteArray) {
|
||||
binaryString += String.fromCharCode(byte)
|
||||
}
|
||||
const b64string = btoa(binaryString)
|
||||
const decoded = decodeBase64(b64string)
|
||||
|
||||
expect(Uint8Array.from(byteArray)).toEqual(decoded)
|
||||
|
||||
// If the above expect(...) assertion fails, we won't reach here.
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
@@ -167,3 +167,131 @@ export function memoizeByReference<T, U>(cb: (t: T) => U): (t: T) => U {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function lazyStatic<T>(cb: () => T): () => T {
|
||||
let last: {result: T} | null = null
|
||||
return () => {
|
||||
if (last == null) {
|
||||
last = {result: cb()}
|
||||
}
|
||||
return last.result
|
||||
}
|
||||
}
|
||||
|
||||
const base64lookupTable = lazyStatic((): Map<string, number> => {
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
const ret = new Map<string, number>()
|
||||
for (let i = 0; i < alphabet.length; i++) {
|
||||
ret.set(alphabet.charAt(i), i)
|
||||
}
|
||||
ret.set('=', -1)
|
||||
return ret
|
||||
})
|
||||
|
||||
// NOTE: There are probably simpler solutions to this problem, but I have this written already, so
|
||||
// until we run into problems with this, let's just use this.
|
||||
//
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem#The_Unicode_Problem
|
||||
export function decodeBase64(encoded: string): Uint8Array {
|
||||
// Reference: https://www.rfc-editor.org/rfc/rfc4648.txt
|
||||
|
||||
const lookupTable = base64lookupTable()
|
||||
|
||||
// 3 byte groups are represented as sequneces of 4 characters.
|
||||
//
|
||||
// "The encoding process represents 24-bit groups of input bits as output
|
||||
// strings of 4 encoded characters."
|
||||
//
|
||||
// "Special processing is performed if fewer than 24 bits are available
|
||||
// at the end of the data being encoded. A full encoding quantum is
|
||||
// always completed at the end of a quantity. When fewer than 24 input
|
||||
// bits are available in an input group bits with value zero are added
|
||||
// (on the right) to form an integral number of 6-bit groups."
|
||||
|
||||
if (encoded.length % 4 !== 0) {
|
||||
throw new Error(
|
||||
`Invalid length for base64 encoded string. Expected length % 4 = 0, got length = ${
|
||||
encoded.length
|
||||
}`,
|
||||
)
|
||||
}
|
||||
|
||||
const quartetCount = encoded.length / 4
|
||||
let byteCount: number
|
||||
|
||||
// Special processing is performed if fewer than 24 bits are available
|
||||
// at the end of the data being encoded. A full encoding quantum is
|
||||
// always completed at the end of a quantity. When fewer than 24 input
|
||||
// bits are available in an input group, bits with value zero are added
|
||||
// (on the right) to form an integral number of 6-bit groups. Padding
|
||||
// at the end of the data is performed using the '=' character. Since
|
||||
// all base 64 input is an integral number of octets, only the following
|
||||
// cases can arise:
|
||||
//
|
||||
// (1) The final quantum of encoding input is an integral multiple of 24
|
||||
// bits; here, the final unit of encoded output will be an integral
|
||||
// multiple of 4 characters with no "=" padding.
|
||||
//
|
||||
// (2) The final quantum of encoding input is exactly 8 bits; here, the
|
||||
// final unit of encoded output will be two characters followed by
|
||||
// two "=" padding characters.
|
||||
//
|
||||
// (3) The final quantum of encoding input is exactly 16 bits; here, the
|
||||
// final unit of encoded output will be three characters followed by
|
||||
// one "=" padding character.
|
||||
if (encoded.length >= 4) {
|
||||
if (encoded.charAt(encoded.length - 1) === '=') {
|
||||
if (encoded.charAt(encoded.length - 2) === '=') {
|
||||
// Case (2)
|
||||
byteCount = quartetCount * 3 - 2
|
||||
} else {
|
||||
// Case (3)
|
||||
byteCount = quartetCount * 3 - 1
|
||||
}
|
||||
} else {
|
||||
// Case (1)
|
||||
byteCount = quartetCount * 3
|
||||
}
|
||||
} else {
|
||||
// Case (1)
|
||||
byteCount = quartetCount * 3
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(byteCount)
|
||||
let offset = 0
|
||||
|
||||
for (let i = 0; i < quartetCount; i++) {
|
||||
const enc1 = encoded.charAt(i * 4 + 0)
|
||||
const enc2 = encoded.charAt(i * 4 + 1)
|
||||
const enc3 = encoded.charAt(i * 4 + 2)
|
||||
const enc4 = encoded.charAt(i * 4 + 3)
|
||||
|
||||
const sextet1 = lookupTable.get(enc1)
|
||||
const sextet2 = lookupTable.get(enc2)
|
||||
const sextet3 = lookupTable.get(enc3)
|
||||
const sextet4 = lookupTable.get(enc4)
|
||||
|
||||
if (sextet1 == null || sextet2 == null || sextet3 == null || sextet4 == null) {
|
||||
throw new Error(
|
||||
`Invalid quartet at indices ${i * 4} .. ${i * 4 + 3}: ${encoded.substring(
|
||||
i * 4,
|
||||
i * 4 + 3,
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
|
||||
bytes[offset++] = (sextet1 << 2) | (sextet2 >> 4)
|
||||
if (enc3 !== '=') {
|
||||
bytes[offset++] = ((sextet2 & 15) << 4) | (sextet3 >> 2)
|
||||
}
|
||||
if (enc4 !== '=') {
|
||||
bytes[offset++] = ((sextet3 & 7) << 6) | sextet4
|
||||
}
|
||||
}
|
||||
|
||||
if (offset !== byteCount) {
|
||||
throw new Error(`Expected to decode ${byteCount} bytes, but only decoded ${offset})`)
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ describe('TimeFormatter', () => {
|
||||
expect(f.format(0.04)).toEqual('40.00µs')
|
||||
expect(f.format(3)).toEqual('3.00ms')
|
||||
expect(f.format(2070)).toEqual('2.07s')
|
||||
expect(f.format(1203123)).toEqual('20.05min')
|
||||
expect(f.format(150000)).toEqual('2:30')
|
||||
expect(f.format(1203123)).toEqual('20:03')
|
||||
})
|
||||
|
||||
test('input units seconds', () => {
|
||||
@@ -15,7 +16,8 @@ describe('TimeFormatter', () => {
|
||||
expect(f.format(0.00004)).toEqual('40.00µs')
|
||||
expect(f.format(0.003)).toEqual('3.00ms')
|
||||
expect(f.format(2.07)).toEqual('2.07s')
|
||||
expect(f.format(1203.123)).toEqual('20.05min')
|
||||
expect(f.format(150)).toEqual('2:30')
|
||||
expect(f.format(1203.123)).toEqual('20:03')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {FileFormat} from './file-format-spec'
|
||||
import {zeroPad} from './utils'
|
||||
|
||||
export interface ValueFormatter {
|
||||
unit: FileFormat.ValueUnit
|
||||
@@ -25,7 +26,11 @@ export class TimeFormatter implements ValueFormatter {
|
||||
formatUnsigned(v: number) {
|
||||
const s = v * this.multiplier
|
||||
|
||||
if (s / 60 >= 1) return `${(s / 60).toFixed(2)}min`
|
||||
if (s / 60 >= 1) {
|
||||
const minutes = Math.floor(s / 60)
|
||||
const seconds = Math.floor(s - minutes * 60).toString()
|
||||
return `${minutes}:${zeroPad(seconds, 2)}`
|
||||
}
|
||||
if (s / 1 >= 1) return `${s.toFixed(2)}s`
|
||||
if (s / 1e-3 >= 1) return `${(s / 1e-3).toFixed(2)}ms`
|
||||
if (s / 1e-6 >= 1) return `${(s / 1e-6).toFixed(2)}µs`
|
||||
|
||||
+36
-19
@@ -18,8 +18,23 @@ import {Graphics} from '../gl/graphics'
|
||||
const importModule = import('../import')
|
||||
// Force eager loading of the module
|
||||
importModule.then(() => {})
|
||||
async function importProfiles(fileName: string, contents: string): Promise<ProfileGroup | null> {
|
||||
return (await importModule).importProfileGroup(fileName, contents)
|
||||
|
||||
async function importProfilesFromText(
|
||||
fileName: string,
|
||||
contents: string,
|
||||
): Promise<ProfileGroup | null> {
|
||||
return (await importModule).importProfileGroupFromText(fileName, contents)
|
||||
}
|
||||
|
||||
async function importProfilesFromBase64(
|
||||
fileName: string,
|
||||
contents: string,
|
||||
): Promise<ProfileGroup | null> {
|
||||
return (await importModule).importProfileGroupFromBase64(fileName, contents)
|
||||
}
|
||||
|
||||
async function importProfilesFromFile(file: File): Promise<ProfileGroup | null> {
|
||||
return (await importModule).importProfilesFromFile(file)
|
||||
}
|
||||
async function importFromFileSystemDirectoryEntry(entry: FileSystemDirectoryEntry) {
|
||||
return (await importModule).importFromFileSystemDirectoryEntry(entry)
|
||||
@@ -320,16 +335,7 @@ export class Application extends StatelessComponent<ApplicationProps> {
|
||||
|
||||
loadFromFile(file: File) {
|
||||
this.loadProfile(async () => {
|
||||
const reader = new FileReader()
|
||||
const loadPromise = new Promise(resolve => reader.addEventListener('loadend', resolve))
|
||||
reader.readAsText(file)
|
||||
await loadPromise
|
||||
|
||||
if (typeof reader.result !== 'string') {
|
||||
throw new Error('Expected ArrayBuffer')
|
||||
}
|
||||
|
||||
const profiles = await importProfiles(file.name, reader.result)
|
||||
const profiles = await importProfilesFromFile(file)
|
||||
if (profiles) {
|
||||
for (let profile of profiles.profiles) {
|
||||
if (!profile.getName()) {
|
||||
@@ -344,7 +350,19 @@ export class Application extends StatelessComponent<ApplicationProps> {
|
||||
// a symbol map. If that's the case, we want to parse it, and apply the symbol
|
||||
// mapping to the already loaded profile. This can be use to take an opaque
|
||||
// profile and make it readable.
|
||||
const map = importEmscriptenSymbolMap(reader.result)
|
||||
const reader = new FileReader()
|
||||
const fileContentsPromise = new Promise<string>(resolve => {
|
||||
reader.addEventListener('loadend', () => {
|
||||
if (typeof reader.result !== 'string') {
|
||||
throw new Error('Expected reader.result to be a string')
|
||||
}
|
||||
resolve(reader.result)
|
||||
})
|
||||
})
|
||||
reader.readAsText(file)
|
||||
const fileContents = await fileContentsPromise
|
||||
|
||||
const map = importEmscriptenSymbolMap(fileContents)
|
||||
if (map) {
|
||||
const {profile, index} = this.props.activeProfileState
|
||||
console.log('Importing as emscripten symbol map')
|
||||
@@ -365,7 +383,7 @@ export class Application extends StatelessComponent<ApplicationProps> {
|
||||
this.loadProfile(async () => {
|
||||
const filename = 'perf-vertx-stacks-01-collapsed-all.txt'
|
||||
const data = await fetch(exampleProfileURL).then(resp => resp.text())
|
||||
return await importProfiles(filename, data)
|
||||
return await importProfilesFromText(filename, data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -463,7 +481,7 @@ export class Application extends StatelessComponent<ApplicationProps> {
|
||||
|
||||
const pasted = (ev as ClipboardEvent).clipboardData.getData('text')
|
||||
this.loadProfile(async () => {
|
||||
return await importProfiles('From Clipboard', pasted)
|
||||
return await importProfilesFromText('From Clipboard', pasted)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -489,12 +507,12 @@ export class Application extends StatelessComponent<ApplicationProps> {
|
||||
return
|
||||
}
|
||||
this.loadProfile(async () => {
|
||||
const response = await fetch(this.props.hashParams.profileURL!)
|
||||
const response: Response = await fetch(this.props.hashParams.profileURL!)
|
||||
let filename = new URL(this.props.hashParams.profileURL!).pathname
|
||||
if (filename.includes('/')) {
|
||||
filename = filename.slice(filename.lastIndexOf('/') + 1)
|
||||
}
|
||||
return await importProfiles(filename, await response.text())
|
||||
return await importProfilesFromText(filename, await response.text())
|
||||
})
|
||||
} else if (this.props.hashParams.localProfilePath) {
|
||||
// There isn't good cross-browser support for XHR of local files, even from
|
||||
@@ -502,8 +520,7 @@ export class Application extends StatelessComponent<ApplicationProps> {
|
||||
// as a JavaScript file which will invoke a global function.
|
||||
;(window as any)['speedscope'] = {
|
||||
loadFileFromBase64: (filename: string, base64source: string) => {
|
||||
const source = atob(base64source)
|
||||
this.loadProfile(() => importProfiles(filename, source))
|
||||
this.loadProfile(() => importProfilesFromBase64(filename, base64source))
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user