...

Source file src/internal/trace/traceviewer/http.go

Documentation: internal/trace/traceviewer

     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  	"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  // 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  

View as plain text