http.mx raw

   1  // Copyright 2023 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package traceviewer
   6  
   7  import (
   8  	"embed"
   9  	"fmt"
  10  	"html/template"
  11  	"net/http"
  12  	"bytes"
  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 []byte
 253  
 254  const (
 255  	ViewProc   ViewType = "proc"
 256  	ViewThread ViewType = "thread"
 257  )
 258  
 259  func (v View) URL(rangeIdx int) []byte {
 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      []byte
 268  	Start     int
 269  	End       int
 270  	StartTime int64
 271  	EndTime   int64
 272  }
 273  
 274  func (r Range) URL(viewType ViewType) []byte {
 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 := bytes.ReplaceAll(templTrace, "{{PARAMS}}", r.Form.Encode())
 285  		w.Write([]byte(html))
 286  	})
 287  }
 288  
 289  // https://chromium.googlesource.com/catapult/+/9508452e18f130c98499cb4c4f1e1efaedee8962/tracing/docs/embedding-trace-viewer.md
 290  // This is almost verbatim copy of https://chromium-review.googlesource.com/c/catapult/+/2062938/2/tracing/bin/index.html
 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  //go:embed static/trace_viewer_full.html static/webcomponents.min.js
 418  var staticContent embed.FS
 419  
 420  func StaticHandler() http.Handler {
 421  	return http.FileServer(http.FS(staticContent))
 422  }
 423