summaryrefslogtreecommitdiffstats
path: root/src/internal/trace/traceviewer/http.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal/trace/traceviewer/http.go')
-rw-r--r--src/internal/trace/traceviewer/http.go422
1 files changed, 422 insertions, 0 deletions
diff --git a/src/internal/trace/traceviewer/http.go b/src/internal/trace/traceviewer/http.go
new file mode 100644
index 0000000..5258db0
--- /dev/null
+++ b/src/internal/trace/traceviewer/http.go
@@ -0,0 +1,422 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package traceviewer
+
+import (
+ "embed"
+ "fmt"
+ "html/template"
+ "net/http"
+ "strings"
+)
+
+func MainHandler(views []View) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ if err := templMain.Execute(w, views); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ })
+}
+
+const CommonStyle = `
+/* See https://github.com/golang/pkgsite/blob/master/static/shared/typography/typography.css */
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
+ font-size: 1rem;
+ line-height: normal;
+ max-width: 9in;
+ margin: 1em;
+}
+h1 { font-size: 1.5rem; }
+h2 { font-size: 1.375rem; }
+h1,h2 {
+ font-weight: 600;
+ line-height: 1.25em;
+ word-break: break-word;
+}
+p { color: grey85; font-size:85%; }
+code,
+pre,
+textarea.code {
+ font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
+ font-size: 0.875rem;
+ line-height: 1.5em;
+}
+
+pre,
+textarea.code {
+ background-color: var(--color-background-accented);
+ border: var(--border);
+ border-radius: var(--border-radius);
+ color: var(--color-text);
+ overflow-x: auto;
+ padding: 0.625rem;
+ tab-size: 4;
+ white-space: pre;
+}
+`
+
+var templMain = template.Must(template.New("").Parse(`
+<html>
+<style>` + CommonStyle + `</style>
+<body>
+<h1>cmd/trace: the Go trace event viewer</h1>
+<p>
+ This web server provides various visualizations of an event log gathered during
+ the execution of a Go program that uses the <a href='https://pkg.go.dev/runtime/trace'>runtime/trace</a> package.
+</p>
+
+<h2>Event timelines for running goroutines</h2>
+{{range $i, $view := $}}
+{{if $view.Ranges}}
+{{if eq $i 0}}
+<p>
+ Large traces are split into multiple sections of equal data size
+ (not duration) to avoid overwhelming the visualizer.
+</p>
+{{end}}
+<ul>
+ {{range $index, $e := $view.Ranges}}
+ <li><a href="{{$view.URL $index}}">View trace by {{$view.Type}} ({{$e.Name}})</a></li>
+ {{end}}
+</ul>
+{{else}}
+<ul>
+ <li><a href="{{$view.URL -1}}">View trace by {{$view.Type}}</a></li>
+</ul>
+{{end}}
+{{end}}
+<p>
+ This view displays a series of timelines for a type of resource.
+ The "by proc" view consists of a timeline for each of the GOMAXPROCS
+ logical processors, showing which goroutine (if any) was running on that
+ logical processor at each moment.
+ The "by thread" view (if available) consists of a similar timeline for each
+ OS thread.
+
+ Each goroutine has an identifying number (e.g. G123), main function,
+ and color.
+
+ A colored bar represents an uninterrupted span of execution.
+
+ Execution of a goroutine may migrate from one logical processor to another,
+ causing a single colored bar to be horizontally continuous but
+ vertically displaced.
+</p>
+<p>
+ Clicking on a span reveals information about it, such as its
+ duration, its causal predecessors and successors, and the stack trace
+ at the final moment when it yielded the logical processor, for example
+ because it made a system call or tried to acquire a mutex.
+
+ Directly underneath each bar, a smaller bar or more commonly a fine
+ vertical line indicates an event occurring during its execution.
+ Some of these are related to garbage collection; most indicate that
+ a goroutine yielded its logical processor but then immediately resumed execution
+ on the same logical processor. Clicking on the event displays the stack trace
+ at the moment it occurred.
+</p>
+<p>
+ The causal relationships between spans of goroutine execution
+ can be displayed by clicking the Flow Events button at the top.
+</p>
+<p>
+ At the top ("STATS"), there are three additional timelines that
+ display statistical information.
+
+ "Goroutines" is a time series of the count of existing goroutines;
+ clicking on it displays their breakdown by state at that moment:
+ running, runnable, or waiting.
+
+ "Heap" is a time series of the amount of heap memory allocated (in orange)
+ and (in green) the allocation limit at which the next GC cycle will begin.
+
+ "Threads" shows the number of kernel threads in existence: there is
+ always one kernel thread per logical processor, and additional threads
+ are created for calls to non-Go code such as a system call or a
+ function written in C.
+</p>
+<p>
+ Above the event trace for the first logical processor are
+ traces for various runtime-internal events.
+
+ The "GC" bar shows when the garbage collector is running, and in which stage.
+ Garbage collection may temporarily affect all the logical processors
+ and the other metrics.
+
+ The "Network", "Timers", and "Syscalls" traces indicate events in
+ the runtime that cause goroutines to wake up.
+</p>
+<p>
+ The visualization allows you to navigate events at scales ranging from several
+ seconds to a handful of nanoseconds.
+
+ Consult the documentation for the Chromium <a href='https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/'>Trace Event Profiling Tool<a/>
+ for help navigating the view.
+</p>
+
+<ul>
+<li><a href="/goroutines">Goroutine analysis</a></li>
+</ul>
+<p>
+ This view displays information about each set of goroutines that
+ shares the same main function.
+
+ Clicking on a main function shows links to the four types of
+ blocking profile (see below) applied to that subset of goroutines.
+
+ It also shows a table of specific goroutine instances, with various
+ execution statistics and a link to the event timeline for each one.
+
+ The timeline displays only the selected goroutine and any others it
+ interacts with via block/unblock events. (The timeline is
+ goroutine-oriented rather than logical processor-oriented.)
+</p>
+
+<h2>Profiles</h2>
+<p>
+ Each link below displays a global profile in zoomable graph form as
+ produced by <a href='https://go.dev/blog/pprof'>pprof</a>'s "web" command.
+
+ In addition there is a link to download the profile for offline
+ analysis with pprof.
+
+ All four profiles represent causes of delay that prevent a goroutine
+ from running on a logical processor: because it was waiting for the network,
+ for a synchronization operation on a mutex or channel, for a system call,
+ or for a logical processor to become available.
+</p>
+<ul>
+<li><a href="/io">Network blocking profile</a> (<a href="/io?raw=1" download="io.profile">⬇</a>)</li>
+<li><a href="/block">Synchronization blocking profile</a> (<a href="/block?raw=1" download="block.profile">⬇</a>)</li>
+<li><a href="/syscall">Syscall profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)</li>
+<li><a href="/sched">Scheduler latency profile</a> (<a href="/sched?raw=1" download="sched.profile">⬇</a>)</li>
+</ul>
+
+<h2>User-defined tasks and regions</h2>
+<p>
+ The trace API allows a target program to annotate a <a
+ href='https://pkg.go.dev/runtime/trace#Region'>region</a> of code
+ within a goroutine, such as a key function, so that its performance
+ can be analyzed.
+
+ <a href='https://pkg.go.dev/runtime/trace#Log'>Log events</a> may be
+ associated with a region to record progress and relevant values.
+
+ The API also allows annotation of higher-level
+ <a href='https://pkg.go.dev/runtime/trace#Task'>tasks</a>,
+ which may involve work across many goroutines.
+</p>
+<p>
+ The links below display, for each region and task, a histogram of its execution times.
+
+ Each histogram bucket contains a sample trace that records the
+ sequence of events such as goroutine creations, log events, and
+ subregion start/end times.
+
+ For each task, you can click through to a logical-processor or
+ goroutine-oriented view showing the tasks and regions on the
+ timeline.
+
+ Such information may help uncover which steps in a region are
+ unexpectedly slow, or reveal relationships between the data values
+ logged in a request and its running time.
+</p>
+<ul>
+<li><a href="/usertasks">User-defined tasks</a></li>
+<li><a href="/userregions">User-defined regions</a></li>
+</ul>
+
+<h2>Garbage collection metrics</h2>
+<ul>
+<li><a href="/mmu">Minimum mutator utilization</a></li>
+</ul>
+<p>
+ This chart indicates the maximum GC pause time (the largest x value
+ for which y is zero), and more generally, the fraction of time that
+ the processors are available to application goroutines ("mutators"),
+ for any time window of a specified size, in the worst case.
+</p>
+</body>
+</html>
+`))
+
+type View struct {
+ Type ViewType
+ Ranges []Range
+}
+
+type ViewType string
+
+const (
+ ViewProc ViewType = "proc"
+ ViewThread ViewType = "thread"
+)
+
+func (v View) URL(rangeIdx int) string {
+ if rangeIdx < 0 {
+ return fmt.Sprintf("/trace?view=%s", v.Type)
+ }
+ return v.Ranges[rangeIdx].URL(v.Type)
+}
+
+type Range struct {
+ Name string
+ Start int
+ End int
+ StartTime int64
+ EndTime int64
+}
+
+func (r Range) URL(viewType ViewType) string {
+ return fmt.Sprintf("/trace?view=%s&start=%d&end=%d", viewType, r.Start, r.End)
+}
+
+func TraceHandler() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ html := strings.ReplaceAll(templTrace, "{{PARAMS}}", r.Form.Encode())
+ w.Write([]byte(html))
+ })
+}
+
+// https://chromium.googlesource.com/catapult/+/9508452e18f130c98499cb4c4f1e1efaedee8962/tracing/docs/embedding-trace-viewer.md
+// This is almost verbatim copy of https://chromium-review.googlesource.com/c/catapult/+/2062938/2/tracing/bin/index.html
+var templTrace = `
+<html>
+<head>
+<script src="/static/webcomponents.min.js"></script>
+<script>
+'use strict';
+
+function onTraceViewerImportFail() {
+ document.addEventListener('DOMContentLoaded', function() {
+ document.body.textContent =
+ '/static/trace_viewer_full.html is missing. File a bug in https://golang.org/issue';
+ });
+}
+</script>
+
+<link rel="import" href="/static/trace_viewer_full.html"
+ onerror="onTraceViewerImportFail(event)">
+
+<style type="text/css">
+ html, body {
+ box-sizing: border-box;
+ overflow: hidden;
+ margin: 0px;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ }
+ #trace-viewer {
+ width: 100%;
+ height: 100%;
+ }
+ #trace-viewer:focus {
+ outline: none;
+ }
+</style>
+<script>
+'use strict';
+(function() {
+ var viewer;
+ var url;
+ var model;
+
+ function load() {
+ var req = new XMLHttpRequest();
+ var isBinary = /[.]gz$/.test(url) || /[.]zip$/.test(url);
+ req.overrideMimeType('text/plain; charset=x-user-defined');
+ req.open('GET', url, true);
+ if (isBinary)
+ req.responseType = 'arraybuffer';
+
+ req.onreadystatechange = function(event) {
+ if (req.readyState !== 4)
+ return;
+
+ window.setTimeout(function() {
+ if (req.status === 200)
+ onResult(isBinary ? req.response : req.responseText);
+ else
+ onResultFail(req.status);
+ }, 0);
+ };
+ req.send(null);
+ }
+
+ function onResultFail(err) {
+ var overlay = new tr.ui.b.Overlay();
+ overlay.textContent = err + ': ' + url + ' could not be loaded';
+ overlay.title = 'Failed to fetch data';
+ overlay.visible = true;
+ }
+
+ function onResult(result) {
+ model = new tr.Model();
+ var opts = new tr.importer.ImportOptions();
+ opts.shiftWorldToZero = false;
+ var i = new tr.importer.Import(model, opts);
+ var p = i.importTracesWithProgressDialog([result]);
+ p.then(onModelLoaded, onImportFail);
+ }
+
+ function onModelLoaded() {
+ viewer.model = model;
+ viewer.viewTitle = "trace";
+
+ if (!model || model.bounds.isEmpty)
+ return;
+ var sel = window.location.hash.substr(1);
+ if (sel === '')
+ return;
+ var parts = sel.split(':');
+ var range = new (tr.b.Range || tr.b.math.Range)();
+ range.addValue(parseFloat(parts[0]));
+ range.addValue(parseFloat(parts[1]));
+ viewer.trackView.viewport.interestRange.set(range);
+ }
+
+ function onImportFail(err) {
+ var overlay = new tr.ui.b.Overlay();
+ overlay.textContent = tr.b.normalizeException(err).message;
+ overlay.title = 'Import error';
+ overlay.visible = true;
+ }
+
+ document.addEventListener('WebComponentsReady', function() {
+ var container = document.createElement('track-view-container');
+ container.id = 'track_view_container';
+
+ viewer = document.createElement('tr-ui-timeline-view');
+ viewer.track_view_container = container;
+ Polymer.dom(viewer).appendChild(container);
+
+ viewer.id = 'trace-viewer';
+ viewer.globalMode = true;
+ Polymer.dom(document.body).appendChild(viewer);
+
+ url = '/jsontrace?{{PARAMS}}';
+ load();
+ });
+}());
+</script>
+</head>
+<body>
+</body>
+</html>
+`
+
+//go:embed static/trace_viewer_full.html static/webcomponents.min.js
+var staticContent embed.FS
+
+func StaticHandler() http.Handler {
+ return http.FileServer(http.FS(staticContent))
+}