Compare commits

..
34 Commits
Author SHA1 Message Date
Jamie Wong d36c3a5442 1.21.0 2024-11-16 22:13:13 -08:00
Robert Kruszewski e468c60229 Add support for Instruments 16 Time Profile Deep Copy (#484)
* Add support for Instruments 16 time profiles

* rename

* prettier
2024-11-16 22:06:50 -08:00
Tom Levy d69f3d0eec Fix bug where import after error continues failing (#463)
Steps to reproduce:
1. Open https://www.speedscope.app/
2. Try to import an invalid file such as [invalid.json](https://github.com/jlfwong/speedscope/files/14167326/invalid.json). The page says "Something went wrong. Check the JS console for more details."
3. Now try to import a valid file such as [simple.json](https://github.com/jlfwong/speedscope/files/14167335/simple.json). The page says "Something went wrong. Check the JS console for more details." even though this second file is valid.

Explanation of the fix (copied from the commit message):
> We need to clear the error flag, otherwise once there is an error and we display "Something went wrong" we will keep displaying that forever even when the user imports a valid file.
2024-04-15 11:50:13 -07:00
Timo Tijhof 25f671e9d8 Bundle the font (#472)
For performance, offline support and privacy.

Continues from https://github.com/jlfwong/speedscope/pull/412
2024-04-15 11:45:12 -07:00
Tom Levy 0121cf9342 Clarify specification of startValue in speedscope file format (#464)
The previous description ("event values will be relative to this
startValue") was ambiguous.

Suppose the profile starts at wall time 1000ms and the first event is
at wall time 1003ms.

The intention is that startValue should be set to 1000 and the "at"
value of the event should be set to 1003. The viewer's time axis will
start at 0ms and the first event will be displayed at 3ms.

But the previous description could be incorrectly interpreted as
saying that the "at" value of the first event should be set to 3 (the
time relative to startValue, as opposed to the absolute wall time).

Clarify that "relative" is referring to how the viewer displays the
data, not about which values to store in the file.
2024-02-05 10:58:18 -08:00
Jamie Wong 68fd88ceaf 1.20.0 2024-01-12 09:57:30 -08:00
Zachary Marion a6d700194f Partition based on samples instead of traceEvents when importing a sample-based chrome trace (#460)
In the existing code, if `traceEvents` did not have any importable events, the profile would be marked as empty. This was a bug, as the dev Hermes profiles I was testing with had one X event which made the code work. However we do not need to guarantee (and the spec doesn't seem to) any traceEvents being present as long as we have samples and stack frames. 

When I tested a production profile taken from Hermes it did not have any importable events, just a metadata event with a thread name. This PR updates the implementation to iterate over partitioned samples instead of traceEvents so we construct profiles for all thread + process pairs referenced in the samples array.

| Before | After |
| --- | ----- |
| <img width="1454" alt="Screenshot 2024-01-03 at 9 58 56 AM" src="https://github.com/jlfwong/speedscope/assets/9957046/78c098a7-b374-4492-ad13-9ca79afdb40c"> | <img width="1450" alt="Screenshot 2024-01-03 at 9 58 17 AM" src="https://github.com/jlfwong/speedscope/assets/9957046/d2d5e82b-fa3e-43db-bf8a-e8c3b84cd13a"> |
2024-01-03 16:04:22 -05:00
Jamie Wong a3c0b15935 1.19.0 2023-12-27 23:57:14 -05:00
Zachary Marion e9a17d5d14 Improve hermes profile frame keys to better group frames (#459)
Because all args are serialized in the key for hermes profiles, frames were not properly getting grouped since the "parent" property was different. This PR ensures that the frame key is properly constructed from the function name, file name, line + column number.

| Before | After |
| ----- | ----- |
| <img width="1727" alt="Screenshot 2023-12-27 at 2 25 32 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/4ec04653-5a80-4f34-9506-48e0eb415983"> | <img width="1728" alt="Screenshot 2023-12-27 at 2 26 02 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/c3edc447-e8ad-4757-8576-cbb8c27eafe3"> |
| <img width="1720" alt="Screenshot 2023-12-27 at 2 13 37 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/10194aba-8f26-48ca-bf15-06917b44ea99"> | <img width="1728" alt="Screenshot 2023-12-27 at 2 14 20 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/75c32401-1705-4b23-b71a-5f0a720160dd"> |
2023-12-27 23:56:27 -05:00
Jamie Wong 3f3da22853 Update README.md
- Add link to Hermes import instructions
- Re-order import sources higher in the list
- Update first line
2023-12-27 10:48:07 -05:00
Jamie Wong bea0ef6913 1.18.0 2023-12-26 17:12:59 -05:00
Zachary Marion 4feb1e564d Add hermes-specific support for the trace event format (#458)
Profiles that are transformed into the hermes trace event format are guaranteed to have specific arguments that supply metadata that is useful for debugging - namely the filename, line + col number that the function call originated from, with sourcemaps applied. This PR adds specific support for this information to the trace event importer. This means that we can have the Frame name be just the name of the function, since we know all the information we want to be displayed in the UI is captured in the frame info, which makes the traces cleaner to look at.

| Before | After |
|-----|-----|
| <img width="1728" alt="Screenshot 2023-12-26 at 2 40 01 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/f9dff608-5df3-4098-b1f8-91a69185d906"> | <img width="1726" alt="Screenshot 2023-12-26 at 2 39 13 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/b8ff360e-a316-4bef-8ebc-620c9ff1a998"> |
| <img width="1728" alt="Screenshot 2023-12-26 at 2 41 03 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/127a49b5-458e-4ac8-934a-202e565cb20f"> | <img width="1728" alt="Screenshot 2023-12-26 at 2 41 29 PM" src="https://github.com/jlfwong/speedscope/assets/9957046/ebb285ce-6b33-4535-8e45-b9ada4e4d97f"> |
2023-12-26 17:00:18 -05:00
Jamie Wong 88f4fe6105 Update README-ADMINS.md with npm login instructions (#457) 2023-12-25 22:52:46 -05:00
Jamie Wong 60f1812e51 1.17.0 2023-12-25 22:40:40 -05:00
Jamie Wong 1717fecafb Upgrade prettier, update prettier & react-hooks eslint plugins (#456)
Re-ran prettier with latest version
2023-12-25 22:37:45 -05:00
Jamie Wong c296f530c7 Upgrade typescript & eslint to latest, fix resulting errors (#455)
Also updated the ci.yml node test versions to 18.x and 20.x, given current support: https://endoflife.date/nodejs
2023-12-25 22:23:33 -05:00
Jamie Wong 8e0fa58d65 Re-enable eslint prettier rule after being accidentally disabled for 3 years (#454)
It looks like in #267 (which was 3 years ago!), I accidentally disabled prettier linting altogether 😱

https://github.com/jlfwong/speedscope/pull/267/files#diff-e2954b558f2aa82baff0e30964490d12942e0e251c1aa56c3294de6ec67b7cf5

There's no comment in that PR about this being an intentional thing, so I have to assume this was a dumb mistake.
2023-12-25 21:22:56 -05:00
Zachary Marion b21480494e Support the chrome JSON trace format (allows viewing of hermes traces) (#453) 2023-12-25 21:11:45 -05:00
Zachary Marion dfd3a0dfb3 Fix bug in selectQueueToTakeFromNext for trace profiles (#450)
I have been taking a lot of profiles using the Hermes profiler, but I noticed that they sometimes to not show up properly. After debugging what exactly was going on, I realized it was because the logic in `selectQueueToTakeFromNext` only checks for name, instead of the key for the event. I had a bunch of events with the name `anonymous` that were getting improperly exited before they should have been due to this logic. 

This fix makes the code more robust if there are added "args" which differentiate an event from another (as is the case in Hermes profiles), however it would still be an issue if they key just defaults to the name.

Example profile before:

<img width="1728" alt="Screenshot 2023-12-15 at 12 54 04 AM" src="https://github.com/jlfwong/speedscope/assets/9957046/345f556e-f944-40f1-b59c-748133acb950">

What it should look like (in Perfetto):
<img width="1051" alt="Screenshot 2023-12-15 at 8 51 38 AM" src="https://github.com/jlfwong/speedscope/assets/9957046/7473cdf8-95f1-49de-a0c7-ef4ac081ff85">

After the fix:

<img width="1728" alt="Screenshot 2023-12-15 at 12 54 29 AM" src="https://github.com/jlfwong/speedscope/assets/9957046/56b0870a-538b-4916-acc8-de2b7dfd78eb">
2023-12-16 00:27:03 -08:00
Jamie Wong ac4a015559 Add bounds checking for sampleTypeIndex (#449)
Wow this was surprising. As reported in #448, the `simple.speedscope.json` file failed import. This was surprising to me because there's a test that covers that file to ensure it imports correctly.

The file provided in #448, however, is from a version of speedscope from 5 years ago before the file had been pretty printed. It turns out that when this *particular* file is not pretty-printed, it's a schematically valid pprof profile.

The fix is to do some bounds checks and return null. After the change, the file imports as you'd expect after realizing its not actually a valid pprof profile.

Fixes #448
2023-12-07 18:43:47 -08:00
byronhe de17f128d0 Update README-zh_CN.md (#442)
fix url
2023-09-04 09:54:35 -07:00
Jamie Wong 304ccf33ab Update publish-and-deploy to remove automated release creation (#440)
Turns out the `--attach` command was hallucinated by GPT!
2023-07-16 15:31:31 -07:00
Jamie Wong 650e505b55 1.16.0 2023-07-16 03:04:29 -07:00
Jamie Wong 8dad28e5e2 Automate more of the release process (#439)
The publish, deploy, and release process is annoying enough at the moment that I avoid doing it frequently. Let's automate most of it to reduce the friction
2023-07-16 03:01:50 -07:00
Nguyễn Văn Đức f62519ab69 Improve profile builder performance (#437)
Follow up on this PR #435. 

Currently, it took roughly 22 seconds to load my 1.3GB file. After inspecting the profiler, there's a large chunk of time spending in Frame.getOrInsert. I figure we can reduce the number of invocations by half. It reduces the load time to roughly 18 seconds.I also tested with a smaller file (~350MB), and it show similar gains, about 15-20%
2023-06-28 22:11:12 -07:00
Nguyễn Văn Đức 984bf1296a Fix crash when importing big linux perf tool files (#435)
Currently, importing files generated by linux perf tool whose some blocks exceed V8 strings limit can crash the application. This issue is similar to the one in #385.

This PR fixes it by changing parseEvents to work directly with lines instead of chunking lines into blocks first.

Fixes #433
2023-06-28 01:30:08 -07:00
Nguyễn Văn Đức 26884c116c Improve splitLines: return iterator instead (#434)
The current behavior of splitLines is to eagerly split all the lines and return an array of strings.

This PR improves this by returning an iterator instead, which will emit lines. This lets callers decide how to best use the splitLines function (i.e. lazily enumerate over lines)

Relates to #433
2023-06-27 23:27:15 -07:00
Jamie Wong bb063e49e3 Fix trimTextMid (#431)
There was a subtle bug in `trimTextMid` caused by calling substring methods with non-integer values. This happens because `findValueBisect` returns non-integer values, and there was no special handling of this.

The bug results in strings cutting off many of the last few characters in a string, rather than always displaying it when possible.

Before:
<img width="368" alt="image" src="https://github.com/jlfwong/speedscope/assets/150329/754a25f1-a6f7-46f1-8e34-059503d9e4cf">

After:
<img width="386" alt="image" src="https://github.com/jlfwong/speedscope/assets/150329/b2688ca1-54af-4d2e-b704-9f3322d2e5b4">

Fixes #411
2023-06-23 13:22:53 -07:00
xieve 693545b77a Added support for Papyrus profiles (#428)
Fixed #427
2023-06-23 13:02:08 -07:00
Jamie Wong b3b4b1492a 1.15.2 2023-06-21 17:28:24 -07:00
Jamie Wong 9cdceede15 Support showing pprof lines from the pprof Line object (take 2) (#430)
The previous behavior was to use the StartLine of a function as the line number to show in speedscope. However, the Line object has more precise line information, and we should only fallback to StartLine if we don't have this more detailed information.

Looking at the [documentation for the pprof proto](https://github.com/google/pprof/tree/main/proto#general-structure-of-a-profile), this is more how it intends to interpret line information:

> location: A unique place in the program, commonly mapped to a single instruction address. It has a unique nonzero id, to be referenced from the samples. It contains source information in the form of lines, and a mapping id that points to a binary.
> function: A program function as defined in the program source. It has a unique nonzero id, referenced from the location lines. It contains a human-readable name for the function (eg a C++ demangled name), a system name (eg a C++ mangled name), the name of the corresponding source file, and other function attributes.

Here is a sample profile that had line-level info on the Line object of the profile:

Before:

<img width="449" alt="Screen Shot 2022-11-03 at 11 17 11 AM" src="https://user-images.githubusercontent.com/618615/199760730-712daa70-6cfb-4e90-b037-b571809c26d9.png">

After:

<img width="449" alt="Screen Shot 2022-11-03 at 11 17 22 AM" src="https://user-images.githubusercontent.com/618615/199760777-1c0d5581-7b29-42b7-b642-6035f7d25405.png">
2023-06-21 15:25:16 -07:00
Manuel Correa 741fdeb427 Stackprof: weight on-cpu samples by period rather than timestamp delta (#425)
This attempts to improve the quality of the on-CPU profiles stackprof provides. Rather than weighing samples by their timestamp deltas, which, in our opinion, are only valid in wall-clock mode, this weighs callchains by:


```
S = number of samples
P = sample period in nanoseconds

W = S * P
```

The difference after this change is quite substantial, specially in profiles that previously were showing up with heavy IO frames:  

* Total profile weight is almost down by 90%, which actually makes sense for an on-CPU profile if the app is relatively idle
* Certain callchains that blocked in syscalls / IO are now much lower weight. This was what I was expecting to find.
Here is an example of the latter point.

In delta mode, we see an io select taking a long time, it is a significant portion of the profile:

<img width="1100" alt="236936508-709bee01-d616-4246-ba74-ab004331dcd3" src="https://github.com/dalehamel/speedscope/assets/4398256/39140f1e-50a9-4f33-8a61-ec98b6273fd4">

But in period scaling mode, it is only a couple of sample periods ultimately:

<img width="206" alt="236936693-9d44304e-a1c2-4906-b3c8-50e19e6f9f27" src="https://github.com/dalehamel/speedscope/assets/4398256/7d19077f-ef25-4d79-980b-cfa1775d928d">
2023-06-17 20:50:19 -07:00
Jake Zimmerman e9133be353 Use frame.name?.startsWith for stackprof (#419)
Sometimes, stackprof frames don't get generated with a `name` in the frame.
I think it's probably worth tracking down why that is, but in the mean
time, speedscope simply crashes with a method call on `undefined`. The
crash is bad because it only shows up in the console--there's no visible
message saying that speedscope failed to parse and load a profile.

For more information, see #378

This fixes the crash by simply skipping the logic in demangle if the name
field isn't present on a frame. That's probably a fine tradeoff? Because in
this case, stackprof is generating ruby frames, which means that C++ name
demangling won't apply.

I have tested this by running the scripts/prepare-test-installation.sh
script and verifying that `bin/cli.js` can now successfully load the
included profile. Before these changes, I verified that speedscope failed
with the behavior mentioned in #378.

I've also included a snapshot test case, but it seems that the Jest test
harness only tests the parsing, not the rendering (correct me if I'm wrong).
So I haven't actually been able to create an automated test that would catch
a regression. Please let me know if there's a better way to have written this
test.

I've staged the commits on this branch so that the second commit (dcb9840)
showcases the minimal diff to a stackprof file that reproduces the bug. That is,
rather than look at the thousands of new lines in the stackprof profile, you can
view the second commit to see the salient part of the file.
2023-06-15 01:30:53 -07:00
Dave Vasilevsky fcc1fa5689 fix pprof defaultSampleType (#424)
Fixes #415

* Interprets pprof's defaultSampleType as an index into the string table [as documented in the proto](https://github.com/jlfwong/speedscope/blob/0414c2f617742e7fb0cf31a66ac0f77c2f5c0540/src/import/profile.proto#L85), not as an index into the sample types repeated-field. This allows parsing to succeed when the string-table index is not a valid sampleTypes index, which is common on allocation profiles.
* Update the pprof snapshot. This is necessarily because we were previously interpreting an empty defaultSampleType as truthy-but-zero when long.js is present, ie: the first sample-type. But Speedscope-in-the-browser doesn't seem to include long.js, so our tests were disagreeing with in-browser behavior. With this PR, that should be fixed.
2023-06-15 01:14:47 -07:00
68 changed files with 6843 additions and 2255 deletions
+1
View File
@@ -9,6 +9,7 @@ module.exports = {
plugins: ['prettier', '@typescript-eslint', 'react-hooks'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'prettier/prettier': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': [
'error',
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v2
+50
View File
@@ -1,3 +1,53 @@
## [1.21.0] - 2024-11-16
- Add support for Instruments 16 Time Profile Deep Copy [[#484](https://github.com/jlfwong/speedscope/pull/484)] (by @robert3005)
- Fix bug where import after error continues failing [[#463](https://github.com/jlfwong/speedscope/pull/463)] (by @tom93)
- Bundle the font [[#472](https://github.com/jlfwong/speedscope/pull/472)] (by @Krinkle)
- Clarify specification of startValue in speedscope file format [[#464](https://github.com/jlfwong/speedscope/pull/464)] (by @tom93)
## [1.20.0] - 2024-01-12
- Partition based on samples instead of traceEvents when importing a sample-based chrome trace [[#460](https://github.com/jlfwong/speedscope/pull/460)] (by @zacharyfmarion)
## [1.19.0] - 2023-12-27
- Improve hermes profile frame keys to better group frames [[#459](https://github.com/jlfwong/speedscope/pull/459)] (by @zacharyfmarion)
- Update README.md
## [1.18.0] - 2023-12-26
- Add hermes-specific support for the trace event format [[#458](https://github.com/jlfwong/speedscope/pull/458)] (by @zacharyfmarion)
- Update README-ADMINS.md with npm login instructions [[#457](https://github.com/jlfwong/speedscope/pull/457)] (by @jlfwong)
## [1.17.0] - 2023-12-25
- Upgrade prettier, update prettier & react-hooks eslint plugins [[#456](https://github.com/jlfwong/speedscope/pull/456)] (by @jlfwong)
- Upgrade typescript & eslint to latest, fix resulting errors [[#455](https://github.com/jlfwong/speedscope/pull/455)] (by @jlfwong)
- Re-enable eslint prettier rule after being accidentally disabled for 3 years [[#454](https://github.com/jlfwong/speedscope/pull/454)] (by @jlfwong)
- Support the chrome JSON trace format (allows viewing of hermes traces) [[#453](https://github.com/jlfwong/speedscope/pull/453)] (by @zacharyfmarion)
- Fix bug in selectQueueToTakeFromNext for trace profiles [[#450](https://github.com/jlfwong/speedscope/pull/450)] (by @zacharyfmarion)
- Add bounds checking for sampleTypeIndex [[#449](https://github.com/jlfwong/speedscope/pull/449)] (by @jlfwong)
- Update README-zh_CN.md [[#442](https://github.com/jlfwong/speedscope/pull/442)] (by @byronhe)
- Update publish-and-deploy to remove automated release creation [[#440](https://github.com/jlfwong/speedscope/pull/440)] (by @jlfwong)
## [1.16.0] - 2023-07-16
- Automate more of the release process [[#439](https://github.com/jlfwong/speedscope/pull/439)] (by @jlfwong)
- Improve profile builder performance [[#437](https://github.com/jlfwong/speedscope/pull/437)] (by @Goose97)
- Fix crash when importing big linux perf tool files [[#435](https://github.com/jlfwong/speedscope/pull/435)] (by @Goose97)
- Improve splitLines: return iterator instead [[#434](https://github.com/jlfwong/speedscope/pull/434)] (by @Goose97)
- Fix trimTextMid [[#431](https://github.com/jlfwong/speedscope/pull/431)] (by @jlfwong)
- Added support for Papyrus profiles [[#428](https://github.com/jlfwong/speedscope/pull/428)] (by @xieve)
## [1.15.2] - 2023-06-21
### Fixed
- Use more accurate line information for pprof profiles [[#430](https://github.com/jlfwong/speedscope/pull/430)] (by @dalehamel)
- Stackprof: weight on-cpu samples by period rather than timestamp delta [[#425](https://github.com/jlfwong/speedscope/pull/425)] (by @manuelfelipe)
- Prevent crashes when stackprof profiles frames are missing names [[#419](https://github.com/jlfwong/speedscope/pull/419)] (by @jez)
- fix pprof defaultSampleType [[#424](https://github.com/jlfwong/speedscope/pull/424)] (by @vasi-stripe)
## [1.15.1] - 2023-06-04
### Fixed
+14 -58
View File
@@ -1,15 +1,5 @@
This document describes processes needed by admins of this repository.
# Publishing
Publishing speedscope is a multi-step process:
1. Test the release
2. Prepare the release
3. Publish to npm
4. Deploy the website
5. Upload a release to GitHub
At time of writing, deployment assumes you're running macOS. It probably
works if you're on a linux, and almost definitely does not work on Windows.
@@ -18,8 +8,9 @@ works if you're on a linux, and almost definitely does not work on Windows.
Speedscope is tested in CI, so all the automated tests should be passing. We'll
just be doing a few sanity checks to make sure the build & deployment machinery is working correctly.
Run `scripts/prepare-test-installation.sh`. This will do a mock publish &
installation to ensure that the version we're about to publish is going to
scripts/prepare-test-installation.sh
This will do a mock publish & installation to ensure that the version we're about to publish is going to
work. At the end of this command, it should echo a `cd` command to run in your shell
to switch to the installation directory. Something like this:
@@ -36,58 +27,23 @@ Try importing a profile from disk via the browse button and make sure it works.
Next, try running `bin/cli.js dist/release/perf-vertx*`. This should immediately open
speedscope in browser, and the perf-vertx file should load immediately.
If everything looks good, proceed to "Prepare the release".
## Create & publish the new release
## Prepare the release
Ensure you have the Github CLI tools installed and you're authenticated. Try running the following if you're unsure:
1. Update the version manually in package.json (we intentionally don't use the `npm version` command)
2. Update CHANGELOG.md to indicate the changes that were made as part of this release
3. Commit the changes with the version name as the commit message, e.g. `git commit -m 0.6.0`
4. `git tag` the release. We use tags like `v0.6.0`, e.g. `git tag v0.6.0`
5. `git push && git push --tags`
gh auth status
npm whoami
## Publish to npm
In your default browser, ensure that you're logged into your npm account, otherwise you'll see a 404 page when you open the authenticate link during the npm publish.
Assuming everything went well in the previous two phases, publishing should just be
a matter of running `npm publish`.
Once ready to publish, run:
### Verifying the publish
scripts/publish-and-deploy.sh
To verify that the publish was successful, run `npm install -g speedscope`.
## Verifying the release
To verify that the npm publish was successful, run `npm install -g speedscope`.
Try `speedscope`, which should open speedscope in browser.
Try `speedscope sample/profiles/stackcollapse/simple.txt`, which should immediately load the profile.
## Deploying the website
This step must follow the "Publish to npm" step, since it uses assets from
the npm publish.
https://www.speedscope.app/ is hosted on GitHub pages, and is published via pushing
to the `gh-pages` branch. The `gh-pages` branch has totally different contents than
other branches of this repository: https://github.com/jlfwong/speedscope/tree/gh-pages.
It's populated by a deploy script which is invoked by running `npm run deploy` script. This populate a directory with assets pulled from npm, and
boot a local server for you to test the compiled assets. Please do not skip
the manual testing in this step.
If everything looks good, you should be able to hit Ctrl+C, and you should see this prompt:
```
Commit release? [yes/no]:
```
If everything looks good, type `yes` then enter. This will commit to the `gh-pages` branch, and the site should automatically deploy shortly after.
To check if a deploy has happened, you can check https://www.speedscope.app/release.txt
which includes the version, the date, and the commit of the deploy.
## Upload a release to GitHub
This step must follow the "Publish to npm" step, since it uses assets from
the npm publish.
To make a zipfile suitable for uploading to GitHub as a release, run `scripts/prepare-zip-file.sh`.
Once that's done, you should have a zip file in `dist/release/`
Upload that file along with changelog notes to https://github.com/jlfwong/speedscope/releases/new
To verify the website has finished deploying, check the version number shown in the console of https://www.speedscope.app/
+2 -2
View File
@@ -12,7 +12,7 @@
# 使用
访问https://www.speedscope.app,上传文件或者拖拽到页面上。配置文件不会上传到任何地方——应用程序完全在浏览器中。
访问 <https://www.speedscope.app> ,上传文件或者拖拽到页面上。配置文件不会上传到任何地方——应用程序完全在浏览器中。
## 命令行中使用
@@ -24,7 +24,7 @@
## 独立使用
如果你不想使用npm或者node下载,你也可以在这里下载独立的版本https://github.com/jlfwong/speedscope/releases.
如果你不想使用npm或者node下载,你也可以在这里下载独立的版本 <https://github.com/jlfwong/speedscope/releases>.
下载完一个版本的压缩文件之后,解压并在谷歌或者火狐浏览器中打开`index.html`文件即可。
## 支持的文件格式
+22 -23
View File
@@ -1,7 +1,6 @@
English | [简体中文](./README-zh_CN.md)
# 🔬speedscope
A fast, interactive web-based viewer for performance profiles. An alternative viewer for [FlameGraphs][1]. Will happily display multi-megabyte profiles without crashing your browser.
A fast, interactive web-based viewer for performance profiles. Supports import from a variety of profiles in a variety of languages (JS, Ruby, Python, Go & more). Try it here: https://www.speedscope.app
Given raw profiling data, speedscope allows you to interactively explore the data to get insight into what's slow in your application, or allocating all the memory, or whatever data is represented in the profiling data.
@@ -10,27 +9,7 @@ Given raw profiling data, speedscope allows you to interactively explore the dat
[0]: https://en.wikipedia.org/wiki/Profiling_(computer_programming)#Statistical_profilers
[1]: https://github.com/brendangregg/FlameGraph
# Usage
Visit https://www.speedscope.app, then either browse to find a profile file or drag-and-drop one onto the page. The profiles are not uploaded anywhere -- the application is totally in-browser.
## Command line usage
For offline use, or convenience in the terminal, you can also install speedscope
via npm:
npm install -g speedscope
Invoking `speedscope /path/to/profile` will load speedscope in your default browser.
## Self-contained directory
If you don't have npm or node installed, you can also download a
self-contained version from https://github.com/jlfwong/speedscope/releases.
After you download the zip file from a release, simply unzip it and open the
contained `index.html` in Chrome or Firefox.
## Supported file formats
# Supported file formats
speedscope is designed to ingest profiles from a variety of different profilers for different programming languages & environments. Click the links below for documentation on how to import from a specific source.
@@ -39,6 +18,7 @@ speedscope is designed to ingest profiles from a variety of different profilers
- [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)
- [Importing from Hermes (for React Native)](https://github.com/jlfwong/speedscope/wiki/Importing-from-Hermes)
- 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))
@@ -62,6 +42,25 @@ speedscope is designed to ingest profiles from a variety of different profilers
- [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).
# Usage
Visit https://www.speedscope.app, then either browse to find a profile file or drag-and-drop one onto the page. The profiles are not uploaded anywhere -- the application is totally in-browser.
## Command line usage
For offline use, or convenience in the terminal, you can also install speedscope
via npm:
npm install -g speedscope
Invoking `speedscope /path/to/profile` will load speedscope in your default browser.
## Self-contained directory
If you don't have npm or node installed, you can also download a
self-contained version from https://github.com/jlfwong/speedscope/releases.
After you download the zip file from a release, simply unzip it and open the
contained `index.html` in Chrome or Firefox.
## Importing via URL
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>speedscope</title>
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet">
<link href="source-code-pro.css" rel="stylesheet">
<script>
// https://github.com/evanw/webgl-recorder
false && (function () {
+7
View File
@@ -0,0 +1,7 @@
@font-face{
font-family: 'Source Code Pro';
font-weight: 400;
font-style: normal;
font-stretch: normal;
src: url('./source-code-pro/SourceCodePro-Regular.ttf.woff2') format('woff2');
}
+93
View File
@@ -0,0 +1,93 @@
Copyright 2010-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+3114 -1735
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "speedscope",
"version": "1.15.1",
"version": "1.21.0",
"description": "",
"repository": "jlfwong/speedscope",
"main": "index.js",
@@ -34,24 +34,24 @@
"@types/jszip": "3.1.4",
"@types/node": "14.0.1",
"@types/pako": "1.0.0",
"@typescript-eslint/eslint-plugin": "4.19.0",
"@typescript-eslint/parser": "4.19.0",
"@typescript-eslint/eslint-plugin": "6.16.0",
"@typescript-eslint/parser": "6.16.0",
"acorn": "7.2.0",
"aphrodite": "2.1.0",
"eslint": "6.0.0",
"eslint-plugin-prettier": "2.6.0",
"eslint-plugin-react-hooks": "4.0.2",
"eslint": "8.0.0",
"eslint-plugin-prettier": "5.1.2",
"eslint-plugin-react-hooks": "4.6.0",
"jest": "24.3.0",
"jsverify": "0.8.3",
"jszip": "3.1.5",
"pako": "1.0.6",
"parcel-bundler": "1.12.4",
"preact": "10.4.1",
"prettier": "2.0.4",
"prettier": "3.1.1",
"protobufjs": "6.8.8",
"source-map": "0.6.1",
"ts-jest": "24.3.0",
"typescript": "4.2.3",
"typescript": "5.3.3",
"typescript-json-schema": "0.42.0",
"uglify-es": "3.2.2",
"uint8array-json-parser": "0.0.2"
@@ -0,0 +1,95 @@
Weight Self Weight Symbol Names
4.93 s 100.0% 0 s start
4.93 s 99.9% 0 s main
2.45 s 0.0% 846.00 ms delta()
797.00 ms 0.0% 786.00 ms alpha()
9.00 ms 0.0% 0 s leakMemory()
8.00 ms 0.0% 1.00 ms szone_malloc_should_clear
6.00 ms 0.0% 4.00 ms small_malloc_should_clear
2.00 ms 0.0% 2.00 ms small_malloc_from_free_list
1.00 ms 0.0% 1.00 ms small_malloc_from_free_list
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
2.00 ms 0.0% 2.00 ms _malloc_zone_malloc
795.00 ms 0.0% 778.00 ms beta()
14.00 ms 0.0% 1.00 ms leakMemory()
11.00 ms 0.0% 2.00 ms szone_malloc_should_clear
9.00 ms 0.0% 3.00 ms small_malloc_should_clear
5.00 ms 0.0% 5.00 ms small_malloc_from_free_list
1.00 ms 0.0% 1.00 ms rack_get_thread_index
1.00 ms 0.0% 1.00 ms nanov2_malloc_type
1.00 ms 0.0% 1.00 ms DYLD-STUB$$malloc
2.00 ms 0.0% 2.00 ms nanov2_malloc_type
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
10.00 ms 0.0% 0 s leakMemory()
7.00 ms 0.0% 3.00 ms szone_malloc_should_clear
4.00 ms 0.0% 3.00 ms small_malloc_should_clear
1.00 ms 0.0% 0 s mvm_allocate_pages_plat
1.00 ms 0.0% 1.00 ms _kernelrpc_mach_vm_map_trap
2.00 ms 0.0% 2.00 ms small_malloc_should_clear
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
3.00 ms 0.0% 3.00 ms _malloc_zone_malloc
838.00 ms 0.0% 827.00 ms gamma()
9.00 ms 0.0% 1.00 ms leakMemory()
5.00 ms 0.0% 0 s szone_malloc_should_clear
4.00 ms 0.0% 1.00 ms small_malloc_should_clear
3.00 ms 0.0% 3.00 ms small_malloc_from_free_list
1.00 ms 0.0% 1.00 ms rack_get_thread_index
1.00 ms 0.0% 1.00 ms small_malloc_should_clear
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
1.00 ms 0.0% 1.00 ms nanov2_malloc_type
2.00 ms 0.0% 2.00 ms _malloc_zone_malloc
834.00 ms 0.0% 824.00 ms beta()
9.00 ms 0.0% 0 s leakMemory()
7.00 ms 0.0% 0 s szone_malloc_should_clear
6.00 ms 0.0% 1.00 ms small_malloc_should_clear
4.00 ms 0.0% 4.00 ms small_malloc_from_free_list
1.00 ms 0.0% 1.00 ms rack_get_thread_index
1.00 ms 0.0% 1.00 ms rack_get_thread_index
1.00 ms 0.0% 1.00 ms nanov2_malloc_type
1.00 ms 0.0% 1.00 ms small_malloc_should_clear
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
803.00 ms 0.0% 788.00 ms alpha()
14.00 ms 0.0% 0 s leakMemory()
11.00 ms 0.0% 1.00 ms szone_malloc_should_clear
10.00 ms 0.0% 3.00 ms small_malloc_should_clear
5.00 ms 0.0% 5.00 ms small_malloc_from_free_list
2.00 ms 0.0% 2.00 ms rack_get_thread_index
1.00 ms 0.0% 1.00 ms nanov2_malloc_type
1.00 ms 0.0% 1.00 ms small_malloc_should_clear
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
1.00 ms 0.0% 1.00 ms _malloc_zone_malloc
4.00 ms 0.1% 0 s dyld4::start(dyld4::KernelArgs*, void*, void*)::$_0::operator()() const
4.00 ms 0.1% 0 s dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*)
3.00 ms 0.0% 0 s dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&)
3.00 ms 0.0% 0 s mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const
3.00 ms 0.0% 0 s mach_o::Header::forEachLoadCommand(void (load_command const*, bool&) block_pointer) const
3.00 ms 0.0% 0 s invocation function for block in mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const
3.00 ms 0.0% 0 s invocation function for block in dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&)
3.00 ms 0.0% 0 s dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&)
3.00 ms 0.0% 0 s dyld4::Loader::forEachPath(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer)
3.00 ms 0.0% 0 s dyld4::ProcessConfig::PathOverrides::forEachPathVariant(char const*, dyld3::Platform, bool, bool, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer) const
3.00 ms 0.0% 0 s dyld4::Loader::forEachResolvedAtPathVar(dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, dyld4::ProcessConfig::PathOverrides::Type, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer)
3.00 ms 0.0% 0 s invocation function for block in dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&)
3.00 ms 0.0% 0 s dyld4::Loader::makeDyldCacheLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, unsigned int, mach_o::Layout const*)
3.00 ms 0.0% 0 s dyld4::RuntimeState::findPrebuiltLoader(char const*) const
3.00 ms 0.0% 0 s dyld4::PrebuiltLoader::isValid(dyld4::RuntimeState const&) const
3.00 ms 0.0% 0 s dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const
3.00 ms 0.0% 0 s dyld4::ProcessConfig::PathOverrides::forEachPathVariant(char const*, dyld3::Platform, bool, bool, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer) const
3.00 ms 0.0% 0 s invocation function for block in dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const
3.00 ms 0.0% 0 s dyld4::SyscallDelegate::fileExists(char const*, dyld4::FileID*, int*) const
3.00 ms 0.0% 0 s dyld3::stat(char const*, stat*)
3.00 ms 0.0% 3.00 ms stat
1.00 ms 0.0% 0 s dyld4::APIs::runAllInitializersForMain()
1.00 ms 0.0% 0 s dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const
1.00 ms 0.0% 0 s dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::operator()() const
1.00 ms 0.0% 0 s dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const
1.00 ms 0.0% 0 s dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const
1.00 ms 0.0% 0 s dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const
1.00 ms 0.0% 0 s dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const
1.00 ms 0.0% 0 s dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const
1.00 ms 0.0% 0 s dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const
1.00 ms 0.0% 0 s dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const
1.00 ms 0.0% 0 s dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const
1.00 ms 0.0% 0 s invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const
1.00 ms 0.0% 0 s invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const
1.00 ms 0.0% 1.00 ms _GLOBAL__sub_I_stdlib_typed_new.cpp
+16
View File
@@ -0,0 +1,16 @@
Script_abc_example_quest log opened (PC)
500:PUSH:3053:1:abcExampleQuest (24021278):abc_example_quest..exampleFunction1
1500:POP:3053:1:abcExampleQuest (24021278):abc_example_quest..exampleFunction1
1700:PUSH:3053:2:abcExampleQuest (24021278):abc_example_quest..exampleFunction2
1750:POP:3053:2:abcExampleQuest (24021278):abc_example_quest..exampleFunction2
2000:PUSH:3208:2:abcExampleQuest (24021278):abc_example_quest..exampleFunction3
2000:PUSH:3053:3:None:abc_example_quest..exampleFunction4
2000:POP:3208:2:abcExampleQuest (24021278):abc_example_quest..exampleFunction3
2000:POP:3053:3:None:abc_example_quest..exampleFunction4
2000:PUSH:3947:1:None:abc_example_quest..exampleFunction5
2250:PUSH:3949:1:None:abc_example_quest..exampleFunction6
2500:POP:3949:1:None:abc_example_quest..exampleFunction6
3000:POP:3947:1:None:abc_example_quest..exampleFunction5
3450:PUSH:3947:1:abcExampleQuest (24021278):abc_example_quest..exampleFunction1
3500:POP:3947:1:abcExampleQuest (24021278):abc_example_quest..exampleFunction1
Log closed
+24
View File
@@ -0,0 +1,24 @@
Stack_3185 log opened (PC)
50002:START:3185
50002:POP:3185:3:None:Debug..StartStackProfiling
50002:QUEUE_PUSH:3185:3: (00018A56):Location.??.GetFormID
50018:PUSH:3185:3: (00018A56):Form..GetFormID
50018:POP:3185:3: (00018A56):Form..GetFormID
50018:PUSH:3185:3:None:abc_example_mod_quest..exampleFunction1
50018:QUEUE_PUSH:3185:4: (00018A56):Location.??.GetFormID
50035:PUSH:3185:4: (00018A56):Form..GetFormID
50035:POP:3185:4: (00018A56):Form..GetFormID
50035:PUSH:3185:4:None:Game..GetModName
50035:POP:3185:4:None:Game..GetModName
50035:POP:3185:3:None:abc_example_mod_quest..exampleFunction1
50035:POP:3185:2:None:abc_example_mod_quest..exampleFunction2
50035:QUEUE_PUSH:3185:2:WhiterunPLainsDistrict03 (0001A27A):Cell.??.IsInterior
50051:PUSH:3185:2:WhiterunPLainsDistrict03 (0001A27A):Cell..IsInterior
50051:POP:3185:2:WhiterunPLainsDistrict03 (0001A27A):Cell..IsInterior
50051:POP:3185:1:abcExampleModQuest (24021278):abc_example_mod_quest..exampleFunction3
50051:QUEUE_PUSH:3185:1:None:utility.??.WaitMenuMode
50068:PUSH:3185:1:None:utility..WaitMenuMode
50602:POP:3185:1:None:utility..WaitMenuMode
50602:QUEUE_POP:3185:0:None:abc_example_mod_effect..OnEffectStart
50619:POP:3185:0:None:abc_example_mod_effect..OnEffectStart
Log closed
@@ -0,0 +1 @@
{"version":"0.0.1","$schema":"https://www.speedscope.app/file-format-schema.json","shared":{"frames":[{"name":"a"},{"name":"b"},{"name":"c"},{"name":"d"}]},"profiles":[{"type":"evented","name":"simple.txt","unit":"none","startValue":0,"endValue":14,"events":[{"type":"O","frame":0,"at":0},{"type":"O","frame":1,"at":0},{"type":"O","frame":2,"at":0},{"type":"C","frame":2,"at":2},{"type":"O","frame":3,"at":2},{"type":"C","frame":3,"at":6},{"type":"O","frame":2,"at":6},{"type":"C","frame":2,"at":9},{"type":"C","frame":1,"at":14},{"type":"C","frame":0,"at":14}]}]}
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,156 @@
[
{
"pid":0,
"tid":0,
"ph":"B",
"name":"[root]",
"ts":0,
"args":{
"name":"[root]",
"category":"root",
"url":null,
"line":null,
"column":null,
"params":null,
"allocatedCategory":"root",
"allocatedName":"[root]"
}
},
{
"pid":0,
"tid":0,
"ph":"B",
"name":"beta",
"ts":1,
"args":{
"line":54,
"column":12,
"funcLine":"1",
"funcColumn":"1",
"name":"beta",
"category":"JavaScript",
"parent":1,
"url":"/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params":null,
"allocatedCategory":"JavaScript",
"allocatedName":"beta"
}
},
{
"pid":0,
"tid":0,
"ph":"E",
"name":"beta",
"ts":2,
"args":{
"line":54,
"column":12,
"funcLine":"1",
"funcColumn":"1",
"name":"beta",
"category":"JavaScript",
"parent":1,
"url":"/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params":null,
"allocatedCategory":"JavaScript",
"allocatedName":"beta"
}
},
{
"pid":0,
"tid":0,
"ph":"B",
"name":"gamma",
"ts":4,
"args":{
"line":54,
"column":12,
"funcLine":"1",
"funcColumn":"1",
"name":"beta",
"category":"blah",
"parent":1,
"url":"/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params":null,
"allocatedCategory":"JavaScript",
"allocatedName":"beta"
}
},
{
"pid":0,
"tid":0,
"ph":"E",
"name":"gamma",
"ts":13,
"args":{
"line":54,
"column":12,
"funcLine":"1",
"funcColumn":"1",
"name":"beta",
"category":"blah",
"parent":1,
"url":"/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params":null,
"allocatedCategory":"JavaScript",
"allocatedName":"beta"
}
},
{
"pid":0,
"tid":0,
"ph":"B",
"name":"beta",
"ts":4,
"args":{
"line":54,
"column":12,
"funcLine":"1",
"funcColumn":"1",
"name":"beta",
"category":"blah",
"parent":1,
"url":"/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params":null,
"allocatedCategory":"JavaScript",
"allocatedName":"beta"
}
},
{
"pid":0,
"tid":0,
"ph":"E",
"name":"beta",
"ts":10,
"args":{
"line":54,
"column":12,
"funcLine":"1",
"funcColumn":"1",
"name":"beta",
"category":"blah",
"parent":1,
"url":"/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params":null,
"allocatedCategory":"JavaScript",
"allocatedName":"beta"
}
},
{
"pid":0,
"tid":0,
"ph":"E",
"name":"[root]",
"ts":14,
"args":{
"name":"[root]",
"category":"root",
"url":null,
"line":null,
"column":null,
"params":null,
"allocatedCategory":"root",
"allocatedName":"[root]"
}
}
]
@@ -0,0 +1,150 @@
{
"traceEvents": [
{
"name": "process_name",
"ph": "M",
"cat": "__metadata",
"pid": 7512,
"ts": "11550183666",
"tid": "-1",
"args": {
"name": "hermes"
}
},
{
"name": "thread_name",
"ph": "M",
"cat": "__metadata",
"pid": 7512,
"ts": "11550183666",
"tid": "1",
"args": {
"name": "mqt_js"
}
},
{
"name": "mqt_js",
"cat": "mqt_js",
"ph": "X",
"dur": 0,
"pid": 7512,
"ts": "11550183666",
"tid": "1",
"args": {}
},
{
"name": "mqt_js",
"cat": "mqt_js",
"ph": "X",
"dur": 0,
"pid": 7512,
"ts": "11550183666",
"tid": "2",
"args": {}
}
],
"samples": [
{
"cpu": "-1",
"name": "",
"ts": "11552125241",
"pid": 7512,
"tid": "1",
"weight": "1",
"sf": 1
},
{
"cpu": "-1",
"name": "",
"ts": "11552125241",
"pid": 7512,
"tid": "2",
"weight": "1",
"sf": 4
},
{
"cpu": "-1",
"name": "",
"ts": "11552146011",
"pid": 7512,
"tid": "1",
"weight": "1",
"sf": 2
},
{
"cpu": "-1",
"name": "",
"ts": "11552146011",
"pid": 7512,
"tid": "2",
"weight": "1",
"sf": 5
},
{
"cpu": "-1",
"name": "",
"ts": "11552159337",
"pid": 7512,
"tid": "1",
"weight": "1",
"sf": 3
},
{
"cpu": "-1",
"name": "",
"ts": "11552159337",
"pid": 7512,
"tid": "2",
"weight": "1",
"sf": 6
},
{
"cpu": "-1",
"name": "",
"ts": "11552169337",
"pid": 7512,
"tid": "2",
"weight": "1",
"sf": 4
},
{
"cpu": "-1",
"name": "",
"ts": "11552179337",
"pid": 7512,
"tid": "2",
"weight": "1",
"sf": 4
}
],
"stackFrames": {
"1": {
"name": "[root]",
"category": "root"
},
"2": {
"name": "function1",
"category": "JavaScript",
"parent": 1
},
"3": {
"name": "[GC Young Gen]",
"category": "Metadata",
"parent": 1
},
"4": {
"name": "[root thread 2]",
"category": "root"
},
"5": {
"name": "function3",
"category": "JavaScript",
"parent": 4
},
"6": {
"name": "function4",
"category": "Metadata",
"parent": 4
}
}
}
@@ -0,0 +1,76 @@
[
{
"pid": 0,
"tid": 0,
"ph": "B",
"name": "[root]",
"ts": 0,
"args": {
"name": "[root]",
"category": "root",
"url": null,
"line": null,
"column": null,
"params": null,
"allocatedCategory": "root",
"allocatedName": "[root]"
}
},
{
"pid": 0,
"tid": 0,
"ph": "B",
"name": "beta",
"ts": 1,
"args": {
"line": 54,
"column": 12,
"funcLine": "1",
"funcColumn": "1",
"name": "beta",
"category": "JavaScript",
"parent": 1,
"url": "/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params": null,
"allocatedCategory": "JavaScript",
"allocatedName": "beta"
}
},
{
"pid": 0,
"tid": 0,
"ph": "E",
"name": "beta",
"ts": 13,
"args": {
"line": 54,
"column": 12,
"funcLine": "1",
"funcColumn": "1",
"name": "beta",
"category": "JavaScript",
"parent": 1,
"url": "/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"params": null,
"allocatedCategory": "JavaScript",
"allocatedName": "beta"
}
},
{
"pid": 0,
"tid": 0,
"ph": "E",
"name": "[root]",
"ts": 14,
"args": {
"name": "[root]",
"category": "root",
"url": null,
"line": null,
"column": null,
"params": null,
"allocatedCategory": "root",
"allocatedName": "[root]"
}
}
]
@@ -0,0 +1,117 @@
{
"traceEvents": [
{
"name": "process_name",
"ph": "M",
"cat": "__metadata",
"pid": 7512,
"ts": "11550183666",
"tid": "-1",
"args": {
"name": "hermes"
}
},
{
"name": "thread_name",
"ph": "M",
"cat": "__metadata",
"pid": 7512,
"ts": "11550183666",
"tid": "7695",
"args": {
"name": "mqt_js"
}
}
],
"samples": [
{
"cpu": "-1",
"name": "",
"ts": "11552125241",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 1
},
{
"cpu": "-1",
"name": "",
"ts": "11552129681",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 1
},
{
"cpu": "-1",
"name": "",
"ts": "11552146011",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 2
},
{
"cpu": "-1",
"name": "",
"ts": "11552159337",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 4
},
{
"cpu": "-1",
"name": "",
"ts": "11552169942",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 1
},
{
"cpu": "-1",
"name": "",
"ts": "11552179951",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 5
},
{
"cpu": "-1",
"name": "",
"ts": "11552189951",
"pid": 7512,
"tid": "7695",
"weight": "1",
"sf": 5
}
],
"stackFrames": {
"1": {
"name": "[root]",
"category": "root"
},
"2": {
"name": "function1(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:1:1)",
"category": "JavaScript",
"parent": 1
},
"3": {
"name": "function2(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:418874:4)",
"category": "JavaScript",
"parent": 1
},
"4": {
"name": "function3(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:17:3)",
"category": "JavaScript",
"parent": 3
},
"5": {
"name": "[GC Young Gen]",
"category": "Metadata",
"parent": 1
}
}
}
@@ -0,0 +1,14 @@
[
{"pid": 0, "tid": 0, "ph": "B", "name": "anonymous", "ts": 0},
{"pid": 0, "tid": 0, "ph": "B", "name": "anonymous", "ts": 1, "args": { "parent": 1 }},
{"pid": 0, "tid": 0, "ph": "B", "name": "function1", "ts": 1, "args": { "parent": 2 }},
{"pid": 0, "tid": 0, "ph": "B", "name": "anonymous", "ts": 1, "args": { "parent": 3 }},
{"pid": 0, "tid": 0, "ph": "E", "name": "anonymous", "ts": 3, "args": { "parent": 3 }},
{"pid": 0, "tid": 0, "ph": "E", "name": "function1", "ts": 3, "args": { "parent": 2 }},
{"pid": 0, "tid": 0, "ph": "E", "name": "anonymous", "ts": 3, "args": { "parent": 1 }},
{"pid": 0, "tid": 0, "ph": "B", "name": "anonymous", "ts": 3, "args": { "parent": 1 }},
{"pid": 0, "tid": 0, "ph": "B", "name": "anonymous", "ts": 3, "args": { "parent": 8 }},
{"pid": 0, "tid": 0, "ph": "E", "name": "anonymous", "ts": 5, "args": { "parent": 8 }},
{"pid": 0, "tid": 0, "ph": "E", "name": "anonymous", "ts": 5, "args": { "parent": 1 }},
{"pid": 0, "tid": 0, "ph": "E", "name": "anonymous", "ts": 5}
]
+8 -2
View File
@@ -5,6 +5,7 @@ def a
for i in 0..100 do
b
c
e
end
end
@@ -28,8 +29,13 @@ def d
prod
end
profile = StackProf.run(mode: :wall, raw: true) do
def e
sleep 0.05
end
mode = (ARGV[0] || :wall).to_sym
profile = StackProf.run(mode: mode, raw: true) do
a
end
puts JSON.generate(profile)
puts JSON.generate(profile)
+4
View File
@@ -22,5 +22,9 @@ git rev-parse HEAD >> "$OUTDIR"/release.txt
# Place a json schema for the file format into the build directory too
node scripts/generate-file-format-schema-json.js > "$OUTDIR"/file-format-schema.json
# Include licenses
# https://github.com/jlfwong/speedscope/pull/412
cp assets/source-code-pro/LICENSE.md "$OUTDIR"/source-code-pro.LICENSE.md
# Build the compiled assets
node_modules/.bin/parcel build assets/index.html --no-cache --out-dir "$OUTDIR" --public-url "./" --detailed-report
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Print changes since the last tagged release in a format to match CHANGELOG.md
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 <version>"
echo "e.g. $0 1.15.2"
exit 1
fi
version="$1"
# Get the most recent tagged commit that is an ancestor of HEAD
tagged_commit=$(git log --simplify-by-decoration --oneline --decorate | grep -E '^\w+ \(tag: .+\) .+$' | head -n1 | cut -d' ' -f 1)
gitlog=$(git log --graph --pretty=format:"%s" --abbrev-commit "$tagged_commit"..HEAD)
version="$1"
current_date=$(date +%Y-%m-%d)
echo "## [$version] - $current_date"
echo
while IFS= read line; do
message=$(echo "$line" | sed 's/^* //g')
message_without_pr_num=$(echo "$message" | sed 's/ (#[0-9][0-9]*)//g')
pr_number=$(echo "$message" | grep -Eo '#[0-9]+' | grep -Eo '[0-9]+'; true)
if [[ -n "$pr_number" ]]; then
author=$(gh pr view $pr_number --json author -q ".author.login")
pr_link="https://github.com/jlfwong/speedscope/pull/$pr_number"
echo "- $message_without_pr_num [[#$pr_number]($pr_link)] (by @$author)"
else
echo "- $message_without_pr_num"
fi
done <<< "$gitlog"
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
# Run the full release process. This means...
# - Bumping the version in package.json
# - Updating the changelog
# - Commiting and tagging the release
# - Pushing to Github
# - Publishing a new version to npm
# - Deploying the website
# - Create a new release in Github with the zip-file standalone version
set -euxo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 <minor | patch | version>"
echo "e.g. $0 patch"
exit 1
fi
version_arg="$1"
# Bump versions in package.json and package-lock.json, but don't commit or make tags yet
version=$(npm version "$version_arg" --no-git-tag-version --no-commit-hooks | sed 's/^v//g')
tagname="v$version"
script_dir=$(dirname "$0")
# Prepend the changelog update to CHANGELOG.md
changelog_update=$("$script_dir/print-changelog-update.sh" "$version")
echo -e "$changelog_update\n" > CHANGELOG.md.new
cat CHANGELOG.md >> CHANGELOG.md.new
mv CHANGELOG.md.new CHANGELOG.md
# Commit and tag the release
git add CHANGELOG.md package.json package-lock.json
git commit -m "$version"
git tag "$tagname"
# Push to Github
git push
git push --tags
# Publish to npm
npm publish
# Deploy the website
npm run deploy
# Create a new release on Github
"$script_dir/prepare-zip-file.sh"
# Don't double echo the below commands
set +x
echo
echo
echo "Visit https://github.com/jlfwong/speedscope/releases/new to create a new release."
echo
echo "tag: $tagname"
echo "title: $tagname"
echo "attachment: dist/release/speedscope-$version.zip"
echo "$changelog_update"
# NOTE: This part is almost-but-not-quite-automatable using a command like this:
#
# gh release create "$tagname" --title "$tagname" --notes "$changelog_update"
# --attach "dist/release/speedscope-$version.zip"
#
# There are two problems.
#
# 1. The "--attach" flag doesn't exit
# 2. I don't want the changelog notes to include the version and date like they do in CHANGELOG.md
#
# If 1. was solveable, then 2. would be easy to work-around.
+4 -1
View File
@@ -102,7 +102,10 @@ export class FlamechartColorPassRenderer {
private material: Graphics.Material
private buffer: Graphics.VertexBuffer
constructor(private gl: Graphics.Context, theme: Theme) {
constructor(
private gl: Graphics.Context,
theme: Theme,
) {
const vertices = [
{pos: [-1, 1], uv: [0, 1]},
{pos: [1, 1], uv: [1, 1]},
+4 -1
View File
@@ -70,7 +70,10 @@ export class ViewportRectangleRenderer {
private material: Graphics.Material
private buffer: Graphics.VertexBuffer
constructor(private gl: Graphics.Context, theme: Theme) {
constructor(
private gl: Graphics.Context,
theme: Theme,
) {
const vertices = [
[-1, 1],
[1, 1],
@@ -654,6 +654,504 @@ Object {
}
`;
exports[`importFromInstrumentsDeepCopy time profile, version 16 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": ":start",
"line": undefined,
"name": "start",
"selfWeight": 0,
"totalWeight": 4479,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":main",
"line": undefined,
"name": "main",
"selfWeight": 0,
"totalWeight": 4475,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":delta()",
"line": undefined,
"name": "delta()",
"selfWeight": 395,
"totalWeight": 2000,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":alpha()",
"line": undefined,
"name": "alpha()",
"selfWeight": 1574,
"totalWeight": 1600,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":leakMemory()",
"line": undefined,
"name": "leakMemory()",
"selfWeight": 2,
"totalWeight": 65,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":szone_malloc_should_clear",
"line": undefined,
"name": "szone_malloc_should_clear",
"selfWeight": 7,
"totalWeight": 49,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":small_malloc_should_clear",
"line": undefined,
"name": "small_malloc_should_clear",
"selfWeight": 20,
"totalWeight": 44,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":small_malloc_from_free_list",
"line": undefined,
"name": "small_malloc_from_free_list",
"selfWeight": 20,
"totalWeight": 20,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_malloc_zone_malloc",
"line": undefined,
"name": "_malloc_zone_malloc",
"selfWeight": 14,
"totalWeight": 14,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":beta()",
"line": undefined,
"name": "beta()",
"selfWeight": 1602,
"totalWeight": 1629,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":rack_get_thread_index",
"line": undefined,
"name": "rack_get_thread_index",
"selfWeight": 6,
"totalWeight": 6,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":nanov2_malloc_type",
"line": undefined,
"name": "nanov2_malloc_type",
"selfWeight": 6,
"totalWeight": 6,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":DYLD-STUB$$malloc",
"line": undefined,
"name": "DYLD-STUB$$malloc",
"selfWeight": 1,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":mvm_allocate_pages_plat",
"line": undefined,
"name": "mvm_allocate_pages_plat",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_kernelrpc_mach_vm_map_trap",
"line": undefined,
"name": "_kernelrpc_mach_vm_map_trap",
"selfWeight": 1,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":gamma()",
"line": undefined,
"name": "gamma()",
"selfWeight": 827,
"totalWeight": 838,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::start(dyld4::KernelArgs*, void*, void*)::$_0::operator()() const",
"line": undefined,
"name": "dyld4::start(dyld4::KernelArgs*, void*, void*)::$_0::operator()() const",
"selfWeight": 0,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*)",
"line": undefined,
"name": "dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*)",
"selfWeight": 0,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&)",
"line": undefined,
"name": "dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const",
"line": undefined,
"name": "mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":mach_o::Header::forEachLoadCommand(void (load_command const*, bool&) block_pointer) const",
"line": undefined,
"name": "mach_o::Header::forEachLoadCommand(void (load_command const*, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":invocation function for block in mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const",
"line": undefined,
"name": "invocation function for block in mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":invocation function for block in dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&)",
"line": undefined,
"name": "invocation function for block in dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&)",
"line": undefined,
"name": "dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::forEachPath(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer)",
"line": undefined,
"name": "dyld4::Loader::forEachPath(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::ProcessConfig::PathOverrides::forEachPathVariant(char const*, dyld3::Platform, bool, bool, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer) const",
"line": undefined,
"name": "dyld4::ProcessConfig::PathOverrides::forEachPathVariant(char const*, dyld3::Platform, bool, bool, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::forEachResolvedAtPathVar(dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, dyld4::ProcessConfig::PathOverrides::Type, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer)",
"line": undefined,
"name": "dyld4::Loader::forEachResolvedAtPathVar(dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, dyld4::ProcessConfig::PathOverrides::Type, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":invocation function for block in dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&)",
"line": undefined,
"name": "invocation function for block in dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::makeDyldCacheLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, unsigned int, mach_o::Layout const*)",
"line": undefined,
"name": "dyld4::Loader::makeDyldCacheLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, unsigned int, mach_o::Layout const*)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::RuntimeState::findPrebuiltLoader(char const*) const",
"line": undefined,
"name": "dyld4::RuntimeState::findPrebuiltLoader(char const*) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::PrebuiltLoader::isValid(dyld4::RuntimeState const&) const",
"line": undefined,
"name": "dyld4::PrebuiltLoader::isValid(dyld4::RuntimeState const&) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const",
"line": undefined,
"name": "dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":invocation function for block in dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const",
"line": undefined,
"name": "invocation function for block in dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::SyscallDelegate::fileExists(char const*, dyld4::FileID*, int*) const",
"line": undefined,
"name": "dyld4::SyscallDelegate::fileExists(char const*, dyld4::FileID*, int*) const",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld3::stat(char const*, stat*)",
"line": undefined,
"name": "dyld3::stat(char const*, stat*)",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":stat",
"line": undefined,
"name": "stat",
"selfWeight": 3,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::APIs::runAllInitializersForMain()",
"line": undefined,
"name": "dyld4::APIs::runAllInitializersForMain()",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const",
"line": undefined,
"name": "dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::operator()() const",
"line": undefined,
"name": "dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::operator()() const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const",
"line": undefined,
"name": "dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const",
"line": undefined,
"name": "dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const",
"line": undefined,
"name": "dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const",
"line": undefined,
"name": "dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const",
"line": undefined,
"name": "dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const",
"line": undefined,
"name": "dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const",
"line": undefined,
"name": "invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const",
"line": undefined,
"name": "invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": ":_GLOBAL__sub_I_stdlib_typed_new.cpp",
"line": undefined,
"name": "_GLOBAL__sub_I_stdlib_typed_new.cpp",
"selfWeight": 1,
"totalWeight": 1,
},
],
"name": "simple-time-profile-deep-copy.txt",
"stacks": Array [
"start;main;delta();alpha();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;small_malloc_from_free_list 2.00ms",
"start;main;delta();alpha();leakMemory();szone_malloc_should_clear;small_malloc_should_clear 4.00ms",
"start;main;delta();alpha();leakMemory();szone_malloc_should_clear;small_malloc_from_free_list 1.00ms",
"start;main;delta();alpha();leakMemory();szone_malloc_should_clear 1.00ms",
"start;main;delta();alpha();leakMemory();_malloc_zone_malloc 1.00ms",
"start;main;delta();alpha();_malloc_zone_malloc 2.00ms",
"start;main;delta();alpha() 786.00ms",
"start;main;delta();beta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;small_malloc_from_free_list 5.00ms",
"start;main;delta();beta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;rack_get_thread_index 1.00ms",
"start;main;delta();beta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear 3.00ms",
"start;main;delta();beta();leakMemory();szone_malloc_should_clear 2.00ms",
"start;main;delta();beta();leakMemory();nanov2_malloc_type 1.00ms",
"start;main;delta();beta();leakMemory();DYLD-STUB$$malloc 1.00ms",
"start;main;delta();beta();leakMemory() 1.00ms",
"start;main;delta();beta();nanov2_malloc_type 2.00ms",
"start;main;delta();beta();_malloc_zone_malloc 1.00ms",
"start;main;delta();beta() 778.00ms",
"start;main;delta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;mvm_allocate_pages_plat;_kernelrpc_mach_vm_map_trap 1.00ms",
"start;main;delta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear 3.00ms",
"start;main;delta();leakMemory();szone_malloc_should_clear 3.00ms",
"start;main;delta();leakMemory();small_malloc_should_clear 2.00ms",
"start;main;delta();leakMemory();_malloc_zone_malloc 1.00ms",
"start;main;delta();_malloc_zone_malloc 3.00ms",
"start;main;delta() 395.00ms",
"start;main;gamma();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;small_malloc_from_free_list 3.00ms",
"start;main;gamma();leakMemory();szone_malloc_should_clear;small_malloc_should_clear 1.00ms",
"start;main;gamma();leakMemory();szone_malloc_should_clear;rack_get_thread_index 1.00ms",
"start;main;gamma();leakMemory();small_malloc_should_clear 1.00ms",
"start;main;gamma();leakMemory();_malloc_zone_malloc 1.00ms",
"start;main;gamma();leakMemory();nanov2_malloc_type 1.00ms",
"start;main;gamma();leakMemory() 1.00ms",
"start;main;gamma();_malloc_zone_malloc 2.00ms",
"start;main;gamma() 827.00ms",
"start;main;beta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;small_malloc_from_free_list 4.00ms",
"start;main;beta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;rack_get_thread_index 1.00ms",
"start;main;beta();leakMemory();szone_malloc_should_clear;small_malloc_should_clear 1.00ms",
"start;main;beta();leakMemory();szone_malloc_should_clear;rack_get_thread_index 1.00ms",
"start;main;beta();leakMemory();nanov2_malloc_type 1.00ms",
"start;main;beta();leakMemory();small_malloc_should_clear 1.00ms",
"start;main;beta();_malloc_zone_malloc 1.00ms",
"start;main;beta() 824.00ms",
"start;main;alpha();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;small_malloc_from_free_list 5.00ms",
"start;main;alpha();leakMemory();szone_malloc_should_clear;small_malloc_should_clear;rack_get_thread_index 2.00ms",
"start;main;alpha();leakMemory();szone_malloc_should_clear;small_malloc_should_clear 3.00ms",
"start;main;alpha();leakMemory();szone_malloc_should_clear 1.00ms",
"start;main;alpha();leakMemory();nanov2_malloc_type 1.00ms",
"start;main;alpha();leakMemory();small_malloc_should_clear 1.00ms",
"start;main;alpha();leakMemory();_malloc_zone_malloc 1.00ms",
"start;main;alpha();_malloc_zone_malloc 1.00ms",
"start;main;alpha() 788.00ms",
"start;dyld4::start(dyld4::KernelArgs*, void*, void*)::$_0::operator()() const;dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*);dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&);mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const;mach_o::Header::forEachLoadCommand(void (load_command const*, bool&) block_pointer) const;invocation function for block in mach_o::Header::forEachLinkedDylib(void (char const*, mach_o::LinkedDylibAttributes, mach_o::Version32, mach_o::Version32, bool&) block_pointer) const;invocation function for block in dyld4::JustInTimeLoader::loadDependents(Diagnostics&, dyld4::RuntimeState&, dyld4::Loader::LoadOptions const&);dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&);dyld4::Loader::forEachPath(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer);dyld4::ProcessConfig::PathOverrides::forEachPathVariant(char const*, dyld3::Platform, bool, bool, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer) const;dyld4::Loader::forEachResolvedAtPathVar(dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, dyld4::ProcessConfig::PathOverrides::Type, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer);invocation function for block in dyld4::Loader::getLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&);dyld4::Loader::makeDyldCacheLoader(Diagnostics&, dyld4::RuntimeState&, char const*, dyld4::Loader::LoadOptions const&, unsigned int, mach_o::Layout const*);dyld4::RuntimeState::findPrebuiltLoader(char const*) const;dyld4::PrebuiltLoader::isValid(dyld4::RuntimeState const&) const;dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const;dyld4::ProcessConfig::PathOverrides::forEachPathVariant(char const*, dyld3::Platform, bool, bool, bool&, void (char const*, dyld4::ProcessConfig::PathOverrides::Type, bool&) block_pointer) const;invocation function for block in dyld4::PrebuiltLoader::invalidateInIsolation(dyld4::RuntimeState const&) const;dyld4::SyscallDelegate::fileExists(char const*, dyld4::FileID*, int*) const;dyld3::stat(char const*, stat*);stat 3.00ms",
"start;dyld4::start(dyld4::KernelArgs*, void*, void*)::$_0::operator()() const;dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*);dyld4::APIs::runAllInitializersForMain();dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const;dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::operator()() const;dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const;dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const;dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const;dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const;dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const;dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const;dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const;dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const;invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const;invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const;_GLOBAL__sub_I_stdlib_typed_new.cpp 1.00ms",
],
}
`;
exports[`importFromInstrumentsDeepCopy time profile, version 16: indexToView 1`] = `0`;
exports[`importFromInstrumentsDeepCopy time profile, version 16: profileGroup.name 1`] = `"simple-time-profile-deep-copy.txt"`;
exports[`importFromInstrumentsDeepCopy time profile: indexToView 1`] = `0`;
exports[`importFromInstrumentsDeepCopy time profile: profileGroup.name 1`] = `"simple-time-profile-deep-copy.txt"`;
@@ -0,0 +1,196 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`importFromPapyrus script profile 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "STACK 3053",
"line": undefined,
"name": "STACK 3053",
"selfWeight": 200,
"totalWeight": 1250,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_quest..exampleFunction1",
"line": undefined,
"name": "abc_example_quest..exampleFunction1",
"selfWeight": 1050,
"totalWeight": 1050,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_quest..exampleFunction2",
"line": undefined,
"name": "abc_example_quest..exampleFunction2",
"selfWeight": 50,
"totalWeight": 50,
},
Frame {
"col": undefined,
"file": undefined,
"key": "STACK 3208",
"line": undefined,
"name": "STACK 3208",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_quest..exampleFunction3",
"line": undefined,
"name": "abc_example_quest..exampleFunction3",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_quest..exampleFunction4",
"line": undefined,
"name": "abc_example_quest..exampleFunction4",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "STACK 3947",
"line": undefined,
"name": "STACK 3947",
"selfWeight": 450,
"totalWeight": 1500,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_quest..exampleFunction5",
"line": undefined,
"name": "abc_example_quest..exampleFunction5",
"selfWeight": 750,
"totalWeight": 1000,
},
Frame {
"col": undefined,
"file": undefined,
"key": "STACK 3949",
"line": undefined,
"name": "STACK 3949",
"selfWeight": 0,
"totalWeight": 250,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_quest..exampleFunction6",
"line": undefined,
"name": "abc_example_quest..exampleFunction6",
"selfWeight": 250,
"totalWeight": 250,
},
],
"name": "script.log",
"stacks": Array [
"STACK 3053;abc_example_quest..exampleFunction1 1.00s",
"STACK 3053 200.00ms",
"STACK 3053;abc_example_quest..exampleFunction2 50.00ms",
" 250.00ms",
"STACK 3947;abc_example_quest..exampleFunction5 250.00ms",
"STACK 3947;abc_example_quest..exampleFunction5;STACK 3949;abc_example_quest..exampleFunction6 250.00ms",
"STACK 3947;abc_example_quest..exampleFunction5 500.00ms",
"STACK 3947 450.00ms",
"STACK 3947;abc_example_quest..exampleFunction1 50.00ms",
],
}
`;
exports[`importFromPapyrus script profile: indexToView 1`] = `0`;
exports[`importFromPapyrus script profile: profileGroup.name 1`] = `"script.log"`;
exports[`importFromPapyrus stack profile 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "STACK 3185",
"line": undefined,
"name": "STACK 3185",
"selfWeight": 66,
"totalWeight": 617,
},
Frame {
"col": undefined,
"file": undefined,
"key": "Debug..StartStackProfiling",
"line": undefined,
"name": "Debug..StartStackProfiling",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "Form..GetFormID",
"line": undefined,
"name": "Form..GetFormID",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "abc_example_mod_quest..exampleFunction1",
"line": undefined,
"name": "abc_example_mod_quest..exampleFunction1",
"selfWeight": 17,
"totalWeight": 17,
},
Frame {
"col": undefined,
"file": undefined,
"key": "Game..GetModName",
"line": undefined,
"name": "Game..GetModName",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "Cell..IsInterior",
"line": undefined,
"name": "Cell..IsInterior",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": "utility..WaitMenuMode",
"line": undefined,
"name": "utility..WaitMenuMode",
"selfWeight": 534,
"totalWeight": 534,
},
],
"name": "stack.log",
"stacks": Array [
"STACK 3185 16.00ms",
"STACK 3185;abc_example_mod_quest..exampleFunction1 17.00ms",
"STACK 3185 33.00ms",
"STACK 3185;utility..WaitMenuMode 534.00ms",
"STACK 3185 17.00ms",
],
}
`;
exports[`importFromPapyrus stack profile: indexToView 1`] = `0`;
exports[`importFromPapyrus stack profile: profileGroup.name 1`] = `"stack.log"`;
+66 -66
View File
@@ -7,239 +7,239 @@ Object {
"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,
"line": 198,
"name": "runtime.main",
"selfWeight": 0,
"totalWeight": 136,
"totalWeight": 1360000000,
},
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,
"line": 49,
"name": "main.main",
"selfWeight": 0,
"totalWeight": 136,
"totalWeight": 1360000000,
},
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,
"line": 28,
"name": "main.delta",
"selfWeight": 22,
"totalWeight": 58,
"selfWeight": 220000000,
"totalWeight": 580000000,
},
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,
"line": 18,
"name": "main.beta",
"selfWeight": 39,
"totalWeight": 39,
"selfWeight": 390000000,
"totalWeight": 390000000,
},
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,
"line": 11,
"name": "main.alpha",
"selfWeight": 48,
"totalWeight": 48,
"selfWeight": 480000000,
"totalWeight": 480000000,
},
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,
"line": 34,
"name": "main.gamma",
"selfWeight": 27,
"totalWeight": 27,
"selfWeight": 270000000,
"totalWeight": 270000000,
},
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,
"line": 480,
"name": "runtime.morestack",
"selfWeight": 0,
"totalWeight": 11,
"totalWeight": 110000000,
},
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,
"line": 957,
"name": "runtime.newstack",
"selfWeight": 0,
"totalWeight": 11,
"totalWeight": 110000000,
},
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,
"line": 412,
"name": "runtime.duffcopy",
"selfWeight": 11,
"totalWeight": 11,
"selfWeight": 110000000,
"totalWeight": 110000000,
},
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,
"line": 247,
"name": "runtime.timerproc",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 125,
"name": "runtime.goroutineReady",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 301,
"name": "runtime.goready",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 409,
"name": "runtime.systemstack",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 302,
"name": "runtime.goready.func1",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 608,
"name": "runtime.ready",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 2083,
"name": "runtime.wakep",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 2017,
"name": "runtime.startm",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 147,
"name": "runtime.notewakeup",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 36,
"name": "runtime.semawakeup",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 465,
"name": "runtime.mach_semrelease",
"selfWeight": 0,
"totalWeight": 1,
"totalWeight": 10000000,
},
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,
"line": 558,
"name": "runtime.mach_semaphore_signal",
"selfWeight": 1,
"totalWeight": 1,
"selfWeight": 10000000,
"totalWeight": 10000000,
},
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,
"line": 1193,
"name": "runtime.mstart",
"selfWeight": 0,
"totalWeight": 2,
"totalWeight": 20000000,
},
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,
"line": 1227,
"name": "runtime.mstart1",
"selfWeight": 0,
"totalWeight": 2,
"totalWeight": 20000000,
},
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,
"line": 4221,
"name": "runtime.sysmon",
"selfWeight": 0,
"totalWeight": 2,
"totalWeight": 20000000,
},
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,
"line": 418,
"name": "runtime.usleep",
"selfWeight": 2,
"totalWeight": 2,
"selfWeight": 20000000,
"totalWeight": 20000000,
},
],
"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",
"runtime.main;main.main;main.delta;main.beta 140.00ms",
"runtime.main;main.main;main.alpha 260.00ms",
"runtime.main;main.main;main.gamma 270.00ms",
"runtime.morestack;runtime.newstack;runtime.duffcopy 110.00ms",
"runtime.main;main.main;main.beta 250.00ms",
"runtime.main;main.main;main.delta 220.00ms",
"runtime.main;main.main;main.delta;main.alpha 220.00ms",
"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 10.00ms",
"runtime.mstart;runtime.mstart1;runtime.sysmon;runtime.usleep 20.00ms",
],
}
`;
@@ -1,5 +1,453 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`importCpuProfileWithProperWeights importFromStackprof cpu mode snapshot 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": "sample/programs/ruby/simple.rb",
"key": 5352342240,
"line": undefined,
"name": "<main>",
"selfWeight": 0,
"totalWeight": 410000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 4530376200,
"line": undefined,
"name": "<main>",
"selfWeight": 0,
"totalWeight": 410000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 4530486680,
"line": 37,
"name": "block in <main>",
"selfWeight": 0,
"totalWeight": 410000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 5335911080,
"line": 4,
"name": "Object#a",
"selfWeight": 0,
"totalWeight": 410000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 5335911040,
"line": 12,
"name": "Object#b",
"selfWeight": 0,
"totalWeight": 154000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 5335909920,
"line": 24,
"name": "Object#d",
"selfWeight": 331000,
"totalWeight": 331000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 5335910560,
"line": 18,
"name": "Object#c",
"selfWeight": 0,
"totalWeight": 177000,
},
Frame {
"col": undefined,
"file": "/Users/mcorrea/src/github.com/dalehamel/speedscope/sample/programs/ruby/simple.rb",
"key": 5335909880,
"line": 32,
"name": "Object#e",
"selfWeight": 79000,
"totalWeight": 79000,
},
Frame {
"col": undefined,
"file": "",
"key": 1,
"line": undefined,
"name": "(garbage collection)",
"selfWeight": 0,
"totalWeight": 79000,
},
Frame {
"col": undefined,
"file": "",
"key": 5,
"line": undefined,
"name": "(sweeping)",
"selfWeight": 71000,
"totalWeight": 71000,
},
Frame {
"col": undefined,
"file": "",
"key": 3,
"line": undefined,
"name": "(marking)",
"selfWeight": 8000,
"totalWeight": 8000,
},
],
"name": "simple-cpu-stackprof.json",
"stacks": Array [
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 5.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(marking) 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"(garbage collection);(sweeping) 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 5.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 5.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 4.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(marking) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 5.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 1.00ms",
"(garbage collection);(sweeping) 2.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#e 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 4.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#b;Object#b;Object#d;Object#d 2.00ms",
"(garbage collection);(sweeping) 1.00ms",
"<main>;<main>;block in <main>;Object#a;Object#a;Object#c;Object#c;Object#d;Object#d 3.00ms",
],
}
`;
exports[`importCpuProfileWithProperWeights importFromStackprof cpu mode snapshot: indexToView 1`] = `0`;
exports[`importCpuProfileWithProperWeights importFromStackprof cpu mode snapshot: profileGroup.name 1`] = `"simple-cpu-stackprof.json"`;
exports[`importFromStackprof 1`] = `
Object {
"frames": Array [
@@ -1046,6 +1494,201 @@ exports[`importFromStackprof object mode: indexToView 1`] = `0`;
exports[`importFromStackprof object mode: profileGroup.name 1`] = `"object-stackprof.json"`;
exports[`importFromStackprof when a profile has a frame with no name 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": "../../alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4317728280,
"line": undefined,
"name": "<main>",
"selfWeight": 0,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4376547240,
"line": undefined,
"name": "<main>",
"selfWeight": 0,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4379035920,
"line": null,
"name": "StackProf.run",
"selfWeight": 0,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4317464320,
"line": 21,
"name": "block in <main>",
"selfWeight": 1,
"totalWeight": 103,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4379033920,
"line": 4,
"name": "Object#a",
"selfWeight": 2,
"totalWeight": 102,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4317869400,
"line": null,
"name": "Range#each",
"selfWeight": 0,
"totalWeight": 102,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4379033880,
"line": 11,
"name": "Object#b",
"selfWeight": 1,
"totalWeight": 5,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4318054440,
"line": null,
"name": "Class#new",
"selfWeight": 4,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": "/Users/alex/src/github.com/alexcoco/speedscope/sample/programs/ruby/object.rb",
"key": 4379033840,
"line": 15,
"name": "Object#c",
"selfWeight": 2,
"totalWeight": 95,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4317868920,
"line": null,
"name": "Range#to_a",
"selfWeight": 1,
"totalWeight": 37,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4379016640,
"line": null,
"name": "Enumerable#to_a",
"selfWeight": 36,
"totalWeight": 36,
},
Frame {
"col": undefined,
"file": "<internal:array>",
"key": 4317563560,
"line": 60,
"name": "Array#sample",
"selfWeight": 20,
"totalWeight": 20,
},
Frame {
"col": undefined,
"file": "<cfunc>",
"key": 4317922760,
"line": null,
"name": "(unknown)",
"selfWeight": 36,
"totalWeight": 36,
},
],
"name": "stackprof-last-frame-no-name.json",
"stacks": Array [
"<main>;<main>;StackProf.run;block in <main> 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b;Class#new 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 3",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b;Class#new 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#b;Class#new 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Range#to_a;Enumerable#to_a 2",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;Array#sample 1",
"<main>;<main>;StackProf.run;block in <main>;Object#a;Range#each;Object#a;Object#c;Range#each;Object#c;(unknown) 2",
],
}
`;
exports[`importFromStackprof when a profile has a frame with no name: indexToView 1`] = `0`;
exports[`importFromStackprof when a profile has a frame with no name: profileGroup.name 1`] = `"stackprof-last-frame-no-name.json"`;
exports[`importFromStackprof: indexToView 1`] = `0`;
exports[`importFromStackprof: profileGroup.name 1`] = `"simple-stackprof.json"`;
@@ -172,6 +172,68 @@ exports[`importTraceEvents bad E events: indexToView 1`] = `0`;
exports[`importTraceEvents bad E events: profileGroup.name 1`] = `"too-many-end-events.json"`;
exports[`importTraceEvents different number of start and end calls to same function at same timestamp 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "anonymous",
"line": undefined,
"name": "anonymous",
"selfWeight": 1,
"totalWeight": 5,
},
Frame {
"col": undefined,
"file": undefined,
"key": "anonymous {\\"parent\\":1}",
"line": undefined,
"name": "anonymous {\\"parent\\":1}",
"selfWeight": 0,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function1 {\\"parent\\":2}",
"line": undefined,
"name": "function1 {\\"parent\\":2}",
"selfWeight": 0,
"totalWeight": 2,
},
Frame {
"col": undefined,
"file": undefined,
"key": "anonymous {\\"parent\\":3}",
"line": undefined,
"name": "anonymous {\\"parent\\":3}",
"selfWeight": 2,
"totalWeight": 2,
},
Frame {
"col": undefined,
"file": undefined,
"key": "anonymous {\\"parent\\":8}",
"line": undefined,
"name": "anonymous {\\"parent\\":8}",
"selfWeight": 2,
"totalWeight": 2,
},
],
"name": "pid 0, tid 0",
"stacks": Array [
"anonymous 1.00µs",
"anonymous;anonymous {\\"parent\\":1};function1 {\\"parent\\":2};anonymous {\\"parent\\":3} 2.00µs",
"anonymous;anonymous {\\"parent\\":1};anonymous {\\"parent\\":8} 2.00µs",
],
}
`;
exports[`importTraceEvents different number of start and end calls to same function at same timestamp: indexToView 1`] = `0`;
exports[`importTraceEvents different number of start and end calls to same function at same timestamp: profileGroup.name 1`] = `"simultaneous-anonymous-calls.json"`;
exports[`importTraceEvents end event with empty stack 1`] = `
Object {
"frames": Array [
@@ -342,6 +404,41 @@ exports[`importTraceEvents event reordering name match: indexToView 1`] = `0`;
exports[`importTraceEvents event reordering name match: profileGroup.name 1`] = `"event-reordering-name-match.json"`;
exports[`importTraceEvents hermes profile with multiple frames 1`] = `
Object {
"frames": Array [
Frame {
"col": null,
"file": null,
"key": "[root]:null:null:null",
"line": null,
"name": "[root]",
"selfWeight": 2,
"totalWeight": 14,
},
Frame {
"col": 12,
"file": "/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"key": "beta:/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js:54:12",
"line": 54,
"name": "beta",
"selfWeight": 12,
"totalWeight": 12,
},
],
"name": "pid 0, tid 0",
"stacks": Array [
"[root] 1.00µs",
"[root];beta 12.00µs",
"[root] 1.00µs",
],
}
`;
exports[`importTraceEvents hermes profile with multiple frames: indexToView 1`] = `0`;
exports[`importTraceEvents hermes profile with multiple frames: profileGroup.name 1`] = `"simple-hermes.json"`;
exports[`importTraceEvents invalid x nesting 1`] = `
Object {
"frames": Array [
@@ -468,6 +565,81 @@ exports[`importTraceEvents mismatched name: indexToView 1`] = `0`;
exports[`importTraceEvents mismatched name: profileGroup.name 1`] = `"mismatched-name.json"`;
exports[`importTraceEvents multi-thread profile with samples 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "[root]:root",
"line": undefined,
"name": "[root]",
"selfWeight": 20770,
"totalWeight": 34096,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function1:JavaScript",
"line": undefined,
"name": "function1",
"selfWeight": 13326,
"totalWeight": 13326,
},
],
"name": "hermes (pid 7512), mqt_js (tid 1)",
"stacks": Array [
"[root] 20.77ms",
"[root];function1 13.33ms",
],
}
`;
exports[`importTraceEvents multi-thread profile with samples 2`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "[root thread 2]:root",
"line": undefined,
"name": "[root thread 2]",
"selfWeight": 30770,
"totalWeight": 54096,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function3:JavaScript",
"line": undefined,
"name": "function3",
"selfWeight": 13326,
"totalWeight": 13326,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function4:Metadata",
"line": undefined,
"name": "function4",
"selfWeight": 10000,
"totalWeight": 10000,
},
],
"name": "hermes (pid 7512, tid 2)",
"stacks": Array [
"[root thread 2] 20.77ms",
"[root thread 2];function3 13.33ms",
"[root thread 2];function4 10.00ms",
"[root thread 2] 10.00ms",
],
}
`;
exports[`importTraceEvents multi-thread profile with samples: indexToView 1`] = `0`;
exports[`importTraceEvents multi-thread profile with samples: profileGroup.name 1`] = `"multi-thread-with-samples.json"`;
exports[`importTraceEvents multiprocess 1`] = `
Object {
"frames": Array [
@@ -1178,6 +1350,41 @@ Object {
}
`;
exports[`importTraceEvents simple hermes profile 1`] = `
Object {
"frames": Array [
Frame {
"col": null,
"file": null,
"key": "[root]:null:null:null",
"line": null,
"name": "[root]",
"selfWeight": 2,
"totalWeight": 14,
},
Frame {
"col": 12,
"file": "/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js",
"key": "beta:/Users/example/test_project/node_modules/metro-runtime/src/polyfills/require.js:54:12",
"line": 54,
"name": "beta",
"selfWeight": 12,
"totalWeight": 12,
},
],
"name": "pid 0, tid 0",
"stacks": Array [
"[root] 1.00µs",
"[root];beta 12.00µs",
"[root] 1.00µs",
],
}
`;
exports[`importTraceEvents simple hermes profile: indexToView 1`] = `0`;
exports[`importTraceEvents simple hermes profile: profileGroup.name 1`] = `"simple-hermes.json"`;
exports[`importTraceEvents simple object 1`] = `
Object {
"frames": Array [
@@ -1224,6 +1431,70 @@ exports[`importTraceEvents simple object: indexToView 1`] = `0`;
exports[`importTraceEvents simple object: profileGroup.name 1`] = `"simple-object.json"`;
exports[`importTraceEvents simple profile with samples 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": "[root]:root",
"line": undefined,
"name": "[root]",
"selfWeight": 30779,
"totalWeight": 64710,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function1(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:1:1):JavaScript",
"line": undefined,
"name": "function1(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:1:1)",
"selfWeight": 13326,
"totalWeight": 13326,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function2(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:418874:4):JavaScript",
"line": undefined,
"name": "function2(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:418874:4)",
"selfWeight": 0,
"totalWeight": 10605,
},
Frame {
"col": undefined,
"file": undefined,
"key": "function3(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:17:3):JavaScript",
"line": undefined,
"name": "function3(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:17:3)",
"selfWeight": 10605,
"totalWeight": 10605,
},
Frame {
"col": undefined,
"file": undefined,
"key": "[GC Young Gen]:Metadata",
"line": undefined,
"name": "[GC Young Gen]",
"selfWeight": 10000,
"totalWeight": 10000,
},
],
"name": "hermes (pid 7512), mqt_js (tid 7695)",
"stacks": Array [
"[root] 20.77ms",
"[root];function1(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:1:1) 13.33ms",
"[root];function2(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:418874:4);function3(http://localhost:8081/index.bundle?platform=android&dev=false&minify=false&app=org.toshi&modulesOnly=false&runModule=true:17:3) 10.61ms",
"[root] 10.01ms",
"[root];[GC Young Gen] 10.00ms",
],
}
`;
exports[`importTraceEvents simple profile with samples: indexToView 1`] = `0`;
exports[`importTraceEvents simple profile with samples: profileGroup.name 1`] = `"simple-with-samples.json"`;
exports[`importTraceEvents simple: indexToView 1`] = `0`;
exports[`importTraceEvents simple: profileGroup.name 1`] = `"simple.json"`;
+9 -3
View File
@@ -95,7 +95,10 @@ class CallGraph {
private totalWeights = new Map<Frame, number>()
private childrenTotalWeights = new Map<Frame, Map<Frame, number>>()
constructor(private fileName: string, private fieldName: string) {}
constructor(
private fileName: string,
private fieldName: string,
) {}
private getOrInsertFrame(info: FrameInfo): Frame {
return Frame.getOrInsert(this.frameSet, info)
@@ -345,8 +348,11 @@ class CallgrindParser {
private savedFileNames: {[id: string]: string} = {}
private savedFunctionNames: {[id: string]: string} = {}
constructor(contents: TextFileContent, private importedFileName: string) {
this.lines = contents.splitLines()
constructor(
contents: TextFileContent,
private importedFileName: string,
) {
this.lines = [...contents.splitLines()]
this.lineNum = 0
}
-17
View File
@@ -1,17 +0,0 @@
// The bits of this API that we care about. This is implemented by WebKitEntry
// https://wicg.github.io/entries-api/#api-entry
export interface FileSystemDirectoryReader {
readEntries(cb: (entries: FileSystemEntry[]) => void, error: (err: Error) => void): void
}
export interface FileSystemEntry {
readonly isFile: boolean
readonly isDirectory: boolean
readonly name: string
readonly fullPath: string
}
export interface FileSystemDirectoryEntry extends FileSystemEntry {
createReader(): FileSystemDirectoryReader
}
export interface FileSystemFileEntry extends FileSystemEntry {
file(cb: (file: File) => void, errCb: (err: Error) => void): void
}
+6 -1
View File
@@ -1,5 +1,4 @@
import {Profile, ProfileGroup} from '../lib/profile'
import {FileSystemDirectoryEntry} from './file-system-entry'
import {
importFromChromeCPUProfile,
@@ -23,6 +22,7 @@ import {decodeBase64} from '../lib/utils'
import {importFromChromeHeapProfile} from './v8heapalloc'
import {isTraceEventFormatted, importTraceEvents} from './trace-event'
import {importFromCallgrind} from './callgrind'
import {importFromPapyrus} from './papyrus'
export async function importProfileGroupFromText(
fileName: string,
@@ -188,6 +188,11 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
return toGroup(importFromInstrumentsDeepCopy(contents))
}
if (/^(Stack_|Script_|Obj_)\S+ log opened \(PC\)\n/.exec(contents.firstChunk())) {
console.log('Importing as Papyrus profile')
return toGroup(importFromPapyrus(contents))
}
const fromLinuxPerf = importFromLinuxPerf(contents)
if (fromLinuxPerf) {
console.log('Importing from linux perf script output')
+19 -5
View File
@@ -3,7 +3,6 @@ import * as path from 'path'
import {dumpProfile, checkProfileSnapshot} from '../lib/test-utils'
import * as JSZip from 'jszip'
import {FileSystemEntry} from './file-system-entry'
import {importFromFileSystemDirectoryEntry} from '.'
describe('importFromInstrumentsDeepCopy', () => {
@@ -13,6 +12,12 @@ describe('importFromInstrumentsDeepCopy', () => {
)
})
test('time profile, version 16', async () => {
await checkProfileSnapshot(
'./sample/profiles/Instruments/16.0/simple-time-profile-deep-copy.txt',
)
})
test('allocations profile', async () => {
await checkProfileSnapshot(
'./sample/profiles/Instruments/7.3.1/random-allocations-deep-copy.txt',
@@ -24,7 +29,10 @@ describe('importFromInstrumentsDeepCopy', () => {
})
})
class ZipBackedFileSystemEntry implements FileSystemEntry {
// This is a bit of a weird type signature. I'm making this almost a
// FileSystemEntry, but ignoring the parts of the API I don't use anywhere and
// are a pain to implement.
class ZipBackedFileSystemEntry implements Omit<FileSystemEntry, 'filesystem' | 'getParent'> {
readonly isFile: boolean
readonly isDirectory: boolean
readonly name: string
@@ -33,7 +41,10 @@ class ZipBackedFileSystemEntry implements FileSystemEntry {
private zipDir: any | null
private zipFile: JSZip.JSZipObject | null
constructor(private zip: JSZip, fullPath: string) {
constructor(
private zip: JSZip,
fullPath: string,
) {
this.fullPath = fullPath
this.zipFile = zip.file(fullPath)
@@ -73,7 +84,7 @@ class ZipBackedFileSystemEntry implements FileSystemEntry {
const ret: FileSystemEntry[] = []
this.zipDir.forEach((relativePath: string, file: {name: string}) => {
if (relativePath.split('/').length === (relativePath.endsWith('/') ? 2 : 1)) {
ret.push(new ZipBackedFileSystemEntry(this.zip, file.name))
ret.push(new ZipBackedFileSystemEntry(this.zip, file.name) as any as FileSystemEntry)
}
})
cb(ret)
@@ -90,7 +101,10 @@ describe('importFromInstrumentsTrace', () => {
JSZip.loadAsync(data).then(resolve)
})
})
const root = new ZipBackedFileSystemEntry(zip, 'simple-time-profile.trace')
const root: FileSystemDirectoryEntry = new ZipBackedFileSystemEntry(
zip,
'simple-time-profile.trace',
) as any
const profileGroup = await importFromFileSystemDirectoryEntry(root)
const profile = profileGroup.profiles[profileGroup.indexToView]
expect(dumpProfile(profile)).toMatchSnapshot()
+7 -3
View File
@@ -10,11 +10,10 @@ import {
} from '../lib/profile'
import {sortBy, getOrThrow, getOrInsert, lastOf, getOrElse, zeroPad} from '../lib/utils'
import {ByteFormatter, TimeFormatter} from '../lib/value-formatters'
import {FileSystemDirectoryEntry, FileSystemEntry, FileSystemFileEntry} from './file-system-entry'
import {MaybeCompressedDataReader, TextFileContent} from './utils'
function parseTSV<T>(contents: TextFileContent): T[] {
const lines = contents.splitLines().map(l => l.split('\t'))
const lines = [...contents.splitLines()].map(l => l.split('\t'))
const headerLine = lines.shift()
if (!headerLine) return []
@@ -39,12 +38,14 @@ interface PastedTimeProfileRow {
Weight?: string
'Source Path'?: string
'Symbol Name'?: string
'Symbol Names'?: string
}
interface PastedAllocationsProfileRow {
'Bytes Used'?: string
'Source Path'?: string
'Symbol Name'?: string
'Symbol Names'?: string
}
interface FrameInfoWithWeight extends FrameInfo {
@@ -109,9 +110,12 @@ export function importFromInstrumentsDeepCopy(contents: TextFileContent): Profil
const stack: FrameInfoWithWeight[] = []
let cumulativeValue: number = 0
let leadingFirstRowSpaces: number = rows[0]['Symbol Names']
? rows[0]['Symbol Names'].lastIndexOf(' ') + 1
: 0
for (let row of rows) {
const symbolName = row['Symbol Name']
const symbolName = row['Symbol Name'] || row['Symbol Names']?.slice(leadingFirstRowSpaces)
if (!symbolName) continue
const trimmedSymbolName = symbolName.trim()
let stackDepth = symbolName.length - trimmedSymbolName.length
+16 -32
View File
@@ -23,8 +23,21 @@ interface PerfEvent {
stack: PerfStackFrame[]
}
function parseEvent(rawEvent: string): PerfEvent | null {
const lines = rawEvent.split('\n').filter(l => !/^\s*#/.exec(l))
function* parseEvents(contents: TextFileContent): Generator<PerfEvent | null> {
let buffer: string[] = []
for (let line of contents.splitLines()) {
if (line === '') {
yield parseEvent(buffer)
buffer = []
} else buffer.push(line)
}
if (buffer.length > 0) yield parseEvent(buffer)
}
// rawEvent is splitted into lines
function parseEvent(rawEvent: string[]): PerfEvent | null {
const lines = rawEvent.filter(l => !/^\s*#/.exec(l))
const event: PerfEvent = {
command: null,
@@ -81,41 +94,12 @@ function parseEvent(rawEvent: string): PerfEvent | null {
return event
}
function splitBlocks(contents: TextFileContent): string[] {
// In perf files, blocks are separated by '\n\n'. If our input was a simple
// string, we could use str.split('\n\n'), but since we have a TextFileContent
// object which may be backed by several strings, we can't easily split this
// way.
//
// Instead, we'll split into lines, and then re-group runs of non-empty strings.
const blocks: string[] = []
let pending: string = ''
for (let line of contents.splitLines()) {
if (line === '') {
if (pending.length > 0) {
blocks.push(pending)
}
pending = line
} else {
if (pending.length > 0) {
pending += '\n'
}
pending += line
}
}
if (pending.length > 0) {
blocks.push(pending)
}
return blocks
}
export function importFromLinuxPerf(contents: TextFileContent): ProfileGroup | null {
const profiles = new Map<string, StackListProfileBuilder>()
let eventType: string | null = null
const events = splitBlocks(contents).map(parseEvent)
for (let event of events) {
for (let event of parseEvents(contents)) {
if (event == null) continue
if (eventType != null && eventType != event.eventType) continue
if (event.time == null) continue
+9
View File
@@ -0,0 +1,9 @@
import {checkProfileSnapshot} from '../lib/test-utils'
test('importFromPapyrus script profile', async () => {
await checkProfileSnapshot('./sample/profiles/papyrus/script.log')
})
test('importFromPapyrus stack profile', async () => {
await checkProfileSnapshot('./sample/profiles/papyrus/stack.log')
})
+187
View File
@@ -0,0 +1,187 @@
// This importer is for Papyrus, a proprietary DSL written by Bethesda for Skyrim and FO4. It is used both for the base
// games and for mods. You can find documentation (such as the language reference) here:
// https://ck.uesp.net/wiki/Category:Papyrus
//
// For mod authors: you can find documentation on how to start profiling from the console here:
// https://ck.uesp.net/wiki/StartPapyrusScriptProfile
// and you can also start from your script:
// https://ck.uesp.net/wiki/StartScriptProfiling_-_Debug
// If you want to profile an entire mod, it is often helpful to use `StartFormProfile` on your "main quest". This will
// then profile all scripts attached to that Form.
//
// Papyrus works with a queue system, because it is multithreaded, but only one thread can exist per script.
// (And we can only profile one script at a time.) We parse most "QUEUE_PUSH" events, specifically those that come
// directly before their corresponding "PUSH" event. Other "QUEUE_PUSH" (and all "QUEUE_POP") events come, as far as I
// can tell, from Events and are thus asynchronous. They are ignored.
//
// Stack profiling also puts a "START" operation at the top and a "STOP" operation at the bottom of the file. These are
// completely useless to us.
//
// Stack profiling also logs the Form a method is run on. For those that are not familiar with
// Papyrus terminology, a "Form" is an instance of a type defined by a script. E.g. a quest is a form that extends
// the "Quest" script, and thus it has certain methods, like "CompleteQuest()". This information would be useful
// for Debugging, but for profiling, it would hinder meaningful output in left heavy mode.
import {CallTreeProfileBuilder, Frame, Profile} from '../lib/profile'
import {KeyedSet, lastOf} from '../lib/utils'
import {TimeFormatter} from '../lib/value-formatters'
import {TextFileContent} from './utils'
type ParsedLine = {
at: number
event: string
stackInt: number
name: string
}
export function importFromPapyrus(papyrusProfile: TextFileContent): Profile {
const profile = new CallTreeProfileBuilder()
profile.setValueFormatter(new TimeFormatter('milliseconds'))
const papyrusProfileLines = [...papyrusProfile.splitLines()].filter(
line => !/^$|^Log closed$|log opened/.exec(line),
)
let startValue = -1
const firstLineParsed = parseLine(papyrusProfileLines[0])
if (firstLineParsed === null) throw Error
startValue = firstLineParsed.at
const lastLine = lastOf(papyrusProfileLines)
if (lastLine === null) throw Error
const lastLineParsed = parseLine(lastLine)
if (lastLineParsed === null) throw Error
const endValue = lastLineParsed.at
const nameSet = new KeyedSet<Frame>()
const frameStack: string[] = []
let lastEventAt = 0
let lastQueueFrameName: string
let lastQueueFrameAt: number = -1
function enterFrame(stackInt: number, at: number, frameName: string) {
function enterFrameHelper(at: number, frameName: string) {
frameStack.push(frameName)
profile.enterFrame(Frame.getOrInsert(nameSet, {name: frameName, key: frameName}), at)
lastEventAt = at
}
// Check if the last event was "QUEUE_PUSH"
if (lastQueueFrameAt > -1) {
lastQueueFrameAt = -1
// If the queue from last event matches our current frame,
if (lastQueueFrameName === frameName && lastQueueFrameAt >= lastEventAt) {
// first enter the queue frame at its earlier time
enterFrame(stackInt, lastQueueFrameAt, `QUEUE ${frameName}`)
}
}
const stackFrameStr = `STACK ${stackInt}`
// If the uppermost STACK frame on the frameStack isn't stackFrameStr
if (
[...frameStack].reverse().find(frameName => frameName.startsWith('STACK ')) !== stackFrameStr
) {
// If we're at the bottom of the frameStack, STACK frames are kept open as long as functions only run in that
// specific stack and closed with the function's end if the next function runs on a different stack.
if (frameStack.length === 1) leaveFrame(lastEventAt)
enterFrameHelper(at, stackFrameStr)
}
enterFrameHelper(at, frameName)
}
function leaveFrame(at: number) {
const frame = frameStack.pop()
if (frame === undefined) throw Error('Tried to leave frame when nothing was on stack.')
profile.leaveFrame(Frame.getOrInsert(nameSet, {name: frame, key: frame}), at)
let topOfStack = lastOf(frameStack)
// Technically, the frame is popped from queue once it is pushed onto the stack (once we have "entered the frame")
// but since we want to visualize meaningfully, we count from QUEUE_PUSH to POP and prefix with "QUEUE ".
if (topOfStack !== null && topOfStack.startsWith('QUEUE ')) {
leaveFrame(at)
topOfStack = lastOf(frameStack)
}
if (frameStack.length > 1 && topOfStack !== null && topOfStack.startsWith('STACK ')) {
leaveFrame(at)
}
lastEventAt = at
}
function tryToLeaveFrame(stackInt: number, at: number, frameName: string) {
if (lastOf(frameStack) === frameName) {
leaveFrame(at)
} else {
if (lastEventAt === 0) {
console.log(
`Tried to leave frame "${frameName}" which was never entered. Assuming it has been running since the start.`,
)
enterFrame(stackInt, 0, frameName)
leaveFrame(at)
} else {
console.log(
`Tried to leave frame "${frameName}" which was never entered. Other events have happened since the start, ignoring line.`,
)
}
}
}
function parseLine(lineStr: string): ParsedLine | null {
if (lineStr === undefined) throw Error('Probably tried to import empty file.')
const lineArr = lineStr.split(':')
if (lineArr.length < 3) return null
if (startValue !== -1) {
return {
at: parseInt(lineArr[0]) - startValue,
event: lineArr[1],
stackInt: parseInt(lineArr[2]),
name: lineArr[5],
}
} else {
// When parsing the first line, we return an absolute `at` value to initialize `startValue`
return {
at: parseInt(lineArr[0]),
event: lineArr[1],
stackInt: parseInt(lineArr[2]),
name: lineArr[5],
}
}
}
papyrusProfileLines.forEach((lineStr, i, papyrusProfileLines) => {
const parsedLine = parseLine(lineStr)
if (parsedLine === null) return // continue
if (parsedLine.event === 'PUSH') {
enterFrame(parsedLine.stackInt, parsedLine.at, parsedLine.name)
i += 1
let parsedNextLine = parseLine(papyrusProfileLines[i])
// Search all future events in the current event for one that leaves the current frame. If it exists, leave now.
// This way, we avoid speedscope choking on the possibly wrong order of events. The changed order is still
// functionally correct, as the function took less than a millisecond to execute, which is measured as 0 (ms).
while (parsedNextLine !== null && parsedNextLine.at === parsedLine.at) {
if (
parsedNextLine.name === parsedLine.name &&
parsedNextLine.stackInt === parsedLine.stackInt &&
parsedNextLine.event === 'POP'
) {
tryToLeaveFrame(parsedNextLine.stackInt, parsedNextLine.at, parsedNextLine.name)
// Delete the line that we successfully parsed and imported such that it is not processed twice
papyrusProfileLines.splice(i, 1)
parsedNextLine = null
} else {
i += 1
if (i < papyrusProfileLines.length) parsedNextLine = parseLine(papyrusProfileLines[i])
}
}
} else if (parsedLine.event === 'POP') {
tryToLeaveFrame(parsedLine.stackInt, parsedLine.at, parsedLine.name)
} else if (parsedLine.event === 'QUEUE_PUSH') {
lastQueueFrameName = parsedLine.name.replace(/\?/g, '')
lastQueueFrameAt = parsedLine.at
return
}
})
// Close frames that are still open
while (frameStack.length > 0) {
leaveFrame(endValue)
}
return profile.build()
}
+38 -5
View File
@@ -2,12 +2,32 @@ 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'
import Long from 'long'
interface SampleType {
type: string
unit: string
}
// Find the index of the SampleType which should be used as our default
function getSampleTypeIndex(profile: perftools.profiles.Profile): number {
const dflt = profile.defaultSampleType
const sampleTypes = profile.sampleType
const fallback = sampleTypes.length - 1
// string_table[0] will always be empty-string, so we can assume dflt === 0 is just the proto
// empty-value, and means no defaultSampleType was specified.
if (!dflt || !+dflt) {
return fallback
}
const idx = sampleTypes.findIndex(e => e.type === dflt)
if (idx === -1) {
return fallback
}
return idx
}
export function importAsPprofProfile(rawProfile: ArrayBuffer): Profile | null {
if (rawProfile.byteLength === 0) return null
@@ -81,7 +101,12 @@ export function importAsPprofProfile(rawProfile: ArrayBuffer): Profile | null {
if (lastLine == null) return null
if (lastLine.functionId) {
return frameInfoByFunctionID.get(i32(lastLine.functionId)) || null
let funcFrame = frameInfoByFunctionID.get(i32(lastLine.functionId))
const line = lastLine.line instanceof Long ? lastLine.line.toNumber() : lastLine.line
if (line && line > 0 && funcFrame != null) {
funcFrame.line = line
}
return funcFrame || null
} else {
return null
}
@@ -103,9 +128,12 @@ export function importAsPprofProfile(rawProfile: ArrayBuffer): Profile | null {
unit: (type.unit && stringVal(type.unit)) || 'count',
}))
const sampleTypeIndex = protoProfile.defaultSampleType
? +protoProfile.defaultSampleType
: sampleTypes.length - 1
const sampleTypeIndex = getSampleTypeIndex(protoProfile)
if (sampleTypeIndex < 0 || sampleTypeIndex >= sampleTypes.length) {
return null
}
const sampleType = sampleTypes[sampleTypeIndex]
const profileBuilder = new StackListProfileBuilder()
@@ -126,7 +154,12 @@ export function importAsPprofProfile(rawProfile: ArrayBuffer): Profile | null {
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]
if (s.value == null || s.value.length <= sampleTypeIndex) {
return null
}
const value = s.value[sampleTypeIndex]
profileBuilder.appendSampleWithWeight(stack.filter(f => f != null) as FrameInfo[], +value)
}
+31
View File
@@ -1,3 +1,5 @@
import {readFileSync} from 'fs'
import {importProfileGroupFromText} from '.'
import {checkProfileSnapshot} from '../lib/test-utils'
test('importFromStackprof', async () => {
@@ -7,3 +9,32 @@ test('importFromStackprof', async () => {
test('importFromStackprof object mode', async () => {
await checkProfileSnapshot('./sample/profiles/stackprof/object-stackprof.json')
})
test('importFromStackprof when a profile has a frame with no name', async () => {
await checkProfileSnapshot('./sample/profiles/stackprof/stackprof-last-frame-no-name.json')
})
describe('importCpuProfileWithProperWeights', () => {
test('importFromStackprof cpu mode snapshot', async () => {
await checkProfileSnapshot('./sample/profiles/stackprof/simple-cpu-stackprof.json')
})
test('uses samples count for weight when importing cpu profile', async () => {
const profileFile = readFileSync('./sample/profiles/stackprof/simple-cpu-stackprof.json')
const profileGroup = await importProfileGroupFromText(
'simple-cpu-stackprof.json',
profileFile.toString(),
)
expect(profileGroup).not.toBeNull()
if (profileGroup) {
const profile = profileGroup.profiles[profileGroup.indexToView]
expect(profile).not.toBeNull()
if (profile) {
expect(profile.getWeightUnit()).toBe('microseconds')
expect(profile.getTotalWeight()).toBe(489000)
}
}
})
})
+28 -22
View File
@@ -1,10 +1,10 @@
// https://github.com/tmm1/stackprof
import {Profile, FrameInfo, StackListProfileBuilder} from '../lib/profile'
import {TimeFormatter} from '../lib/value-formatters'
import {RawValueFormatter, TimeFormatter} from '../lib/value-formatters'
interface StackprofFrame {
name: string
name?: string
file?: string
line?: number
}
@@ -15,14 +15,13 @@ export interface StackprofProfile {
raw: number[]
raw_timestamp_deltas: number[]
samples: number
interval: number
}
export function importFromStackprof(stackprofProfile: StackprofProfile): Profile {
const {frames, mode, raw, raw_timestamp_deltas, samples} = stackprofProfile
const objectMode = mode == 'object'
const size = objectMode ? samples : stackprofProfile.raw_timestamp_deltas.reduce((a, b) => a + b, 0)
const profile = new StackListProfileBuilder(size)
const {frames, mode, raw, raw_timestamp_deltas, interval} = stackprofProfile
const profile = new StackListProfileBuilder()
profile.setValueFormatter(new TimeFormatter('microseconds')) // default to time format unless we're in object mode
let sampleIndex = 0
@@ -34,33 +33,40 @@ export function importFromStackprof(stackprofProfile: StackprofProfile): Profile
let stack: FrameInfo[] = []
for (let j = 0; j < stackHeight; j++) {
const id = raw[i++]
stack.push({
let frameName = frames[id].name
if (frameName == null) {
frameName = '(unknown)'
}
const frame = {
key: id,
...frames[id],
})
name: frameName,
}
stack.push(frame)
}
if (stack.length === 1 && stack[0].name === '(garbage collection)') {
stack = prevStack.concat(stack)
}
const nSamples = raw[i++]
if (objectMode) {
profile.appendSampleWithWeight(stack, nSamples)
} else {
let sampleDuration = 0
for (let j = 0; j < nSamples; j++) {
sampleDuration += raw_timestamp_deltas[sampleIndex++]
}
profile.appendSampleWithWeight(stack, sampleDuration)
switch (mode) {
case 'object':
profile.appendSampleWithWeight(stack, nSamples)
profile.setValueFormatter(new RawValueFormatter())
break
case 'cpu':
profile.appendSampleWithWeight(stack, nSamples * interval)
break
default:
let sampleDuration = 0
for (let j = 0; j < nSamples; j++) {
sampleDuration += raw_timestamp_deltas[sampleIndex++]
}
profile.appendSampleWithWeight(stack, sampleDuration)
}
prevStack = stack
}
if (!objectMode) {
profile.setValueFormatter(new TimeFormatter('microseconds'))
}
return profile.build()
}
+20
View File
@@ -47,6 +47,10 @@ test('importTraceEvents bad E events', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/too-many-end-events.json')
})
test('importTraceEvents different number of start and end calls to same function at same timestamp', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simultaneous-anonymous-calls.json')
})
test('importTraceEvents event re-ordering', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/must-retain-original-order.json')
})
@@ -118,3 +122,19 @@ test('importTraceEvents invalid x nesting', async () => {
test('importTraceEvents event reordering name match', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/event-reordering-name-match.json')
})
test('importTraceEvents simple hermes profile', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-hermes.json')
})
test('importTraceEvents hermes profile with multiple frames', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-hermes.json')
})
test('importTraceEvents simple profile with samples', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/simple-with-samples.json')
})
test('importTraceEvents multi-thread profile with samples', async () => {
await checkProfileSnapshot('./sample/profiles/trace-event/multi-thread-with-samples.json')
})
+455 -148
View File
@@ -1,5 +1,11 @@
import {sortBy, zeroPad, getOrInsert, lastOf} from '../lib/utils'
import {ProfileGroup, CallTreeProfileBuilder, FrameInfo, Profile} from '../lib/profile'
import {
ProfileGroup,
CallTreeProfileBuilder,
FrameInfo,
Profile,
StackListProfileBuilder,
} from '../lib/profile'
import {TimeFormatter} from '../lib/value-formatters'
// This file concerns import from the "Trace Event Format", authored by Google
@@ -42,12 +48,46 @@ interface TraceEvent {
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
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
}
enum ExporterSource {
HERMES = 'HERMES',
UNKNOWN = 'UNKNOWN',
}
interface HermesTraceEventArgs {
line: number | null
column: number | null
funcLine?: string | null
funcColumn?: string | null
name: string
category: string
parent?: number
url: string | null
params: string | null
allocatedCategory: string
allocatedName: string
}
const requiredHermesArguments: Array<keyof HermesTraceEventArgs> = [
'line',
'column',
'name',
'category',
'url',
'params',
'allocatedCategory',
'allocatedName',
]
type HermesTraceEvent = TraceEvent & {
args: HermesTraceEventArgs
}
interface BTraceEvent extends TraceEvent {
ph: 'B'
}
@@ -65,16 +105,53 @@ interface XTraceEvent extends TraceEvent {
// The trace format supports a number of event types that we ignore.
type ImportableTraceEvent = BTraceEvent | ETraceEvent | XTraceEvent
interface StackFrame {
line: string
column: string
funcLine: string
funcColumn: string
name: string
category: string
// A parent function may or may not exist
parent?: number
}
interface Sample {
cpu: string
name: string
ts: string
pid: number
tid: string
weight: string
// Will refer to an element in the stackFrames object
sf: number
stackFrameData?: StackFrame
}
interface TraceWithSamples {
traceEvents: TraceEvent[]
samples: Sample[]
stackFrames: {[key: string]: StackFrame}
}
interface TraceEventObject {
traceEvents: TraceEvent[]
}
type Trace = TraceEvent[] | TraceEventObject | TraceWithSamples
function pidTidKey(pid: number, tid: number): string {
// We zero-pad the PID and TID to make sorting them by pid/tid pair later easier.
return `${zeroPad('' + pid, 10)}:${zeroPad('' + tid, 10)}`
}
function partitionByPidTid(events: ImportableTraceEvent[]): Map<string, ImportableTraceEvent[]> {
const map = new Map<string, ImportableTraceEvent[]>()
function partitionByPidTid<T extends {tid: number | string; pid: number | string}>(
events: T[],
): Map<string, T[]> {
const map = new Map<string, T[]>()
for (let ev of events) {
const list = getOrInsert(map, pidTidKey(ev.pid, ev.tid), () => [])
const list = getOrInsert(map, pidTidKey(Number(ev.pid), Number(ev.tid)), () => [])
list.push(ev)
}
@@ -103,12 +180,12 @@ function selectQueueToTakeFromNext(
// If we got here, the 'B' event queue and the 'E' event queue have events at
// the front with equal timestamps.
// If the front of the 'E' queue matches the front of the 'B' queue by name,
// If the front of the 'E' queue matches the front of the 'B' queue by key,
// then it means we have a zero duration event. Process the 'B' queue first
// to ensure it opens before we try to close it.
//
// Otherwise, process the 'E' queue first.
return bFront.name === eFront.name ? 'B' : 'E'
return getEventId(bFront) === getEventId(eFront) ? 'B' : 'E'
}
function convertToEventQueues(events: ImportableTraceEvent[]): [BTraceEvent[], ETraceEvent[]] {
@@ -228,126 +305,221 @@ function getThreadNamesByPidTid(events: TraceEvent[]): Map<string, string> {
return threadNameByPidTid
}
function keyForEvent(event: TraceEvent): string {
let name = `${event.name || '(unnamed)'}`
if (event.args) {
name += ` ${JSON.stringify(event.args)}`
}
return name
function getEventName(event: TraceEvent): string {
return `${event.name || '(unnamed)'}`
}
function frameInfoForEvent(event: TraceEvent): FrameInfo {
const key = keyForEvent(event)
/**
* Attempt to construct a unique identifier for an event. Note that this
* is different from the frame key, as in some cases we don't want to include
* some arguments to allow from frame grouping (e.g. parent in the case of
* hermes profiles)
*/
function getEventId(event: TraceEvent): string {
let key = getEventName(event)
if (event.args) {
key += ` ${JSON.stringify(event.args)}`
}
return key
}
function frameInfoForEvent(
event: TraceEvent,
exporterSource: ExporterSource = ExporterSource.UNKNOWN,
): FrameInfo {
// In Hermes profiles we have additional guaranteed metadata we can use to
// more accurately populate profiles with info such as line + col number
if (exporterSource === ExporterSource.HERMES) {
const hermesFrameKey = `${event.name}:${event.args.url}:${event.args.line}:${event.args.column}`
return {
name: getEventName(event),
key: hermesFrameKey,
file: event.args.url,
line: event.args.line,
col: event.args.column,
}
}
const key = getEventId(event)
return {
name: key,
key: key,
}
}
function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
const importableEvents = filterIgnoredEventTypes(events)
const partitioned = partitionByPidTid(importableEvents)
function getProfileName(
processName: string | undefined,
threadName: string | undefined,
pid: number,
tid: number,
): string {
if (processName != null && threadName != null) {
return `${processName} (pid ${pid}), ${threadName} (tid ${tid})`
} else if (processName != null) {
return `${processName} (pid ${pid}, tid ${tid})`
} else if (threadName != null) {
return `${threadName} (pid ${pid}, tid ${tid})`
} else {
return `pid ${pid}, tid ${tid}`
}
}
function getProfileNamesFromSamples(
events: TraceEvent[],
partitionedSamples: Map<string, Sample[]>,
): Map<string, string> {
const processNamesByPid = getProcessNamesByPid(events)
const threadNamesByPidTid = getThreadNamesByPidTid(events)
const profilePairs: [string, Profile][] = []
const profileNamesByPidTid = new Map<string, string>()
partitioned.forEach(eventsForThread => {
if (eventsForThread.length === 0) return
partitionedSamples.forEach(samples => {
if (samples.length === 0) return
const {pid, tid} = eventsForThread[0]
const profile = new CallTreeProfileBuilder()
profile.setValueFormatter(new TimeFormatter('microseconds'))
const pid = Number(samples[0].pid)
const tid = Number(samples[0].tid)
const profileKey = pidTidKey(pid, tid)
const processName = processNamesByPid.get(pid)
const threadName = threadNamesByPidTid.get(pidTidKey(pid, tid))
const threadName = threadNamesByPidTid.get(profileKey)
const profileName = getProfileName(processName, threadName, 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}`)
profileNamesByPidTid.set(profileKey, profileName)
})
return profileNamesByPidTid
}
function getProfileNamesFromTraceEvents(
events: TraceEvent[],
partitionedTraceEvents: Map<string, TraceEvent[]>,
): Map<string, string> {
const processNamesByPid = getProcessNamesByPid(events)
const threadNamesByPidTid = getThreadNamesByPidTid(events)
const profileNamesByPidTid = new Map<string, string>()
partitionedTraceEvents.forEach(importableEvents => {
if (importableEvents.length === 0) return
const {pid, tid} = importableEvents[0]
const profileKey = pidTidKey(pid, tid)
const processName = processNamesByPid.get(pid)
const threadName = threadNamesByPidTid.get(profileKey)
const profileName = getProfileName(processName, threadName, pid, tid)
profileNamesByPidTid.set(profileKey, profileName)
})
return profileNamesByPidTid
}
function eventListToProfile(
importableEvents: ImportableTraceEvent[],
name: string,
exporterSource: ExporterSource = ExporterSource.UNKNOWN,
): Profile {
// The trace event format is hard to deal with because it specifically
// allows events to be recorded out of order, *but* event ordering is still
// important for events with the same timestamp. Because of this, rather
// than thinking about the entire event stream as a single queue of events,
// we're going to first construct two time-ordered lists of events:
//
// 1. ts ordered queue of 'B' events
// 2. ts ordered queue of 'E' events
//
// We deal with 'X' events by converting them to one entry in the 'B' event
// queue and one entry in the 'E' event queue.
//
// The high level goal is to deal with 'B' events in 'ts' order, breaking
// ties by the order the events occurred in the file, and deal with 'E'
// events in 'ts' order, breaking ties in whatever order causes the 'E'
// events to match whatever is on the top of the stack.
const [bEventQueue, eEventQueue] = convertToEventQueues(importableEvents)
const profileBuilder = new CallTreeProfileBuilder()
profileBuilder.setValueFormatter(new TimeFormatter('microseconds'))
profileBuilder.setName(name)
const frameStack: BTraceEvent[] = []
const enterFrame = (b: BTraceEvent) => {
frameStack.push(b)
profileBuilder.enterFrame(frameInfoForEvent(b, exporterSource), b.ts)
}
const tryToLeaveFrame = (e: ETraceEvent) => {
const b = lastOf(frameStack)
if (b == null) {
console.warn(
`Tried to end frame "${
frameInfoForEvent(e, exporterSource).key
}", but the stack was empty. Doing nothing instead.`,
)
return
}
// The trace event format is hard to deal with because it specifically
// allows events to be recorded out of order, *but* event ordering is still
// important for events with the same timestamp. Because of this, rather
// than thinking about the entire event stream as a single queue of events,
// we're going to first construct two time-ordered lists of events:
//
// 1. ts ordered queue of 'B' events
// 2. ts ordered queue of 'E' events
//
// We deal with 'X' events by converting them to one entry in the 'B' event
// queue and one entry in the 'E' event queue.
//
// The high level goal is to deal with 'B' events in 'ts' order, breaking
// ties by the order the events occurred in the file, and deal with 'E'
// events in 'ts' order, breaking ties in whatever order causes the 'E'
// events to match whatever is on the top of the stack.
const [bEventQueue, eEventQueue] = convertToEventQueues(eventsForThread)
const eFrameInfo = frameInfoForEvent(e, exporterSource)
const bFrameInfo = frameInfoForEvent(b, exporterSource)
const frameStack: BTraceEvent[] = []
const enterFrame = (b: BTraceEvent) => {
frameStack.push(b)
profile.enterFrame(frameInfoForEvent(b), b.ts)
if (e.name !== b.name) {
console.warn(
`ts=${e.ts}: Tried to end "${eFrameInfo.key}" when "${bFrameInfo.key}" was on the top of the stack. Doing nothing instead.`,
)
return
}
const tryToLeaveFrame = (e: ETraceEvent) => {
const b = lastOf(frameStack)
if (b == null) {
console.warn(
`Tried to end frame "${
frameInfoForEvent(e).key
}", but the stack was empty. Doing nothing instead.`,
)
return
}
const eFrameInfo = frameInfoForEvent(e)
const bFrameInfo = frameInfoForEvent(b)
if (e.name !== b.name) {
console.warn(
`ts=${e.ts}: Tried to end "${eFrameInfo.key}" when "${bFrameInfo.key}" was on the top of the stack. Doing nothing instead.`,
)
return
}
if (eFrameInfo.key !== bFrameInfo.key) {
console.warn(
`ts=${e.ts}: Tried to end "${eFrameInfo.key}" when "${bFrameInfo.key}" was on the top of the stack. Ending ${bFrameInfo.key} instead.`,
)
}
frameStack.pop()
profile.leaveFrame(bFrameInfo, e.ts)
if (eFrameInfo.key !== bFrameInfo.key) {
console.warn(
`ts=${e.ts}: Tried to end "${eFrameInfo.key}" when "${bFrameInfo.key}" was on the top of the stack. Ending ${bFrameInfo.key} instead.`,
)
}
while (bEventQueue.length > 0 || eEventQueue.length > 0) {
const queueName = selectQueueToTakeFromNext(bEventQueue, eEventQueue)
switch (queueName) {
case 'B': {
enterFrame(bEventQueue.shift()!)
break
}
case 'E': {
// Before we take the first event in the 'E' queue, let's first see if
// there are any e events that exactly match the top of the stack.
// We'll prioritize first by key, then by name if we can't find a key
// match.
const stackTop = lastOf(frameStack)
if (stackTop != null) {
const bFrameInfo = frameInfoForEvent(stackTop)
frameStack.pop()
profileBuilder.leaveFrame(bFrameInfo, e.ts)
}
let swapped: boolean = false
while (bEventQueue.length > 0 || eEventQueue.length > 0) {
const queueName = selectQueueToTakeFromNext(bEventQueue, eEventQueue)
switch (queueName) {
case 'B': {
enterFrame(bEventQueue.shift()!)
break
}
case 'E': {
// Before we take the first event in the 'E' queue, let's first see if
// there are any e events that exactly match the top of the stack.
// We'll prioritize first by key, then by name if we can't find a key
// match.
const stackTop = lastOf(frameStack)
if (stackTop != null) {
const bFrameInfo = frameInfoForEvent(stackTop, exporterSource)
let swapped: boolean = false
for (let i = 1; i < eEventQueue.length; i++) {
const eEvent = eEventQueue[i]
if (eEvent.ts > eEventQueue[0].ts) {
// Only consider 'E' events with the same ts as the front of the queue.
break
}
const eFrameInfo = frameInfoForEvent(eEvent, exporterSource)
if (bFrameInfo.key === eFrameInfo.key) {
// We have a match! Process this one first.
const temp = eEventQueue[0]
eEventQueue[0] = eEventQueue[i]
eEventQueue[i] = temp
swapped = true
break
}
}
if (!swapped) {
// There was no key match, let's see if we can find a name match
for (let i = 1; i < eEventQueue.length; i++) {
const eEvent = eEventQueue[i]
if (eEvent.ts > eEventQueue[0].ts) {
@@ -355,8 +527,7 @@ function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
break
}
const eFrameInfo = frameInfoForEvent(eEvent)
if (bFrameInfo.key === eFrameInfo.key) {
if (eEvent.name === stackTop.name) {
// We have a match! Process this one first.
const temp = eEventQueue[0]
eEventQueue[0] = eEventQueue[i]
@@ -365,50 +536,160 @@ function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
break
}
}
if (!swapped) {
// There was no key match, let's see if we can find a name match
for (let i = 1; i < eEventQueue.length; i++) {
const eEvent = eEventQueue[i]
if (eEvent.ts > eEventQueue[0].ts) {
// Only consider 'E' events with the same ts as the front of the queue.
break
}
if (eEvent.name === stackTop.name) {
// We have a match! Process this one first.
const temp = eEventQueue[0]
eEventQueue[0] = eEventQueue[i]
eEventQueue[i] = temp
swapped = true
break
}
}
}
// If swapped is still false at this point, it means we're about to
// pop a stack frame that doesn't even match by name. Bummer.
}
const e = eEventQueue.shift()!
tryToLeaveFrame(e)
break
// If swapped is still false at this point, it means we're about to
// pop a stack frame that doesn't even match by name. Bummer.
}
default:
const _exhaustiveCheck: never = queueName
return _exhaustiveCheck
const e = eEventQueue.shift()!
tryToLeaveFrame(e)
break
}
default:
const _exhaustiveCheck: never = queueName
return _exhaustiveCheck
}
}
for (let i = frameStack.length - 1; i >= 0; i--) {
const frame = frameInfoForEvent(frameStack[i], exporterSource)
console.warn(`Frame "${frame.key}" was still open at end of profile. Closing automatically.`)
profileBuilder.leaveFrame(frame, profileBuilder.getTotalWeight())
}
return profileBuilder.build()
}
/**
* Returns an array containing the time difference in microseconds between the current
* sample and the next sample
*/
function getTimeDeltasForSamples(samples: Sample[]): number[] {
const timeDeltas: number[] = []
let lastTimeStamp = Number(samples[0].ts)
samples.forEach((sample: Sample, idx: number) => {
if (idx === 0) return
const timeDiff = Number(sample.ts) - lastTimeStamp
lastTimeStamp = Number(sample.ts)
timeDeltas.push(timeDiff)
})
timeDeltas.push(0)
return timeDeltas
}
/**
* The chrome json trace event spec only specifies name and category
* as required stack frame properties
*
* https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.b4y98p32171
*/
function frameInfoForSampleFrame({name, category}: StackFrame): FrameInfo {
return {
key: `${name}:${category}`,
name: name,
}
}
function getActiveFramesForSample(
stackFrames: {[key: string]: StackFrame},
frameId: number,
): FrameInfo[] {
const frames = []
let parent: number | undefined = frameId
while (parent) {
const frame: StackFrame = stackFrames[parent]
if (!frame) {
throw new Error(`Could not find frame for id ${parent}`)
}
for (let i = frameStack.length - 1; i >= 0; i--) {
const frame = frameInfoForEvent(frameStack[i])
console.warn(`Frame "${frame.key}" was still open at end of profile. Closing automatically.`)
profile.leaveFrame(frame, profile.getTotalWeight())
frames.push(frameInfoForSampleFrame(frame))
parent = frame.parent
}
return frames.reverse()
}
function sampleListToProfile(contents: TraceWithSamples, samples: Sample[], name: string): Profile {
const profileBuilder = new StackListProfileBuilder()
profileBuilder.setValueFormatter(new TimeFormatter('microseconds'))
profileBuilder.setName(name)
const timeDeltas = getTimeDeltasForSamples(samples)
samples.forEach((sample, index) => {
const timeDelta = timeDeltas[index]
const activeFrames = getActiveFramesForSample(contents.stackFrames, sample.sf)
profileBuilder.appendSampleWithWeight(activeFrames, timeDelta)
})
return profileBuilder.build()
}
function eventListToProfileGroup(
events: TraceEvent[],
exporterSource: ExporterSource = ExporterSource.UNKNOWN,
): ProfileGroup {
const importableEvents = filterIgnoredEventTypes(events)
const partitionedTraceEvents = partitionByPidTid(importableEvents)
const profileNamesByPidTid = getProfileNamesFromTraceEvents(events, partitionedTraceEvents)
const profilePairs: [string, Profile][] = []
profileNamesByPidTid.forEach((name, profileKey) => {
const importableEventsForPidTid = partitionedTraceEvents.get(profileKey)
if (!importableEventsForPidTid) {
throw new Error(`Could not find events for key: ${importableEventsForPidTid}`)
}
profilePairs.push([pidTidKey(pid, tid), profile.build()])
profilePairs.push([
profileKey,
eventListToProfile(importableEventsForPidTid, name, exporterSource),
])
})
// 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 :)
sortBy(profilePairs, p => p[0])
return {
name: '',
indexToView: 0,
profiles: profilePairs.map(p => p[1]),
}
}
function sampleListToProfileGroup(contents: TraceWithSamples): ProfileGroup {
const partitionedSamples = partitionByPidTid(contents.samples)
const profileNamesByPidTid = getProfileNamesFromSamples(contents.traceEvents, partitionedSamples)
const profilePairs: [string, Profile][] = []
profileNamesByPidTid.forEach((name, profileKey) => {
const samplesForPidTid = partitionedSamples.get(profileKey)
if (!samplesForPidTid) {
throw new Error(`Could not find samples for key: ${samplesForPidTid}`)
}
if (samplesForPidTid.length === 0) {
return
}
profilePairs.push([profileKey, sampleListToProfile(contents, samplesForPidTid, name)])
})
// For now, we just sort processes by pid & tid.
@@ -456,27 +737,53 @@ function isTraceEventList(maybeEventList: any): maybeEventList is TraceEvent[] {
return true
}
function isTraceEventObject(
maybeTraceEventObject: any,
): maybeTraceEventObject is {traceEvents: TraceEvent[]} {
function isHermesTraceEvent(traceEventArgs: any): traceEventArgs is HermesTraceEventArgs {
if (!traceEventArgs) {
return false
}
return requiredHermesArguments.every(prop => prop in traceEventArgs)
}
function isHermesTraceEventList(maybeEventList: any): maybeEventList is HermesTraceEvent[] {
if (!isTraceEventList(maybeEventList)) return false
// We just check the first element to avoid iterating over all trace events,
// and asumme that if the first one is formatted like a hermes profile then
// all events will be
return isHermesTraceEvent(maybeEventList[0].args)
}
function isTraceEventObject(maybeTraceEventObject: any): maybeTraceEventObject is TraceEventObject {
if (!('traceEvents' in maybeTraceEventObject)) return false
return isTraceEventList(maybeTraceEventObject['traceEvents'])
}
export function isTraceEventFormatted(
rawProfile: any,
): rawProfile is {traceEvents: TraceEvent[]} | TraceEvent[] {
function isTraceEventWithSamples(
maybeTraceEventObject: any,
): maybeTraceEventObject is TraceWithSamples {
return (
'traceEvents' in maybeTraceEventObject &&
'stackFrames' in maybeTraceEventObject &&
'samples' in maybeTraceEventObject &&
isTraceEventList(maybeTraceEventObject['traceEvents'])
)
}
export function isTraceEventFormatted(rawProfile: any): rawProfile is Trace {
// 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)) {
export function importTraceEvents(rawProfile: Trace): ProfileGroup {
if (isTraceEventWithSamples(rawProfile)) {
return sampleListToProfileGroup(rawProfile)
} else if (isTraceEventObject(rawProfile)) {
return eventListToProfileGroup(rawProfile.traceEvents)
} else if (isHermesTraceEventList(rawProfile)) {
return eventListToProfileGroup(rawProfile, ExporterSource.HERMES)
} else if (isTraceEventList(rawProfile)) {
return eventListToProfileGroup(rawProfile)
} else {
+24 -12
View File
@@ -8,7 +8,7 @@ export interface ProfileDataSource {
}
export interface TextFileContent {
splitLines(): string[]
splitLines(): Iterable<string>
firstChunk(): string
parseAsJSON(): any
}
@@ -19,7 +19,7 @@ export interface TextFileContent {
//
// We provide a simple splitLines() which returns simple strings under the
// assumption that most extremely large text profiles will be broken into many
// lines. This isn't true in the general case, but will be true for most common
// lines. This isn't true in the general case, but will be true for most common
// large files.
//
// See: https://github.com/v8/v8/blob/8b663818fc311217c2cdaaab935f020578bfb7a8/src/objects/string.h#L479-L483
@@ -151,17 +151,26 @@ export class BufferBackedTextFileContent implements TextFileContent {
}
}
splitLines(): string[] {
let parts: string[] = this.chunks[0].split('\n')
for (let i = 1; i < this.chunks.length; i++) {
const chunkParts = this.chunks[i].split('\n')
if (chunkParts.length === 0) continue
if (parts.length > 0) {
parts[parts.length - 1] += chunkParts.shift()
splitLines(): Iterable<string> {
const iterator = function* (this: BufferBackedTextFileContent) {
let lineBuffer: string = ''
for (let chunk of this.chunks) {
const fragments = chunk.split('\n')
for (let i = 0; i < fragments.length; i++) {
if (i === 0) lineBuffer += fragments[i]
else {
yield lineBuffer
lineBuffer = fragments[i]
}
}
}
parts = parts.concat(chunkParts)
yield lineBuffer
}
return {
[Symbol.iterator]: iterator.bind(this),
}
return parts
}
firstChunk(): string {
@@ -195,7 +204,10 @@ export class StringBackedTextFileContent implements TextFileContent {
}
export class TextProfileDataSource implements ProfileDataSource {
constructor(private fileName: string, private contents: string) {}
constructor(
private fileName: string,
private contents: string,
) {}
async name() {
return this.fileName
}
+2 -2
View File
@@ -87,8 +87,8 @@ function codeToFrameInfo(code: Code, v8log: V8LogProfile): FrameInfo {
matches[1].length > 0
? matches[1]
: file
? `(anonymous ${file.split('/').pop()}:${line})`
: '(anonymous)'
? `(anonymous ${file.split('/').pop()}:${line})`
: '(anonymous)'
return {
key: name,
name: functionName,
@@ -1,5 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`importSpeedscopeProfiles 0.0.1 evented profile (no pretty print) 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": 0,
"line": undefined,
"name": "a",
"selfWeight": 0,
"totalWeight": 14,
},
Frame {
"col": undefined,
"file": undefined,
"key": 1,
"line": undefined,
"name": "b",
"selfWeight": 5,
"totalWeight": 14,
},
Frame {
"col": undefined,
"file": undefined,
"key": 2,
"line": undefined,
"name": "c",
"selfWeight": 5,
"totalWeight": 5,
},
Frame {
"col": undefined,
"file": undefined,
"key": 3,
"line": undefined,
"name": "d",
"selfWeight": 4,
"totalWeight": 4,
},
],
"name": "simple.txt",
"stacks": Array [
"a;b;c 2",
"a;b;d 4",
"a;b;c 3",
"a;b 5",
],
}
`;
exports[`importSpeedscopeProfiles 0.0.1 evented profile (no pretty print): indexToView 1`] = `0`;
exports[`importSpeedscopeProfiles 0.0.1 evented profile (no pretty print): profileGroup.name 1`] = `"simple.txt"`;
exports[`importSpeedscopeProfiles 0.0.1 evented profile 1`] = `
Object {
"frames": Array [
+4 -1
View File
@@ -82,7 +82,10 @@ if (process.env.NODE_ENV === 'development') {
export class Atom<T> {
private observers: AtomListener[] = []
constructor(protected state: T, debugKey: string) {
constructor(
protected state: T,
debugKey: string,
) {
if (process.env.NODE_ENV === 'development') {
if (hotReloadStash?.has(debugKey)) {
// If we have a stored value from a previous hot reload, use that
+8 -8
View File
@@ -20,14 +20,14 @@ export class Color {
hPrime < 1
? [C, X, 0]
: hPrime < 2
? [X, C, 0]
: hPrime < 3
? [0, C, X]
: hPrime < 4
? [0, X, C]
: hPrime < 5
? [X, 0, C]
: [C, 0, X]
? [X, C, 0]
: hPrime < 3
? [0, C, X]
: hPrime < 4
? [0, X, C]
: hPrime < 5
? [X, 0, C]
: [C, 0, X]
const m = L - (0.3 * R1 + 0.59 * G1 + 0.11 * B1)
+2 -2
View File
@@ -65,7 +65,7 @@ export namespace FileFormat {
unit: ValueUnit
// The starting value of the profile. This will typically be a timestamp.
// All event values will be relative to this startValue.
// All event values will be displayed relative to this startValue.
startValue: number
// The final value of the profile. This will typically be a timestamp. This
@@ -93,7 +93,7 @@ export namespace FileFormat {
unit: ValueUnit
// The starting value of the profile. This will typically be a timestamp.
// All event values will be relative to this startValue.
// All event values will be displayed relative to this startValue.
startValue: number
// The final value of the profile. This will typically be a timestamp. This
+6
View File
@@ -5,6 +5,12 @@ describe('importSpeedscopeProfiles', () => {
await checkProfileSnapshot('./sample/profiles/speedscope/0.0.1/simple.speedscope.json')
})
test('0.0.1 evented profile (no pretty print)', async () => {
await checkProfileSnapshot(
'./sample/profiles/speedscope/0.0.1/simple-no-pretty-print.speedscope.json',
)
})
test('0.1.2 sampled profile', async () => {
await checkProfileSnapshot('./sample/profiles/speedscope/0.1.2/simple-sampled.speedscope.json')
})
+8 -2
View File
@@ -5,7 +5,10 @@ export function clamp(x: number, minVal: number, maxVal: number) {
}
export class Vec2 {
constructor(readonly x: number, readonly y: number) {}
constructor(
readonly x: number,
readonly y: number,
) {}
withX(x: number) {
return new Vec2(x, this.y)
}
@@ -251,7 +254,10 @@ export class AffineTransform {
}
export class Rect {
constructor(readonly origin: Vec2, readonly size: Vec2) {}
constructor(
readonly origin: Vec2,
readonly size: Vec2,
) {}
isEmpty() {
return this.width() == 0 || this.height() == 0
+8 -2
View File
@@ -39,7 +39,10 @@ export function exactMatchStrings(text: string, pattern: string): [number, numbe
// 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) {}
constructor(
readonly profile: Profile,
readonly searchQuery: string,
) {}
private matches: Map<Frame, [number, number][] | null> | null = null
getMatchForFrame(frame: Frame): [number, number][] | null {
@@ -65,7 +68,10 @@ interface CachedFlamechartResult {
}
export class FlamechartSearchResults {
constructor(readonly flamechart: Flamechart, readonly profileResults: ProfileSearchResults) {}
constructor(
readonly flamechart: Flamechart,
readonly profileResults: ProfileSearchResults,
) {}
private matches: CachedFlamechartResult | null = null
private getResults(): CachedFlamechartResult {
+9 -6
View File
@@ -99,7 +99,10 @@ export class CallTreeNode extends HasWeights {
this.frozen = true
}
constructor(readonly frame: Frame, readonly parent: CallTreeNode | null) {
constructor(
readonly frame: Frame,
readonly parent: CallTreeNode | null,
) {
super()
}
}
@@ -439,14 +442,13 @@ export class Profile {
}
export class StackListProfileBuilder extends Profile {
_appendSample(stack: FrameInfo[], weight: number, useAppendOrder: boolean) {
_appendSample(stack: Frame[], weight: number, useAppendOrder: boolean) {
if (isNaN(weight)) throw new Error('invalid weight')
let node = useAppendOrder ? this.appendOrderCalltreeRoot : this.groupedCalltreeRoot
let framesInStack = new Set<Frame>()
for (let frameInfo of stack) {
const frame = Frame.getOrInsert(this.frames, frameInfo)
for (let frame of stack) {
const last = useAppendOrder
? lastOf(node.children)
: node.children.find(c => c.frame === frame)
@@ -500,8 +502,9 @@ export class StackListProfileBuilder extends Profile {
throw new Error('Samples must have positive weights')
}
this._appendSample(stack, weight, true)
this._appendSample(stack, weight, false)
const frames = stack.map(fr => Frame.getOrInsert(this.frames, fr))
this._appendSample(frames, weight, true)
this._appendSample(frames, weight, false)
}
private pendingSample: {
+5 -1
View File
@@ -76,7 +76,11 @@ class Panel {
private GRAPH_WIDTH = 74 * PR
private GRAPH_HEIGHT = 30 * PR
constructor(private name: string, private fg: string, private bg: string) {
constructor(
private name: string,
private fg: string,
private bg: string,
) {
this.canvas.width = this.WIDTH
this.canvas.height = this.HEIGHT
this.canvas.style.cssText = 'width:80px;height:48px'
+1 -1
View File
@@ -105,6 +105,6 @@ export async function expectImportFailure(filepath: string) {
await importProfilesFromArrayBuffer(path.basename(filepath), arrayBuffer)
fail('Expected import to fail but it succeeded')
} catch (error) {
expect(error.message).toMatchSnapshot()
expect((error as Error).message).toMatchSnapshot()
}
}
+4 -4
View File
@@ -43,8 +43,8 @@ export function buildTrimmedText(text: string, length: number): TrimmedTextResul
let prefixLength = Math.floor(length / 2)
const suffixLength = length - prefixLength - 1
const prefix = text.substr(0, prefixLength)
const suffix = text.substr(text.length - suffixLength, suffixLength)
const prefix = text.substring(0, prefixLength)
const suffix = text.substring(text.length - suffixLength, text.length)
const trimmedString = prefix + ELLIPSIS + suffix
return {
trimmedString,
@@ -69,11 +69,11 @@ export function trimTextMid(
0,
text.length,
n => {
return cachedMeasureTextWidth(ctx, buildTrimmedText(text, n).trimmedString)
return cachedMeasureTextWidth(ctx, buildTrimmedText(text, Math.floor(n)).trimmedString)
},
maxWidth,
)
return buildTrimmedText(text, lo)
return buildTrimmedText(text, Math.floor(lo))
}
enum IndexTypeInTrimmed {
+14 -4
View File
@@ -42,7 +42,10 @@ test('getOrInsert', () => {
})
class ValueType {
private constructor(readonly a: string, readonly num: number) {}
private constructor(
readonly a: string,
readonly num: number,
) {}
get key() {
return `${this.a}_${this.num}`
}
@@ -247,11 +250,18 @@ test('BufferBackedTextFileContent.firstChunk', async () => {
})
test('BufferBackedTextFileContent.splitLines', async () => {
await withMockedFileChunkSizeForTests(2, () => {
const str = 'may\nyour\nrope\nbe\nlong'
function testWithString(str: string) {
const buffer = new TextEncoder().encode(str).buffer
const content = new BufferBackedTextFileContent(buffer)
expect(content.splitLines()).toEqual(['may', 'your', 'rope', 'be', 'long'])
expect([...content.splitLines()]).toEqual(str.split('\n'))
}
await withMockedFileChunkSizeForTests(2, () => {
testWithString('may\nyour\nrope\nbe\nlong')
testWithString('one-single-line')
testWithString('multiple\n\nnew\n\nlines')
testWithString('end-with-new-line\n')
testWithString('')
})
})
+9 -11
View File
@@ -213,17 +213,15 @@ export function lazyStatic<T>(cb: () => T): () => T {
}
}
const base64lookupTable = lazyStatic(
(): Map<string, number> => {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const ret = new Map<string, number>()
for (let i = 0; i < alphabet.length; i++) {
ret.set(alphabet.charAt(i), i)
}
ret.set('=', -1)
return ret
},
)
const base64lookupTable = lazyStatic((): Map<string, number> => {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const ret = new Map<string, number>()
for (let i = 0; i < alphabet.length; i++) {
ret.set(alphabet.charAt(i), i)
}
ret.set('=', -1)
return ret
})
// NOTE: There are probably simpler solutions to this problem, but I have this written already, so
// until we run into problems with this, let's just use this.
+4 -4
View File
@@ -20,10 +20,10 @@ import {Application} from './application'
export const ApplicationContainer = memo(() => {
const canvas = useAtom(glCanvasAtom)
const theme = useTheme()
const canvasContext = useMemo(() => (canvas ? getCanvasContext({theme, canvas}) : null), [
theme,
canvas,
])
const canvasContext = useMemo(
() => (canvas ? getCanvasContext({theme, canvas}) : null),
[theme, canvas],
)
return (
<ProfileSearchContextProvider>
+14 -5
View File
@@ -1,6 +1,5 @@
import {h} from 'preact'
import {StyleSheet, css} from 'aphrodite'
import {FileSystemDirectoryEntry} from '../import/file-system-entry'
import {ProfileGroup, SymbolRemapper} from '../lib/profile'
import {FontFamily, FontSize, Duration} from './style'
@@ -60,6 +59,10 @@ async function importFromFileSystemDirectoryEntry(entry: FileSystemDirectoryEntr
declare function require(x: string): any
const exampleProfileURL = require('../../sample/profiles/stackcollapse/perf-vertx-stacks-01-collapsed-all.txt')
function isFileSystemDirectoryEntry(entry: FileSystemEntry): entry is FileSystemDirectoryEntry {
return entry != null && entry.isDirectory
}
interface GLCanvasProps {
canvasContext: CanvasContext | null
theme: Theme
@@ -165,6 +168,7 @@ export type ApplicationProps = {
export class Application extends StatelessComponent<ApplicationProps> {
private async loadProfile(loader: () => Promise<ProfileGroup | null>) {
this.props.setError(false)
this.props.setLoading(true)
await new Promise(resolve => setTimeout(resolve, 0))
@@ -306,13 +310,18 @@ export class Application extends StatelessComponent<ApplicationProps> {
const firstItem = ev.dataTransfer.items[0]
if ('webkitGetAsEntry' in firstItem) {
const webkitEntry: FileSystemDirectoryEntry = firstItem.webkitGetAsEntry()
const webkitEntry: FileSystemEntry | null = firstItem.webkitGetAsEntry()
// Instrument.app file format is actually a directory.
if (webkitEntry.isDirectory && webkitEntry.name.endsWith('.trace')) {
if (
webkitEntry &&
isFileSystemDirectoryEntry(webkitEntry) &&
webkitEntry.name.endsWith('.trace')
) {
console.log('Importing as Instruments.app .trace file')
const webkitDirectoryEntry: FileSystemDirectoryEntry = webkitEntry
this.loadProfile(async () => {
return await importFromFileSystemDirectoryEntry(webkitEntry)
return await importFromFileSystemDirectoryEntry(webkitDirectoryEntry)
})
return
}
@@ -416,7 +425,7 @@ export class Application extends StatelessComponent<ApplicationProps> {
}
async maybeLoadHashParamProfile() {
const {profileURL} = this.props.hashParams;
const {profileURL} = this.props.hashParams
if (profileURL) {
if (!canUseXHR) {
alert(
+2 -3
View File
@@ -308,9 +308,8 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
const logicalSpaceMouse = this.windowToLogicalViewSpace().transformPosition(
new Vec2(ev.clientX, ev.clientY),
)
const physicalSpaceMouse = this.logicalToPhysicalViewSpace().transformPosition(
logicalSpaceMouse,
)
const physicalSpaceMouse =
this.logicalToPhysicalViewSpace().transformPosition(logicalSpaceMouse)
return this.configSpaceToPhysicalViewSpace().inverseTransformPosition(physicalSpaceMouse)
}
+25 -17
View File
@@ -239,10 +239,7 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
)
if (match) {
const rangesToHighlightInTrimmedText = remapRangesToTrimmedText(
trimmedText,
match
)
const rangesToHighlightInTrimmedText = remapRangesToTrimmedText(trimmedText, match)
// Once we have the character ranges to highlight, we need to
// actually do the highlighting.
@@ -546,12 +543,10 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
private zoom(logicalViewSpaceCenter: Vec2, multiplier: number) {
this.interactionLock = 'zoom'
const physicalCenter = this.logicalToPhysicalViewSpace().transformPosition(
logicalViewSpaceCenter,
)
const configSpaceCenter = this.configSpaceToPhysicalViewSpace().inverseTransformPosition(
physicalCenter,
)
const physicalCenter =
this.logicalToPhysicalViewSpace().transformPosition(logicalViewSpaceCenter)
const configSpaceCenter =
this.configSpaceToPhysicalViewSpace().inverseTransformPosition(physicalCenter)
if (!configSpaceCenter) return
const zoomTransform = AffineTransform.withTranslation(configSpaceCenter.times(-1))
@@ -634,14 +629,12 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
this.onMouseDrag(ev)
return
}
this.hoveredLabel = null
const logicalViewSpaceMouse = new Vec2(ev.offsetX, ev.offsetY)
const physicalViewSpaceMouse = this.logicalToPhysicalViewSpace().transformPosition(
logicalViewSpaceMouse,
)
const configSpaceMouse = this.configSpaceToPhysicalViewSpace().inverseTransformPosition(
physicalViewSpaceMouse,
)
const physicalViewSpaceMouse =
this.logicalToPhysicalViewSpace().transformPosition(logicalViewSpaceMouse)
const configSpaceMouse =
this.configSpaceToPhysicalViewSpace().inverseTransformPosition(physicalViewSpaceMouse)
if (!configSpaceMouse) return
@@ -664,6 +657,21 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
}
}
// This is a dumb hack to get around what appears to be a bug in
// TypeScript's reachability analysis. If I do the this.hoveredLabel = null
// in the outer function body, the code below accessing
// this.hoveredLabel!.node inside of the `if (this.hoveredLabel) {`
// complains that "no property node on never", indicating that it thinks
// that codepath is unreachable.
//
// Because this.hoveredLabel is accessed in the bound function
// setHoveredLabel, the codepath is obviously reachable, but the type
// checker is confused about this for some reason.
const clearHoveredLabel = () => {
this.hoveredLabel = null
}
clearHoveredLabel()
for (let frame of this.props.flamechart.getLayers()[0] || []) {
setHoveredLabel(frame)
}
+36 -30
View File
@@ -3,7 +3,7 @@ import {Sizes, FontSize, FontFamily, ZIndex} from './style'
import {css, StyleSheet} from 'aphrodite'
import {ComponentChildren, h} from 'preact'
import {useTheme, withTheme} from './themes/theme'
import { useCallback } from 'preact/hooks'
import {useCallback} from 'preact/hooks'
interface HovertipProps {
containerSize: Vec2
@@ -20,42 +20,48 @@ export function Hovertip(props: HovertipProps) {
const OFFSET_FROM_MOUSE = 7
const updateLocation = useCallback((el: HTMLDivElement | null) => {
if (!el) return
const updateLocation = useCallback(
(el: HTMLDivElement | null) => {
if (!el) return
const clientRect = el.getBoundingClientRect()
const clientRect = el.getBoundingClientRect()
// Place the hovertip to the right of the cursor.
let leftEdgeX = offset.x + OFFSET_FROM_MOUSE
// Place the hovertip to the right of the cursor.
let leftEdgeX = offset.x + OFFSET_FROM_MOUSE
// If this would cause it to overflow the container, align the right
// edge of the hovertip with the right edge of the container.
if (leftEdgeX + clientRect.width > containerWidth - 1) {
leftEdgeX = containerWidth - clientRect.width - 1
// If this would cause it to overflow the container, align the right
// edge of the hovertip with the right edge of the container.
if (leftEdgeX + clientRect.width > containerWidth - 1) {
leftEdgeX = containerWidth - clientRect.width - 1
// If aligning the right edge overflows the container, align the left edge
// of the hovertip with the left edge of the container.
if (leftEdgeX < 1) { leftEdgeX = 1 }
}
el.style.left = `${leftEdgeX}px`
// If aligning the right edge overflows the container, align the left edge
// of the hovertip with the left edge of the container.
if (leftEdgeX < 1) {
leftEdgeX = 1
}
}
el.style.left = `${leftEdgeX}px`
// Place the tooltip below the cursor
let topEdgeY = offset.y + OFFSET_FROM_MOUSE
// Place the tooltip below the cursor
let topEdgeY = offset.y + OFFSET_FROM_MOUSE
// If this would cause it to overflow the container, place the hovertip
// above the cursor instead. This intentionally differs from the horizontal
// axis logic to avoid the cursor being in the middle of a hovertip when
// possible.
if (topEdgeY + clientRect.height > containerHeight - 1) {
topEdgeY = offset.y - clientRect.height - 1
// If this would cause it to overflow the container, place the hovertip
// above the cursor instead. This intentionally differs from the horizontal
// axis logic to avoid the cursor being in the middle of a hovertip when
// possible.
if (topEdgeY + clientRect.height > containerHeight - 1) {
topEdgeY = offset.y - clientRect.height - 1
// If placing the hovertip above the cursor overflows the container, align
// the top edge of the hovertip with the top edge of the container.
if (topEdgeY < 1) { topEdgeY = 1 }
}
el.style.top = `${topEdgeY}px`
}, [containerWidth, containerHeight, offset.x, offset.y])
// If placing the hovertip above the cursor overflows the container, align
// the top edge of the hovertip with the top edge of the container.
if (topEdgeY < 1) {
topEdgeY = 1
}
}
el.style.top = `${topEdgeY}px`
},
[containerWidth, containerHeight, offset.x, offset.y],
)
return (
<div className={css(style.hoverTip)} ref={updateLocation}>
+8 -6
View File
@@ -268,12 +268,14 @@ export const ProfileTableView = memo(
[sandwichContext],
)
const onTotalClick = useCallback((ev: MouseEvent) => onSortClick(SortField.TOTAL, ev), [
onSortClick,
])
const onSelfClick = useCallback((ev: MouseEvent) => onSortClick(SortField.SELF, ev), [
onSortClick,
])
const onTotalClick = useCallback(
(ev: MouseEvent) => onSortClick(SortField.TOTAL, ev),
[onSortClick],
)
const onSelfClick = useCallback(
(ev: MouseEvent) => onSortClick(SortField.SELF, ev),
[onSortClick],
)
const onSymbolNameClick = useCallback(
(ev: MouseEvent) => onSortClick(SortField.SYMBOL_NAME, ev),
[onSortClick],