1
2
3
4
5 package traceviewer
6
7 import (
8 "embed"
9 "fmt"
10 "html/template"
11 "net/http"
12 "strings"
13 )
14
15 func MainHandler(views []View) http.Handler {
16 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
17 if err := templMain.Execute(w, views); err != nil {
18 http.Error(w, err.Error(), http.StatusInternalServerError)
19 return
20 }
21 })
22 }
23
24 const CommonStyle = `
25 /* See https://github.com/golang/pkgsite/blob/master/static/shared/typography/typography.css */
26 body {
27 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
28 font-size: 1rem;
29 line-height: normal;
30 max-width: 9in;
31 margin: 1em;
32 }
33 h1 { font-size: 1.5rem; }
34 h2 { font-size: 1.375rem; }
35 h1,h2 {
36 font-weight: 600;
37 line-height: 1.25em;
38 word-break: break-word;
39 }
40 p { color: grey85; font-size:85%; }
41 code,
42 pre,
43 textarea.code {
44 font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
45 font-size: 0.875rem;
46 line-height: 1.5em;
47 }
48
49 pre,
50 textarea.code {
51 background-color: var(--color-background-accented);
52 border: var(--border);
53 border-radius: var(--border-radius);
54 color: var(--color-text);
55 overflow-x: auto;
56 padding: 0.625rem;
57 tab-size: 4;
58 white-space: pre;
59 }
60 `
61
62 var templMain = template.Must(template.New("").Parse(`
63 <html>
64 <style>` + CommonStyle + `</style>
65 <body>
66 <h1>cmd/trace: the Go trace event viewer</h1>
67 <p>
68 This web server provides various visualizations of an event log gathered during
69 the execution of a Go program that uses the <a href='https://pkg.go.dev/runtime/trace'>runtime/trace</a> package.
70 </p>
71
72 <h2>Event timelines for running goroutines</h2>
73 {{range $i, $view := $}}
74 {{if $view.Ranges}}
75 {{if eq $i 0}}
76 <p>
77 Large traces are split into multiple sections of equal data size
78 (not duration) to avoid overwhelming the visualizer.
79 </p>
80 {{end}}
81 <ul>
82 {{range $index, $e := $view.Ranges}}
83 <li><a href="{{$view.URL $index}}">View trace by {{$view.Type}} ({{$e.Name}})</a></li>
84 {{end}}
85 </ul>
86 {{else}}
87 <ul>
88 <li><a href="{{$view.URL -1}}">View trace by {{$view.Type}}</a></li>
89 </ul>
90 {{end}}
91 {{end}}
92 <p>
93 This view displays a series of timelines for a type of resource.
94 The "by proc" view consists of a timeline for each of the GOMAXPROCS
95 logical processors, showing which goroutine (if any) was running on that
96 logical processor at each moment.
97 The "by thread" view (if available) consists of a similar timeline for each
98 OS thread.
99
100 Each goroutine has an identifying number (e.g. G123), main function,
101 and color.
102
103 A colored bar represents an uninterrupted span of execution.
104
105 Execution of a goroutine may migrate from one logical processor to another,
106 causing a single colored bar to be horizontally continuous but
107 vertically displaced.
108 </p>
109 <p>
110 Clicking on a span reveals information about it, such as its
111 duration, its causal predecessors and successors, and the stack trace
112 at the final moment when it yielded the logical processor, for example
113 because it made a system call or tried to acquire a mutex.
114
115 Directly underneath each bar, a smaller bar or more commonly a fine
116 vertical line indicates an event occurring during its execution.
117 Some of these are related to garbage collection; most indicate that
118 a goroutine yielded its logical processor but then immediately resumed execution
119 on the same logical processor. Clicking on the event displays the stack trace
120 at the moment it occurred.
121 </p>
122 <p>
123 The causal relationships between spans of goroutine execution
124 can be displayed by clicking the Flow Events button at the top.
125 </p>
126 <p>
127 At the top ("STATS"), there are three additional timelines that
128 display statistical information.
129
130 "Goroutines" is a time series of the count of existing goroutines;
131 clicking on it displays their breakdown by state at that moment:
132 running, runnable, or waiting.
133
134 "Heap" is a time series of the amount of heap memory allocated (in orange)
135 and (in green) the allocation limit at which the next GC cycle will begin.
136
137 "Threads" shows the number of kernel threads in existence: there is
138 always one kernel thread per logical processor, and additional threads
139 are created for calls to non-Go code such as a system call or a
140 function written in C.
141 </p>
142 <p>
143 Above the event trace for the first logical processor are
144 traces for various runtime-internal events.
145
146 The "GC" bar shows when the garbage collector is running, and in which stage.
147 Garbage collection may temporarily affect all the logical processors
148 and the other metrics.
149
150 The "Network", "Timers", and "Syscalls" traces indicate events in
151 the runtime that cause goroutines to wake up.
152 </p>
153 <p>
154 The visualization allows you to navigate events at scales ranging from several
155 seconds to a handful of nanoseconds.
156
157 Consult the documentation for the Chromium <a href='https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/'>Trace Event Profiling Tool<a/>
158 for help navigating the view.
159 </p>
160
161 <ul>
162 <li><a href="/goroutines">Goroutine analysis</a></li>
163 </ul>
164 <p>
165 This view displays information about each set of goroutines that
166 shares the same main function.
167
168 Clicking on a main function shows links to the four types of
169 blocking profile (see below) applied to that subset of goroutines.
170
171 It also shows a table of specific goroutine instances, with various
172 execution statistics and a link to the event timeline for each one.
173
174 The timeline displays only the selected goroutine and any others it
175 interacts with via block/unblock events. (The timeline is
176 goroutine-oriented rather than logical processor-oriented.)
177 </p>
178
179 <h2>Profiles</h2>
180 <p>
181 Each link below displays a global profile in zoomable graph form as
182 produced by <a href='https://go.dev/blog/pprof'>pprof</a>'s "web" command.
183
184 In addition there is a link to download the profile for offline
185 analysis with pprof.
186
187 All four profiles represent causes of delay that prevent a goroutine
188 from running on a logical processor: because it was waiting for the network,
189 for a synchronization operation on a mutex or channel, for a system call,
190 or for a logical processor to become available.
191 </p>
192 <ul>
193 <li><a href="/io">Network blocking profile</a> (<a href="/io?raw=1" download="io.profile">⬇</a>)</li>
194 <li><a href="/block">Synchronization blocking profile</a> (<a href="/block?raw=1" download="block.profile">⬇</a>)</li>
195 <li><a href="/syscall">Syscall profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)</li>
196 <li><a href="/sched">Scheduler latency profile</a> (<a href="/sched?raw=1" download="sched.profile">⬇</a>)</li>
197 </ul>
198
199 <h2>User-defined tasks and regions</h2>
200 <p>
201 The trace API allows a target program to annotate a <a
202 href='https://pkg.go.dev/runtime/trace#Region'>region</a> of code
203 within a goroutine, such as a key function, so that its performance
204 can be analyzed.
205
206 <a href='https://pkg.go.dev/runtime/trace#Log'>Log events</a> may be
207 associated with a region to record progress and relevant values.
208
209 The API also allows annotation of higher-level
210 <a href='https://pkg.go.dev/runtime/trace#Task'>tasks</a>,
211 which may involve work across many goroutines.
212 </p>
213 <p>
214 The links below display, for each region and task, a histogram of its execution times.
215
216 Each histogram bucket contains a sample trace that records the
217 sequence of events such as goroutine creations, log events, and
218 subregion start/end times.
219
220 For each task, you can click through to a logical-processor or
221 goroutine-oriented view showing the tasks and regions on the
222 timeline.
223
224 Such information may help uncover which steps in a region are
225 unexpectedly slow, or reveal relationships between the data values
226 logged in a request and its running time.
227 </p>
228 <ul>
229 <li><a href="/usertasks">User-defined tasks</a></li>
230 <li><a href="/userregions">User-defined regions</a></li>
231 </ul>
232
233 <h2>Garbage collection metrics</h2>
234 <ul>
235 <li><a href="/mmu">Minimum mutator utilization</a></li>
236 </ul>
237 <p>
238 This chart indicates the maximum GC pause time (the largest x value
239 for which y is zero), and more generally, the fraction of time that
240 the processors are available to application goroutines ("mutators"),
241 for any time window of a specified size, in the worst case.
242 </p>
243 </body>
244 </html>
245 `))
246
247 type View struct {
248 Type ViewType
249 Ranges []Range
250 }
251
252 type ViewType string
253
254 const (
255 ViewProc ViewType = "proc"
256 ViewThread ViewType = "thread"
257 )
258
259 func (v View) URL(rangeIdx int) string {
260 if rangeIdx < 0 {
261 return fmt.Sprintf("/trace?view=%s", v.Type)
262 }
263 return v.Ranges[rangeIdx].URL(v.Type)
264 }
265
266 type Range struct {
267 Name string
268 Start int
269 End int
270 StartTime int64
271 EndTime int64
272 }
273
274 func (r Range) URL(viewType ViewType) string {
275 return fmt.Sprintf("/trace?view=%s&start=%d&end=%d", viewType, r.Start, r.End)
276 }
277
278 func TraceHandler() http.Handler {
279 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
280 if err := r.ParseForm(); err != nil {
281 http.Error(w, err.Error(), http.StatusInternalServerError)
282 return
283 }
284 html := strings.ReplaceAll(templTrace, "{{PARAMS}}", r.Form.Encode())
285 w.Write([]byte(html))
286 })
287 }
288
289
290
291 var templTrace = `
292 <html>
293 <head>
294 <script src="/static/webcomponents.min.js"></script>
295 <script>
296 'use strict';
297
298 function onTraceViewerImportFail() {
299 document.addEventListener('DOMContentLoaded', function() {
300 document.body.textContent =
301 '/static/trace_viewer_full.html is missing. File a bug in https://golang.org/issue';
302 });
303 }
304 </script>
305
306 <link rel="import" href="/static/trace_viewer_full.html"
307 onerror="onTraceViewerImportFail(event)">
308
309 <style type="text/css">
310 html, body {
311 box-sizing: border-box;
312 overflow: hidden;
313 margin: 0px;
314 padding: 0;
315 width: 100%;
316 height: 100%;
317 }
318 #trace-viewer {
319 width: 100%;
320 height: 100%;
321 }
322 #trace-viewer:focus {
323 outline: none;
324 }
325 </style>
326 <script>
327 'use strict';
328 (function() {
329 var viewer;
330 var url;
331 var model;
332
333 function load() {
334 var req = new XMLHttpRequest();
335 var isBinary = /[.]gz$/.test(url) || /[.]zip$/.test(url);
336 req.overrideMimeType('text/plain; charset=x-user-defined');
337 req.open('GET', url, true);
338 if (isBinary)
339 req.responseType = 'arraybuffer';
340
341 req.onreadystatechange = function(event) {
342 if (req.readyState !== 4)
343 return;
344
345 window.setTimeout(function() {
346 if (req.status === 200)
347 onResult(isBinary ? req.response : req.responseText);
348 else
349 onResultFail(req.status);
350 }, 0);
351 };
352 req.send(null);
353 }
354
355 function onResultFail(err) {
356 var overlay = new tr.ui.b.Overlay();
357 overlay.textContent = err + ': ' + url + ' could not be loaded';
358 overlay.title = 'Failed to fetch data';
359 overlay.visible = true;
360 }
361
362 function onResult(result) {
363 model = new tr.Model();
364 var opts = new tr.importer.ImportOptions();
365 opts.shiftWorldToZero = false;
366 var i = new tr.importer.Import(model, opts);
367 var p = i.importTracesWithProgressDialog([result]);
368 p.then(onModelLoaded, onImportFail);
369 }
370
371 function onModelLoaded() {
372 viewer.model = model;
373 viewer.viewTitle = "trace";
374
375 if (!model || model.bounds.isEmpty)
376 return;
377 var sel = window.location.hash.substr(1);
378 if (sel === '')
379 return;
380 var parts = sel.split(':');
381 var range = new (tr.b.Range || tr.b.math.Range)();
382 range.addValue(parseFloat(parts[0]));
383 range.addValue(parseFloat(parts[1]));
384 viewer.trackView.viewport.interestRange.set(range);
385 }
386
387 function onImportFail(err) {
388 var overlay = new tr.ui.b.Overlay();
389 overlay.textContent = tr.b.normalizeException(err).message;
390 overlay.title = 'Import error';
391 overlay.visible = true;
392 }
393
394 document.addEventListener('WebComponentsReady', function() {
395 var container = document.createElement('track-view-container');
396 container.id = 'track_view_container';
397
398 viewer = document.createElement('tr-ui-timeline-view');
399 viewer.track_view_container = container;
400 Polymer.dom(viewer).appendChild(container);
401
402 viewer.id = 'trace-viewer';
403 viewer.globalMode = true;
404 Polymer.dom(document.body).appendChild(viewer);
405
406 url = '/jsontrace?{{PARAMS}}';
407 load();
408 });
409 }());
410 </script>
411 </head>
412 <body>
413 </body>
414 </html>
415 `
416
417
418 var staticContent embed.FS
419
420 func StaticHandler() http.Handler {
421 return http.FileServer(http.FS(staticContent))
422 }
423
View as plain text