Compare commits

...
8 Commits
Author SHA1 Message Date
Jamie Wong 0414c2f617 1.15.1 2023-06-04 04:14:07 -07:00
Jamie Wong f23f65b3af Callgrind: Subposition compression and weight correction (#423)
This fixes a number of bugs with callgrind import. Dealing with this file format is a big pain because the documentation on https://www.valgrind.org/docs/manual/cl-format.html doesn't contain enough examples to disambiguate some of the behaviour, and because there's a fundamental impedance mismatch between call-trees and call-graphs.

In any case, after this PR, the behavior of callgrind file import is much better.
The file provided in #414 now imports correctly and, as far as I can tell, displays the same weights as what I see in KCacheGrind.

Some of the key changes:
- Implementing subposition compression. This was just a TODO in the code that was never implemented
- Fixing a misinterpretation of how `fe` and `fi` were intended to be used. Previously, I was using it to change the filename of a symbol, meaning that an `fi` or an `fe` line in the middle of a block describing costs for an `fn` would split a node in the call-graph into multiple nodes causing all manners of problems
- Fixing a bug where `cfn` was persisting beyond a single call, also resulting in call graph nodes being split when they shouldn't be

Fixes #414
2023-06-04 04:06:22 -07:00
Jamie Wong 8da9088ec1 Fix import from Chrome Devtools performance tab in Chrome >= 114 (#422)
The file format uses by Chrome Devtools performance tab periodically changes. It uses the Chrome trace event format (https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview).

This format, however, has two different types: one is `TraceEvent[]`, the other is `{traceEvents: traceEvent[]}`. The importer for non-Chrome devtools profiles already handled this, but the one for Chrome Devtools didn't because Chrome < 114 never used it. It seems like they changed the file format. This PR addresses that change.

Fixes #420
2023-06-03 22:35:13 -07:00
Jamie Wong 81a6f29ad1 1.15.0 2022-10-22 00:18:24 +08:00
Jamie Wong 263f7d513e Update package.json to use upstream version of uint8array-json-parser (#408)
In #385, I introduced a dependency on the `uint8array-json-parser` npm package, but used a fork because of a typescript error. This was resolved in evanw/uint8array-json-parser#1 and published as part of `uint8array-json-parser@0.0.2`. Let's use the upstream.

This also conveniently fixes a new typechecking error that was preventing deployment. The error looked like this:

```
src/import/utils.ts(2,26): error TS2306: File '\''/Users/jlfwong/code/speedscope/node_modules/uint8array-json-parser/uint8array-json-parser.ts'\'' is not a module.'
```

After updating to the upstream, the problem is fixed.
2022-10-22 00:12:45 +08:00
Jamie Wong 1bce806933 Replace fuzzy matching with exact substring matching for finding matching frames (#407)
In #297, I re-used the fuzzy matching logic I implemented in #282 for profile selection. Based on feedback from several people in #352, this is surprising behavior.

Upon reflection, this should have been obvious -- I hijacked the Ctrl/Cmd+F browser behaviour, so I should try to replicate the expected behaviour there as closely as possible. Given more patience, I also would've done some user research :)

This PR updates this logic to try to more closely match browser behaviour. This means case-insensitive, exact-substring matching.

I've left the fuzzy matching alone for profile selection since that doesn't attempt to mimic browser behaviour.

The non-fuzzy matching feels slightly odd to me given the filtering behaviour on the sandwich view, but I think consistency across this find UI is important.

Here are the before & after results when searching for the string "ca" in the example profile.

|Before|After|
|-|-|
|<img width="1791" alt="image" src="https://user-images.githubusercontent.com/150329/197232741-6d1d7a8a-8b8c-4a4f-98e3-2c043fd7efd5.png">|<img width="1789" alt="image" src="https://user-images.githubusercontent.com/150329/197232694-82697b68-ca15-49e7-887b-2606646ee5e9.png">|

Fixes #352 
Supersedes #403
2022-10-21 23:53:46 +08:00
Jamie Wong 6493c5f66f Update deploy script to python3 2022-07-30 23:20:36 -07:00
Evan Wallace 639dae322b Add support for cycle-based Instruments deep copy (#400)
Unlike the Time Profiler, the CPU Profiler in Instruments use `cycles` for units instead of `ms`:

<img width="872" src="https://user-images.githubusercontent.com/406394/175755999-289cb7c0-f29a-44b1-b00e-b55ef17ee303.png">

Currently Speedscope fails to import the data with the following error in the console:

```
Failed to load format Error: Unrecognized units Gc
```

This PR adds support for `cycles` as a unit to the Instruments deep copy importer as well as `Kc`, `Mc`, and `Gc`, which I'm assuming are increasing in multiples of 1000. Hopefully I've added support for this correctly and this PR is helpful.
2022-07-02 22:01:08 -04:00
24 changed files with 4157 additions and 87 deletions
+13
View File
@@ -1,3 +1,16 @@
## [1.15.1] - 2023-06-04
### Fixed
- Fix import from Chrome Devtools performance tab in Chrome >= 114 [[#422](https://github.com/jlfwong/speedscope/pull/422)]
- Callgrind: Subposition compression and weight correction [[#423](https://github.com/jlfwong/speedscope/pull/423)]
## [1.15.0] - 2022-10-22
### Fixed
- Replace fuzzy matching with exact substring matching for finding matching frames [[#407](https://github.com/jlfwong/speedscope/pull/407)]
## [1.14.0] - 2022-05-19
### Added
+13 -10
View File
@@ -1,11 +1,12 @@
{
"name": "speedscope",
"version": "1.13.0",
"version": "1.15.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.13.0",
"name": "speedscope",
"version": "1.15.0",
"license": "MIT",
"dependencies": {
"open": "7.2.0"
@@ -38,7 +39,7 @@
"typescript": "4.2.3",
"typescript-json-schema": "0.42.0",
"uglify-es": "3.2.2",
"uint8array-json-parser": "jlfwong/uint8array-json-parser#edce51ce"
"uint8array-json-parser": "0.0.2"
}
},
"node_modules/@babel/code-frame": {
@@ -5843,6 +5844,7 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz",
"integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
@@ -14095,10 +14097,10 @@
}
},
"node_modules/uint8array-json-parser": {
"version": "0.0.1",
"resolved": "git+ssh://git@github.com/jlfwong/uint8array-json-parser.git#b4eb100aa3107b5829980171e1dee7d8bf35afe2",
"dev": true,
"license": "MIT"
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/uint8array-json-parser/-/uint8array-json-parser-0.0.2.tgz",
"integrity": "sha512-Cp5ycrI17rzdAkybRfZydr6XdLmOUfhm2jS1i35p3UY2G8TcT7fL+hmFY3zjMGNtoS8uSpdTS7NjiHh+LY/7Bg==",
"dev": true
},
"node_modules/uncss": {
"version": "0.17.3",
@@ -26921,9 +26923,10 @@
}
},
"uint8array-json-parser": {
"version": "git+ssh://git@github.com/jlfwong/uint8array-json-parser.git#b4eb100aa3107b5829980171e1dee7d8bf35afe2",
"dev": true,
"from": "uint8array-json-parser@jlfwong/uint8array-json-parser#edce51ce"
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/uint8array-json-parser/-/uint8array-json-parser-0.0.2.tgz",
"integrity": "sha512-Cp5ycrI17rzdAkybRfZydr6XdLmOUfhm2jS1i35p3UY2G8TcT7fL+hmFY3zjMGNtoS8uSpdTS7NjiHh+LY/7Bg==",
"dev": true
},
"uncss": {
"version": "0.17.3",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "speedscope",
"version": "1.14.0",
"version": "1.15.1",
"description": "",
"repository": "jlfwong/speedscope",
"main": "index.js",
@@ -54,7 +54,7 @@
"typescript": "4.2.3",
"typescript-json-schema": "0.42.0",
"uglify-es": "3.2.2",
"uint8array-json-parser": "jlfwong/uint8array-json-parser#edce51ce"
"uint8array-json-parser": "0.0.2"
},
"jest": {
"transform": {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,46 @@
Weight Self Weight Symbol Name
96.08 Gc 100.0% - example_app (40414)
96.08 Gc 100.0% - start
96.08 Gc 100.0% - main
96.08 Gc 100.0% - std::rt::lang_start::hdba6f1ebfd1bdcf8
96.08 Gc 100.0% - std::rt::lang_start_internal::hc453db0ee48af82e
96.08 Gc 100.0% - std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c
96.08 Gc 100.0% - std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc
96.08 Gc 100.0% - core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8
96.08 Gc 100.0% - example_app::main::hddab13ae8d8b6a6e
47.82 Gc 49.7% - example_app::example_a::h622d6879b734496d
46.92 Gc 48.8% 404.75 Mc std::time::Instant::elapsed::h2e3793148fc23529
45.92 Gc 47.7% 45.92 Gc mach_absolute_time
600.78 Mc 0.6% 600.78 Mc std::time::Instant::elapsed::h2e3793148fc23529
1.00 Mc 0.0% 1.00 Mc DYLD-STUB$$mach_absolute_time
634.35 Mc 0.6% 634.35 Mc core::time::Duration::as_millis::hae6ad1b9cf7bb5ec
181.50 Mc 0.1% 181.50 Mc example_app::example_a::h622d6879b734496d
84.45 Mc 0.0% - example_app::example_b::h71b31fcd89b9ffd2
81.45 Mc 0.0% - std::time::Instant::elapsed::h2e3793148fc23529
80.45 Mc 0.0% 80.45 Mc mach_absolute_time
1.00 Mc 0.0% 1.00 Mc std::time::Instant::elapsed::h2e3793148fc23529
1.00 Mc 0.0% - example_app::example_c::h631759c10d7dee93
1.00 Mc 0.0% - alloc::vec::Vec$LT$T$C$A$GT$::push::ha3426502ffc42c8b
1.00 Mc 0.0% - alloc::raw_vec::RawVec$LT$T$C$A$GT$::reserve_for_push::he4a29b8274e35dbb
1.00 Mc 0.0% - alloc::raw_vec::RawVec$LT$T$C$A$GT$::grow_amortized::h78db9ba423623c58
1.00 Mc 0.0% - alloc::raw_vec::finish_grow::h876f5af1c66d74c8
1.00 Mc 0.0% - _$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::hcc733a40a34fbc94
1.00 Mc 0.0% - alloc::alloc::Global::alloc_impl::hc55feba7d6266dfa
1.00 Mc 0.0% - alloc::alloc::alloc::hfac63f3d6850b759
1.00 Mc 0.0% - _malloc_zone_malloc
1.00 Mc 0.0% - nanov2_malloc
1.00 Mc 0.0% - nanov2_allocate
1.00 Mc 0.0% - nanov2_allocate
1.00 Mc 0.0% 1.00 Mc nanov2_find_block_and_allocate
1.00 Mc 0.0% 1.00 Mc example_app::example_b::h71b31fcd89b9ffd2
1.00 Mc 0.0% 1.00 Mc core::time::Duration::as_millis::hae6ad1b9cf7bb5ec
47.45 Gc 49.3% 415.12 Mc std::time::Instant::elapsed::h2e3793148fc23529
46.39 Gc 48.2% 46.39 Gc mach_absolute_time
649.70 Mc 0.6% 649.70 Mc std::time::Instant::elapsed::h2e3793148fc23529
614.22 Mc 0.6% 614.22 Mc core::time::Duration::as_millis::hae6ad1b9cf7bb5ec
190.55 Mc 0.1% 190.55 Mc example_app::main::hddab13ae8d8b6a6e
1.34 Kc 0.0% - std::io::stdio::_print::hdecfefdeb43586ed
1.34 Kc 0.0% - _$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h2cb06dcd2b172844
1.34 Kc 0.0% - core::fmt::write::hed96bcfc6342aee5
532 cycles 0.0% - _$LT$std..io..Write..write_fmt..Adapter$LT$T$GT$$u20$as$u20$core..fmt..Write$GT$::write_str::hbe33ec23de24ce2f
532 cycles 0.0% 532 cycles _$LT$std..io..stdio..StdoutLock$u20$as$u20$std..io..Write$GT$::write_all::h22667d0a03b2151b
@@ -0,0 +1,30 @@
# callgrind format
events: Instructions
fl=alpha.c
fn=alpha
1 10
cfl=beta.c
cfn=beta
calls=1 1
1 10
cfn=gamma
calls=1 1
1 10
cfn=delta
calls=1 1
1 20
fn=delta
1 10
cfn=gamma
calls=1 1
1 10
fn=gamma
1 10
cfl=
fl=beta.c
fn=beta
1 10
@@ -18,7 +18,7 @@ calls=3 51
16 400 3000
fn=(2)
51 100 4000
51 100 15000
cfi=(2)
cfn=(3)
calls=2 20
@@ -26,4 +26,4 @@ calls=2 20
fl=(2)
fn=(3)
20 700 6000
20 700 8000
@@ -0,0 +1,24 @@
# callgrind format
events: Instructions
fl=file1.c
fn=main
16 20
cfn=func1
calls=1 50
* 400
cfi=file2.c
cfn=func2
calls=3 20
* *
fn=func1
+35 -300
cfi=file2.c
cfn=func2
calls=2 20
* 300
fl=file2.c
fn=func2
-31 +400
+1 -1
View File
@@ -58,5 +58,5 @@ echo "Build complete. Starting server on http://localhost:4444/"
echo "Hit Ctrl+C to complete or cancel the release"
echo
echo
python -m SimpleHTTPServer 4444 .
python3 -m http.server 4444
set +x
+105 -5
View File
@@ -41,6 +41,61 @@ Object {
}
`;
exports[`importFromCallgrind cfn reset 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": "alpha.c",
"key": "alpha.c:alpha",
"line": undefined,
"name": "alpha",
"selfWeight": 10,
"totalWeight": 50,
},
Frame {
"col": undefined,
"file": "beta.c",
"key": "beta.c:beta",
"line": undefined,
"name": "beta",
"selfWeight": 10,
"totalWeight": 10,
},
Frame {
"col": undefined,
"file": "alpha.c",
"key": "alpha.c:gamma",
"line": undefined,
"name": "gamma",
"selfWeight": 20,
"totalWeight": 20,
},
Frame {
"col": undefined,
"file": "alpha.c",
"key": "alpha.c:delta",
"line": undefined,
"name": "delta",
"selfWeight": 10,
"totalWeight": 20,
},
],
"name": "callgrind.cfn-reset.log -- Instructions",
"stacks": Array [
"alpha;beta 10",
"alpha;gamma 10",
"alpha;delta;gamma 10",
"alpha;delta 10",
"alpha 10",
],
}
`;
exports[`importFromCallgrind cfn reset: indexToView 1`] = `0`;
exports[`importFromCallgrind cfn reset: profileGroup.name 1`] = `"callgrind.cfn-reset.log"`;
exports[`importFromCallgrind multiple event types 1`] = `
Object {
"frames": Array [
@@ -100,7 +155,7 @@ Object {
"key": "file1.c:func1",
"line": undefined,
"name": "func1",
"selfWeight": 8888.888888888889,
"selfWeight": 15000,
"totalWeight": 20000,
},
Frame {
@@ -109,14 +164,14 @@ Object {
"key": "file2.c:func2",
"line": undefined,
"name": "func2",
"selfWeight": 14111.111111111111,
"totalWeight": 14111.111111111111,
"selfWeight": 8000,
"totalWeight": 8000,
},
],
"name": "callgrind.multiple-event-types.log -- Memory",
"stacks": Array [
"main;func1;func2 10.85 KB",
"main;func1 8.68 KB",
"main;func1;func2 4.88 KB",
"main;func1 14.65 KB",
"main;func2 2.93 KB",
"main 14.65 KB",
],
@@ -172,6 +227,51 @@ exports[`importFromCallgrind name compression: indexToView 1`] = `0`;
exports[`importFromCallgrind name compression: profileGroup.name 1`] = `"callgrind.name-compression.log"`;
exports[`importFromCallgrind subposition compression 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": "file1.c",
"key": "file1.c:main",
"line": undefined,
"name": "main",
"selfWeight": 20,
"totalWeight": 820,
},
Frame {
"col": undefined,
"file": "file1.c",
"key": "file1.c:func1",
"line": undefined,
"name": "func1",
"selfWeight": 100,
"totalWeight": 400,
},
Frame {
"col": undefined,
"file": "file2.c",
"key": "file2.c:func2",
"line": undefined,
"name": "func2",
"selfWeight": 700,
"totalWeight": 700,
},
],
"name": "callgrind.subposition-compression.log -- Instructions",
"stacks": Array [
"main;func1;func2 300",
"main;func1 100",
"main;func2 400",
"main 20",
],
}
`;
exports[`importFromCallgrind subposition compression: indexToView 1`] = `0`;
exports[`importFromCallgrind subposition compression: profileGroup.name 1`] = `"callgrind.subposition-compression.log"`;
exports[`importFromCallgrind: indexToView 1`] = `0`;
exports[`importFromCallgrind: profileGroup.name 1`] = `"callgrind.example.log"`;
@@ -515,6 +515,192 @@ exports[`importFromChromeTimeline Chrome 69: indexToView 1`] = `0`;
exports[`importFromChromeTimeline Chrome 69: profileGroup.name 1`] = `"simple.json"`;
exports[`importFromChromeTimeline Chrome 116 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "(program):undefined:undefined:undefined",
"line": undefined,
"name": "(program)",
"selfWeight": 9312,
"totalWeight": 9312,
},
Frame {
"col": 1,
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
"key": "(anonymous simple.js:1):file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:1:1",
"line": 1,
"name": "(anonymous simple.js:1)",
"selfWeight": 0,
"totalWeight": 25866,
},
Frame {
"col": 15,
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
"key": "alpha:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:1:15",
"line": 1,
"name": "alpha",
"selfWeight": 426,
"totalWeight": 25866,
},
Frame {
"col": 14,
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
"key": "beta:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:8:14",
"line": 8,
"name": "beta",
"selfWeight": 8028,
"totalWeight": 12703,
},
Frame {
"col": 15,
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
"key": "gamma:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:20:15",
"line": 20,
"name": "gamma",
"selfWeight": 8593,
"totalWeight": 8593,
},
Frame {
"col": 15,
"file": "file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js",
"key": "delta:file:///Users/jlfwong/code/speedscope/sample/programs/javascript/simple.js:14:15",
"line": 14,
"name": "delta",
"selfWeight": 8169,
"totalWeight": 12737,
},
],
"name": "Trace-20230603T221323.json",
"stacks": Array [
" 4.39ms",
"(program) 7.71ms",
" 6.51ms",
"(program) 267.00µs",
" 122.00µs",
"(program) 338.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 297.00µs",
"(anonymous simple.js:1);alpha 155.00µs",
"(anonymous simple.js:1);alpha;delta 144.00µs",
"(anonymous simple.js:1);alpha;beta 115.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 145.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 651.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 267.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 351.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 603.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 132.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 259.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 129.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 130.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 130.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 254.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 256.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 258.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 128.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 107.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 134.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 132.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 241.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 128.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 129.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 259.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 131.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 131.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 129.00µs",
"(anonymous simple.js:1);alpha;delta 130.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 133.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 255.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 250.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 137.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 137.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 126.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 116.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 540.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 107.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 108.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 118.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 134.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 139.00µs",
"(anonymous simple.js:1);alpha 134.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 135.00µs",
"(anonymous simple.js:1);alpha 137.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 275.00µs",
"(anonymous simple.js:1);alpha;delta;gamma 110.00µs",
"(anonymous simple.js:1);alpha;beta;gamma 262.00µs",
"(anonymous simple.js:1);alpha;delta 246.00µs",
"(anonymous simple.js:1);alpha;beta 259.00µs",
"(anonymous simple.js:1);alpha;delta 129.00µs",
"(anonymous simple.js:1);alpha;beta 129.00µs",
"(anonymous simple.js:1);alpha;delta 241.00µs",
"(anonymous simple.js:1);alpha;beta 611.00µs",
"(anonymous simple.js:1);alpha;delta 133.00µs",
"(anonymous simple.js:1);alpha;beta 354.00µs",
"(anonymous simple.js:1);alpha;delta 226.00µs",
"(anonymous simple.js:1);alpha;beta 226.00µs",
"(anonymous simple.js:1);alpha;delta 106.00µs",
"(anonymous simple.js:1);alpha;beta 125.00µs",
"(anonymous simple.js:1);alpha;delta 368.00µs",
"(anonymous simple.js:1);alpha;beta 496.00µs",
"(anonymous simple.js:1);alpha;delta 129.00µs",
"(anonymous simple.js:1);alpha;beta 107.00µs",
"(anonymous simple.js:1);alpha;delta 387.00µs",
"(anonymous simple.js:1);alpha;beta 130.00µs",
"(anonymous simple.js:1);alpha;delta 129.00µs",
"(anonymous simple.js:1);alpha;beta 258.00µs",
"(anonymous simple.js:1);alpha;delta 239.00µs",
"(anonymous simple.js:1);alpha;beta 446.00µs",
"(anonymous simple.js:1);alpha;delta 494.00µs",
"(anonymous simple.js:1);alpha;beta 132.00µs",
"(anonymous simple.js:1);alpha;delta 264.00µs",
"(anonymous simple.js:1);alpha;beta 245.00µs",
"(anonymous simple.js:1);alpha;delta 129.00µs",
"(anonymous simple.js:1);alpha;beta 129.00µs",
"(anonymous simple.js:1);alpha;delta 260.00µs",
"(anonymous simple.js:1);alpha;beta 128.00µs",
"(anonymous simple.js:1);alpha;delta 129.00µs",
"(anonymous simple.js:1);alpha;beta 258.00µs",
"(anonymous simple.js:1);alpha;delta 130.00µs",
"(anonymous simple.js:1);alpha;beta 258.00µs",
"(anonymous simple.js:1);alpha;delta 129.00µs",
"(anonymous simple.js:1);alpha;beta 130.00µs",
"(anonymous simple.js:1);alpha;delta 260.00µs",
"(anonymous simple.js:1);alpha;beta 130.00µs",
"(anonymous simple.js:1);alpha;delta 128.00µs",
"(anonymous simple.js:1);alpha;beta 275.00µs",
"(anonymous simple.js:1);alpha;delta 241.00µs",
"(anonymous simple.js:1);alpha;beta 258.00µs",
"(anonymous simple.js:1);alpha;delta 257.00µs",
"(anonymous simple.js:1);alpha;beta 258.00µs",
"(anonymous simple.js:1);alpha;delta 137.00µs",
"(anonymous simple.js:1);alpha;beta 405.00µs",
"(anonymous simple.js:1);alpha;delta 937.00µs",
"(anonymous simple.js:1);alpha;beta 269.00µs",
"(anonymous simple.js:1);alpha;delta 134.00µs",
"(anonymous simple.js:1);alpha;beta 269.00µs",
"(anonymous simple.js:1);alpha;delta 136.00µs",
"(anonymous simple.js:1);alpha;beta 272.00µs",
"(anonymous simple.js:1);alpha;delta 133.00µs",
"(anonymous simple.js:1);alpha;beta 542.00µs",
"(anonymous simple.js:1);alpha;delta 945.00µs",
"(anonymous simple.js:1);alpha;beta 270.00µs",
"(anonymous simple.js:1);alpha;delta 406.00µs",
"(anonymous simple.js:1);alpha;beta 409.00µs",
"(anonymous simple.js:1);alpha;delta 140.00µs",
"(anonymous simple.js:1);alpha;beta 135.00µs",
"(anonymous simple.js:1);alpha;delta 173.00µs",
"(anonymous simple.js:1);alpha;delta;(program) 650.00µs",
" 211.00µs",
"(program) 348.00µs",
],
}
`;
exports[`importFromChromeTimeline Chrome 116: indexToView 1`] = `0`;
exports[`importFromChromeTimeline Chrome 116: profileGroup.name 1`] = `"Trace-20230603T221323.json"`;
exports[`importFromChromeTimeline Workers Chrome 66 1`] = `
Object {
"frames": Array [
@@ -99,10 +99,332 @@ Object {
}
`;
exports[`importFromInstrumentsDeepCopy allocations profile 2`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": ":example_app (40414)",
"line": undefined,
"name": "example_app (40414)",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":start",
"line": undefined,
"name": "start",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":main",
"line": undefined,
"name": "main",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":std::rt::lang_start::hdba6f1ebfd1bdcf8",
"line": undefined,
"name": "std::rt::lang_start::hdba6f1ebfd1bdcf8",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":std::rt::lang_start_internal::hc453db0ee48af82e",
"line": undefined,
"name": "std::rt::lang_start_internal::hc453db0ee48af82e",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c",
"line": undefined,
"name": "std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc",
"line": undefined,
"name": "std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8",
"line": undefined,
"name": "core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8",
"selfWeight": 0,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":example_app::main::hddab13ae8d8b6a6e",
"line": undefined,
"name": "example_app::main::hddab13ae8d8b6a6e",
"selfWeight": 1385999000,
"totalWeight": 96000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":example_app::example_a::h622d6879b734496d",
"line": undefined,
"name": "example_app::example_a::h622d6879b734496d",
"selfWeight": 282000000,
"totalWeight": 47000000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":std::time::Instant::elapsed::h2e3793148fc23529",
"line": undefined,
"name": "std::time::Instant::elapsed::h2e3793148fc23529",
"selfWeight": 2000000000,
"totalWeight": 93081000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":mach_absolute_time",
"line": undefined,
"name": "mach_absolute_time",
"selfWeight": 91080000000,
"totalWeight": 91080000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":DYLD-STUB$$mach_absolute_time",
"line": undefined,
"name": "DYLD-STUB$$mach_absolute_time",
"selfWeight": 1000000,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":core::time::Duration::as_millis::hae6ad1b9cf7bb5ec",
"line": undefined,
"name": "core::time::Duration::as_millis::hae6ad1b9cf7bb5ec",
"selfWeight": 1249000000,
"totalWeight": 1249000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":example_app::example_b::h71b31fcd89b9ffd2",
"line": undefined,
"name": "example_app::example_b::h71b31fcd89b9ffd2",
"selfWeight": 1000000,
"totalWeight": 84000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":example_app::example_c::h631759c10d7dee93",
"line": undefined,
"name": "example_app::example_c::h631759c10d7dee93",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alloc::vec::Vec$LT$T$C$A$GT$::push::ha3426502ffc42c8b",
"line": undefined,
"name": "alloc::vec::Vec$LT$T$C$A$GT$::push::ha3426502ffc42c8b",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alloc::raw_vec::RawVec$LT$T$C$A$GT$::reserve_for_push::he4a29b8274e35dbb",
"line": undefined,
"name": "alloc::raw_vec::RawVec$LT$T$C$A$GT$::reserve_for_push::he4a29b8274e35dbb",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alloc::raw_vec::RawVec$LT$T$C$A$GT$::grow_amortized::h78db9ba423623c58",
"line": undefined,
"name": "alloc::raw_vec::RawVec$LT$T$C$A$GT$::grow_amortized::h78db9ba423623c58",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alloc::raw_vec::finish_grow::h876f5af1c66d74c8",
"line": undefined,
"name": "alloc::raw_vec::finish_grow::h876f5af1c66d74c8",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::hcc733a40a34fbc94",
"line": undefined,
"name": "_$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::hcc733a40a34fbc94",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alloc::alloc::Global::alloc_impl::hc55feba7d6266dfa",
"line": undefined,
"name": "alloc::alloc::Global::alloc_impl::hc55feba7d6266dfa",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alloc::alloc::alloc::hfac63f3d6850b759",
"line": undefined,
"name": "alloc::alloc::alloc::hfac63f3d6850b759",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_malloc_zone_malloc",
"line": undefined,
"name": "_malloc_zone_malloc",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":nanov2_malloc",
"line": undefined,
"name": "nanov2_malloc",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":nanov2_allocate",
"line": undefined,
"name": "nanov2_allocate",
"selfWeight": 0,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":nanov2_find_block_and_allocate",
"line": undefined,
"name": "nanov2_find_block_and_allocate",
"selfWeight": 1000000,
"totalWeight": 1000000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":std::io::stdio::_print::hdecfefdeb43586ed",
"line": undefined,
"name": "std::io::stdio::_print::hdecfefdeb43586ed",
"selfWeight": 0,
"totalWeight": 1000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h2cb06dcd2b172844",
"line": undefined,
"name": "_$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h2cb06dcd2b172844",
"selfWeight": 0,
"totalWeight": 1000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":core::fmt::write::hed96bcfc6342aee5",
"line": undefined,
"name": "core::fmt::write::hed96bcfc6342aee5",
"selfWeight": 468,
"totalWeight": 1000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_$LT$std..io..Write..write_fmt..Adapter$LT$T$GT$$u20$as$u20$core..fmt..Write$GT$::write_str::hbe33ec23de24ce2f",
"line": undefined,
"name": "_$LT$std..io..Write..write_fmt..Adapter$LT$T$GT$$u20$as$u20$core..fmt..Write$GT$::write_str::hbe33ec23de24ce2f",
"selfWeight": 0,
"totalWeight": 532,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_$LT$std..io..stdio..StdoutLock$u20$as$u20$std..io..Write$GT$::write_all::h22667d0a03b2151b",
"line": undefined,
"name": "_$LT$std..io..stdio..StdoutLock$u20$as$u20$std..io..Write$GT$::write_all::h22667d0a03b2151b",
"selfWeight": 532,
"totalWeight": 532,
},
],
"name": "cycles-example-deep-copy.txt",
"stacks": Array [
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;std::time::Instant::elapsed::h2e3793148fc23529;mach_absolute_time 750000:00",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;std::time::Instant::elapsed::h2e3793148fc23529;std::time::Instant::elapsed::h2e3793148fc23529 10000:00",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;std::time::Instant::elapsed::h2e3793148fc23529;DYLD-STUB$$mach_absolute_time 16:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;std::time::Instant::elapsed::h2e3793148fc23529 6650:00",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;core::time::Duration::as_millis::hae6ad1b9cf7bb5ec 10566:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;example_app::example_a::h622d6879b734496d 3016:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;example_app::example_b::h71b31fcd89b9ffd2;std::time::Instant::elapsed::h2e3793148fc23529;mach_absolute_time 1333:20",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;example_app::example_b::h71b31fcd89b9ffd2;std::time::Instant::elapsed::h2e3793148fc23529;std::time::Instant::elapsed::h2e3793148fc23529 16:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;example_app::example_b::h71b31fcd89b9ffd2;example_app::example_c::h631759c10d7dee93;alloc::vec::Vec$LT$T$C$A$GT$::push::ha3426502ffc42c8b;alloc::raw_vec::RawVec$LT$T$C$A$GT$::reserve_for_push::he4a29b8274e35dbb;alloc::raw_vec::RawVec$LT$T$C$A$GT$::grow_amortized::h78db9ba423623c58;alloc::raw_vec::finish_grow::h876f5af1c66d74c8;_$LT$alloc..alloc..Global$u20$as$u20$core..alloc..Allocator$GT$::allocate::hcc733a40a34fbc94;alloc::alloc::Global::alloc_impl::hc55feba7d6266dfa;alloc::alloc::alloc::hfac63f3d6850b759;_malloc_zone_malloc;nanov2_malloc;nanov2_allocate;nanov2_allocate;nanov2_find_block_and_allocate 16:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;example_app::example_b::h71b31fcd89b9ffd2;example_app::example_b::h71b31fcd89b9ffd2 16:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d;example_app::example_b::h71b31fcd89b9ffd2;core::time::Duration::as_millis::hae6ad1b9cf7bb5ec 16:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::example_a::h622d6879b734496d 1683:20",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;std::time::Instant::elapsed::h2e3793148fc23529;mach_absolute_time 766666:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;std::time::Instant::elapsed::h2e3793148fc23529;std::time::Instant::elapsed::h2e3793148fc23529 10816:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;std::time::Instant::elapsed::h2e3793148fc23529 5850:00",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;core::time::Duration::as_millis::hae6ad1b9cf7bb5ec 10233:20",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;example_app::main::hddab13ae8d8b6a6e 3166:40",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;std::io::stdio::_print::hdecfefdeb43586ed;_$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h2cb06dcd2b172844;core::fmt::write::hed96bcfc6342aee5;_$LT$std..io..Write..write_fmt..Adapter$LT$T$GT$$u20$as$u20$core..fmt..Write$GT$::write_str::hbe33ec23de24ce2f;_$LT$std..io..stdio..StdoutLock$u20$as$u20$std..io..Write$GT$::write_all::h22667d0a03b2151b 532.00ms",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e;std::io::stdio::_print::hdecfefdeb43586ed;_$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$::write_fmt::h2cb06dcd2b172844;core::fmt::write::hed96bcfc6342aee5 468.00ms",
"example_app (40414);start;main;std::rt::lang_start::hdba6f1ebfd1bdcf8;std::rt::lang_start_internal::hc453db0ee48af82e;std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd856f2663871206c;std::sys_common::backtrace::__rust_begin_short_backtrace::h4869fd82068bc1dc;core::ops::function::FnOnce::call_once::h06e0c27ee740c6a8;example_app::main::hddab13ae8d8b6a6e 19933:19",
],
}
`;
exports[`importFromInstrumentsDeepCopy allocations profile: indexToView 1`] = `0`;
exports[`importFromInstrumentsDeepCopy allocations profile: indexToView 2`] = `0`;
exports[`importFromInstrumentsDeepCopy allocations profile: profileGroup.name 1`] = `"random-allocations-deep-copy.txt"`;
exports[`importFromInstrumentsDeepCopy allocations profile: profileGroup.name 2`] = `"cycles-example-deep-copy.txt"`;
exports[`importFromInstrumentsDeepCopy time profile 1`] = `
Object {
"frames": Array [
+8
View File
@@ -11,3 +11,11 @@ test('importFromCallgrind name compression', async () => {
test('importFromCallgrind multiple event types', async () => {
await checkProfileSnapshot('./sample/profiles/callgrind/callgrind.multiple-event-types.log')
})
test('importFromCallgrind subposition compression', async () => {
await checkProfileSnapshot('./sample/profiles/callgrind/callgrind.subposition-compression.log')
})
test('importFromCallgrind cfn reset', async () => {
await checkProfileSnapshot('./sample/profiles/callgrind/callgrind.cfn-reset.log')
})
+168 -55
View File
@@ -129,26 +129,6 @@ class CallGraph {
}
toProfile(): Profile {
// To convert a call graph into a profile, we first need to identify what
// the "root weights" are. "root weights" are the total weight of each frame
// while at the bottom of the call-stack. The majority of functions will have
// zero weight while at the bottom of the call-stack, since most functions
// are never at the bottom of the call-stack.
const rootWeights = new Map<Frame, number>()
for (let [frame, totalWeight] of this.totalWeights) {
rootWeights.set(frame, totalWeight)
}
for (let [_, childMap] of this.childrenTotalWeights) {
for (let [child, weight] of childMap) {
rootWeights.set(child, getOrElse(rootWeights, child, () => weight) - weight)
}
}
let totalProfileWeight = 0
for (let [_, rootWeight] of rootWeights) {
totalProfileWeight += rootWeight
}
const profile = new CallTreeProfileBuilder()
let unitMultiplier = 1
@@ -170,7 +150,12 @@ class CallGraph {
const currentStack = new Set<Frame>()
const visit = (frame: Frame, callTreeWeight: number) => {
let maxWeight = 0
for (let [_, totalWeight] of this.totalWeights) {
maxWeight = Math.max(maxWeight, totalWeight)
}
const visit = (frame: Frame, subtreeTotalWeight: number) => {
if (currentStack.has(frame)) {
// Call-graphs are allowed to have cycles. Call-trees are not. In case
// we run into a cycle, we'll just avoid recursing into the same subtree
@@ -198,7 +183,7 @@ class CallGraph {
// See the comment at the top of the file for an example where this
// assumption can yield especially misleading results.
if (callTreeWeight < 1e-4 * totalProfileWeight) {
if (subtreeTotalWeight < 1e-4 * maxWeight) {
// This assumption about even distribution can cause us to generate a
// call tree with dramatically more nodes than the call graph.
//
@@ -220,45 +205,113 @@ class CallGraph {
// 1000*250=250,000 nodes in the resulting call graph.
//
// To mitigate this explosion of the # of nodes, we ignore subtrees
// whose weights are less than 0.01% of the total weight of the profile.
// whose weights are less than 0.01% of the heaviest node in the call
// graph.
return
}
// totalWeightForFrame is the total weight for the given frame in the
// entire call graph.
const callGraphWeightForFrame = getOrElse(this.totalWeights, frame, () => 0)
if (callGraphWeightForFrame === 0) {
const totalWeightForFrameInCallgraph = getOrElse(this.totalWeights, frame, () => 0)
if (totalWeightForFrameInCallgraph === 0) {
return
}
// This is the portion of the total time the given child spends within the
// given parent that we'll attribute to this specific path in the call
// tree.
const ratio = callTreeWeight / callGraphWeightForFrame
let selfWeightForNodeInCallTree = subtreeTotalWeight
let selfWeightForFrame = callGraphWeightForFrame
profile.enterFrame(frame, totalCumulative * unitMultiplier)
profile.enterFrame(frame, Math.round(totalCumulative * unitMultiplier))
currentStack.add(frame)
for (let [child, callGraphEdgeWeight] of this.childrenTotalWeights.get(frame) || []) {
selfWeightForFrame -= callGraphEdgeWeight
const childCallTreeWeight = callGraphEdgeWeight * ratio
for (let [child, totalWeightAsChild] of this.childrenTotalWeights.get(frame) || []) {
// To determine the weight of the child in the call tree, we look at the
// weight of the child in the call graph relative to its parent.
const childCallTreeWeight =
subtreeTotalWeight * (totalWeightAsChild / totalWeightForFrameInCallgraph)
let prevTotalCumulative = totalCumulative
visit(child, childCallTreeWeight)
// Even though we tried to add a child with total weight equal to
// childCallTreeWeight, we might have failed for a variety of data
// consistency reasons, or due to cycles.
//
// We want to avoid losing weight in the call tree by subtracting from
// the self weight on the assumption it was added to the subtree, so we
// only subtree from the self weight the amount that was *actually* used
// by the subtree, rather than the amount we *intended* for it to use.
const actualChildCallTreeWeight = totalCumulative - prevTotalCumulative
selfWeightForNodeInCallTree -= actualChildCallTreeWeight
}
currentStack.delete(frame)
totalCumulative += selfWeightForFrame * ratio
profile.leaveFrame(frame, totalCumulative * unitMultiplier)
totalCumulative += selfWeightForNodeInCallTree
profile.leaveFrame(frame, Math.round(totalCumulative * unitMultiplier))
}
for (let [rootFrame, rootWeight] of rootWeights) {
if (rootWeight <= 0) {
continue
// It's surprisingly hard to figure out which nodes in the call graph
// constitute the root nodes of call trees.
//
// Here are a few intuitive options, and reasons why they're not always
// correct or good.
//
// ## 1. Find nodes in the call graph that have no callers
//
// This is probably right 99% of the time in practice, but since the
// callgrind is totally general, it's totally valid to have a file
// representing a profile for the following code:
//
// function a() {
// b()
// }
// function b() {
// }
// a()
// b()
//
// In this case, even though b has a caller, some of the real calltree for
// an execution trace of the program will have b on the top of the stack.
//
// ## 2. Find nodes in the call graph that still have weight if you
// subtract all of the weight caused by callers.
//
// The callgraph format, in theory, provides inclusive times for every
// function call. That means if you have a function `alpha` with a total
// weight of 20, and its only in-edge in the call-graph has weight of 10,
// that should indicate that `alpha` exists both as the root-node of a
// calltree, and as a node in some other call-tree.
//
// In theory, you should be able to figure out the weight of it as a root
// node by subtracting the weights of all the in-edges. In practice, real
// callgrind files are inconsistent in how they do accounting for in-edges
// where you end up in weird situations where the weight of in-edges
// *exceeds* the weight of nodes (where the weight of a node is its
// self-weight plus the weight of all its out-edges).
//
// ## 3. Find the heaviest node in the call graph, build its call-tree, and
// decrease the weights of other nodes in the call graph while you
// build the call tree. After you've done this, repeat with the new
// heaviest.
//
// I think this version is probably fully correct, but the performance is
// awful. The naive-version is O(n^2) because you have to re-determine which
// node is the heaviest after each time you finish building a call-tree. You
// can't just sort, because the relative ordering also changes with the
// construction of each call tree.
//
// There's probably a clever solution here which puts all of the nodes into
// a min-heap and then deletes and re-inserts nodes as their weights change,
// but reasoning about the performance of that is a big pain in the butt.
//
// Despite not always being correct, I'm opting for option (1).
const rootNodes = new Set<Frame>(this.frameSet)
for (let [_, childMap] of this.childrenTotalWeights) {
for (let [child, _] of childMap) {
rootNodes.delete(child)
}
// If we've reached here, it means that the given root frame has some
// weight while at the top of the call-stack.
visit(rootFrame, rootWeight)
}
for (let rootNode of rootNodes) {
visit(rootNode, this.totalWeights.get(rootNode)!)
}
return profile.build()
@@ -344,7 +397,7 @@ class CallgrindParser {
}
private calleeFrameInfo(): FrameInfo {
const file = this.calleeFilename || '(unknown)'
const file = this.calleeFilename || this.filename || '(unknown)'
const name = this.calleeFunctionName || '(unknown)'
const key = `${file}:${name}`
return {key, name, file}
@@ -384,10 +437,20 @@ class CallgrindParser {
switch (key) {
case 'fe':
case 'fi':
case 'fi': {
// fe/fi are used to indicate the source-file of a function definition
// changed mid-definition. This is for inlined code, but doesn't
// indicate that we've actually switched to referring to a different
// function, so we mostly ignore it.
//
// We still need to do the parseNameWithCompression call in case a name
// is defined and then referenced later on for name compression.
this.parseNameWithCompression(value, this.savedFileNames)
break
}
case 'fl': {
this.filename = this.parseNameWithCompression(value, this.savedFileNames)
this.calleeFilename = this.filename
break
}
@@ -398,6 +461,8 @@ class CallgrindParser {
case 'cfi':
case 'cfl': {
// NOTE: unlike the fe/fi distinction described above, cfi and cfl are
// interchangeable.
this.calleeFilename = this.parseNameWithCompression(value, this.savedFileNames)
break
}
@@ -413,6 +478,22 @@ class CallgrindParser {
// since it'll just be copying the exact same frame over-and-over again,
// but that might be better than ignoring it.
this.parseCostLine(this.lines[this.lineNum++], 'child')
// This isn't specified anywhere in the spec, but empirically the and
// "cfn" scope should only persist for a single "call".
//
// This seems to be what KCacheGrind does too:
//
// https://github.com/KDE/kcachegrind/blob/ea4314db2785cb8f279fe884ee7f82445642b692/libcore/cachegrindloader.cpp#L1259
this.calleeFilename = null
this.calleeFunctionName = null
break
}
case 'cob':
case 'ob': {
// We ignore these for now. They're valid lines, but we don't capture or
// display information about them.
break
}
@@ -458,21 +539,52 @@ class CallgrindParser {
return name
}
private prevCostLineNumbers: number[] = []
private parseCostLine(line: string, costType: 'self' | 'child'): boolean {
// TODO(jlfwong): Handle "Subposition compression"
// TODO(jlfwong): Allow hexadecimal encoding
const parts = line.split(/\s+/)
const nums: number[] = []
for (let part of parts) {
// As far as I can tell from the specification, the callgrind format does
// not accept floating point numbers.
const asNum = parseInt(part)
if (isNaN(asNum)) {
for (let i = 0; i < parts.length; i++) {
const part = parts[i]
if (part.length === 0) {
return false
}
nums.push(asNum)
if (part === '*' || part[0] === '-' || part[1] === '+') {
// This handles "Subposition compression"
// See: https://valgrind.org/docs/manual/cl-format.html#cl-format.overview.compression2
if (this.prevCostLineNumbers.length <= i) {
throw new Error(
`Line ${this.lineNum} has a subposition on column ${i} but ` +
`previous cost line has only ${this.prevCostLineNumbers.length} ` +
`columns. Line contents: ${line}`,
)
}
const prevCostForSubposition = this.prevCostLineNumbers[i]
if (part === '*') {
nums.push(prevCostForSubposition)
} else {
// This handles both the '-' and '+' cases
const offset = parseInt(part)
if (isNaN(offset)) {
throw new Error(
`Line ${this.lineNum} has a subposition on column ${i} but ` +
`the offset is not a number. Line contents: ${line}`,
)
}
nums.push(prevCostForSubposition + offset)
}
} else {
const asNum = parseInt(part)
if (isNaN(asNum)) {
return false
}
nums.push(asNum)
}
}
if (nums.length == 0) {
@@ -506,6 +618,7 @@ class CallgrindParser {
}
}
this.prevCostLineNumbers = nums
return true
}
}
+4
View File
@@ -30,3 +30,7 @@ test('importFromChromeTimeline Workers Chrome 66', async () => {
test('importFromChromeTimeline Workers Chrome 70', async () => {
await checkProfileSnapshot('./sample/profiles/Chrome/70/worker.json')
})
test('importFromChromeTimeline Chrome 116', async () => {
await checkProfileSnapshot('./sample/profiles/Chrome/116/Trace-20230603T221323.json')
})
+9 -1
View File
@@ -49,7 +49,7 @@ export interface CPUProfile {
timeDeltas: number[]
}
export function isChromeTimeline(rawProfile: any): boolean {
export function isChromeTimeline(rawProfile: any): rawProfile is TimelineEvent[] {
if (!Array.isArray(rawProfile)) return false
if (rawProfile.length < 1) return false
const first = rawProfile[0]
@@ -63,6 +63,14 @@ export function isChromeTimeline(rawProfile: any): boolean {
return true
}
export function isChromeTimelineObject(
rawProfile: any,
): rawProfile is {traceEvents: TimelineEvent[]} {
// Starting with Chrome 114, the timeline format output by devtools is an object
if (!('traceEvents' in rawProfile)) return false
return isChromeTimeline(rawProfile.traceEvents)
}
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.
+7
View File
@@ -6,6 +6,7 @@ import {
importFromChromeTimeline,
isChromeTimeline,
importFromOldV8CPUProfile,
isChromeTimelineObject,
} from './chrome'
import {importFromStackprof} from './stackprof'
import {importFromInstrumentsDeepCopy, importFromInstrumentsTrace} from './instruments'
@@ -92,6 +93,9 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
if (fileName.endsWith('.speedscope.json')) {
console.log('Importing as speedscope json file')
return importSpeedscopeProfiles(contents.parseAsJSON())
} else if (/Trace-\d{8}T\d{6}/.exec(fileName)) {
console.log('Importing as Chrome Timeline Object')
return importFromChromeTimeline(contents.parseAsJSON().traceEvents, fileName)
} else if (fileName.endsWith('.chrome.json') || /Profile-\d{8}T\d{6}/.exec(fileName)) {
console.log('Importing as Chrome Timeline')
return importFromChromeTimeline(contents.parseAsJSON(), fileName)
@@ -136,6 +140,9 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
} else if (isChromeTimeline(parsed)) {
console.log('Importing as Chrome Timeline')
return importFromChromeTimeline(parsed, fileName)
} else if (isChromeTimelineObject(parsed)) {
console.log('Importing as Chrome Timeline Object')
return importFromChromeTimeline(parsed.traceEvents, fileName)
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
console.log('Importing as Chrome CPU Profile')
return toGroup(importFromChromeCPUProfile(parsed))
+4
View File
@@ -18,6 +18,10 @@ describe('importFromInstrumentsDeepCopy', () => {
'./sample/profiles/Instruments/7.3.1/random-allocations-deep-copy.txt',
)
})
test('allocations profile', async () => {
await checkProfileSnapshot('./sample/profiles/Instruments/13.4/cycles-example-deep-copy.txt')
})
})
class ZipBackedFileSystemEntry implements FileSystemEntry {
+9
View File
@@ -85,7 +85,16 @@ function getWeight(deepCopyRow: any): number {
case 's':
return 1000 * value
case 'min':
return 60 * 1000 * value
case 'cycles':
return value
case 'Kc':
return 1000 * value
case 'Mc':
return 1000 * 1000 * value
case 'Gc':
return 1000 * 1000 * 1000 * value
}
throw new Error(`Unrecognized units ${units}`)
}
+50
View File
@@ -0,0 +1,50 @@
import {exactMatchStrings} from './profile-search'
function assertMatch(text: string, pattern: string, expected: string) {
const match = exactMatchStrings(text, pattern)
let highlighted = ''
let last = 0
for (let range of match) {
highlighted += `${text.slice(last, range[0])}[${text.slice(range[0], range[1])}]`
last = range[1]
}
highlighted += text.slice(last)
expect(highlighted).toEqual(expected)
}
function assertNoMatch(text: string, pattern: string) {
assertMatch(text, pattern, text)
}
describe('exactMatchStrings', () => {
test('no match', () => {
assertNoMatch('a', 'b')
assertNoMatch('aa', 'ab')
assertNoMatch('a', 'aa')
assertNoMatch('ca', 'ac')
})
test('full text match', () => {
assertMatch('hello', 'hello', '[hello]')
assertMatch('multiple words', 'multiple words', '[multiple words]')
})
test('case sensitivity', () => {
assertMatch('HELLO', 'hello', '[HELLO]')
assertMatch('Hello', 'hello', '[Hello]')
assertMatch('hello', 'Hello', '[hello]')
assertMatch('hello', 'HELLO', '[hello]')
})
test('multiple occurrences', () => {
assertMatch('hello hello', 'hello', '[hello] [hello]')
assertMatch('hellohello', 'hello', '[hello][hello]')
})
test('overlapping occurrences', () => {
assertMatch('aaaaa', 'aa', '[aa][aa]a')
assertMatch('abababa', 'aba', '[aba]b[aba]')
})
})
+33 -6
View File
@@ -1,5 +1,4 @@
import {Profile, Frame, CallTreeNode} from './profile'
import {FuzzyMatch, fuzzyMatchStrings} from './fuzzy-find'
import {Flamechart, FlamechartFrame} from './flamechart'
import {Rect, Vec2} from './math'
@@ -8,19 +7,47 @@ export enum FlamechartType {
LEFT_HEAVY_FLAME_GRAPH,
}
// In previous versions of speedscope, searching for strings within the profile
// was done using fuzzy finding. As it turns out, this was surprising behavior
// to most people, so we've switched to a more traditional substring search that
// more closely mimics browser behavior.
//
// This is case insensitive for both the needle & the haystack. This means
// searching for "hello" will match "Hello" and "HELLO", and searching for
// "HELLO" will match both "hello" and "Hello". This matches Chrome's behavior
// as far as I can tell.
//
// See https://github.com/jlfwong/speedscope/issues/352
//
// Return ranges for all matches in order to highlight them.
export function exactMatchStrings(text: string, pattern: string): [number, number][] {
const lowerText = text.toLocaleLowerCase()
const lowerPattern = pattern.toLocaleLowerCase()
let lastIndex = 0
const matchedRanges: Array<[number, number]> = []
while (true) {
let index = lowerText.indexOf(lowerPattern, lastIndex)
if (index === -1) {
return matchedRanges
}
matchedRanges.push([index, index + pattern.length])
lastIndex = index + pattern.length
}
}
// A utility class for storing cached search results to avoid recomputation when
// the search results & profile did not change.
export class ProfileSearchResults {
constructor(readonly profile: Profile, readonly searchQuery: string) {}
private matches: Map<Frame, FuzzyMatch> | null = null
getMatchForFrame(frame: Frame): FuzzyMatch | null {
private matches: Map<Frame, [number, number][] | null> | null = null
getMatchForFrame(frame: Frame): [number, number][] | null {
if (!this.matches) {
this.matches = new Map()
this.profile.forEachFrame(frame => {
const match = fuzzyMatchStrings(frame.name, this.searchQuery)
if (match == null) return
this.matches!.set(frame, match)
const match = exactMatchStrings(frame.name, this.searchQuery)
this.matches!.set(frame, match.length === 0 ? null : match)
})
}
return this.matches.get(frame) || null
+1 -1
View File
@@ -241,7 +241,7 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
if (match) {
const rangesToHighlightInTrimmedText = remapRangesToTrimmedText(
trimmedText,
match.matchedRanges,
match
)
// Once we have the character ranges to highlight, we need to
+1 -1
View File
@@ -217,7 +217,7 @@ export const ProfileTableView = memo(
rows.push(
ProfileTableRowView({
frame,
matchedRanges: match == null ? null : match.matchedRanges,
matchedRanges: match == null ? null : match,
index: i,
profile: profile,
selectedFrame: selectedFrame,
+2 -3
View File
@@ -11,7 +11,6 @@ import {SandwichSearchView} from './sandwich-search-view'
import {ActiveProfileState} from '../app-state/active-profile-state'
import {sortBy} from '../lib/utils'
import {ProfileSearchContext} from './search-view'
import {FuzzyMatch} from '../lib/fuzzy-find'
import {Theme, useTheme, withTheme} from './themes/theme'
import {SortField, SortDirection, profileGroupAtom, tableSortMethodAtom} from '../app-state'
import {useAtom} from '../lib/atom'
@@ -141,7 +140,7 @@ interface SandwichViewContextData {
selectedFrame: Frame | null
setSelectedFrame: (frame: Frame | null) => void
getIndexForFrame: (frame: Frame) => number | null
getSearchMatchForFrame: (frame: Frame) => FuzzyMatch | null
getSearchMatchForFrame: (frame: Frame) => [number, number][] | null
}
export const SandwichViewContext = createContext<SandwichViewContextData | null>(null)
@@ -204,7 +203,7 @@ export const SandwichViewContainer = memo((ownProps: SandwichViewContainerProps)
}
}, [rowList])
const getSearchMatchForFrame: (frame: Frame) => FuzzyMatch | null = useMemo(() => {
const getSearchMatchForFrame: (frame: Frame) => [number, number][] | null = useMemo(() => {
return (frame: Frame) => {
if (profileSearchResults == null) return null
return profileSearchResults.getMatchForFrame(frame)