Repository: google/pprof (go)

Issue:
Sometimes very large flamegraphs don't render
Please answer these questions before submitting your issue. Thanks!

### What version of pprof are you using?

Git Commit: `20978b51388db0648809a2c5cc88b494c7945ec1` (https://github.com/google/pprof/commit/20978b51388db0648809a2c5cc88b494c7945ec1)

We are generating data for PProf through the julia package https://github.com/JuliaPerf/PProf.jl

### What operating system and processor architecture are you using?

MacOS Intel x86

### What did you do?

Very large profile data sometimes does not render in the flamegraphs view. It will render okay in the Graph view, but not in the Flamegraph view.



<img width="1440" alt="Screen Shot 2021-11-02 at 2 45 53 PM" src="https://user-images.githubusercontent.com/1582097/139946812-6b736a31-c0ea-4796-959d-73d1b4129fa4.png">


<img width="1440" alt="Screen Shot 2021-11-02 at 2 45 33 PM" src="https://user-images.githubusercontent.com/1582097/139946831-4e48e4d0-4e12-409d-ad05-fbd649673ea5.png">


### What did you expect to see?

I expected to see the standard flamegraph view.

### What did you see instead?

Nothing 😢 

-----

I'm sorry that I don't have a .pb.gz to upload, because the profile i currently have is from our internal, closed source codebase.

I'll try to find a reproducer profile that I can upload here with the issue. In the meantime, maybe this report is already useful.

Thanks! :)



Pull request:
allow rendering big flame graphs by avoiding stack overflow in JS parser
Fixes #663 

## Before

Very large flame graphs fail to render, with a mysterious error in the console: `Maximum call stack size exceeded`, without a stack trace.

e.g. [big-profile.pb.gz](https://github.com/google/pprof/files/8094230/big-profile.pb.gz):

```
pprof -http localhost:9876 ~/Downloads/big-profile.pb.gz
```

<img width="1193" alt="image" src="https://user-images.githubusercontent.com/7341/154617713-ed27c92d-45c8-442a-80ef-57c26c184536.png">

I suspected that the very large array in `var data = …` might be a problem, so I inserted `console.log`s before and after it. To my surprise, neither log showed up. This, combined with the missing stack trace, indicated to me that the stack overflow was not in javascript execution, but in parsing the javascript itself.

## After

The same flame graph now renders.

<img width="1904" alt="image" src="https://user-images.githubusercontent.com/7341/154621134-1a4af1b0-be5d-4c2d-88de-3962228ca440.png">

The fix is to use the browser's JSON parser (via `JSON.parse`) instead of its JS parser. Due to the JSON grammar being simpler, the browser can handle larger strings.

Presumably JSON parsing has a limit too, past which you'd have to chunk the string or something; but this should be good enough for profiles that are quite big.

## Drawbacks

Go's `template.JS` seems to escape the string in a very verbose way. Before, it came out as

```js
var data = {"n":"root", ...}
```

Now it's:

```js
var data = JSON.parse("{\u0022n\u0022:\u0022root\u0022…")
```

It works, but it kind of blows up the size. Not sure what an idiomatic Go way of fixing this is.

cc @NHDaly 

Patch:
diff --git a/internal/driver/webhtml.go b/internal/driver/webhtml.go
index 63df668..9f7a87d 100644
--- a/internal/driver/webhtml.go
+++ b/internal/driver/webhtml.go
@@ -1329,7 +1329,9 @@ function viewer(baseUrl, nodes) {
   <script>viewer(new URL(window.location.href), {{.Nodes}});</script>
   <script>{{template "d3flamegraphscript" .}}</script>
   <script>
-    var data = {{.FlameGraph}};
+    {{- /* Deserialize as JSON instead of a JS object literal because the browser's
+           JSON parser can handle larger payloads than its JS parser. */ -}}
+    var data = JSON.parse("{{.FlameGraph}}");
 
     var width = document.getElementById('chart').clientWidth;
 


Test patch:
diff --git a/internal/driver/webui_test.go b/internal/driver/webui_test.go
index 81a68ff..58bcd3d 100644
--- a/internal/driver/webui_test.go
+++ b/internal/driver/webui_test.go
@@ -88,8 +88,8 @@ func TestWebInterface(t *testing.T) {
 		{"/flamegraph", []string{
 			"File: testbin",
 			// Check profile frame JSON is included.
-			"\"n\":\"root\"",
-			"\"n\":\"F1\"",
+			`\\u0022n\\u0022:\\u0022root\\u0022`,
+			`\\u0022n\\u0022:\\u0022F1\\u0022`,
 			// Check minified d3-flame-graph JS is included.
 			`flamegraph:\(\)=>`,
 			// Check d3-flame-graph CSS is included.