Haskell GHC JSON format support (fixes #182) (#183)

Fixes #182 by adding support for importing the JSON profiling format created by GHC's built in profiling support when the executable is passed the `-pj` option. Produces a profile group containing both a time and allocation profile.

Unfortunately, GHC doesn't provide the raw sample information to get the time view to be useful, so only left heavy and sandwich are useful.

Includes a test profile, and I've also tested it on a more real large 2MB profile file in the UI and it works great.

I also modified the Readme to link to a wiki page I'm unable to create, but that should have something like this content copy-pasted into it:

# Importing from Haskell

GHC provides built in profiling support that can export a JSON file.
In order to do this you need to compile your executable with profiling
support and then pass the `-pj` RTS flag to the executable.

This will produce a `my-binary.prof` file in the current directory which
you can import into speedscope.

## Using GHC

See the [GHC manual page on profiling](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/profiling.html)
for more extensive information on the command line flags available.

```
$ ghc -prof -fprof-auto -rtsopts Main.hs
$ ./Main +RTS -pj -RTS
```

## Using Stack

### With executables

```
$ stack build --profile
$ stack exec -- my-executable +RTS -pj -RTS
```

### With tests

```
stack test --profile --test-arguments "+RTS -pj -RTS"
```
This commit is contained in:
Tristan Hume 2018-10-29 09:37:11 -07:00 committed by Jamie Wong
parent 86f4ba636d
commit e35335fe3c
6 changed files with 1165 additions and 0 deletions

View File

@ -45,6 +45,7 @@ speedscope is designed to ingest profiles from a variety of different profilers
- Native code
- [Importing from Instruments.app](https://github.com/jlfwong/speedscope/wiki/Importing-from-Instruments.app) (macOS)
- [Importing from `perf`](https://github.com/jlfwong/speedscope/wiki/Importing-from-perf-(linux)) (linux)
- [Importing from GHC (Haskell)](https://github.com/jlfwong/speedscope/wiki/Importing-from-Haskell)
- [Importing from custom sources](https://github.com/jlfwong/speedscope/wiki/Importing-from-custom-sources)
Contributions to add support for additional formats are welcome! See issues with the ["import source" tag](https://github.com/jlfwong/speedscope/issues?q=is%3Aissue+is%3Aopen+label%3A%22import+source%22).

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,864 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`importFromHaskell 1`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": 144,
"line": undefined,
"name": "MAIN.MAIN",
"selfWeight": 0,
"totalWeight": 798,
},
Frame {
"col": undefined,
"file": undefined,
"key": 56,
"line": undefined,
"name": "GHC.Conc.Signal.CAF",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 73,
"line": undefined,
"name": "GHC.IO.Encoding.CAF",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 75,
"line": undefined,
"name": "GHC.IO.Encoding.Iconv.CAF",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 84,
"line": undefined,
"name": "GHC.IO.Handle.FD.CAF",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 119,
"line": undefined,
"name": "Text.Printf.CAF",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 26,
"line": undefined,
"name": "Main.CAF:eta1_r6nI",
"selfWeight": 0,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(21,1)-(38,42)",
"key": 19,
"line": undefined,
"name": "Main.main",
"selfWeight": 0,
"totalWeight": 733,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(56,1)-(57,42)",
"key": 4,
"line": undefined,
"name": "Main.check",
"selfWeight": 320,
"totalWeight": 320,
},
Frame {
"col": undefined,
"file": "src/Main.hs:31:9-29",
"key": 16,
"line": undefined,
"name": "Main.main.long",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": undefined,
"key": 28,
"line": undefined,
"name": "Main.CAF:eta_r6nG",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 35,
"line": undefined,
"name": "Main.CAF:io1",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 27,
"line": undefined,
"name": "Main.CAF:lvl2_r6nH",
"selfWeight": 0,
"totalWeight": 3,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(61,1)-(63,26)",
"key": 3,
"line": undefined,
"name": "Main.make",
"selfWeight": 406,
"totalWeight": 409,
},
Frame {
"col": undefined,
"file": "src/Main.hs:63:19-26",
"key": 1,
"line": undefined,
"name": "Main.make.d2",
"selfWeight": 2,
"totalWeight": 2,
},
Frame {
"col": undefined,
"file": "src/Main.hs:63:9-16",
"key": 2,
"line": undefined,
"name": "Main.make.i2",
"selfWeight": 1,
"totalWeight": 1,
},
Frame {
"col": undefined,
"file": undefined,
"key": 24,
"line": undefined,
"name": "Main.CAF:lvl4_r6nL",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 22,
"line": undefined,
"name": "Main.CAF:lvl8_r6nP",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 21,
"line": undefined,
"name": "Main.CAF:main1",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 29,
"line": undefined,
"name": "Main.CAF:main11",
"selfWeight": 0,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": "src/Main.hs:27:9-35",
"key": 15,
"line": undefined,
"name": "Main.main.c",
"selfWeight": 0,
"totalWeight": 8,
},
Frame {
"col": undefined,
"file": undefined,
"key": 30,
"line": undefined,
"name": "Main.CAF:main12",
"selfWeight": 0,
"totalWeight": 4,
},
Frame {
"col": undefined,
"file": undefined,
"key": 23,
"line": undefined,
"name": "Main.CAF:main5",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 31,
"line": undefined,
"name": "Main.CAF:main7",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 25,
"line": undefined,
"name": "Main.CAF:main9",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:23:9-12",
"key": 33,
"line": undefined,
"name": "Main.CAF:main_maxN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:23:9-35",
"key": 13,
"line": undefined,
"name": "Main.main.maxN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:22:9",
"key": 34,
"line": undefined,
"name": "Main.CAF:main_n",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:22:9-14",
"key": 12,
"line": undefined,
"name": "Main.main.n",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:24:9-16",
"key": 32,
"line": undefined,
"name": "Main.CAF:main_stretchN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:24:9-27",
"key": 14,
"line": undefined,
"name": "Main.main.stretchN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:17:1-4",
"key": 36,
"line": undefined,
"name": "Main.CAF:minN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:17:1-8",
"key": 9,
"line": undefined,
"name": "Main.minN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 146,
"line": undefined,
"name": "GC.GC",
"selfWeight": 46,
"totalWeight": 46,
},
Frame {
"col": undefined,
"file": undefined,
"key": 147,
"line": undefined,
"name": "PROFILING.OVERHEAD_of",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 145,
"line": undefined,
"name": "SYSTEM.SYSTEM",
"selfWeight": 19,
"totalWeight": 19,
},
Frame {
"col": undefined,
"file": "src/Main.hs:35:26-54",
"key": 18,
"line": undefined,
"name": "Main.main.\\\\",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:19:1-56",
"key": 8,
"line": undefined,
"name": "Main.io",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:34:9-28",
"key": 17,
"line": undefined,
"name": "Main.main.vs",
"selfWeight": 0,
"totalWeight": 721,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(42,1)-(45,38)",
"key": 11,
"line": undefined,
"name": "Main.depth",
"selfWeight": 0,
"totalWeight": 721,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(49,1)-(52,31)",
"key": 7,
"line": undefined,
"name": "Main.sumT",
"selfWeight": 0,
"totalWeight": 721,
},
Frame {
"col": undefined,
"file": "src/Main.hs:51:9-31",
"key": 6,
"line": undefined,
"name": "Main.sumT.a",
"selfWeight": 3,
"totalWeight": 369,
},
Frame {
"col": undefined,
"file": "src/Main.hs:52:9-31",
"key": 5,
"line": undefined,
"name": "Main.sumT.b",
"selfWeight": 1,
"totalWeight": 352,
},
Frame {
"col": undefined,
"file": "src/Main.hs:45:9-38",
"key": 10,
"line": undefined,
"name": "Main.depth.n",
"selfWeight": 0,
"totalWeight": 0,
},
],
"name": "binary-trees time",
"stacks": Array [
"MAIN.MAIN;Main.CAF:eta1_r6nI;Main.main;Main.check 1.00ms",
"MAIN.MAIN;Main.CAF:lvl2_r6nH;Main.main;Main.main.long;Main.make 3.00ms",
"MAIN.MAIN;Main.CAF:main11;Main.main;Main.main.c;Main.check 4.00ms",
"MAIN.MAIN;Main.CAF:main12;Main.main;Main.main.c;Main.make 4.00ms",
"MAIN.MAIN;GC.GC 46.00ms",
"MAIN.MAIN;SYSTEM.SYSTEM 19.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.check 153.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make;Main.make.d2 2.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make;Main.make.i2 1.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make 210.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a 3.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.check 162.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.make 189.00ms",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b 1.00ms",
],
}
`;
exports[`importFromHaskell 2`] = `
Object {
"frames": Array [
Frame {
"col": undefined,
"file": undefined,
"key": 144,
"line": undefined,
"name": "MAIN.MAIN",
"selfWeight": 648,
"totalWeight": 1921672664,
},
Frame {
"col": undefined,
"file": undefined,
"key": 56,
"line": undefined,
"name": "GHC.Conc.Signal.CAF",
"selfWeight": 640,
"totalWeight": 640,
},
Frame {
"col": undefined,
"file": undefined,
"key": 73,
"line": undefined,
"name": "GHC.IO.Encoding.CAF",
"selfWeight": 2768,
"totalWeight": 2768,
},
Frame {
"col": undefined,
"file": undefined,
"key": 75,
"line": undefined,
"name": "GHC.IO.Encoding.Iconv.CAF",
"selfWeight": 200,
"totalWeight": 200,
},
Frame {
"col": undefined,
"file": undefined,
"key": 84,
"line": undefined,
"name": "GHC.IO.Handle.FD.CAF",
"selfWeight": 34704,
"totalWeight": 34704,
},
Frame {
"col": undefined,
"file": undefined,
"key": 119,
"line": undefined,
"name": "Text.Printf.CAF",
"selfWeight": 528,
"totalWeight": 528,
},
Frame {
"col": undefined,
"file": undefined,
"key": 26,
"line": undefined,
"name": "Main.CAF:eta1_r6nI",
"selfWeight": 0,
"totalWeight": 2097152,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(21,1)-(38,42)",
"key": 19,
"line": undefined,
"name": "Main.main",
"selfWeight": 32,
"totalWeight": 1921591544,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(56,1)-(57,42)",
"key": 4,
"line": undefined,
"name": "Main.check",
"selfWeight": 406149056,
"totalWeight": 406149056,
},
Frame {
"col": undefined,
"file": "src/Main.hs:31:9-29",
"key": 16,
"line": undefined,
"name": "Main.main.long",
"selfWeight": 32,
"totalWeight": 7864112,
},
Frame {
"col": undefined,
"file": undefined,
"key": 28,
"line": undefined,
"name": "Main.CAF:eta_r6nG",
"selfWeight": 1096,
"totalWeight": 1096,
},
Frame {
"col": undefined,
"file": undefined,
"key": 35,
"line": undefined,
"name": "Main.CAF:io1",
"selfWeight": 1888,
"totalWeight": 1888,
},
Frame {
"col": undefined,
"file": undefined,
"key": 27,
"line": undefined,
"name": "Main.CAF:lvl2_r6nH",
"selfWeight": 0,
"totalWeight": 7864112,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(61,1)-(63,26)",
"key": 3,
"line": undefined,
"name": "Main.make",
"selfWeight": 1512575520,
"totalWeight": 1512575520,
},
Frame {
"col": undefined,
"file": "src/Main.hs:63:19-26",
"key": 1,
"line": undefined,
"name": "Main.make.d2",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:63:9-16",
"key": 2,
"line": undefined,
"name": "Main.make.i2",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 24,
"line": undefined,
"name": "Main.CAF:lvl4_r6nL",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 22,
"line": undefined,
"name": "Main.CAF:lvl8_r6nP",
"selfWeight": 520,
"totalWeight": 520,
},
Frame {
"col": undefined,
"file": undefined,
"key": 21,
"line": undefined,
"name": "Main.CAF:main1",
"selfWeight": 16,
"totalWeight": 16,
},
Frame {
"col": undefined,
"file": undefined,
"key": 29,
"line": undefined,
"name": "Main.CAF:main11",
"selfWeight": 0,
"totalWeight": 4194304,
},
Frame {
"col": undefined,
"file": "src/Main.hs:27:9-35",
"key": 15,
"line": undefined,
"name": "Main.main.c",
"selfWeight": 64,
"totalWeight": 19922736,
},
Frame {
"col": undefined,
"file": undefined,
"key": 30,
"line": undefined,
"name": "Main.CAF:main12",
"selfWeight": 0,
"totalWeight": 15728432,
},
Frame {
"col": undefined,
"file": undefined,
"key": 23,
"line": undefined,
"name": "Main.CAF:main5",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 31,
"line": undefined,
"name": "Main.CAF:main7",
"selfWeight": 880,
"totalWeight": 880,
},
Frame {
"col": undefined,
"file": undefined,
"key": 25,
"line": undefined,
"name": "Main.CAF:main9",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:23:9-12",
"key": 33,
"line": undefined,
"name": "Main.CAF:main_maxN",
"selfWeight": 0,
"totalWeight": 32,
},
Frame {
"col": undefined,
"file": "src/Main.hs:23:9-35",
"key": 13,
"line": undefined,
"name": "Main.main.maxN",
"selfWeight": 32,
"totalWeight": 32,
},
Frame {
"col": undefined,
"file": "src/Main.hs:22:9",
"key": 34,
"line": undefined,
"name": "Main.CAF:main_n",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:22:9-14",
"key": 12,
"line": undefined,
"name": "Main.main.n",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:24:9-16",
"key": 32,
"line": undefined,
"name": "Main.CAF:main_stretchN",
"selfWeight": 0,
"totalWeight": 32,
},
Frame {
"col": undefined,
"file": "src/Main.hs:24:9-27",
"key": 14,
"line": undefined,
"name": "Main.main.stretchN",
"selfWeight": 32,
"totalWeight": 32,
},
Frame {
"col": undefined,
"file": "src/Main.hs:17:1-4",
"key": 36,
"line": undefined,
"name": "Main.CAF:minN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": "src/Main.hs:17:1-8",
"key": 9,
"line": undefined,
"name": "Main.minN",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 146,
"line": undefined,
"name": "GC.GC",
"selfWeight": 0,
"totalWeight": 0,
},
Frame {
"col": undefined,
"file": undefined,
"key": 147,
"line": undefined,
"name": "PROFILING.OVERHEAD_of",
"selfWeight": 2496,
"totalWeight": 2496,
},
Frame {
"col": undefined,
"file": undefined,
"key": 145,
"line": undefined,
"name": "SYSTEM.SYSTEM",
"selfWeight": 34736,
"totalWeight": 34736,
},
Frame {
"col": undefined,
"file": "src/Main.hs:35:26-54",
"key": 18,
"line": undefined,
"name": "Main.main.\\\\",
"selfWeight": 2224,
"totalWeight": 46672,
},
Frame {
"col": undefined,
"file": "src/Main.hs:19:1-56",
"key": 8,
"line": undefined,
"name": "Main.io",
"selfWeight": 67912,
"totalWeight": 67912,
},
Frame {
"col": undefined,
"file": "src/Main.hs:34:9-28",
"key": 17,
"line": undefined,
"name": "Main.main.vs",
"selfWeight": 0,
"totalWeight": 1891637344,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(42,1)-(45,38)",
"key": 11,
"line": undefined,
"name": "Main.depth",
"selfWeight": 1120,
"totalWeight": 1891637344,
},
Frame {
"col": undefined,
"file": "src/Main.hs:(49,1)-(52,31)",
"key": 7,
"line": undefined,
"name": "Main.sumT",
"selfWeight": 0,
"totalWeight": 1891636224,
},
Frame {
"col": undefined,
"file": "src/Main.hs:51:9-31",
"key": 6,
"line": undefined,
"name": "Main.sumT.a",
"selfWeight": 1397760,
"totalWeight": 945818112,
},
Frame {
"col": undefined,
"file": "src/Main.hs:52:9-31",
"key": 5,
"line": undefined,
"name": "Main.sumT.b",
"selfWeight": 1397760,
"totalWeight": 945818112,
},
Frame {
"col": undefined,
"file": "src/Main.hs:45:9-38",
"key": 10,
"line": undefined,
"name": "Main.depth.n",
"selfWeight": 0,
"totalWeight": 0,
},
],
"name": "binary-trees allocation",
"stacks": Array [
"MAIN.MAIN;GHC.Conc.Signal.CAF 640 B",
"MAIN.MAIN;GHC.IO.Encoding.CAF 2.70 KB",
"MAIN.MAIN;GHC.IO.Encoding.Iconv.CAF 200 B",
"MAIN.MAIN;GHC.IO.Handle.FD.CAF 33.89 KB",
"MAIN.MAIN;Text.Printf.CAF 528 B",
"MAIN.MAIN;Main.CAF:eta1_r6nI;Main.main;Main.check 2.00 MB",
"MAIN.MAIN;Main.CAF:eta1_r6nI;Main.main 32 B",
"MAIN.MAIN;Main.CAF:eta_r6nG 1.07 KB",
"MAIN.MAIN;Main.CAF:io1 1.84 KB",
"MAIN.MAIN;Main.CAF:lvl2_r6nH;Main.main;Main.main.long;Main.make 7.50 MB",
"MAIN.MAIN;Main.CAF:lvl2_r6nH;Main.main;Main.main.long 32 B",
"MAIN.MAIN;Main.CAF:lvl8_r6nP 520 B",
"MAIN.MAIN;Main.CAF:main1 16 B",
"MAIN.MAIN;Main.CAF:main11;Main.main;Main.main.c;Main.check 4.00 MB",
"MAIN.MAIN;Main.CAF:main11;Main.main;Main.main.c 32 B",
"MAIN.MAIN;Main.CAF:main12;Main.main;Main.main.c;Main.make 15.00 MB",
"MAIN.MAIN;Main.CAF:main12;Main.main;Main.main.c 32 B",
"MAIN.MAIN;Main.CAF:main7 880 B",
"MAIN.MAIN;Main.CAF:main_maxN;Main.main;Main.main.maxN 32 B",
"MAIN.MAIN;Main.CAF:main_stretchN;Main.main;Main.main.stretchN 32 B",
"MAIN.MAIN;PROFILING.OVERHEAD_of 2.44 KB",
"MAIN.MAIN;SYSTEM.SYSTEM 33.92 KB",
"MAIN.MAIN;Main.main;Main.main.\\\\;Main.io 43.41 KB",
"MAIN.MAIN;Main.main;Main.main.\\\\ 2.17 KB",
"MAIN.MAIN;Main.main;Main.io 22.91 KB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.check 190.67 MB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a;Main.make 710.00 MB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.a 1.33 MB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.check 190.67 MB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b;Main.make 710.00 MB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth;Main.sumT;Main.sumT.b 1.33 MB",
"MAIN.MAIN;Main.main;Main.main.vs;Main.depth 1.09 KB",
"MAIN.MAIN 648 B",
],
}
`;
exports[`importFromHaskell: indexToView 1`] = `0`;
exports[`importFromHaskell: profileGroup.name 1`] = `"binary-trees"`;

View File

@ -0,0 +1,5 @@
import {checkProfileSnapshot} from '../lib/test-utils'
test('importFromHaskell', async () => {
await checkProfileSnapshot('./sample/profiles/haskell/simple.prof')
})

98
src/import/haskell.ts Normal file
View File

@ -0,0 +1,98 @@
import {ProfileGroup, FrameInfo, CallTreeProfileBuilder} from '../lib/profile'
import {TimeFormatter, ByteFormatter} from '../lib/value-formatters'
// See https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/profiling.html#json-profile-format
// for information on the GHC profiler JSON output format.
interface CostCentre {
id: number
label: string
module: string
src_loc: string
is_caf: boolean
}
interface ProfileTree {
id: number
entries: number
alloc: number
ticks: number
children: ProfileTree[]
}
interface HaskellProfile {
program: string
arguments: string[]
rts_arguments: string[]
end_time: string
initial_capabilities: number
total_time: number
total_ticks: number
tick_interval: number
total_alloc: number
cost_centres: CostCentre[]
profile: ProfileTree
}
// The profiler already collapses recursion before output so using the JS stack here should be fine
function addToProfile(
tree: ProfileTree,
startVal: number,
profile: CallTreeProfileBuilder,
infos: Map<number, FrameInfo>,
attribute: (tree: ProfileTree) => number,
): number {
// If the expression never did anything we don't care about it
if (tree.ticks === 0 && tree.entries === 0 && tree.alloc === 0 && tree.children.length === 0)
return startVal
let curVal = startVal
let frameInfo = infos.get(tree.id)!
profile.enterFrame(frameInfo, curVal)
for (let child of tree.children) {
curVal = addToProfile(child, curVal, profile, infos, attribute)
}
curVal += attribute(tree)
profile.leaveFrame(frameInfo, curVal)
return curVal
}
export function importFromHaskell(haskellProfile: HaskellProfile): ProfileGroup {
const idToFrameInfo = new Map<number, FrameInfo>()
for (let centre of haskellProfile.cost_centres) {
const frameInfo: FrameInfo = {
key: centre.id,
name: `${centre.module}.${centre.label}`,
}
// Ignore things like <entire-module> and <no location info>
if (!centre.src_loc.startsWith('<')) {
// This also contains line and column information, but sometimes it contains ranges,
// and in varying formats, so it's a better experience just to leave it as is
frameInfo.file = centre.src_loc
}
idToFrameInfo.set(centre.id, frameInfo)
}
const timeProfile = new CallTreeProfileBuilder(haskellProfile.total_ticks)
addToProfile(haskellProfile.profile, 0, timeProfile, idToFrameInfo, tree => tree.ticks)
timeProfile.setValueFormatter(new TimeFormatter('milliseconds'))
timeProfile.setName(`${haskellProfile.program} time`)
const allocProfile = new CallTreeProfileBuilder(haskellProfile.total_ticks)
addToProfile(haskellProfile.profile, 0, allocProfile, idToFrameInfo, tree => tree.alloc)
allocProfile.setValueFormatter(new ByteFormatter())
allocProfile.setName(`${haskellProfile.program} allocation`)
return {
name: haskellProfile.program,
indexToView: 0,
profiles: [timeProfile.build(), allocProfile.build()],
}
}

View File

@ -9,6 +9,7 @@ import {importFromFirefox} from './firefox'
import {importSpeedscopeProfiles} from '../lib/file-format'
import {importFromV8ProfLog} from './v8proflog'
import {importFromLinuxPerf} from './linux-tools-perf'
import {importFromHaskell} from './haskell'
import {ProfileDataSource, TextProfileDataSource, MaybeCompressedDataReader} from './utils'
import {importAsPprofProfile} from './pprof'
import {decodeBase64} from '../lib/utils'
@ -136,6 +137,9 @@ async function _importProfileGroup(dataSource: ProfileDataSource): Promise<Profi
} else if ('head' in parsed && 'selfSize' in parsed['head']) {
console.log('Importing as Chrome Heap Profile')
return toGroup(importFromChromeHeapProfile(JSON.parse(contents)))
} else if ('rts_arguments' in parsed && 'initial_capabilities' in parsed) {
console.log('Importing as Haskell GHC JSON Profile')
return importFromHaskell(parsed)
}
} else {
// Format is not JSON