Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aee2dfdf89 | ||
|
|
d9b3950274 | ||
|
|
177696e359 | ||
|
|
16e32dc08e | ||
|
|
8a4f38a8cb | ||
|
|
9361a6baf2 | ||
|
|
ede9c74d50 | ||
|
|
f758130455 | ||
|
|
f3a1c09c9b | ||
|
|
069c0194a6 | ||
|
|
64fe369c42 | ||
|
|
f55c53f699 | ||
|
|
a0b3fe8420 | ||
|
|
9452aeae82 | ||
|
|
b26cdb5be4 | ||
|
|
1c5bdba36e | ||
|
|
c3b35d7b0f | ||
|
|
dfaefe54fd | ||
|
|
7514f4c0c9 | ||
|
|
ff447c2719 | ||
|
|
668bb032ba | ||
|
|
9ed1eb192c | ||
|
|
8620432cbc | ||
|
|
dead3f9ad9 | ||
|
|
80b747a55e | ||
|
|
351994972d | ||
|
|
ca1abfdd32 | ||
|
|
dee9e5ade4 | ||
|
|
2077a905a9 | ||
|
|
e969178e65 | ||
|
|
56f6459af9 | ||
|
|
3f79e0fe96 | ||
|
|
d30bb2ef7e | ||
|
|
fd4195da10 | ||
|
|
707462e9cf | ||
|
|
375040e892 | ||
|
|
5ae9abcf1d | ||
|
|
bdd9301c59 | ||
|
|
cc9750923a | ||
|
|
c3074b7343 | ||
|
|
b15a08b3ff | ||
|
|
68683aa054 | ||
|
|
eb0e1ce731 | ||
|
|
bd546ca893 | ||
|
|
0c1e477f35 | ||
|
|
30ca6291ca | ||
|
|
66a9e5d1cf | ||
|
|
a2022a07a2 | ||
|
|
b7806a1c5f | ||
|
|
7f19a13012 | ||
|
|
abd74be9fa | ||
|
|
c706bdfe04 | ||
|
|
cfc8fe8f6e | ||
|
|
ddc61302e8 | ||
|
|
864c065053 | ||
|
|
7cca1a76bc | ||
|
|
8c574d1c92 | ||
|
|
fbc8946caa | ||
|
|
8cddf3fe81 | ||
|
|
c15ca263d3 | ||
|
|
519847b489 | ||
|
|
6d4f3499da | ||
|
|
ad49dacb29 | ||
|
|
6562fec7a7 | ||
|
|
23d4042e04 | ||
|
|
9961ed8295 | ||
|
|
e35335fe3c | ||
|
|
86f4ba636d | ||
|
|
0fe0c454d3 | ||
|
|
4b292b2acf | ||
|
|
d78d20e005 | ||
|
|
7d0a3a2c59 | ||
|
|
3f205ec3e9 | ||
|
|
c70171836c | ||
|
|
ee0c2c5025 | ||
|
|
3ba60a424d |
+11
-4
@@ -1,13 +1,20 @@
|
||||
module.exports = {
|
||||
parser: 'typescript-eslint-parser',
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
plugins: ['prettier', '@typescript-eslint', 'react-hooks'],
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': [
|
||||
'error',
|
||||
{
|
||||
additionalHooks: '(useSelector|useAppSelector|useActionCreator)',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Coveralls Parallel
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.github_token }}
|
||||
flag-name: run-${{ matrix.node-version }}
|
||||
parallel: true
|
||||
|
||||
finish:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Coveralls Finished
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.github_token }}
|
||||
parallel-finished: true
|
||||
@@ -1,3 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '9'
|
||||
+169
-21
@@ -1,77 +1,225 @@
|
||||
## Unreleased
|
||||
|
||||
## [1.11.0] - 2020-10-13
|
||||
|
||||
### Added
|
||||
|
||||
- Support remapping profiles using source maps [[#317](https://github.com/jlfwong/speedscope/pull/317)]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix line & column numbers in imports from Chrome & Firefox [[#318](https://github.com/jlfwong/speedscope/pull/318)]
|
||||
|
||||
## [1.10.0] - 2020-09-29
|
||||
|
||||
### Added
|
||||
|
||||
- Support for importing profiles from Safari [[#300](https://github.com/jlfwong/speedscope/pull/300)] (by [@radex](https://github.com/radex))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed browser not opening on Windows when using the CLI [[#307](https://github.com/jlfwong/speedscope/pull/307)] (by [@spillerrec](https://github.com/spillerrec))
|
||||
- Fixed import of UTF-16 encoded files w/ BOM [[#314](https://github.com/jlfwong/speedscope/pull/314)]
|
||||
- Removed accidental dependency on React [[#315](https://github.com/jlfwong/speedscope/pull/315)]
|
||||
|
||||
## [1.9.0] - 2020-08-05
|
||||
|
||||
### Added
|
||||
|
||||
- Provide prev/next buttons to cycle through search results, make search results more visually prominen [[#304](https://github.com/jlfwong/speedscope/pull/304)]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix accumulated errors in Chrome profile imports caused by zeroed negative timeDeltas [[#305](https://github.com/jlfwong/speedscope/pull/305)] (by [@taneliang](https://github.com/taneliang))
|
||||
|
||||
## [1.8.0] - 2020-07-19
|
||||
|
||||
### Added
|
||||
|
||||
- Added search highlighting in time order & left heavy views [[#297](https://github.com/jlfwong/speedscope/pull/297)]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix performance issues for the caller/callee flamegraphs in the sandwich view [[#296](https://github.com/jlfwong/speedscope/pull/296)]
|
||||
|
||||
## [1.7.0] - 2020-07-13
|
||||
|
||||
### Added
|
||||
|
||||
- Introduced filtering via Ctrl+F/Cmd+F into the sandwich view [[#293](https://github.com/jlfwong/speedscope/pull/293)]
|
||||
|
||||
## [1.6.0] - 2020-05-30
|
||||
|
||||
### Added
|
||||
|
||||
- Improved profile/thread selection UI [[#282](https://github.com/jlfwong/speedscope/pull/282)]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Crash instead of incorrectly interpreting profiles with incorrectly ordered events [[#273](https://github.com/jlfwong/speedscope/pull/273)]
|
||||
- A large refactor to upgrade to Preact X was performed [[#267](https://github.com/jlfwong/speedscope/pull/267)]
|
||||
|
||||
## [1.5.3] - 2020-01-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bump dependency versions to unbreak build [[#253](https://github.com/jlfwong/speedscope/pull/253)] (by [@jlfwong](https://github.com/jlfwong), with changes from [@Archerlly](https://github.com/Archerlly)'s [#215](https://github.com/jlfwong/speedscope/pull/215))
|
||||
- Trace event: Prevent event re-ordering from generating incorrect flamegraphs ([#252](https://github.com/jlfwong/speedscope/pull/252), with changes from [@hwajaywang](https://github.com/hwajaywang)'s [#249](https://github.com/jlfwong/speedscope/pull/249))
|
||||
- Make tooltip width wider [[#239](https://github.com/jlfwong/speedscope/pull/239)] (by [@miso11](https://github.com/miso11))
|
||||
|
||||
## [1.5.2] - 2019-10-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix emscripten remapping when symbols are hex-escaped, like `a\20b` [[#233](https://github.com/jlfwong/speedscope/pull/233)] (by [@jyc](https://github.com/jyc))
|
||||
|
||||
## [1.5.1] - 2019-06-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed import of trace event files which contain unmatched "E" events ([#222](https://github.com/jlfwong/speedscope/pull/222)) (by [@jlfwong](https://github.com/jlfwong))
|
||||
|
||||
## [1.5.0] - 2019-02-17
|
||||
|
||||
### Added
|
||||
|
||||
- Support importing unterminated JSON in simple cases ([#208](https://github.com/jlfwong/speedscope/pull/208)) (by [@jlfwong](https://github.com/jlfwong))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix crash when importing from stackprof without raw_timestamp_deltas ([#207](https://github.com/jlfwong/speedscope/pull/207)) (by [@jlfwong](https://github.com/jlfwong))
|
||||
- Alert instead of crash when importing a file containing no profiles ([#205](https://github.com/jlfwong/speedscope/pull/205)) (by [@jlfwong](https://github.com/jlfwong))
|
||||
- Fixed import of multithreaded profiles from Chrome 66 ([#206](https://github.com/jlfwong/speedscope/pull/206)) (by [@jlfwong](https://github.com/jlfwong))
|
||||
- Fixed import of instruments trace files with missing run number ([#203](https://github.com/jlfwong/speedscope/pull/203)) (by [@Archerlly](https://github.com/Archerlly))
|
||||
|
||||
## [1.4.1] - 2019-01-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix importing of Trace Event Format files with no ts field on M events [[#198](https://github.com/jlfwong/speedscope/pull/198)] (by [@jlfwong](https://github.com/jlfwong))
|
||||
|
||||
## [1.4.0] - 2019-01-22
|
||||
|
||||
### Added
|
||||
|
||||
- Import v8 cpu profile (old format) [[#177](https://github.com/jlfwong/speedscope/pull/177)] (by [@vmarchaud](https://github.com/vmarchaud))
|
||||
- Import basic "Trace Event Format" profiles [[#197](https://github.com/jlfwong/speedscope/pull/197)] (by [@jlfwong](https://github.com/jlfwong))
|
||||
|
||||
## [1.3.2] - 2018-12-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed import of multithreaded Chrome profiles [[#19](https://github.com/jlfwong/speedscope/pull/19)] (by [@jlfwong](https://github.com/jlfwong))
|
||||
|
||||
## [1.3.1] - 2018-11-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a file import performance regression by using TextDecoder [[#188](https://github.com/jlfwong/speedscope/pull/188)] (by [@jlfwong](https://github.com/jlfwong))
|
||||
|
||||
## [1.3.0] - 2018-10-29
|
||||
|
||||
### Added
|
||||
|
||||
- Support import from Haskell GHC JSON format support [[#183](https://github.com/jlfwong/speedscope/pull/183)] (by [@trishume](https://github.com/trishume))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Make the wasd keymappings work on azerty keyboards [[#184](https://github.com/jlfwong/speedscope/pull/184)] (by [@vrischmann](https://github.com/vrischmann))
|
||||
- Fix import of binary formats via profileURL [[#179](https://github.com/jlfwong/speedscope/pull/179)] (by [@f](https://github.com/f)-hj)
|
||||
|
||||
## [1.2.0] - 2018-10-08
|
||||
|
||||
### Added
|
||||
|
||||
- Add import of v8 heap allocation profile [[#170](https://github.com/jlfwong/speedscope/pull/170)] (by [@vmarchaud](https://github.com/vmarchaud))
|
||||
|
||||
## [1.1.0] - 2018-09-26
|
||||
|
||||
### Added
|
||||
|
||||
- Add go tool pprof import support [[#165](https://github.com/jlfwong/speedscope/pull/165)]
|
||||
|
||||
## [1.0.4] - 2018-09-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix import from Chrome < 69 when there are multiple profiles [[#161](https://github.com/jlfwong/speedscope/pull/161)]
|
||||
|
||||
## [1.0.3] - 2018-09-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix import for Chrome 69, support leading idle time before first call [[#160](https://github.com/jlfwong/speedscope/pull/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]
|
||||
- Allow optional CR before LF when probing collapsed stacks files [[#154](https://github.com/jlfwong/speedscope/pull/154)]
|
||||
- Fix import for Firefox 63 [[#156](https://github.com/jlfwong/speedscope/pull/156)]
|
||||
- Change time formatting for minutes from 1.50min to 1:30 [[#153](https://github.com/jlfwong/speedscope/pull/153)] (by [@Alex](https://github.com/Alex)-Diez)
|
||||
|
||||
## [1.0.1] - 2018-08-23
|
||||
|
||||
* Fixed an issue where flamegraph bounds were not always being cleared correctly, leading to visual artifacts [#150]
|
||||
- Fixed an issue where flamegraph bounds were not always being cleared correctly, leading to visual artifacts [[#150](https://github.com/jlfwong/speedscope/pull/150)]
|
||||
|
||||
## [1.0.0] - 2018-08-23
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed rendering issues when switching between screens w/ different `devicePixelRatios` [#147]
|
||||
- Fixed rendering issues when switching between screens w/ different `devicePixelRatios` [[#147](https://github.com/jlfwong/speedscope/pull/147)]
|
||||
|
||||
## [0.7.1] - 2018-08-20
|
||||
|
||||
### Fixed
|
||||
|
||||
* Removed dependency on regl in order to allow speedscope to run in strict content-security-policy environments [#140]
|
||||
* Fixed text culling bug [#143]
|
||||
- Removed dependency on regl in order to allow speedscope to run in strict content-security-policy environments [[#140](https://github.com/jlfwong/speedscope/pull/140)]
|
||||
- Fixed text culling bug [[#143](https://github.com/jlfwong/speedscope/pull/143)]
|
||||
|
||||
## [0.7.0] - 2018-08-16
|
||||
|
||||
### Added
|
||||
|
||||
* Added support to import from linux `perf script` [#135]
|
||||
- Added support to import from linux `perf script` [[#135](https://github.com/jlfwong/speedscope/pull/135)]
|
||||
|
||||
## [0.6.0] - 2018-08-14
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for multiple threads/processes [#130]
|
||||
* Import all runs & threads from Instruments .trace files instead of just main thread from selected run [#130]
|
||||
- Added support for multiple threads/processes [[#130](https://github.com/jlfwong/speedscope/pull/130)]
|
||||
- Import all runs & threads from Instruments .trace files instead of just main thread from selected run [[#130](https://github.com/jlfwong/speedscope/pull/130)]
|
||||
|
||||
### Fixed
|
||||
|
||||
* Ensure the JSON schema has actual contents [#133]
|
||||
- Ensure the JSON schema has actual contents [[#133](https://github.com/jlfwong/speedscope/pull/133)]
|
||||
|
||||
## [0.5.1] - 2018-08-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed broken CLI
|
||||
- Fixed broken CLI
|
||||
|
||||
## [0.5.0] - 2018-08-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix emscripten remapping when symbols contain dashes, like `527:i32s-div` [#129]
|
||||
* Improved firefox import speed and fixed bugs in it [#128]
|
||||
* Prevent non-contiguous blocks in the time ordered flamechart from appearing as a single node for selection [#123]
|
||||
* Prevent dragging from changing selection [#122]
|
||||
* Clamp zoom to prevent floating point issues [#121]
|
||||
* Preserve view state when switching tabs [#100]
|
||||
- Fix emscripten remapping when symbols contain dashes, like `527:i32s-div` [[#129](https://github.com/jlfwong/speedscope/pull/129)]
|
||||
- Improved firefox import speed and fixed bugs in it [[#128](https://github.com/jlfwong/speedscope/pull/128)]
|
||||
- Prevent non-contiguous blocks in the time ordered flamechart from appearing as a single node for selection [[#123](https://github.com/jlfwong/speedscope/pull/123)]
|
||||
- Prevent dragging from changing selection [[#122](https://github.com/jlfwong/speedscope/pull/122)]
|
||||
- Clamp zoom to prevent floating point issues [[#121](https://github.com/jlfwong/speedscope/pull/121)]
|
||||
- Preserve view state when switching tabs [[#100](https://github.com/jlfwong/speedscope/pull/100)]
|
||||
|
||||
## [0.4.0] - 2018-07-21
|
||||
|
||||
### Added
|
||||
|
||||
* Support for importing v8 logs from node [#98]
|
||||
* Optionally read from stdin via cli [#99]
|
||||
- Support for importing v8 logs from node [[#98](https://github.com/jlfwong/speedscope/pull/98)]
|
||||
- Optionally read from stdin via cli [[#99](https://github.com/jlfwong/speedscope/pull/99)]
|
||||
|
||||
## [0.3.0] - 2018-07-18
|
||||
|
||||
### Added
|
||||
|
||||
* Support for remapping profiles using a wasm symbol file [#93]
|
||||
- Support for remapping profiles using a wasm symbol file [[#93](https://github.com/jlfwong/speedscope/pull/93)]
|
||||
|
||||
@@ -36,13 +36,25 @@ speedscope is designed to ingest profiles from a variety of different profilers
|
||||
- JavaScript
|
||||
- [Importing from Chrome](https://github.com/jlfwong/speedscope/wiki/Importing-from-Chrome)
|
||||
- [Importing from Firefox](https://github.com/jlfwong/speedscope/wiki/Importing-from-Firefox)
|
||||
- [Importing from Safari](https://github.com/jlfwong/speedscope/wiki/Importing-from-Safari)
|
||||
- [Importing from Node.js](https://github.com/jlfwong/speedscope/wiki/Importing-from-Node.js)
|
||||
- Ruby
|
||||
- [Importing from stackprof](https://github.com/jlfwong/speedscope/wiki/Importing-from-stackprof-(ruby))
|
||||
- [Importing from rbspy](https://github.com/jlfwong/speedscope/wiki/Importing-from-rbspy-(ruby))
|
||||
- [Importing from ruby-prof](https://github.com/jlfwong/speedscope/wiki/Importing-from-ruby-prof)
|
||||
- Python
|
||||
- [Importing from py-spy](https://github.com/jlfwong/speedscope/wiki/Importing-from-py-spy-(python))
|
||||
- [pyspeedscope](https://github.com/windelbouwman/pyspeedscope)
|
||||
- [Importing from Austin](https://github.com/P403n1x87/austin-python#format-conversion)
|
||||
- Go
|
||||
- [Importing from pprof](https://github.com/jlfwong/speedscope/wiki/Importing-from-pprof-(go))
|
||||
- Rust
|
||||
- [flamescope](https://github.com/coolreader18/flamescope)
|
||||
- Native code
|
||||
- [Importing from Instruments.app](https://github.com/jlfwong/speedscope/wiki/Importing-from-Instruments.app) (macOS)
|
||||
- [Importing from `perf`](https://github.com/jlfwong/speedscope/wiki/Importing-from-perf-(linux)) (linux)
|
||||
- [Importing from .NET Core](https://github.com/jlfwong/speedscope/wiki/Importing-from-.NET-Core)
|
||||
- [Importing from GHC (Haskell)](https://github.com/jlfwong/speedscope/wiki/Importing-from-Haskell)
|
||||
- [Importing from custom sources](https://github.com/jlfwong/speedscope/wiki/Importing-from-custom-sources)
|
||||
|
||||
Contributions to add support for additional formats are welcome! See issues with the ["import source" tag](https://github.com/jlfwong/speedscope/issues?q=is%3Aissue+is%3Aopen+label%3A%22import+source%22).
|
||||
@@ -105,6 +117,8 @@ Once a profile has loaded, the main view is split into two: the top area is the
|
||||
* `Cmd+O`/`Ctrl+O` to open a new profile
|
||||
* `n`: Go to next profile/thread if one is available
|
||||
* `p`: Go to previous profile/thread if one is available
|
||||
* `t`: Open the profile/thread selector if available
|
||||
* `Cmd+F`/`Ctrl+F`: to open search. While open, `Enter` and `Shift+Enter` cycle through results
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
+6
-2
@@ -4,7 +4,7 @@ const fs = require('fs')
|
||||
const os = require('os')
|
||||
const stream = require('stream')
|
||||
|
||||
const opn = require('opn')
|
||||
const open = require('open')
|
||||
|
||||
const helpString = `Usage: speedscope [filepath]
|
||||
|
||||
@@ -89,7 +89,11 @@ async function main() {
|
||||
|
||||
console.log('Opening', urlToOpen, 'in your default browser')
|
||||
|
||||
await opn(urlToOpen, {wait: false})
|
||||
// We'd like to avoid blocking the terminal on the browsing closing,
|
||||
// but for some reason this doesn't work at all on Windows if we
|
||||
// don't use wait: true.
|
||||
const wait = process.platform === "win32";
|
||||
await open(urlToOpen, {wait})
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Generated
+6430
-8036
File diff suppressed because it is too large
Load Diff
+44
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "speedscope",
|
||||
"version": "1.0.3",
|
||||
"version": "1.11.0",
|
||||
"description": "",
|
||||
"repository": "jlfwong/speedscope",
|
||||
"main": "index.js",
|
||||
@@ -13,47 +13,71 @@
|
||||
"prettier": "prettier --write 'src/**/*.ts' 'src/**/*.tsx'",
|
||||
"lint": "eslint 'src/**/*.ts' 'src/**/*.tsx'",
|
||||
"jest": "./scripts/test-setup.sh && jest --runInBand",
|
||||
"coverage": "npm run jest -- --coverage && coveralls < coverage/lcov.info",
|
||||
"test": "tsc --noEmit && npm run lint && npm run coverage",
|
||||
"coverage": "npm run jest -- --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "./scripts/ci.sh",
|
||||
"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": {
|
||||
"@types/jest": "22.2.3",
|
||||
"@types/jszip": "3.1.4",
|
||||
"@types/node": "10.1.4",
|
||||
"@types/node": "14.0.1",
|
||||
"@types/pako": "1.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "2.33.0",
|
||||
"@typescript-eslint/parser": "2.33.0",
|
||||
"acorn": "7.2.0",
|
||||
"aphrodite": "2.1.0",
|
||||
"coveralls": "3.0.1",
|
||||
"eslint": "4.19.1",
|
||||
"eslint": "6.0.0",
|
||||
"eslint-plugin-prettier": "2.6.0",
|
||||
"jest": "23.0.1",
|
||||
"eslint-plugin-react-hooks": "4.0.2",
|
||||
"jest": "24.3.0",
|
||||
"jsverify": "0.8.3",
|
||||
"jszip": "3.1.5",
|
||||
"pako": "1.0.6",
|
||||
"parcel-bundler": "1.9.2",
|
||||
"preact": "8.2.7",
|
||||
"preact-redux": "jlfwong/preact-redux#a56dcc4",
|
||||
"prettier": "1.12.0",
|
||||
"quicktype": "15.0.45",
|
||||
"redux": "^4.0.0",
|
||||
"ts-jest": "22.4.6",
|
||||
"typescript": "2.8.1",
|
||||
"parcel-bundler": "1.12.4",
|
||||
"preact": "10.4.1",
|
||||
"prettier": "2.0.4",
|
||||
"protobufjs": "6.8.8",
|
||||
"redux": "^4.0.5",
|
||||
"source-map": "0.6.1",
|
||||
"ts-jest": "24.3.0",
|
||||
"typescript": "3.9.2",
|
||||
"typescript-eslint-parser": "17.0.1",
|
||||
"typescript-json-schema": "0.42.0",
|
||||
"uglify-es": "3.2.2"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
"./src/jest-setup.js"
|
||||
],
|
||||
"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"
|
||||
"open": "7.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,4 +4,5 @@ module.exports = {
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
arrowParens: 'avoid'
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"head":{"functionName":"(root)","url":"","lineNumber":-1,"bailoutReason":"","id":1,"scriptId":0,"hitCount":0,"children":[{"functionName":"","url":"","lineNumber":0,"callUID":1,"bailoutReason":"","id":2,"scriptId":0,"hitCount":0,"children":[{"functionName":"a","url":"","lineNumber":0,"callUID":2,"bailoutReason":"","id":3,"scriptId":0,"hitCount":0,"children":[{"functionName":"b","url":"","lineNumber":5,"callUID":3,"bailoutReason":"","id":4,"scriptId":0,"hitCount":0,"children":[{"functionName":"d","url":"","lineNumber":13,"callUID":4,"bailoutReason":"","id":5,"scriptId":0,"hitCount":14,"children":[]}]},{"functionName":"c","url":"","lineNumber":9,"callUID":3,"bailoutReason":"","id":6,"scriptId":0,"hitCount":0,"children":[{"functionName":"d","url":"","lineNumber":13,"callUID":6,"bailoutReason":"","id":7,"scriptId":0,"hitCount":14,"children":[]}]}]}]}]},"startTime":163140,"endTime":163140,"samples":[2,5,5,5,5,5,5,5,5,5,5,5,5,5,5,7,7,7,7,7,7,7,7,7,7,7,7,7,7],"timestamps":[163140599286,163140610861,163140611921,163140612966,163140614236,163140615507,163140616694,163140617968,163140619238,163140620258,163140621507,163140622765,163140624037,163140625303,163140626378,163140627649,163140628923,163140630191,163140631457,163140632746,163140634032,163140635304,163140636440,163140637716,163140638990,163140640255,163140641520,163140642791,163140644063,163140645206]}
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
This directory contains profiles & source-maps to test if source-map
|
||||
remapping of profiles is working correctly. See the corresponding
|
||||
"sourcemaps" directory in programs/javascript to see how these were
|
||||
generated.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["gamma.ts", "beta.ts", "delta.ts", "alpha.ts", "kludge.ts", "typescript-source-map-test.ts"],
|
||||
"sourcesContent": ["export const gamma = () => {\n let prod = 1\n for (let i = 1; i < 1000; i++) {\n prod *= i\n }\n return prod\n}\n", "import {gamma} from './gamma'\n\nexport function beta() {\n for (let i = 0; i < 10; i++) {\n gamma()\n }\n}\n", "import {gamma} from './gamma'\n\nexport const delta = function () {\n for (let i = 0; i < 10; i++) {\n gamma()\n }\n}\n", "import {beta} from './beta'\nimport {delta} from './delta'\n\nexport function alpha() {\n ;(function () {\n for (let i = 0; i < 1000; i++) {\n beta()\n delta()\n }\n })()\n}\n", "import {alpha} from './alpha'\n\nexport class Kludge {\n constructor() {\n alpha()\n console.log(this.floop)\n }\n\n zap() {\n alpha()\n }\n\n get floop(): number {\n alpha()\n return 1\n }\n}\n", "import {Kludge} from './kludge'\n\nconst k = new Kludge()\nk.zap()\n"],
|
||||
"mappings": "MAAO,KAAM,GAAQ,KACnB,GAAI,GAAO,EACX,OAAS,GAAI,EAAG,EAAI,IAAM,IACxB,GAAQ,EAEV,MAAO,ICHF,aACL,OAAS,GAAI,EAAG,EAAI,GAAI,IACtB,ICFG,KAAM,GAAQ,WACnB,OAAS,GAAI,EAAG,EAAI,GAAI,IACtB,KCDG,aACJ,AAAC,YACA,OAAS,GAAI,EAAG,EAAI,IAAM,IACxB,IACA,QCPN,QAGE,cACE,IACA,QAAQ,IAAI,KAAK,OAGnB,MACE,OAGE,SACF,WACO,GCZX,KAAM,GAAI,GAAI,GACd,EAAE",
|
||||
"names": []
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["gamma.ts","beta.ts","delta.ts","alpha.ts","kludge.ts","typescript-source-map-test.ts"],"names":[],"mappings":";AAAO,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,WAAA,EAAA,IAAM,EAAQ,WAEd,IADD,IAAA,EAAO,EACF,EAAI,EAAG,EAAI,IAAM,IACxB,GAAQ,EAEH,OAAA,GALF,QAAA,MAAA;;ACMN,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,KAAA,EAND,IAAA,EAAA,QAAA,WAEM,SAAU,IACT,IAAA,IAAI,EAAI,EAAG,EAAI,GAAI,KACtB,EAAA,EAAA;;ACFG,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,WAAA,EAFP,IAAA,EAAA,QAAA,WAEa,EAAQ,WACd,IAAA,IAAI,EAAI,EAAG,EAAI,GAAI,KACtB,EAAA,EAAA,UAFG,QAAA,MAAA;;ACQN,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,MAAA,EAVD,IAAA,EAAA,QAAA,UACA,EAAA,QAAA,WAEM,SAAU,KACZ,WACK,IAAA,IAAI,EAAI,EAAG,EAAI,IAAM,KACxB,EAAA,EAAA,SACA,EAAA,EAAA,SAHF;;ACFJ,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,YAAA,EAFA,IAAA,EAAA,QAAA,WAEA,EAAA,WACE,SAAA,KACE,EAAA,EAAA,SACA,QAAQ,IAAI,KAAK,OAWrB,OARE,EAAA,UAAA,IAAA,YACE,EAAA,EAAA,UAGF,OAAA,eAAI,EAAA,UAAA,QAAK,CAAT,IAAA,WAES,OADP,EAAA,EAAA,SACO,GAFA,YAAA,EAVX,cAAA,IAcA,EAdA,GAAA,QAAA,OAAA;;ACCA,aAHA,IAAA,EAAA,QAAA,YAEM,EAAI,IAAI,EAAJ,OACV,EAAE","file":"typescript-source-map-test.js","sourceRoot":"..","sourcesContent":["export const gamma = () => {\n let prod = 1\n for (let i = 1; i < 1000; i++) {\n prod *= i\n }\n return prod\n}\n","import {gamma} from './gamma'\n\nexport function beta() {\n for (let i = 0; i < 10; i++) {\n gamma()\n }\n}\n","import {gamma} from './gamma'\n\nexport const delta = function () {\n for (let i = 0; i < 10; i++) {\n gamma()\n }\n}\n","import {beta} from './beta'\nimport {delta} from './delta'\n\nexport function alpha() {\n ;(function () {\n for (let i = 0; i < 1000; i++) {\n beta()\n delta()\n }\n })()\n}\n","import {alpha} from './alpha'\n\nexport class Kludge {\n constructor() {\n alpha()\n console.log(this.floop)\n }\n\n zap() {\n alpha()\n }\n\n get floop(): number {\n alpha()\n return 1\n }\n}\n","import {Kludge} from './kludge'\n\nconst k = new Kludge()\nk.zap()\n"]}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["webpack://speedscope-sourcemap-test-project/./gamma.ts","webpack://speedscope-sourcemap-test-project/./beta.ts","webpack://speedscope-sourcemap-test-project/./delta.ts","webpack://speedscope-sourcemap-test-project/./alpha.ts","webpack://speedscope-sourcemap-test-project/./typescript-source-map-test.ts","webpack://speedscope-sourcemap-test-project/./kludge.ts"],"names":["gamma","prod","i","beta","delta","alpha","console","log","this","floop","zap"],"mappings":"mBAAO,IAAMA,EAAQ,WAEnB,IADA,IAAIC,EAAO,EACFC,EAAI,EAAGA,EAAI,IAAMA,IACxBD,GAAQC,EAEV,OAAOD,GCHF,SAASE,IACd,IAAK,IAAID,EAAI,EAAGA,EAAI,GAAIA,IACtBF,ICFG,IAAMI,EAAQ,WACnB,IAAK,IAAIF,EAAI,EAAGA,EAAI,GAAIA,IACtBF,KCDG,SAASK,KACb,WACC,IAAK,IAAIH,EAAI,EAAGA,EAAI,IAAMA,IACxBC,IACAC,IAHH,ICFO,ICAV,WACE,aACEC,IACAC,QAAQC,IAAIC,KAAKC,OAWrB,OARE,YAAAC,IAAA,WACEL,KAGF,sBAAI,oBAAK,C,IAAT,WAEE,OADAA,IACO,G,gCAEX,EAdA,KDCEK,O","file":"typescript-source-map-test.js","sourcesContent":["export const gamma = () => {\n let prod = 1\n for (let i = 1; i < 1000; i++) {\n prod *= i\n }\n return prod\n}\n","import {gamma} from './gamma'\n\nexport function beta() {\n for (let i = 0; i < 10; i++) {\n gamma()\n }\n}\n","import {gamma} from './gamma'\n\nexport const delta = function () {\n for (let i = 0; i < 10; i++) {\n gamma()\n }\n}\n","import {beta} from './beta'\nimport {delta} from './delta'\n\nexport function alpha() {\n ;(function () {\n for (let i = 0; i < 1000; i++) {\n beta()\n delta()\n }\n })()\n}\n","import {Kludge} from './kludge'\n\nconst k = new Kludge()\nk.zap()\n","import {alpha} from './alpha'\n\nexport class Kludge {\n constructor() {\n alpha()\n console.log(this.floop)\n }\n\n zap() {\n alpha()\n }\n\n get floop(): number {\n alpha()\n return 1\n }\n}\n"],"sourceRoot":""}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://www.speedscope.app/file-format-schema.json",
|
||||
"shared": {
|
||||
"frames": [{"name": "A"}]
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"type": "evented",
|
||||
"name": "p1",
|
||||
"unit": "none",
|
||||
"startValue": 0,
|
||||
"endValue": 100,
|
||||
"events": [{"type": "O", "frame": 0, "at": 0}]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://www.speedscope.app/file-format-schema.json",
|
||||
"shared": {
|
||||
"frames": [{"name": "A"}, {"name": "B"}]
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"type": "evented",
|
||||
"name": "p1",
|
||||
"unit": "none",
|
||||
"startValue": 0,
|
||||
"endValue": 100,
|
||||
"events": [
|
||||
{"type": "O", "frame": 0, "at": 0},
|
||||
{"type": "C", "frame": 0, "at": 1},
|
||||
{"type": "O", "frame": 1, "at": 2},
|
||||
{"type": "O", "frame": 0, "at": 2},
|
||||
{"type": "O", "frame": 0, "at": 3},
|
||||
{"type": "C", "frame": 0, "at": 4},
|
||||
{"type": "C", "frame": 1, "at": 4},
|
||||
{"type": "C", "frame": 0, "at": 5}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{"pid": 0, "tid": 0, "ph": "M", "name": "process_name", "args": {"name": "p0"}},
|
||||
{"pid": 0, "tid": 0, "ph": "M", "name": "thread_name", "args": {"name": "p0t0"}},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "alpha", "ts": 0, "dur": 1},
|
||||
{"pid": 0, "tid": 1, "ph": "M", "name": "thread_name", "args": {"name": "p0t1"}},
|
||||
{"pid": 0, "tid": 1, "ph": "X", "name": "beta", "ts": 0, "dur": 1},
|
||||
|
||||
{"pid": 1, "tid": 0, "ph": "M", "name": "process_name", "args": {"name": "p1"}},
|
||||
{"pid": 1, "tid": 0, "ph": "M", "name": "thread_name", "args": {"name": "p1t0"}},
|
||||
{"pid": 1, "tid": 0, "ph": "X", "name": "gamma", "ts": 0, "dur": 1},
|
||||
{"pid": 1, "tid": 1, "ph": "M", "name": "thread_name", "args": {"name": "p1t1"}},
|
||||
{"pid": 1, "tid": 1, "ph": "X", "name": "delta", "ts": 0, "dur": 1},
|
||||
|
||||
{"pid": 2, "tid": 0, "ph": "M", "name": "thread_name", "args": {"name": "p2t0"}},
|
||||
{"pid": 2, "tid": 0, "ph": "X", "name": "epsilon", "ts": 0, "dur": 1},
|
||||
{"pid": 2, "tid": 1, "ph": "M", "name": "thread_name", "args": {"name": "p2t1"}},
|
||||
{"pid": 2, "tid": 1, "ph": "X", "name": "phi", "ts": 0, "dur": 1},
|
||||
|
||||
{"pid": 3, "tid": 0, "ph": "M", "name": "process_name", "args": {"name": "p3"}},
|
||||
{"pid": 3, "tid": 0, "ph": "X", "name": "zeta", "ts": 0, "dur": 1},
|
||||
{"pid": 3, "tid": 1, "ph": "X", "name": "eta", "ts": 0, "dur": 1}
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "A", "ts": 0},
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "B", "ts": 0},
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "C", "ts": 0},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "C", "ts": 1},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "B", "ts": 2},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "A", "ts": 3},
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "A", "ts": 4},
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "B", "ts": 5},
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "C", "ts": 6},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "C", "ts": 7},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "B", "ts": 7},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "A", "ts": 7},
|
||||
{"tid": 1, "ph": "B", "pid": 0, "name": "X", "ts": 7},
|
||||
{"tid": 1, "ph": "E", "pid": 0, "name": "X", "ts": 10}
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"traceEvents": [
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "ts": 7, "tdur": 4},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "gamma", "ts": 2, "dur": 5, "args": {"detail": "foobar"}},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "epsilon", "ts": 7, "tdur": 4},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14},
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
[
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "gamma", "ts": 2, "dur": 5, "args": {"detail": "foobar"}},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "epsilon", "ts": 7, "tdur": 4},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14},
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "gamma", "ts": 2, "dur": 5, "args": {"detail": "foobar"}},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "epsilon", "ts": 7, "tdur": 4},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14}
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "gamma", "ts": 2, "dur": 5, "args": {"detail": "foobar"}},
|
||||
{"pid": 0, "tid": 0, "ph": "X", "name": "epsilon", "ts": 7, "tdur": 4},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14}
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "alpha", "ts": 0},
|
||||
{"pid": 0, "tid": 0, "ph": "B", "name": "beta", "ts": 1},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "beta", "ts": 13},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "alpha", "ts": 14},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "gamma", "ts": 5},
|
||||
{"pid": 0, "tid": 0, "ph": "E", "name": "delta", "ts": 5}
|
||||
]
|
||||
@@ -0,0 +1,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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This script is used to reconstruct an cpu profile from chrome with an old format
|
||||
* still used in nodejs with v8-profiler
|
||||
*/
|
||||
const fs = require('fs')
|
||||
|
||||
const data = JSON.parse(fs.readFileSync('../../profiles/Chrome/65/simple.cpuprofile').toString())
|
||||
|
||||
const _convertTimeDeltas = (profile) => {
|
||||
if (!profile.timeDeltas) return null
|
||||
let lastTimeUsec = profile.startTime
|
||||
const timestamps = new Array(profile.timeDeltas.length + 1)
|
||||
for (let i = 0; i < profile.timeDeltas.length; ++i) {
|
||||
timestamps[i] = lastTimeUsec
|
||||
lastTimeUsec += profile.timeDeltas[i]
|
||||
}
|
||||
timestamps[profile.timeDeltas.length] = lastTimeUsec
|
||||
return timestamps
|
||||
}
|
||||
|
||||
const reformatNode = node => {
|
||||
if (!node.children) node.children = []
|
||||
|
||||
node.children = node.children.map(childID => {
|
||||
if (typeof childID !== 'number') return childID
|
||||
const childNode = data.nodes.find(node => node.id === childID)
|
||||
if (typeof childNode !== 'object') return null
|
||||
childNode.callUID = node.id
|
||||
return childNode
|
||||
})
|
||||
return {
|
||||
functionName: node.callFrame.functionName,
|
||||
url: node.callFrame.url,
|
||||
lineNumber: node.callFrame.lineNumber,
|
||||
callUID: node.callUID,
|
||||
bailoutReason: '',
|
||||
id: node.id,
|
||||
scriptId: 0,
|
||||
hitCount: node.hitCount,
|
||||
children: node.children.map(reformatNode)
|
||||
}
|
||||
}
|
||||
|
||||
// reformat then only keep the root as top level node
|
||||
const nodes = data.nodes
|
||||
.map(reformatNode)
|
||||
.filter(node => node.functionName === '(root)')[0]
|
||||
|
||||
// since it can be undefined, create an array so execution still works
|
||||
if (!data.timeDeltas) {
|
||||
data.timeDeltas = []
|
||||
}
|
||||
|
||||
fs.writeFileSync('./new.cpuprofile', JSON.stringify({
|
||||
head: nodes,
|
||||
startTime: Math.floor(data.startTime / 1000000),
|
||||
endTime: Math.floor(data.endTime / 1000000),
|
||||
samples: data.samples,
|
||||
timestamps: _convertTimeDeltas(data)
|
||||
}))
|
||||
@@ -0,0 +1,22 @@
|
||||
## Source Map Test Project
|
||||
|
||||
This directory contains test files used to test whether the remapping of
|
||||
performance profiles using sourcemaps work correctly.
|
||||
|
||||
Run `npm run build` to build the artifacts, then open the appropriate files in
|
||||
the `html` directory in whatever browser you're testing.
|
||||
|
||||
The idea is to sourcemaps generated by a variety of tools, and also to take
|
||||
profiles from a variety of browsers, and hopefully see that they all get
|
||||
remapped as expected.
|
||||
|
||||
This project is set up to go through three levels of source-map indirection,
|
||||
and also using multiple different build chains.
|
||||
|
||||
1. TypeScript -> JavaScript source generation
|
||||
2. JavaScript source -> JavaScript bundling
|
||||
3. Minification
|
||||
|
||||
Some bundlers will swap the order of steps 2 & 3, or potentially merge them,
|
||||
but it's complex yet realistic enough that this will hoepfully suss out
|
||||
problems.
|
||||
@@ -0,0 +1,11 @@
|
||||
import {beta} from './beta'
|
||||
import {delta} from './delta'
|
||||
|
||||
export function alpha() {
|
||||
;(function () {
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
beta()
|
||||
delta()
|
||||
}
|
||||
})()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import {gamma} from './gamma'
|
||||
|
||||
export function beta() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
gamma()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import {gamma} from './gamma'
|
||||
|
||||
export const delta = function () {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
gamma()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const gamma = () => {
|
||||
let prod = 1
|
||||
for (let i = 1; i < 1000; i++) {
|
||||
prod *= i
|
||||
}
|
||||
return prod
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<title>ESbuild</title>
|
||||
<script src="../dist/esbuild/typescript-source-map-test.js"></script>
|
||||
<h1>ESbuild Source Map Test</h1>
|
||||
@@ -0,0 +1,3 @@
|
||||
<title>Parcel</title>
|
||||
<script src="../dist/parcel/typescript-source-map-test.js"></script>
|
||||
<h1>Parcel Source Map Test</h1>
|
||||
@@ -0,0 +1,3 @@
|
||||
<title>Webpack</title>
|
||||
<script src="../dist/webpack/typescript-source-map-test.js"></script>
|
||||
<h1>Webpack Source Map Test</h1>
|
||||
@@ -0,0 +1,17 @@
|
||||
import {alpha} from './alpha'
|
||||
|
||||
export class Kludge {
|
||||
constructor() {
|
||||
alpha()
|
||||
console.log(this.floop)
|
||||
}
|
||||
|
||||
zap() {
|
||||
alpha()
|
||||
}
|
||||
|
||||
get floop(): number {
|
||||
alpha()
|
||||
return 1
|
||||
}
|
||||
}
|
||||
+10497
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "speedscope-sourcemap-test-project",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": "true",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "npm run parcel && npm run webpack && npm run esbuild",
|
||||
"parcel": "parcel build -o parcel/typescript-source-map-test typescript-source-map-test.ts",
|
||||
"webpack": "webpack",
|
||||
"esbuild": "esbuild --sourcemap --minify --bundle --outdir=dist/esbuild typescript-source-map-test.ts"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.7.14",
|
||||
"parcel": "^1.12.4",
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^5.0.0",
|
||||
"webpack-cli": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true,
|
||||
"module": "es6",
|
||||
"target": "es5",
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import {Kludge} from './kludge'
|
||||
|
||||
const k = new Kludge()
|
||||
k.zap()
|
||||
@@ -0,0 +1,22 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: './typescript-source-map-test.ts',
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
output: {
|
||||
filename: 'typescript-source-map-test.js',
|
||||
path: path.resolve(__dirname, 'dist', 'webpack'),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<script>
|
||||
function banana() {
|
||||
let prod = 1
|
||||
for (let i = 1; i < 1000; i++) {
|
||||
prod *= i
|
||||
}
|
||||
return prod
|
||||
}
|
||||
|
||||
function apple() {
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
banana()
|
||||
}
|
||||
}
|
||||
|
||||
let bounces = 0
|
||||
|
||||
const worker = new Worker('./worker.js')
|
||||
|
||||
worker.onmessage = function() {
|
||||
apple()
|
||||
if (bounces++ < 10) {
|
||||
worker.postMessage('ping')
|
||||
}
|
||||
}
|
||||
|
||||
worker.postMessage('ping')
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
function gamma() {
|
||||
let prod = 1
|
||||
for (let i = 1; i < 1000; i++) {
|
||||
prod *= i
|
||||
}
|
||||
return prod
|
||||
}
|
||||
|
||||
function alpha() {
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
gamma()
|
||||
}
|
||||
}
|
||||
|
||||
onmessage = function(e) {
|
||||
alpha()
|
||||
postMessage('pong')
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
npm run coverage
|
||||
node scripts/generate-file-format-schema-json.js > /dev/null
|
||||
@@ -3,7 +3,7 @@ const child_process = require('child_process')
|
||||
|
||||
// Convert the file-format-spec.ts file into a json schema file
|
||||
let jsonSchema = child_process.execSync(
|
||||
'node_modules/.bin/quicktype --lang schema ./src/lib/file-format-spec.ts',
|
||||
'node_modules/.bin/typescript-json-schema ./src/lib/file-format-spec.ts --titles --required --topRef "*"',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
},
|
||||
|
||||
@@ -24,9 +24,7 @@ export class CanvasContext {
|
||||
const webGLInfo = this.gl.getWebGLInfo()
|
||||
if (webGLInfo) {
|
||||
console.log(
|
||||
`WebGL initialized. renderer: ${webGLInfo.renderer}, vendor: ${
|
||||
webGLInfo.vendor
|
||||
}, version: ${webGLInfo.version}`,
|
||||
`WebGL initialized. renderer: ${webGLInfo.renderer}, vendor: ${webGLInfo.vendor}, version: ${webGLInfo.version}`,
|
||||
)
|
||||
}
|
||||
;(window as any)['testContextLoss'] = () => {
|
||||
|
||||
@@ -175,8 +175,8 @@ export class FlamechartRenderer {
|
||||
// and the blue channel to indicate the color bucket to render.
|
||||
// We add one to each so we have zero reserved for the background color.
|
||||
const color = new Color(
|
||||
(1 + i % 255) / 256,
|
||||
(1 + stackDepth % 255) / 256,
|
||||
(1 + (i % 255)) / 256,
|
||||
(1 + (stackDepth % 255)) / 256,
|
||||
(1 + this.flamechart.getColorBucketForFrame(frame.node.frame)) / 256,
|
||||
)
|
||||
batch.addRect(configSpaceBounds, color)
|
||||
@@ -288,10 +288,10 @@ export class FlamechartRenderer {
|
||||
const configSpaceContentWidth = this.flamechart.getTotalWeight()
|
||||
const numAtlasEntriesPerLayer = Math.pow(2, zoomLevel)
|
||||
const left = Math.floor(
|
||||
numAtlasEntriesPerLayer * configSpaceSrcRect.left() / configSpaceContentWidth,
|
||||
(numAtlasEntriesPerLayer * configSpaceSrcRect.left()) / configSpaceContentWidth,
|
||||
)
|
||||
const right = Math.ceil(
|
||||
numAtlasEntriesPerLayer * configSpaceSrcRect.right() / configSpaceContentWidth,
|
||||
(numAtlasEntriesPerLayer * configSpaceSrcRect.right()) / configSpaceContentWidth,
|
||||
)
|
||||
|
||||
const nLayers = this.flamechart.getLayers().length
|
||||
|
||||
+8
-2
@@ -455,7 +455,8 @@ export namespace WebGL {
|
||||
widthInAppUnits: number,
|
||||
heightInAppUnits: number,
|
||||
) {
|
||||
const bounds = this._gl.canvas.getBoundingClientRect()
|
||||
let canvas = this._gl.canvas as HTMLCanvasElement
|
||||
const bounds = canvas.getBoundingClientRect()
|
||||
|
||||
if (
|
||||
this._width === widthInAppUnits &&
|
||||
@@ -467,7 +468,6 @@ export namespace WebGL {
|
||||
return
|
||||
}
|
||||
|
||||
let canvas = this._gl.canvas
|
||||
let style = canvas.style
|
||||
canvas.width = widthInPixels
|
||||
canvas.height = heightInPixels
|
||||
@@ -1086,11 +1086,17 @@ export namespace WebGL {
|
||||
|
||||
_compileShader(gl: WebGLRenderingContext, type: GLenum, source: string) {
|
||||
let shader = gl.createShader(type)
|
||||
if (!shader) {
|
||||
throw new Error('Failed to create shader')
|
||||
}
|
||||
gl.shaderSource(shader, source)
|
||||
gl.compileShader(shader)
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
throw new Error(`${gl.getShaderInfoLog(shader)}`)
|
||||
}
|
||||
if (!this._program) {
|
||||
throw new Error('Tried to attach shader before program was created')
|
||||
}
|
||||
gl.attachShader(this._program, shader)
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,12 @@ export class ViewportRectangleRenderer {
|
||||
private buffer: Graphics.VertexBuffer
|
||||
|
||||
constructor(private gl: Graphics.Context) {
|
||||
const vertices = [[-1, 1], [1, 1], [-1, -1], [1, -1]]
|
||||
const vertices = [
|
||||
[-1, 1],
|
||||
[1, 1],
|
||||
[-1, -1],
|
||||
[1, -1],
|
||||
]
|
||||
const floats: number[] = []
|
||||
for (let v of vertices) {
|
||||
floats.push(v[0])
|
||||
|
||||
@@ -45,7 +45,14 @@ export class RectangleBatch {
|
||||
return this.buffer
|
||||
}
|
||||
|
||||
const corners = [[0, 0], [1, 0], [0, 1], [1, 0], [0, 1], [1, 1]]
|
||||
const corners = [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
]
|
||||
|
||||
const bytes = new Uint8Array(vertexFormat.stride * corners.length * this.rects.length)
|
||||
const floats = new Float32Array(bytes.buffer)
|
||||
|
||||
@@ -104,6 +104,114 @@ exports[`importFromBGFlameGraph with CRLF: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromBGFlameGraph with CRLF: profileGroup.name 1`] = `"simple-crlf.txt"`;
|
||||
|
||||
exports[`importFromBGFlameGraph with UTF-16, Big Endian 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-utf16-be.txt",
|
||||
"stacks": Array [
|
||||
"a;b;c 2",
|
||||
"a;b;d 4",
|
||||
"a;b;c 3",
|
||||
"a;b 5",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromBGFlameGraph with UTF-16, Big Endian: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromBGFlameGraph with UTF-16, Big Endian: profileGroup.name 1`] = `"simple-utf16-be.txt"`;
|
||||
|
||||
exports[`importFromBGFlameGraph with UTF-16, Little Endian 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-utf16-le.txt",
|
||||
"stacks": Array [
|
||||
"a;b;c 2",
|
||||
"a;b;d 4",
|
||||
"a;b;c 3",
|
||||
"a;b 5",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromBGFlameGraph with UTF-16, Little Endian: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromBGFlameGraph with UTF-16, Little Endian: profileGroup.name 1`] = `"simple-utf16-le.txt"`;
|
||||
|
||||
exports[`importFromBGFlameGraph: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromBGFlameGraph: profileGroup.name 1`] = `"simple.txt"`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,17 +64,17 @@ exports[`importFromFirefox ignore self-hosted 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:1",
|
||||
"col": 15,
|
||||
"file": "http://localhost:8000/simple.js",
|
||||
"key": "alpha (http://localhost:8000/simple.js:1:14)",
|
||||
"line": 14,
|
||||
"line": 1,
|
||||
"name": "alpha",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 26.983816999942064,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:14",
|
||||
"col": 15,
|
||||
"file": "http://localhost:8000/simple.js",
|
||||
"key": "delta (http://localhost:8000/simple.js:14:14)",
|
||||
"line": 14,
|
||||
"name": "delta",
|
||||
@@ -82,19 +82,19 @@ Object {
|
||||
"totalWeight": 11.946324001066387,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:20",
|
||||
"col": 15,
|
||||
"file": "http://localhost:8000/simple.js",
|
||||
"key": "gamma (http://localhost:8000/simple.js:20:14)",
|
||||
"line": 14,
|
||||
"line": 20,
|
||||
"name": "gamma",
|
||||
"selfWeight": 26.983816999942064,
|
||||
"totalWeight": 26.983816999942064,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "http://localhost:8000/simple.js:8",
|
||||
"col": 14,
|
||||
"file": "http://localhost:8000/simple.js",
|
||||
"key": "beta (http://localhost:8000/simple.js:8:13)",
|
||||
"line": 13,
|
||||
"line": 8,
|
||||
"name": "beta",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 15.037492998875678,
|
||||
|
||||
@@ -0,0 +1,864 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`importFromHaskell 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 144,
|
||||
"line": undefined,
|
||||
"name": "MAIN.MAIN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 798,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 56,
|
||||
"line": undefined,
|
||||
"name": "GHC.Conc.Signal.CAF",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 73,
|
||||
"line": undefined,
|
||||
"name": "GHC.IO.Encoding.CAF",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 75,
|
||||
"line": undefined,
|
||||
"name": "GHC.IO.Encoding.Iconv.CAF",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 84,
|
||||
"line": undefined,
|
||||
"name": "GHC.IO.Handle.FD.CAF",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 119,
|
||||
"line": undefined,
|
||||
"name": "Text.Printf.CAF",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 26,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:eta1_r6nI",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(21,1)-(38,42)",
|
||||
"key": 19,
|
||||
"line": undefined,
|
||||
"name": "Main.main",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 733,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(56,1)-(57,42)",
|
||||
"key": 4,
|
||||
"line": undefined,
|
||||
"name": "Main.check",
|
||||
"selfWeight": 320,
|
||||
"totalWeight": 320,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:31:9-29",
|
||||
"key": 16,
|
||||
"line": undefined,
|
||||
"name": "Main.main.long",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 3,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 28,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:eta_r6nG",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 35,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:io1",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 27,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:lvl2_r6nH",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 3,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(61,1)-(63,26)",
|
||||
"key": 3,
|
||||
"line": undefined,
|
||||
"name": "Main.make",
|
||||
"selfWeight": 406,
|
||||
"totalWeight": 409,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:63:19-26",
|
||||
"key": 1,
|
||||
"line": undefined,
|
||||
"name": "Main.make.d2",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 2,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:63:9-16",
|
||||
"key": 2,
|
||||
"line": undefined,
|
||||
"name": "Main.make.i2",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 24,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:lvl4_r6nL",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 22,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:lvl8_r6nP",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 21,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main1",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 29,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main11",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:27:9-35",
|
||||
"key": 15,
|
||||
"line": undefined,
|
||||
"name": "Main.main.c",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 8,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 30,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main12",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 23,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main5",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 31,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main7",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 25,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main9",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:23:9-12",
|
||||
"key": 33,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main_maxN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:23:9-35",
|
||||
"key": 13,
|
||||
"line": undefined,
|
||||
"name": "Main.main.maxN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:22:9",
|
||||
"key": 34,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main_n",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:22:9-14",
|
||||
"key": 12,
|
||||
"line": undefined,
|
||||
"name": "Main.main.n",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:24:9-16",
|
||||
"key": 32,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main_stretchN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:24:9-27",
|
||||
"key": 14,
|
||||
"line": undefined,
|
||||
"name": "Main.main.stretchN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:17:1-4",
|
||||
"key": 36,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:minN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:17:1-8",
|
||||
"key": 9,
|
||||
"line": undefined,
|
||||
"name": "Main.minN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 146,
|
||||
"line": undefined,
|
||||
"name": "GC.GC",
|
||||
"selfWeight": 46,
|
||||
"totalWeight": 46,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 147,
|
||||
"line": undefined,
|
||||
"name": "PROFILING.OVERHEAD_of",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 145,
|
||||
"line": undefined,
|
||||
"name": "SYSTEM.SYSTEM",
|
||||
"selfWeight": 19,
|
||||
"totalWeight": 19,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:35:26-54",
|
||||
"key": 18,
|
||||
"line": undefined,
|
||||
"name": "Main.main.\\\\",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:19:1-56",
|
||||
"key": 8,
|
||||
"line": undefined,
|
||||
"name": "Main.io",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:34:9-28",
|
||||
"key": 17,
|
||||
"line": undefined,
|
||||
"name": "Main.main.vs",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 721,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(42,1)-(45,38)",
|
||||
"key": 11,
|
||||
"line": undefined,
|
||||
"name": "Main.depth",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 721,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(49,1)-(52,31)",
|
||||
"key": 7,
|
||||
"line": undefined,
|
||||
"name": "Main.sumT",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 721,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:51:9-31",
|
||||
"key": 6,
|
||||
"line": undefined,
|
||||
"name": "Main.sumT.a",
|
||||
"selfWeight": 3,
|
||||
"totalWeight": 369,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:52:9-31",
|
||||
"key": 5,
|
||||
"line": undefined,
|
||||
"name": "Main.sumT.b",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 352,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:45:9-38",
|
||||
"key": 10,
|
||||
"line": undefined,
|
||||
"name": "Main.depth.n",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
],
|
||||
"name": "binary-trees time",
|
||||
"stacks": Array [
|
||||
"MAIN.MAIN;Main.CAF:eta1_r6nI;Main.main;Main.check 1.00ms",
|
||||
"MAIN.MAIN;Main.CAF:lvl2_r6nH;Main.main;Main.main.long;Main.make 3.00ms",
|
||||
"MAIN.MAIN;Main.CAF:main11;Main.main;Main.main.c;Main.check 4.00ms",
|
||||
"MAIN.MAIN;Main.CAF:main12;Main.main;Main.main.c;Main.make 4.00ms",
|
||||
"MAIN.MAIN;GC.GC 46.00ms",
|
||||
"MAIN.MAIN;SYSTEM.SYSTEM 19.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.check 153.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make;Main.make.d2 2.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make;Main.make.i2 1.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make 210.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a 3.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.check 162.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.make 189.00ms",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b 1.00ms",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromHaskell 2`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 144,
|
||||
"line": undefined,
|
||||
"name": "MAIN.MAIN",
|
||||
"selfWeight": 648,
|
||||
"totalWeight": 1921672664,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 56,
|
||||
"line": undefined,
|
||||
"name": "GHC.Conc.Signal.CAF",
|
||||
"selfWeight": 640,
|
||||
"totalWeight": 640,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 73,
|
||||
"line": undefined,
|
||||
"name": "GHC.IO.Encoding.CAF",
|
||||
"selfWeight": 2768,
|
||||
"totalWeight": 2768,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 75,
|
||||
"line": undefined,
|
||||
"name": "GHC.IO.Encoding.Iconv.CAF",
|
||||
"selfWeight": 200,
|
||||
"totalWeight": 200,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 84,
|
||||
"line": undefined,
|
||||
"name": "GHC.IO.Handle.FD.CAF",
|
||||
"selfWeight": 34704,
|
||||
"totalWeight": 34704,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 119,
|
||||
"line": undefined,
|
||||
"name": "Text.Printf.CAF",
|
||||
"selfWeight": 528,
|
||||
"totalWeight": 528,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 26,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:eta1_r6nI",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 2097152,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(21,1)-(38,42)",
|
||||
"key": 19,
|
||||
"line": undefined,
|
||||
"name": "Main.main",
|
||||
"selfWeight": 32,
|
||||
"totalWeight": 1921591544,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(56,1)-(57,42)",
|
||||
"key": 4,
|
||||
"line": undefined,
|
||||
"name": "Main.check",
|
||||
"selfWeight": 406149056,
|
||||
"totalWeight": 406149056,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:31:9-29",
|
||||
"key": 16,
|
||||
"line": undefined,
|
||||
"name": "Main.main.long",
|
||||
"selfWeight": 32,
|
||||
"totalWeight": 7864112,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 28,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:eta_r6nG",
|
||||
"selfWeight": 1096,
|
||||
"totalWeight": 1096,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 35,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:io1",
|
||||
"selfWeight": 1888,
|
||||
"totalWeight": 1888,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 27,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:lvl2_r6nH",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 7864112,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(61,1)-(63,26)",
|
||||
"key": 3,
|
||||
"line": undefined,
|
||||
"name": "Main.make",
|
||||
"selfWeight": 1512575520,
|
||||
"totalWeight": 1512575520,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:63:19-26",
|
||||
"key": 1,
|
||||
"line": undefined,
|
||||
"name": "Main.make.d2",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:63:9-16",
|
||||
"key": 2,
|
||||
"line": undefined,
|
||||
"name": "Main.make.i2",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 24,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:lvl4_r6nL",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 22,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:lvl8_r6nP",
|
||||
"selfWeight": 520,
|
||||
"totalWeight": 520,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 21,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main1",
|
||||
"selfWeight": 16,
|
||||
"totalWeight": 16,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 29,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main11",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 4194304,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:27:9-35",
|
||||
"key": 15,
|
||||
"line": undefined,
|
||||
"name": "Main.main.c",
|
||||
"selfWeight": 64,
|
||||
"totalWeight": 19922736,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 30,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main12",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 15728432,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 23,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main5",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 31,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main7",
|
||||
"selfWeight": 880,
|
||||
"totalWeight": 880,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 25,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main9",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:23:9-12",
|
||||
"key": 33,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main_maxN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 32,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:23:9-35",
|
||||
"key": 13,
|
||||
"line": undefined,
|
||||
"name": "Main.main.maxN",
|
||||
"selfWeight": 32,
|
||||
"totalWeight": 32,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:22:9",
|
||||
"key": 34,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main_n",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:22:9-14",
|
||||
"key": 12,
|
||||
"line": undefined,
|
||||
"name": "Main.main.n",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:24:9-16",
|
||||
"key": 32,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:main_stretchN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 32,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:24:9-27",
|
||||
"key": 14,
|
||||
"line": undefined,
|
||||
"name": "Main.main.stretchN",
|
||||
"selfWeight": 32,
|
||||
"totalWeight": 32,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:17:1-4",
|
||||
"key": 36,
|
||||
"line": undefined,
|
||||
"name": "Main.CAF:minN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:17:1-8",
|
||||
"key": 9,
|
||||
"line": undefined,
|
||||
"name": "Main.minN",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 146,
|
||||
"line": undefined,
|
||||
"name": "GC.GC",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 147,
|
||||
"line": undefined,
|
||||
"name": "PROFILING.OVERHEAD_of",
|
||||
"selfWeight": 2496,
|
||||
"totalWeight": 2496,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": 145,
|
||||
"line": undefined,
|
||||
"name": "SYSTEM.SYSTEM",
|
||||
"selfWeight": 34736,
|
||||
"totalWeight": 34736,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:35:26-54",
|
||||
"key": 18,
|
||||
"line": undefined,
|
||||
"name": "Main.main.\\\\",
|
||||
"selfWeight": 2224,
|
||||
"totalWeight": 46672,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:19:1-56",
|
||||
"key": 8,
|
||||
"line": undefined,
|
||||
"name": "Main.io",
|
||||
"selfWeight": 67912,
|
||||
"totalWeight": 67912,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:34:9-28",
|
||||
"key": 17,
|
||||
"line": undefined,
|
||||
"name": "Main.main.vs",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1891637344,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(42,1)-(45,38)",
|
||||
"key": 11,
|
||||
"line": undefined,
|
||||
"name": "Main.depth",
|
||||
"selfWeight": 1120,
|
||||
"totalWeight": 1891637344,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:(49,1)-(52,31)",
|
||||
"key": 7,
|
||||
"line": undefined,
|
||||
"name": "Main.sumT",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 1891636224,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:51:9-31",
|
||||
"key": 6,
|
||||
"line": undefined,
|
||||
"name": "Main.sumT.a",
|
||||
"selfWeight": 1397760,
|
||||
"totalWeight": 945818112,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:52:9-31",
|
||||
"key": 5,
|
||||
"line": undefined,
|
||||
"name": "Main.sumT.b",
|
||||
"selfWeight": 1397760,
|
||||
"totalWeight": 945818112,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": "src/Main.hs:45:9-38",
|
||||
"key": 10,
|
||||
"line": undefined,
|
||||
"name": "Main.depth.n",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0,
|
||||
},
|
||||
],
|
||||
"name": "binary-trees allocation",
|
||||
"stacks": Array [
|
||||
"MAIN.MAIN;GHC.Conc.Signal.CAF 640 B",
|
||||
"MAIN.MAIN;GHC.IO.Encoding.CAF 2.70 KB",
|
||||
"MAIN.MAIN;GHC.IO.Encoding.Iconv.CAF 200 B",
|
||||
"MAIN.MAIN;GHC.IO.Handle.FD.CAF 33.89 KB",
|
||||
"MAIN.MAIN;Text.Printf.CAF 528 B",
|
||||
"MAIN.MAIN;Main.CAF:eta1_r6nI;Main.main;Main.check 2.00 MB",
|
||||
"MAIN.MAIN;Main.CAF:eta1_r6nI;Main.main 32 B",
|
||||
"MAIN.MAIN;Main.CAF:eta_r6nG 1.07 KB",
|
||||
"MAIN.MAIN;Main.CAF:io1 1.84 KB",
|
||||
"MAIN.MAIN;Main.CAF:lvl2_r6nH;Main.main;Main.main.long;Main.make 7.50 MB",
|
||||
"MAIN.MAIN;Main.CAF:lvl2_r6nH;Main.main;Main.main.long 32 B",
|
||||
"MAIN.MAIN;Main.CAF:lvl8_r6nP 520 B",
|
||||
"MAIN.MAIN;Main.CAF:main1 16 B",
|
||||
"MAIN.MAIN;Main.CAF:main11;Main.main;Main.main.c;Main.check 4.00 MB",
|
||||
"MAIN.MAIN;Main.CAF:main11;Main.main;Main.main.c 32 B",
|
||||
"MAIN.MAIN;Main.CAF:main12;Main.main;Main.main.c;Main.make 15.00 MB",
|
||||
"MAIN.MAIN;Main.CAF:main12;Main.main;Main.main.c 32 B",
|
||||
"MAIN.MAIN;Main.CAF:main7 880 B",
|
||||
"MAIN.MAIN;Main.CAF:main_maxN;Main.main;Main.main.maxN 32 B",
|
||||
"MAIN.MAIN;Main.CAF:main_stretchN;Main.main;Main.main.stretchN 32 B",
|
||||
"MAIN.MAIN;PROFILING.OVERHEAD_of 2.44 KB",
|
||||
"MAIN.MAIN;SYSTEM.SYSTEM 33.92 KB",
|
||||
"MAIN.MAIN;Main.main;Main.main.\\\\;Main.io 43.41 KB",
|
||||
"MAIN.MAIN;Main.main;Main.main.\\\\ 2.17 KB",
|
||||
"MAIN.MAIN;Main.main;Main.io 22.91 KB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.check 190.67 MB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make 710.00 MB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a 1.33 MB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.check 190.67 MB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.make 710.00 MB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b 1.33 MB",
|
||||
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth 1.09 KB",
|
||||
"MAIN.MAIN 648 B",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromHaskell: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromHaskell: profileGroup.name 1`] = `"binary-trees"`;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"`;
|
||||
@@ -0,0 +1,101 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`importFromSafari 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": 13,
|
||||
"file": "__InjectedScript_InjectedScriptSource.js",
|
||||
"key": "injectModule:__InjectedScript_InjectedScriptSource.js:109:13",
|
||||
"line": 109,
|
||||
"name": "injectModule",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0.001,
|
||||
},
|
||||
Frame {
|
||||
"col": 10,
|
||||
"file": "__InjectedScript_CommandLineAPIModuleSource.js",
|
||||
"key": ":__InjectedScript_CommandLineAPIModuleSource.js:2:10",
|
||||
"line": 2,
|
||||
"name": "(anonymous)",
|
||||
"selfWeight": 0.001,
|
||||
"totalWeight": 0.001,
|
||||
},
|
||||
Frame {
|
||||
"col": 1,
|
||||
"file": "file:///speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "(program):file:///speedscope/sample/programs/javascript/simple.js:1:1",
|
||||
"line": 1,
|
||||
"name": "(program)",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0.03248933597933502,
|
||||
},
|
||||
Frame {
|
||||
"col": 15,
|
||||
"file": "file:///speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "alpha:file:///speedscope/sample/programs/javascript/simple.js:1:15",
|
||||
"line": 1,
|
||||
"name": "alpha",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0.03248933597933502,
|
||||
},
|
||||
Frame {
|
||||
"col": 15,
|
||||
"file": "file:///speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "delta:file:///speedscope/sample/programs/javascript/simple.js:14:15",
|
||||
"line": 14,
|
||||
"name": "delta",
|
||||
"selfWeight": 0.003094222474222382,
|
||||
"totalWeight": 0.020112446082445484,
|
||||
},
|
||||
Frame {
|
||||
"col": 15,
|
||||
"file": "file:///speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "gamma:file:///speedscope/sample/programs/javascript/simple.js:20:15",
|
||||
"line": 20,
|
||||
"name": "gamma",
|
||||
"selfWeight": 0.029395113505112636,
|
||||
"totalWeight": 0.029395113505112636,
|
||||
},
|
||||
Frame {
|
||||
"col": 14,
|
||||
"file": "file:///speedscope/sample/programs/javascript/simple.js",
|
||||
"key": "beta:file:///speedscope/sample/programs/javascript/simple.js:8:14",
|
||||
"line": 8,
|
||||
"name": "beta",
|
||||
"selfWeight": 0,
|
||||
"totalWeight": 0.012376889896889526,
|
||||
},
|
||||
Frame {
|
||||
"col": 102,
|
||||
"file": "",
|
||||
"key": "firstOpenSearchURLString::4:102",
|
||||
"line": 4,
|
||||
"name": "firstOpenSearchURLString",
|
||||
"selfWeight": 0.0005174240213818848,
|
||||
"totalWeight": 0.0005174240213818848,
|
||||
},
|
||||
],
|
||||
"name": "Grabación de Control temporal 1",
|
||||
"stacks": Array [
|
||||
"injectModule;(anonymous) 1.00ms",
|
||||
" 39.93ms",
|
||||
"(program);alpha;delta;gamma 10.83ms",
|
||||
" 2.46ms",
|
||||
"(program);alpha;delta 3.09ms",
|
||||
"(program);alpha;beta;gamma 4.64ms",
|
||||
"(program);alpha;delta;gamma 1.55ms",
|
||||
"(program);alpha;beta;gamma 1.55ms",
|
||||
"(program);alpha;delta;gamma 3.09ms",
|
||||
"(program);alpha;beta;gamma 4.64ms",
|
||||
"(program);alpha;delta;gamma 1.55ms",
|
||||
"(program);alpha;beta;gamma 1.55ms",
|
||||
" 253.50ms",
|
||||
"firstOpenSearchURLString 517.42µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importFromSafari: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importFromSafari: profileGroup.name 1`] = `"Grabación de Control temporal 1"`;
|
||||
@@ -0,0 +1,528 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`importTraceEvents bad E events 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 12,
|
||||
"totalWeight": 12,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 0",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
"alpha;beta 12.00µs",
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents bad E events: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents bad E events: profileGroup.name 1`] = `"too-many-end-events.json"`;
|
||||
|
||||
exports[`importTraceEvents event re-ordering 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "A",
|
||||
"line": undefined,
|
||||
"name": "A",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 6,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "B",
|
||||
"line": undefined,
|
||||
"name": "B",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "C",
|
||||
"line": undefined,
|
||||
"name": "C",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 2,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "X",
|
||||
"line": undefined,
|
||||
"name": "X",
|
||||
"selfWeight": 3,
|
||||
"totalWeight": 3,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 1",
|
||||
"stacks": Array [
|
||||
"A;B;C 1.00µs",
|
||||
"A;B 1.00µs",
|
||||
"A 1.00µs",
|
||||
" 1.00µs",
|
||||
"A 1.00µs",
|
||||
"A;B 1.00µs",
|
||||
"A;B;C 1.00µs",
|
||||
"X 3.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents event re-ordering: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents event re-ordering: profileGroup.name 1`] = `"must-retain-original-order.json"`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p0 (pid 0), p0t0 (tid 0)",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 2`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p0 (pid 0), p0t1 (tid 1)",
|
||||
"stacks": Array [
|
||||
"beta 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 3`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "gamma",
|
||||
"line": undefined,
|
||||
"name": "gamma",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p1 (pid 1), p1t0 (tid 0)",
|
||||
"stacks": Array [
|
||||
"gamma 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 4`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "delta",
|
||||
"line": undefined,
|
||||
"name": "delta",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p1 (pid 1), p1t1 (tid 1)",
|
||||
"stacks": Array [
|
||||
"delta 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 5`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "epsilon",
|
||||
"line": undefined,
|
||||
"name": "epsilon",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p2t0 (pid 2, tid 0)",
|
||||
"stacks": Array [
|
||||
"epsilon 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 6`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "phi",
|
||||
"line": undefined,
|
||||
"name": "phi",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p2t1 (pid 2, tid 1)",
|
||||
"stacks": Array [
|
||||
"phi 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 7`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "zeta",
|
||||
"line": undefined,
|
||||
"name": "zeta",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p3 (pid 3, tid 0)",
|
||||
"stacks": Array [
|
||||
"zeta 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess 8`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "eta",
|
||||
"line": undefined,
|
||||
"name": "eta",
|
||||
"selfWeight": 1,
|
||||
"totalWeight": 1,
|
||||
},
|
||||
],
|
||||
"name": "p3 (pid 3, tid 1)",
|
||||
"stacks": Array [
|
||||
"eta 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents multiprocess: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents multiprocess: profileGroup.name 1`] = `"multiprocess.json"`;
|
||||
|
||||
exports[`importTraceEvents partial json import 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 3,
|
||||
"totalWeight": 12,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"line": undefined,
|
||||
"name": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"selfWeight": 5,
|
||||
"totalWeight": 5,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "epsilon",
|
||||
"line": undefined,
|
||||
"name": "epsilon",
|
||||
"selfWeight": 4,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 0",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
"alpha;beta 1.00µs",
|
||||
"alpha;beta;gamma {\\"detail\\":\\"foobar\\"} 5.00µs",
|
||||
"alpha;beta;epsilon 4.00µs",
|
||||
"alpha;beta 2.00µs",
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents partial json import trailing comma 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 3,
|
||||
"totalWeight": 12,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"line": undefined,
|
||||
"name": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"selfWeight": 5,
|
||||
"totalWeight": 5,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "epsilon",
|
||||
"line": undefined,
|
||||
"name": "epsilon",
|
||||
"selfWeight": 4,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 0",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
"alpha;beta 1.00µs",
|
||||
"alpha;beta;gamma {\\"detail\\":\\"foobar\\"} 5.00µs",
|
||||
"alpha;beta;epsilon 4.00µs",
|
||||
"alpha;beta 2.00µs",
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents partial json import trailing comma: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents partial json import trailing comma: profileGroup.name 1`] = `"simple-partial-trailing-comma.json"`;
|
||||
|
||||
exports[`importTraceEvents partial json import whitespace padding 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 3,
|
||||
"totalWeight": 12,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"line": undefined,
|
||||
"name": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"selfWeight": 5,
|
||||
"totalWeight": 5,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "epsilon",
|
||||
"line": undefined,
|
||||
"name": "epsilon",
|
||||
"selfWeight": 4,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 0",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
"alpha;beta 1.00µs",
|
||||
"alpha;beta;gamma {\\"detail\\":\\"foobar\\"} 5.00µs",
|
||||
"alpha;beta;epsilon 4.00µs",
|
||||
"alpha;beta 2.00µs",
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents partial json import whitespace padding: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents partial json import whitespace padding: profileGroup.name 1`] = `"simple-partial-whitespace.json"`;
|
||||
|
||||
exports[`importTraceEvents partial json import: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents partial json import: profileGroup.name 1`] = `"simple-partial.json"`;
|
||||
|
||||
exports[`importTraceEvents simple 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 3,
|
||||
"totalWeight": 12,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"line": undefined,
|
||||
"name": "gamma {\\"detail\\":\\"foobar\\"}",
|
||||
"selfWeight": 5,
|
||||
"totalWeight": 5,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "epsilon",
|
||||
"line": undefined,
|
||||
"name": "epsilon",
|
||||
"selfWeight": 4,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 0",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
"alpha;beta 1.00µs",
|
||||
"alpha;beta;gamma {\\"detail\\":\\"foobar\\"} 5.00µs",
|
||||
"alpha;beta;epsilon 4.00µs",
|
||||
"alpha;beta 2.00µs",
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents simple object 1`] = `
|
||||
Object {
|
||||
"frames": Array [
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "alpha",
|
||||
"line": undefined,
|
||||
"name": "alpha",
|
||||
"selfWeight": 2,
|
||||
"totalWeight": 14,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "beta",
|
||||
"line": undefined,
|
||||
"name": "beta",
|
||||
"selfWeight": 8,
|
||||
"totalWeight": 12,
|
||||
},
|
||||
Frame {
|
||||
"col": undefined,
|
||||
"file": undefined,
|
||||
"key": "(unnamed)",
|
||||
"line": undefined,
|
||||
"name": "(unnamed)",
|
||||
"selfWeight": 4,
|
||||
"totalWeight": 4,
|
||||
},
|
||||
],
|
||||
"name": "pid 0, tid 0",
|
||||
"stacks": Array [
|
||||
"alpha 1.00µs",
|
||||
"alpha;beta 6.00µs",
|
||||
"alpha;beta;(unnamed) 4.00µs",
|
||||
"alpha;beta 2.00µs",
|
||||
"alpha 1.00µs",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`importTraceEvents simple object: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents simple object: profileGroup.name 1`] = `"simple-object.json"`;
|
||||
|
||||
exports[`importTraceEvents simple: indexToView 1`] = `0`;
|
||||
|
||||
exports[`importTraceEvents simple: profileGroup.name 1`] = `"simple.json"`;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,3 +7,11 @@ test('importFromBGFlameGraph', async () => {
|
||||
test('importFromBGFlameGraph with CRLF', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/stackcollapse/simple-crlf.txt')
|
||||
})
|
||||
|
||||
test('importFromBGFlameGraph with UTF-16, Little Endian', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/stackcollapse/simple-utf16-le.txt')
|
||||
})
|
||||
|
||||
test('importFromBGFlameGraph with UTF-16, Big Endian', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/stackcollapse/simple-utf16-be.txt')
|
||||
})
|
||||
|
||||
@@ -11,3 +11,15 @@ test('importFromChromeTimeline', async () => {
|
||||
test('importFromChromeTimeline Chrome 69', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Chrome/69/simple.json')
|
||||
})
|
||||
|
||||
test('importFromV8Profiler Node 10', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/node/10.11.0/example.cpuprofile')
|
||||
})
|
||||
|
||||
test('importFromChromeTimeline Workers Chrome 66', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Chrome/66/worker.json')
|
||||
})
|
||||
|
||||
test('importFromChromeTimeline Workers Chrome 70', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Chrome/70/worker.json')
|
||||
})
|
||||
|
||||
+91
-25
@@ -1,6 +1,7 @@
|
||||
import {Profile, FrameInfo, CallTreeProfileBuilder} from '../lib/profile'
|
||||
import {getOrInsert, lastOf, sortBy} from '../lib/utils'
|
||||
import {Profile, FrameInfo, CallTreeProfileBuilder, ProfileGroup} from '../lib/profile'
|
||||
import {getOrInsert, lastOf, sortBy, itForEach} from '../lib/utils'
|
||||
import {TimeFormatter} from '../lib/value-formatters'
|
||||
import {chromeTreeToNodes, OldCPUProfile} from './v8cpuFormatter'
|
||||
|
||||
// See: https://github.com/v8/v8/blob/master/src/inspector/js_protocol.json
|
||||
|
||||
@@ -15,6 +16,7 @@ interface TimelineEvent {
|
||||
tdur: number
|
||||
tts: number
|
||||
args: {[key: string]: any}
|
||||
id?: string
|
||||
}
|
||||
|
||||
interface PositionTickInfo {
|
||||
@@ -30,7 +32,7 @@ interface CPUProfileCallFrame {
|
||||
url: string
|
||||
}
|
||||
|
||||
interface CPUProfileNode {
|
||||
export interface CPUProfileNode {
|
||||
callFrame: CPUProfileCallFrame
|
||||
hitCount: number
|
||||
id: number
|
||||
@@ -39,7 +41,7 @@ interface CPUProfileNode {
|
||||
parent?: CPUProfileNode
|
||||
}
|
||||
|
||||
interface CPUProfile {
|
||||
export interface CPUProfile {
|
||||
startTime: number
|
||||
endTime: number
|
||||
nodes: CPUProfileNode[]
|
||||
@@ -61,34 +63,53 @@ export function isChromeTimeline(rawProfile: any): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
export function importFromChromeTimeline(events: TimelineEvent[]): Profile {
|
||||
export function importFromChromeTimeline(events: TimelineEvent[], fileName: string): ProfileGroup {
|
||||
// It seems like sometimes Chrome timeline files contain multiple CpuProfiles?
|
||||
// For now, choose the first one in the list.
|
||||
|
||||
let cpuProfile: CPUProfile | null = null
|
||||
const cpuProfileByID = new Map<string, CPUProfile>()
|
||||
|
||||
// Maps profile IDs (like "0x3") to pid/tid pairs formatted as `${pid}:${tid}`
|
||||
const pidTidById = new Map<string, string>()
|
||||
|
||||
// Maps pid/tid pairs to thread names
|
||||
const threadNameByPidTid = new Map<string, string>()
|
||||
|
||||
// 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') {
|
||||
cpuProfile = event.args.data.cpuProfile as CPUProfile
|
||||
break
|
||||
if (event.name === 'CpuProfile') {
|
||||
const pidTid = `${event.pid}:${event.tid}`
|
||||
const id = event.id || pidTid
|
||||
cpuProfileByID.set(id, event.args.data.cpuProfile as CPUProfile)
|
||||
pidTidById.set(id, pidTid)
|
||||
}
|
||||
|
||||
if (event.name == 'Profile') {
|
||||
cpuProfile = {
|
||||
if (event.name === 'Profile') {
|
||||
const pidTid = `${event.pid}:${event.tid}`
|
||||
cpuProfileByID.set(event.id || pidTid, {
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
nodes: [],
|
||||
samples: [],
|
||||
timeDeltas: [],
|
||||
...event.args.data,
|
||||
})
|
||||
|
||||
if (event.id) {
|
||||
pidTidById.set(event.id, `${event.pid}:${event.tid}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (event.name == 'ProfileChunk') {
|
||||
if (event.name === 'thread_name') {
|
||||
threadNameByPidTid.set(`${event.pid}:${event.tid}`, event.args.name)
|
||||
}
|
||||
|
||||
if (event.name === 'ProfileChunk') {
|
||||
const pidTid = `${event.pid}:${event.tid}`
|
||||
const cpuProfile = cpuProfileByID.get(event.id || pidTid)
|
||||
if (cpuProfile) {
|
||||
const chunk = event.args.data
|
||||
if (chunk.cpuProfile) {
|
||||
@@ -109,13 +130,36 @@ export function importFromChromeTimeline(events: TimelineEvent[]): Profile {
|
||||
cpuProfile.endTime = chunk.endTime
|
||||
}
|
||||
} else {
|
||||
console.log('Ignoring ProfileChunk when no Profile is active')
|
||||
console.warn(`Ignoring ProfileChunk for undeclared Profile with id ${event.id || pidTid}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cpuProfile) {
|
||||
return importFromChromeCPUProfile(cpuProfile as CPUProfile)
|
||||
if (cpuProfileByID.size > 0) {
|
||||
const profiles: Profile[] = []
|
||||
let indexToView = 0
|
||||
|
||||
itForEach(cpuProfileByID.keys(), profileId => {
|
||||
let threadName: string | null = null
|
||||
let pidTid = pidTidById.get(profileId)
|
||||
if (pidTid) {
|
||||
threadName = threadNameByPidTid.get(pidTid) || null
|
||||
if (threadName) {
|
||||
}
|
||||
}
|
||||
const profile = importFromChromeCPUProfile(cpuProfileByID.get(profileId)!)
|
||||
if (threadName && cpuProfileByID.size > 1) {
|
||||
profile.setName(`${fileName} - ${threadName}`)
|
||||
if (threadName === 'CrRendererMain') {
|
||||
indexToView = profiles.length
|
||||
}
|
||||
} else {
|
||||
profile.setName(`${fileName}`)
|
||||
}
|
||||
profiles.push(profile)
|
||||
})
|
||||
|
||||
return {name: fileName, indexToView, profiles}
|
||||
} else {
|
||||
throw new Error('Could not find CPU profile in Timeline')
|
||||
}
|
||||
@@ -126,8 +170,17 @@ function frameInfoForCallFrame(callFrame: CPUProfileCallFrame) {
|
||||
return getOrInsert(callFrameToFrameInfo, callFrame, callFrame => {
|
||||
const name = callFrame.functionName || '(anonymous)'
|
||||
const file = callFrame.url
|
||||
const line = callFrame.lineNumber
|
||||
const col = callFrame.columnNumber
|
||||
|
||||
// In Chrome profiles, line numbers & column numbers are both 0-indexed.
|
||||
//
|
||||
// We're going to normalize these to be 1-based to avoid needing to normalize
|
||||
// these at the presentation layer.
|
||||
let line = callFrame.lineNumber
|
||||
if (line != null) line++
|
||||
|
||||
let col = callFrame.columnNumber
|
||||
if (col != null) col++
|
||||
|
||||
return {
|
||||
key: `${name}:${file}:${line}:${col}`,
|
||||
name,
|
||||
@@ -180,6 +233,10 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
// Ref: https://github.com/v8/v8/blob/44bd8fd7/src/inspector/js_protocol.json#L1485
|
||||
let elapsed = chromeProfile.timeDeltas[0]
|
||||
|
||||
// Prevents negative time deltas from causing bad data. See
|
||||
// https://github.com/jlfwong/speedscope/pull/305 for details.
|
||||
let lastValidElapsed = elapsed
|
||||
|
||||
let lastNodeId = NaN
|
||||
|
||||
// The chrome CPU profile format doesn't collapse identical samples. We'll do that
|
||||
@@ -188,21 +245,26 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
const nodeId = chromeProfile.samples[i]
|
||||
if (nodeId != lastNodeId) {
|
||||
samples.push(nodeId)
|
||||
sampleTimes.push(elapsed)
|
||||
if (elapsed < lastValidElapsed) {
|
||||
sampleTimes.push(lastValidElapsed)
|
||||
} else {
|
||||
sampleTimes.push(elapsed)
|
||||
lastValidElapsed = elapsed
|
||||
}
|
||||
}
|
||||
|
||||
if (i === chromeProfile.samples.length - 1) {
|
||||
if (!isNaN(lastNodeId)) {
|
||||
samples.push(lastNodeId)
|
||||
sampleTimes.push(elapsed)
|
||||
if (elapsed < lastValidElapsed) {
|
||||
sampleTimes.push(lastValidElapsed)
|
||||
} else {
|
||||
sampleTimes.push(elapsed)
|
||||
lastValidElapsed = 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
|
||||
}
|
||||
|
||||
const timeDelta = chromeProfile.timeDeltas[i + 1]
|
||||
elapsed += timeDelta
|
||||
lastNodeId = nodeId
|
||||
}
|
||||
@@ -265,3 +327,7 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile): Profile {
|
||||
profile.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
return profile.build()
|
||||
}
|
||||
|
||||
export function importFromOldV8CPUProfile(content: OldCPUProfile): Profile {
|
||||
return importFromChromeCPUProfile(chromeTreeToNodes(content))
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ export function importFromFirefox(firefoxProfile: FirefoxProfile): Profile {
|
||||
const frameData = thread.frameTable.data[f]
|
||||
const location = thread.stringTable[frameData[0]]
|
||||
|
||||
const match = /(.*)\s+\((.*?):?(\d+)?\)$/.exec(location)
|
||||
const match = /(.*)\s+\((.*?)(?::(\d+))?(?::(\d+))?\)$/.exec(location)
|
||||
|
||||
if (!match) return null
|
||||
|
||||
@@ -193,7 +193,11 @@ export function importFromFirefox(firefoxProfile: FirefoxProfile): Profile {
|
||||
key: location,
|
||||
name: match[1]!,
|
||||
file: match[2]!,
|
||||
|
||||
// In Firefox profiles, line numbers are 1-based, but columns are
|
||||
// 0-based. Let's normalize both to be 1-based.
|
||||
line: match[3] ? parseInt(match[3]) : undefined,
|
||||
col: match[4] ? parseInt(match[4]) + 1 : undefined,
|
||||
}))
|
||||
})
|
||||
.filter(f => f != null) as FrameInfo[]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
|
||||
test('importFromHaskell', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/haskell/simple.prof')
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
import {ProfileGroup, FrameInfo, CallTreeProfileBuilder} from '../lib/profile'
|
||||
import {TimeFormatter, ByteFormatter} from '../lib/value-formatters'
|
||||
|
||||
// See https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/profiling.html#json-profile-format
|
||||
// for information on the GHC profiler JSON output format.
|
||||
|
||||
interface CostCentre {
|
||||
id: number
|
||||
label: string
|
||||
module: string
|
||||
src_loc: string
|
||||
is_caf: boolean
|
||||
}
|
||||
|
||||
interface ProfileTree {
|
||||
id: number
|
||||
entries: number
|
||||
alloc: number
|
||||
ticks: number
|
||||
children: ProfileTree[]
|
||||
}
|
||||
|
||||
interface HaskellProfile {
|
||||
program: string
|
||||
arguments: string[]
|
||||
rts_arguments: string[]
|
||||
end_time: string
|
||||
initial_capabilities: number
|
||||
total_time: number
|
||||
total_ticks: number
|
||||
tick_interval: number
|
||||
total_alloc: number
|
||||
cost_centres: CostCentre[]
|
||||
profile: ProfileTree
|
||||
}
|
||||
|
||||
// The profiler already collapses recursion before output so using the JS stack here should be fine
|
||||
function addToProfile(
|
||||
tree: ProfileTree,
|
||||
startVal: number,
|
||||
profile: CallTreeProfileBuilder,
|
||||
infos: Map<number, FrameInfo>,
|
||||
attribute: (tree: ProfileTree) => number,
|
||||
): number {
|
||||
// If the expression never did anything we don't care about it
|
||||
if (tree.ticks === 0 && tree.entries === 0 && tree.alloc === 0 && tree.children.length === 0)
|
||||
return startVal
|
||||
|
||||
let curVal = startVal
|
||||
let frameInfo = infos.get(tree.id)!
|
||||
|
||||
profile.enterFrame(frameInfo, curVal)
|
||||
|
||||
for (let child of tree.children) {
|
||||
curVal = addToProfile(child, curVal, profile, infos, attribute)
|
||||
}
|
||||
|
||||
curVal += attribute(tree)
|
||||
|
||||
profile.leaveFrame(frameInfo, curVal)
|
||||
|
||||
return curVal
|
||||
}
|
||||
|
||||
export function importFromHaskell(haskellProfile: HaskellProfile): ProfileGroup {
|
||||
const idToFrameInfo = new Map<number, FrameInfo>()
|
||||
for (let centre of haskellProfile.cost_centres) {
|
||||
const frameInfo: FrameInfo = {
|
||||
key: centre.id,
|
||||
name: `${centre.module}.${centre.label}`,
|
||||
}
|
||||
|
||||
// Ignore things like <entire-module> and <no location info>
|
||||
if (!centre.src_loc.startsWith('<')) {
|
||||
// This also contains line and column information, but sometimes it contains ranges,
|
||||
// and in varying formats, so it's a better experience just to leave it as is
|
||||
frameInfo.file = centre.src_loc
|
||||
}
|
||||
|
||||
idToFrameInfo.set(centre.id, frameInfo)
|
||||
}
|
||||
|
||||
const timeProfile = new CallTreeProfileBuilder(haskellProfile.total_ticks)
|
||||
addToProfile(haskellProfile.profile, 0, timeProfile, idToFrameInfo, tree => tree.ticks)
|
||||
timeProfile.setValueFormatter(new TimeFormatter('milliseconds'))
|
||||
timeProfile.setName(`${haskellProfile.program} time`)
|
||||
|
||||
const allocProfile = new CallTreeProfileBuilder(haskellProfile.total_ticks)
|
||||
addToProfile(haskellProfile.profile, 0, allocProfile, idToFrameInfo, tree => tree.alloc)
|
||||
allocProfile.setValueFormatter(new ByteFormatter())
|
||||
allocProfile.setName(`${haskellProfile.program} allocation`)
|
||||
|
||||
return {
|
||||
name: haskellProfile.program,
|
||||
indexToView: 0,
|
||||
profiles: [timeProfile.build(), allocProfile.build()],
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
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)
|
||||
|
||||
// Importing from a version of stackprof which was missing raw_timestamp_deltas should return null
|
||||
const oldStackprof = `{"version":1.2,"mode":"wall","interval":1000,"samples":0,"gc_samples":0,"missed_samples":0,"frames":{}}`
|
||||
expect(await importProfileGroupFromText('unknown', oldStackprof)).toBe(null)
|
||||
})
|
||||
|
||||
+103
-16
@@ -1,7 +1,12 @@
|
||||
import {Profile, ProfileGroup} from '../lib/profile'
|
||||
import {FileSystemDirectoryEntry} from './file-system-entry'
|
||||
|
||||
import {importFromChromeCPUProfile, importFromChromeTimeline, isChromeTimeline} from './chrome'
|
||||
import {
|
||||
importFromChromeCPUProfile,
|
||||
importFromChromeTimeline,
|
||||
isChromeTimeline,
|
||||
importFromOldV8CPUProfile,
|
||||
} from './chrome'
|
||||
import {importFromStackprof} from './stackprof'
|
||||
import {importFromInstrumentsDeepCopy, importFromInstrumentsTrace} from './instruments'
|
||||
import {importFromBGFlameGraph} from './bg-flamegraph'
|
||||
@@ -9,12 +14,45 @@ import {importFromFirefox} from './firefox'
|
||||
import {importSpeedscopeProfiles} from '../lib/file-format'
|
||||
import {importFromV8ProfLog} from './v8proflog'
|
||||
import {importFromLinuxPerf} from './linux-tools-perf'
|
||||
import {importFromHaskell} from './haskell'
|
||||
import {importFromSafari} from './safari'
|
||||
import {ProfileDataSource, TextProfileDataSource, MaybeCompressedDataReader} from './utils'
|
||||
import {importAsPprofProfile} from './pprof'
|
||||
import {decodeBase64} from '../lib/utils'
|
||||
import {importFromChromeHeapProfile} from './v8heapalloc'
|
||||
import {isTraceEventFormatted, importTraceEvents} from './trace-event'
|
||||
|
||||
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,20 +72,48 @@ 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> {
|
||||
function fixUpJSON(content: string): string {
|
||||
// This code is similar to the code from here:
|
||||
// https://github.com/catapult-project/catapult/blob/27e047e0494df162022be6aa8a8862742a270232/tracing/tracing/extras/importer/trace_event_importer.html#L197-L208
|
||||
//
|
||||
// If the event data begins with a [, then we know it should end with a ]. The
|
||||
// reason we check for this is because some tracing implementations cannot
|
||||
// guarantee that a ']' gets written to the trace file. So, we are forgiving
|
||||
// and if this is obviously the case, we fix it up before throwing the string
|
||||
// at JSON.parse.
|
||||
//
|
||||
content = content.trim()
|
||||
if (content[0] === '[') {
|
||||
content = content.replace(/,\s*$/, '')
|
||||
if (content[content.length - 1] !== ']') {
|
||||
content += ']'
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
async function _importProfileGroup(dataSource: ProfileDataSource): Promise<ProfileGroup | null> {
|
||||
const fileName = await dataSource.name()
|
||||
|
||||
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')
|
||||
return importSpeedscopeProfiles(JSON.parse(contents))
|
||||
} else if (fileName.endsWith('.cpuprofile')) {
|
||||
console.log('Importing as Chrome CPU Profile')
|
||||
return toGroup(importFromChromeCPUProfile(JSON.parse(contents)))
|
||||
} else if (fileName.endsWith('.chrome.json') || /Profile-\d{8}T\d{6}/.exec(fileName)) {
|
||||
console.log('Importing as Chrome Timeline')
|
||||
return toGroup(importFromChromeTimeline(JSON.parse(contents)))
|
||||
return importFromChromeTimeline(JSON.parse(contents), fileName)
|
||||
} else if (fileName.endsWith('.stackprof.json')) {
|
||||
console.log('Importing as stackprof profile')
|
||||
return toGroup(importFromStackprof(JSON.parse(contents)))
|
||||
@@ -63,12 +129,18 @@ async function _importProfileGroup(
|
||||
} else if (fileName.endsWith('.v8log.json')) {
|
||||
console.log('Importing as --prof-process v8 log')
|
||||
return toGroup(importFromV8ProfLog(JSON.parse(contents)))
|
||||
} else if (fileName.endsWith('.heapprofile')) {
|
||||
console.log('Importing as Chrome Heap Profile')
|
||||
return toGroup(importFromChromeHeapProfile(JSON.parse(contents)))
|
||||
} else if (fileName.endsWith('-recording.json')) {
|
||||
console.log('Importing as Safari profile')
|
||||
return toGroup(importFromSafari(JSON.parse(contents)))
|
||||
}
|
||||
|
||||
// Second pass: Try to guess what file format it is based on structure
|
||||
let parsed: any
|
||||
try {
|
||||
parsed = JSON.parse(contents)
|
||||
parsed = JSON.parse(fixUpJSON(contents))
|
||||
} catch (e) {}
|
||||
if (parsed) {
|
||||
if (parsed['$schema'] === 'https://www.speedscope.app/file-format-schema.json') {
|
||||
@@ -78,17 +150,32 @@ async function _importProfileGroup(
|
||||
console.log('Importing as Firefox profile')
|
||||
return toGroup(importFromFirefox(parsed))
|
||||
} 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) {
|
||||
console.log('Importing as Chrome Timeline')
|
||||
return importFromChromeTimeline(parsed, fileName)
|
||||
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
|
||||
console.log('Importing as Chrome CPU Profile')
|
||||
return toGroup(importFromChromeCPUProfile(parsed))
|
||||
} else if ('mode' in parsed && 'frames' in parsed) {
|
||||
} else if (isTraceEventFormatted(parsed)) {
|
||||
console.log('Importing as Trace Event Format profile')
|
||||
return importTraceEvents(parsed)
|
||||
} else if ('head' in parsed && 'samples' in parsed && 'timestamps' in parsed) {
|
||||
console.log('Importing as Chrome CPU Profile (old format)')
|
||||
return toGroup(importFromOldV8CPUProfile(parsed))
|
||||
} else if ('mode' in parsed && 'frames' in parsed && 'raw_timestamp_deltas' in parsed) {
|
||||
console.log('Importing as stackprof profile')
|
||||
return toGroup(importFromStackprof(parsed))
|
||||
} else if ('code' in parsed && 'functions' in parsed && 'ticks' in parsed) {
|
||||
console.log('Importing as --prof-process v8 log')
|
||||
return toGroup(importFromV8ProfLog(parsed))
|
||||
} else if ('head' in parsed && 'selfSize' in parsed['head']) {
|
||||
console.log('Importing as Chrome Heap Profile')
|
||||
return toGroup(importFromChromeHeapProfile(JSON.parse(contents)))
|
||||
} else if ('rts_arguments' in parsed && 'initial_capabilities' in parsed) {
|
||||
console.log('Importing as Haskell GHC JSON Profile')
|
||||
return importFromHaskell(parsed)
|
||||
} else if ('recording' in parsed && 'sampleStackTraces' in parsed.recording) {
|
||||
console.log('Importing as Safari profile')
|
||||
return toGroup(importFromSafari(JSON.parse(contents)))
|
||||
}
|
||||
} else {
|
||||
// Format is not JSON
|
||||
|
||||
@@ -98,4 +98,7 @@ describe('importFromInstrumentsTrace', () => {
|
||||
test('Instruments 9.3.1', async () => {
|
||||
await importFromTrace('./sample/profiles/Instruments/9.3.1/simple-time-profile.trace.zip')
|
||||
})
|
||||
test('Instruments 10.0', async () => {
|
||||
await importFromTrace('./sample/profiles/Instruments/10.0/simple-time-profile.trace.zip')
|
||||
})
|
||||
})
|
||||
|
||||
+12
-50
@@ -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 {
|
||||
@@ -405,7 +361,10 @@ async function readFormTemplate(tree: TraceDirectoryTree): Promise<FormTemplateD
|
||||
const archive = readInstrumentsKeyedArchive(await readAsArrayBuffer(formTemplate))
|
||||
|
||||
const version = archive['com.apple.xray.owner.template.version']
|
||||
const selectedRunNumber = archive['com.apple.xray.owner.template'].get('_selectedRunNumber')
|
||||
let selectedRunNumber = 1
|
||||
if ('com.apple.xray.owner.template' in archive) {
|
||||
selectedRunNumber = archive['com.apple.xray.owner.template'].get('_selectedRunNumber')
|
||||
}
|
||||
let instrument = archive['$1']
|
||||
if ('stubInfoByUUID' in archive) {
|
||||
instrument = Array.from(archive['stubInfoByUUID'].keys())[0]
|
||||
@@ -674,7 +633,7 @@ export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export function decodeUTF8(bytes: Uint8Array): string {
|
||||
let text = String.fromCharCode.apply(String, bytes)
|
||||
let text = String.fromCharCode.apply(String, Array.from(bytes))
|
||||
if (text.slice(-1) === '\0') text = text.slice(0, -1) // Remove a single trailing null character if present
|
||||
return decodeURIComponent(escape(text))
|
||||
}
|
||||
@@ -781,7 +740,10 @@ function paternMatchObjectiveC(
|
||||
// Replace NSString with a string
|
||||
case 'NSString':
|
||||
case 'NSMutableString':
|
||||
return decodeUTF8(value['NS.bytes'])
|
||||
if (value['NS.string']) return value['NS.string']
|
||||
if (value['NS.bytes']) return decodeUTF8(value['NS.bytes'])
|
||||
console.warn(`Unexpected ${name} format: `, value)
|
||||
return null
|
||||
|
||||
// Replace NSArray with an Array
|
||||
case 'NSArray':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
|
||||
describe('importFromLinuxPerf', async () => {
|
||||
describe('importFromLinuxPerf', () => {
|
||||
test('simple.linux-perf.txt', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/linux-perf/simple.linux-perf.txt')
|
||||
})
|
||||
|
||||
@@ -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,5 @@
|
||||
import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
|
||||
test('importFromSafari', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Safari/13.1/simple.html-recording.json')
|
||||
})
|
||||
@@ -0,0 +1,120 @@
|
||||
import {Profile, FrameInfo, StackListProfileBuilder} from '../lib/profile'
|
||||
import {TimeFormatter} from '../lib/value-formatters'
|
||||
|
||||
interface Record {
|
||||
type: string
|
||||
eventType?: string
|
||||
startTime?: number
|
||||
endTime?: number
|
||||
// timeline-record-type-cpu
|
||||
timestamp?: number
|
||||
usage?: number
|
||||
threads?: any[]
|
||||
// timeline-record-type-script
|
||||
details?: number | string | any
|
||||
extraDetails?: null | any
|
||||
// timeline-record-type-network
|
||||
archiveStartTime?: number
|
||||
entry?: any
|
||||
// timeline-record-type-layout
|
||||
quad?: number[]
|
||||
}
|
||||
|
||||
interface ExprLocation {
|
||||
line: number
|
||||
column: number
|
||||
}
|
||||
|
||||
interface StackFrame {
|
||||
sourceID: string
|
||||
name: string
|
||||
line: number
|
||||
column: number
|
||||
url: string
|
||||
expressionLocation?: ExprLocation
|
||||
}
|
||||
|
||||
interface Sample {
|
||||
timestamp: number
|
||||
stackFrames: StackFrame[]
|
||||
}
|
||||
|
||||
interface Recording {
|
||||
displayName: string
|
||||
startTime: number
|
||||
endTime: number
|
||||
discontinuities: any[]
|
||||
instrumentTypes: string[]
|
||||
records: Record[]
|
||||
markers: any[]
|
||||
memoryPressureEvents: any[]
|
||||
sampleStackTraces: Sample[]
|
||||
sampleDurations: number[]
|
||||
}
|
||||
|
||||
interface Overview {
|
||||
secondsPerPixel: number
|
||||
scrollStartTime: number
|
||||
selectionStartTime: number
|
||||
selectionDuration: number
|
||||
}
|
||||
|
||||
interface SafariProfile {
|
||||
version: number
|
||||
recording: Recording
|
||||
overview: Overview
|
||||
}
|
||||
|
||||
function makeStack(frames: StackFrame[]): FrameInfo[] {
|
||||
return frames
|
||||
.map(({name, url, line, column}) => ({
|
||||
key: `${name}:${url}:${line}:${column}`,
|
||||
file: url,
|
||||
line,
|
||||
col: column,
|
||||
name: name || '(anonymous)',
|
||||
}))
|
||||
.reverse()
|
||||
}
|
||||
|
||||
export function importFromSafari(contents: SafariProfile): Profile | null {
|
||||
if (contents.version !== 1) {
|
||||
console.warn(`Unknown Safari profile version ${contents.version}... Might be incompatible.`)
|
||||
}
|
||||
|
||||
const {recording} = contents
|
||||
const {sampleStackTraces, sampleDurations} = recording
|
||||
|
||||
const count = sampleStackTraces.length
|
||||
if (count < 1) {
|
||||
console.warn('Empty profile')
|
||||
return null
|
||||
}
|
||||
|
||||
const profileDuration =
|
||||
sampleStackTraces[count - 1].timestamp - sampleStackTraces[0].timestamp + sampleDurations[0]
|
||||
const profile = new StackListProfileBuilder(profileDuration)
|
||||
|
||||
let previousEndTime = Number.MAX_VALUE
|
||||
|
||||
sampleStackTraces.forEach((sample, i) => {
|
||||
const endTime = sample.timestamp
|
||||
const duration = sampleDurations[i]
|
||||
const startTime = endTime - duration
|
||||
const idleDurationBefore = startTime - previousEndTime
|
||||
|
||||
// FIXME: 2ms is a lot, but Safari's timestamps and durations don't line up very well and will create
|
||||
// phantom idle time
|
||||
if (idleDurationBefore > 0.002) {
|
||||
profile.appendSampleWithWeight([], idleDurationBefore)
|
||||
}
|
||||
|
||||
profile.appendSampleWithWeight(makeStack(sample.stackFrames), duration)
|
||||
|
||||
previousEndTime = endTime
|
||||
})
|
||||
|
||||
profile.setValueFormatter(new TimeFormatter('seconds'))
|
||||
profile.setName(recording.displayName)
|
||||
return profile.build()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
|
||||
test('importTraceEvents simple', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/simple.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents simple object', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/simple-object.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents multiprocess', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/multiprocess.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents partial json import', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents partial json import trailing comma', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial-trailing-comma.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents partial json import whitespace padding', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/simple-partial-whitespace.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents bad E events', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/too-many-end-events.json')
|
||||
})
|
||||
|
||||
test('importTraceEvents event re-ordering', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/trace-event/must-retain-original-order.json')
|
||||
})
|
||||
@@ -0,0 +1,323 @@
|
||||
import {sortBy, zeroPad, lastOf} from '../lib/utils'
|
||||
import {ProfileGroup, CallTreeProfileBuilder, FrameInfo} from '../lib/profile'
|
||||
import {TimeFormatter} from '../lib/value-formatters'
|
||||
|
||||
// This file concerns import from the "Trace Event Format", authored by Google
|
||||
// and used for Google's own chrome://trace.
|
||||
//
|
||||
// The file format is extremely general, and we only support the parts of it
|
||||
// that logically map onto speedscope's visualization capabilities.
|
||||
// Specifically, we only support the "B", "E", and "X" event types. Everything
|
||||
// else is ignored. We do, however, support import of profiles that are
|
||||
// multi-process/multi-threaded. Each process is split into a separate profile.
|
||||
//
|
||||
// Note that Chrome Developer Tools uses this format as well, but all the
|
||||
// relevant data used in those profiles is stored in events with the name
|
||||
// "CpuProfile", "Profile", or "ProfileChunk". If we detect those, we prioritize
|
||||
// importing the profile as a Chrome Developer Tools profile. Otherwise,
|
||||
// we try to import it as a "Trace Event Format" file.
|
||||
//
|
||||
// Spec: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
|
||||
|
||||
interface TraceEvent {
|
||||
// The process ID for the process that output this event.
|
||||
pid: number
|
||||
|
||||
// The thread ID for the thread that output this event.
|
||||
tid: number
|
||||
|
||||
// The event type. This is a single character which changes depending on the type of event being output. The valid values are listed in the table below. We will discuss each phase type below.
|
||||
ph: string
|
||||
|
||||
// The tracing clock timestamp of the event. The timestamps are provided at microsecond granularity.
|
||||
ts: number
|
||||
|
||||
// The thread clock timestamp of the event. The timestamps are provided at microsecond granularity.
|
||||
tts?: number
|
||||
|
||||
// The name of the event, as displayed in Trace Viewer
|
||||
name?: string
|
||||
|
||||
// The event categories. This is a comma separated list of categories for the event. The categories can be used to hide events in the Trace Viewer UI.
|
||||
cat?: string
|
||||
|
||||
// Any arguments provided for the event. Some of the event types have required argument fields, otherwise, you can put any information you wish in here. The arguments are displayed in Trace Viewer when you view an event in the analysis section.
|
||||
args: any
|
||||
|
||||
// A fixed color name to associate with the event. If provided, cname must be one of the names listed in trace-viewer's base color scheme's reserved color names list
|
||||
cname?: string
|
||||
}
|
||||
|
||||
interface BTraceEvent extends TraceEvent {
|
||||
ph: 'B'
|
||||
}
|
||||
|
||||
interface ETraceEvent extends TraceEvent {
|
||||
ph: 'E'
|
||||
}
|
||||
|
||||
interface XTraceEvent extends TraceEvent {
|
||||
ph: 'X'
|
||||
dur?: number
|
||||
tdur?: number
|
||||
}
|
||||
|
||||
// The trace format supports a number of event types that we ignore.
|
||||
type ImportableTraceEvent = BTraceEvent | ETraceEvent | XTraceEvent
|
||||
type DurationEvent = BTraceEvent | ETraceEvent
|
||||
|
||||
function filterIgnoredEventTypes(events: TraceEvent[]): ImportableTraceEvent[] {
|
||||
const ret: ImportableTraceEvent[] = []
|
||||
for (let ev of events) {
|
||||
switch (ev.ph) {
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'X':
|
||||
ret.push(ev as ImportableTraceEvent)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
function convertToDurationEvents(events: ImportableTraceEvent[]): DurationEvent[] {
|
||||
const ret: DurationEvent[] = []
|
||||
for (let ev of events) {
|
||||
switch (ev.ph) {
|
||||
case 'B':
|
||||
ret.push(ev)
|
||||
break
|
||||
|
||||
case 'E':
|
||||
ret.push(ev)
|
||||
break
|
||||
|
||||
case 'X':
|
||||
let dur: number | null = null
|
||||
if (ev.dur != null) dur = ev.dur
|
||||
else if (ev.tdur != null) dur = ev.tdur
|
||||
|
||||
if (dur == null) {
|
||||
console.warn('Found a complete event (X) with no duration. Skipping: ', ev)
|
||||
continue
|
||||
}
|
||||
|
||||
ret.push({...ev, ph: 'B'} as BTraceEvent)
|
||||
ret.push({...ev, ph: 'E', ts: ev.ts + dur} as ETraceEvent)
|
||||
break
|
||||
|
||||
default:
|
||||
const _exhaustiveCheck: never = ev
|
||||
return _exhaustiveCheck
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
function getProcessNamesByPid(events: TraceEvent[]): Map<number, string> {
|
||||
const processNamesByPid = new Map<number, string>()
|
||||
for (let ev of events) {
|
||||
if (ev.ph === 'M' && ev.name === 'process_name' && ev.args && ev.args.name) {
|
||||
processNamesByPid.set(ev.pid, ev.args.name)
|
||||
}
|
||||
}
|
||||
return processNamesByPid
|
||||
}
|
||||
|
||||
function getThreadNamesByPidTid(events: TraceEvent[]): Map<string, string> {
|
||||
const threadNameByPidTid = new Map<string, string>()
|
||||
|
||||
for (let ev of events) {
|
||||
if (ev.ph === 'M' && ev.name === 'thread_name' && ev.args && ev.args.name) {
|
||||
const key = `${ev.pid}:${ev.tid}`
|
||||
threadNameByPidTid.set(key, ev.args.name)
|
||||
}
|
||||
}
|
||||
return threadNameByPidTid
|
||||
}
|
||||
|
||||
function keyForEvent(event: TraceEvent): string {
|
||||
let name = `${event.name || '(unnamed)'}`
|
||||
if (event.args) {
|
||||
name += ` ${JSON.stringify(event.args)}`
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
type TraceEventProfileState = {profile: CallTreeProfileBuilder; eventStack: BTraceEvent[]}
|
||||
|
||||
function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
|
||||
const stateByPidTid = new Map<string, TraceEventProfileState>()
|
||||
|
||||
const importableEvents = filterIgnoredEventTypes(events)
|
||||
const durationEvents = convertToDurationEvents(importableEvents)
|
||||
|
||||
const processNamesByPid = getProcessNamesByPid(events)
|
||||
const threadNamesByPidTid = getThreadNamesByPidTid(events)
|
||||
|
||||
durationEvents.sort((a, b) => {
|
||||
if (a.ts < b.ts) return -1
|
||||
if (a.ts > b.ts) return 1
|
||||
if (a.pid < b.pid) return -1
|
||||
if (a.pid > b.pid) return 1
|
||||
if (a.tid < b.tid) return -1
|
||||
if (a.tid > b.tid) return 1
|
||||
|
||||
// We have to be careful with events that have the same timestamp
|
||||
// and the same pid/tid
|
||||
const aKey = keyForEvent(a)
|
||||
const bKey = keyForEvent(b)
|
||||
if (aKey === bKey) {
|
||||
// If the two elements have the same key, we need to process the begin
|
||||
// event before the end event. This will be a zero-duration event.
|
||||
if (a.ph === 'B' && b.ph === 'E') return -1
|
||||
if (a.ph === 'E' && b.ph === 'B') return 1
|
||||
} else {
|
||||
// If the two elements have *different* keys, we want to process
|
||||
// the end of an event before the beginning of the event to prevent
|
||||
// out-of-order push/pops from the call-stack.
|
||||
if (a.ph === 'B' && b.ph === 'E') return 1
|
||||
if (a.ph === 'E' && b.ph === 'B') return -1
|
||||
}
|
||||
|
||||
// In all other cases, retain the original sort order.
|
||||
return 0
|
||||
})
|
||||
|
||||
if (durationEvents.length > 0) {
|
||||
const firstTs = durationEvents[0].ts
|
||||
for (let ev of durationEvents) {
|
||||
ev.ts -= firstTs
|
||||
}
|
||||
}
|
||||
|
||||
function getOrCreateProfileState(pid: number, tid: number): TraceEventProfileState {
|
||||
// We zero-pad the PID and TID to make sorting them by pid/tid pair later easier.
|
||||
const pidTid = `${zeroPad('' + pid, 10)}:${zeroPad('' + tid, 10)}`
|
||||
|
||||
let state = stateByPidTid.get(pidTid)
|
||||
if (state != null) return state
|
||||
let profile = new CallTreeProfileBuilder()
|
||||
state = {profile, eventStack: []}
|
||||
profile.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
stateByPidTid.set(pidTid, state)
|
||||
|
||||
const processName = processNamesByPid.get(pid)
|
||||
const threadName = threadNamesByPidTid.get(`${pid}:${tid}`)
|
||||
|
||||
if (processName != null && threadName != null) {
|
||||
profile.setName(`${processName} (pid ${pid}), ${threadName} (tid ${tid})`)
|
||||
} else if (processName != null) {
|
||||
profile.setName(`${processName} (pid ${pid}, tid ${tid})`)
|
||||
} else if (threadName != null) {
|
||||
profile.setName(`${threadName} (pid ${pid}, tid ${tid})`)
|
||||
} else {
|
||||
profile.setName(`pid ${pid}, tid ${tid}`)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
for (let ev of durationEvents) {
|
||||
const {profile, eventStack} = getOrCreateProfileState(ev.pid, ev.tid)
|
||||
const key = keyForEvent(ev)
|
||||
const frameInfo: FrameInfo = {
|
||||
key: key,
|
||||
name: key,
|
||||
}
|
||||
switch (ev.ph) {
|
||||
case 'B':
|
||||
eventStack.push(ev)
|
||||
profile.enterFrame(frameInfo, ev.ts)
|
||||
break
|
||||
|
||||
case 'E':
|
||||
const lastEvent = lastOf(eventStack)
|
||||
if (lastEvent != null && lastEvent.name === ev.name) {
|
||||
profile.leaveFrame(frameInfo, ev.ts)
|
||||
eventStack.pop()
|
||||
} else {
|
||||
console.warn(
|
||||
'Event discarded because it did not match top-of-stack. Discarded event:',
|
||||
ev,
|
||||
'Top of stack:',
|
||||
lastEvent,
|
||||
)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
const _exhaustiveCheck: never = ev
|
||||
return _exhaustiveCheck
|
||||
}
|
||||
}
|
||||
|
||||
// For now, we just sort processes by pid & tid.
|
||||
// TODO: The standard specifies that metadata events with the name
|
||||
// "process_sort_index" and "thread_sort_index" can be used to influence the
|
||||
// order, but for simplicity we'll ignore that until someone complains :)
|
||||
const profilePairs = Array.from(stateByPidTid.entries())
|
||||
sortBy(profilePairs, p => p[0])
|
||||
|
||||
return {name: '', indexToView: 0, profiles: profilePairs.map(p => p[1].profile)}
|
||||
}
|
||||
|
||||
function isTraceEventList(maybeEventList: any): maybeEventList is TraceEvent[] {
|
||||
if (!Array.isArray(maybeEventList)) return false
|
||||
if (maybeEventList.length === 0) return false
|
||||
|
||||
// Both ph and ts should be provided for every event. In theory, many other
|
||||
// fields are mandatory, but without these fields, we won't usefully be able
|
||||
// to import the data, so we'll rely upon these.
|
||||
for (let el of maybeEventList) {
|
||||
if (!('ph' in el)) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (el.ph) {
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'X':
|
||||
// All B, E, and X events must have a timestamp specified, otherwise we
|
||||
// won't be able to import correctly.
|
||||
if (!('ts' in el)) {
|
||||
return false
|
||||
}
|
||||
|
||||
case 'M':
|
||||
// It's explicitly okay for "M" (metadata) events not to specify a "ts"
|
||||
// field, since usually there is no logical timestamp for them to have
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isTraceEventObject(
|
||||
maybeTraceEventObject: any,
|
||||
): maybeTraceEventObject is {traceEvents: TraceEvent[]} {
|
||||
if (!('traceEvents' in maybeTraceEventObject)) return false
|
||||
return isTraceEventList(maybeTraceEventObject['traceEvents'])
|
||||
}
|
||||
|
||||
export function isTraceEventFormatted(
|
||||
rawProfile: any,
|
||||
): rawProfile is {traceEvents: TraceEvent[]} | TraceEvent[] {
|
||||
// We're only going to support the JSON formatted profiles for now.
|
||||
// The spec also discusses support for data embedded in ftrace supported data: https://lwn.net/Articles/365835/.
|
||||
|
||||
return isTraceEventObject(rawProfile) || isTraceEventList(rawProfile)
|
||||
}
|
||||
|
||||
export function importTraceEvents(
|
||||
rawProfile: {traceEvents: TraceEvent[]} | TraceEvent[],
|
||||
): ProfileGroup {
|
||||
if (isTraceEventObject(rawProfile)) {
|
||||
return eventListToProfileGroup(rawProfile.traceEvents)
|
||||
} else if (isTraceEventList(rawProfile)) {
|
||||
return eventListToProfileGroup(rawProfile)
|
||||
} else {
|
||||
const _exhaustiveCheck: never = rawProfile
|
||||
return _exhaustiveCheck
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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()
|
||||
|
||||
// By default, we assume the file is utf-8 encoded.
|
||||
let encoding = 'utf-8'
|
||||
|
||||
const array = new Uint8Array(buffer)
|
||||
if (array.length > 2) {
|
||||
if (array[0] === 0xff && array[1] === 0xfe) {
|
||||
// UTF-16, Little Endian encoding
|
||||
encoding = 'utf-16le'
|
||||
} else if (array[0] === 0xfe && array[1] === 0xff) {
|
||||
// UTF-16, Big Endian encoding
|
||||
encoding = 'utf-16be'
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
const decoder = new TextDecoder(encoding)
|
||||
return decoder.decode(buffer)
|
||||
} else {
|
||||
// JavaScript strings are UTF-16 encoded, but we're reading data from disk
|
||||
// that we're going to blindly assume it's ASCII encoded. This codepath
|
||||
// only exists for older browser support.
|
||||
console.warn('This browser does not support TextDecoder. Decoding text as ASCII.')
|
||||
let ret: string = ''
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import {CPUProfile, CPUProfileNode} from './chrome'
|
||||
|
||||
/**
|
||||
* This importer handles an old format used by the C++ API of V8. This format is still used by v8-profiler-node8.
|
||||
* There are two differences between the two formats:
|
||||
* - Nodes are a tree in the old format and a flat array in the new format
|
||||
* - Weights are timestamps in the old format and deltas in the new format.
|
||||
*
|
||||
* For more information, see https://github.com/hyj1991/v8-profiler-node8
|
||||
*/
|
||||
|
||||
interface OldCPUProfileNode {
|
||||
functionName: string
|
||||
lineNumber: number
|
||||
scriptId: string
|
||||
url: string
|
||||
hitCount: number
|
||||
bailoutReason: string
|
||||
id: number
|
||||
children: OldCPUProfileNode[]
|
||||
}
|
||||
|
||||
export interface OldCPUProfile {
|
||||
startTime: number
|
||||
endTime: number
|
||||
head: OldCPUProfileNode
|
||||
samples: number[]
|
||||
timestamps: number[]
|
||||
}
|
||||
|
||||
function treeToArray(root: OldCPUProfileNode): CPUProfileNode[] {
|
||||
const nodes: CPUProfileNode[] = []
|
||||
function visit(node: OldCPUProfileNode) {
|
||||
nodes.push({
|
||||
id: node.id,
|
||||
callFrame: {
|
||||
columnNumber: 0,
|
||||
functionName: node.functionName,
|
||||
lineNumber: node.lineNumber,
|
||||
scriptId: node.scriptId,
|
||||
url: node.url,
|
||||
},
|
||||
hitCount: node.hitCount,
|
||||
children: node.children.map(child => child.id),
|
||||
})
|
||||
node.children.forEach(visit)
|
||||
}
|
||||
visit(root)
|
||||
return nodes
|
||||
}
|
||||
|
||||
function timestampsToDeltas(timestamps: number[], startTime: number): number[] {
|
||||
return timestamps.map((timestamp, index) => {
|
||||
const lastTimestamp = index === 0 ? startTime * 1000000 : timestamps[index - 1]
|
||||
return timestamp - lastTimestamp
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the old tree-based format to the new flat-array based format
|
||||
*/
|
||||
export function chromeTreeToNodes(content: OldCPUProfile): CPUProfile {
|
||||
// Note that both startTime and endTime are now in microseconds
|
||||
return {
|
||||
samples: content.samples,
|
||||
startTime: content.startTime * 1000000,
|
||||
endTime: content.endTime * 1000000,
|
||||
nodes: treeToArray(content.head),
|
||||
timeDeltas: timestampsToDeltas(content.timestamps, content.startTime),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import {checkProfileSnapshot} from '../lib/test-utils'
|
||||
|
||||
test('importV8HeapAlloc from Chrome', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/Chrome/69/Heap-20181005T144546.heapprofile')
|
||||
})
|
||||
|
||||
test('importV8HeapAlloc from NodeJS', async () => {
|
||||
await checkProfileSnapshot('./sample/profiles/node/10.11.0/Heap-20181003T105432.heapprofile')
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
import {Profile, FrameInfo, StackListProfileBuilder} from '../lib/profile'
|
||||
import {getOrInsert} from '../lib/utils'
|
||||
import {ByteFormatter} from '../lib/value-formatters'
|
||||
|
||||
/**
|
||||
* The V8 Heap Allocation profile is a way to represent heap allocation for each
|
||||
* javascript function. The format is a simple tree where the weight of each node
|
||||
* represent the memory allocated by the function and all its callee.
|
||||
* You can find more information on how to get a profile there :
|
||||
* https://developers.google.com/web/tools/chrome-devtools/memory-problems/#allocation-profile
|
||||
* You need to scroll down to "Investigate memory allocation by function"
|
||||
*
|
||||
* Note that Node.JS can retrieve this kind of profile via the Inspector protocol.
|
||||
*/
|
||||
|
||||
interface HeapProfileCallFrame {
|
||||
columnNumber: number
|
||||
functionName: string
|
||||
lineNumber: number
|
||||
scriptId: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface HeapProfileNode {
|
||||
callFrame: HeapProfileCallFrame
|
||||
selfSize: number
|
||||
children: HeapProfileNode[]
|
||||
id: number
|
||||
parent?: number
|
||||
totalSize: number
|
||||
}
|
||||
|
||||
interface HeapProfile {
|
||||
head: HeapProfileNode
|
||||
}
|
||||
|
||||
const callFrameToFrameInfo = new Map<HeapProfileCallFrame, FrameInfo>()
|
||||
function frameInfoForCallFrame(callFrame: HeapProfileCallFrame) {
|
||||
return getOrInsert(callFrameToFrameInfo, callFrame, callFrame => {
|
||||
const name = callFrame.functionName || '(anonymous)'
|
||||
const file = callFrame.url
|
||||
const line = callFrame.lineNumber
|
||||
const col = callFrame.columnNumber
|
||||
return {
|
||||
key: `${name}:${file}:${line}:${col}`,
|
||||
name,
|
||||
file,
|
||||
line,
|
||||
col,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function importFromChromeHeapProfile(chromeProfile: HeapProfile): Profile {
|
||||
const nodeById = new Map<number, HeapProfileNode>()
|
||||
let currentId = 0
|
||||
const computeId = (node: HeapProfileNode, parent?: HeapProfileNode) => {
|
||||
node.id = currentId++
|
||||
nodeById.set(node.id, node)
|
||||
if (parent) {
|
||||
node.parent = parent.id
|
||||
}
|
||||
|
||||
node.children.forEach(children => computeId(children, node))
|
||||
}
|
||||
computeId(chromeProfile.head)
|
||||
|
||||
// Compute the total size
|
||||
const computeTotalSize = (node: HeapProfileNode): number => {
|
||||
if (node.children.length === 0) return node.selfSize || 0
|
||||
const totalChild = node.children.reduce((total: number, children) => {
|
||||
total += computeTotalSize(children)
|
||||
return total
|
||||
}, node.selfSize)
|
||||
node.totalSize = totalChild
|
||||
return totalChild
|
||||
}
|
||||
const total = computeTotalSize(chromeProfile.head)
|
||||
|
||||
// Compute all stacks by taking each last node and going upward
|
||||
const stacks: HeapProfileNode[][] = []
|
||||
for (let currentNode of nodeById.values()) {
|
||||
let stack: HeapProfileNode[] = []
|
||||
stack.push(currentNode)
|
||||
// While we found a parent
|
||||
while (true) {
|
||||
if (currentNode.parent === undefined) break
|
||||
const parent = nodeById.get(currentNode.parent)
|
||||
if (parent === undefined) break
|
||||
// Push the parent at the beginning of the stack
|
||||
stack.unshift(parent)
|
||||
currentNode = parent
|
||||
}
|
||||
stacks.push(stack)
|
||||
}
|
||||
|
||||
const profile = new StackListProfileBuilder(total)
|
||||
|
||||
for (let stack of stacks) {
|
||||
const lastFrame = stack[stack.length - 1]
|
||||
profile.appendSampleWithWeight(
|
||||
stack.map(frame => frameInfoForCallFrame(frame.callFrame)),
|
||||
lastFrame.selfSize,
|
||||
)
|
||||
}
|
||||
|
||||
profile.setValueFormatter(new ByteFormatter())
|
||||
return profile.build()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user