mirror of https://github.com/golang/go.git
411 lines
12 KiB
Go
411 lines
12 KiB
Go
// Copyright 2014 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 main
|
|
|
|
import (
|
|
"bufio"
|
|
"cmd/internal/browser"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"internal/trace"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"sync"
|
|
|
|
_ "net/http/pprof" // Required to use pprof
|
|
)
|
|
|
|
const usageMessage = "" +
|
|
`Usage of 'go tool trace':
|
|
Given a trace file produced by 'go test':
|
|
go test -trace=trace.out pkg
|
|
|
|
Open a web browser displaying trace:
|
|
go tool trace [flags] [pkg.test] trace.out
|
|
|
|
Generate a pprof-like profile from the trace:
|
|
go tool trace -pprof=TYPE [pkg.test] trace.out
|
|
|
|
[pkg.test] argument is required for traces produced by Go 1.6 and below.
|
|
Go 1.7 does not require the binary argument.
|
|
|
|
Supported profile types are:
|
|
- net: network blocking profile
|
|
- sync: synchronization blocking profile
|
|
- syscall: syscall blocking profile
|
|
- sched: scheduler latency profile
|
|
|
|
Flags:
|
|
-http=addr: HTTP service address (e.g., ':6060')
|
|
-pprof=type: print a pprof-like profile instead
|
|
-d: print debug info such as parsed events
|
|
|
|
Note that while the various profiles available when launching
|
|
'go tool trace' work on every browser, the trace viewer itself
|
|
(the 'view trace' page) comes from the Chrome/Chromium project
|
|
and is only actively tested on that browser.
|
|
`
|
|
|
|
var (
|
|
httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
|
|
pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead")
|
|
debugFlag = flag.Bool("d", false, "print debug information such as parsed events list")
|
|
|
|
// The binary file name, left here for serveSVGProfile.
|
|
programBinary string
|
|
traceFile string
|
|
)
|
|
|
|
func main() {
|
|
flag.Usage = func() {
|
|
fmt.Fprint(os.Stderr, usageMessage)
|
|
os.Exit(2)
|
|
}
|
|
flag.Parse()
|
|
|
|
// Go 1.7 traces embed symbol info and does not require the binary.
|
|
// But we optionally accept binary as first arg for Go 1.5 traces.
|
|
switch flag.NArg() {
|
|
case 1:
|
|
traceFile = flag.Arg(0)
|
|
case 2:
|
|
programBinary = flag.Arg(0)
|
|
traceFile = flag.Arg(1)
|
|
default:
|
|
flag.Usage()
|
|
}
|
|
|
|
var pprofFunc func(io.Writer, *http.Request) error
|
|
switch *pprofFlag {
|
|
case "net":
|
|
pprofFunc = pprofByGoroutine(computePprofIO)
|
|
case "sync":
|
|
pprofFunc = pprofByGoroutine(computePprofBlock)
|
|
case "syscall":
|
|
pprofFunc = pprofByGoroutine(computePprofSyscall)
|
|
case "sched":
|
|
pprofFunc = pprofByGoroutine(computePprofSched)
|
|
}
|
|
if pprofFunc != nil {
|
|
if err := pprofFunc(os.Stdout, &http.Request{}); err != nil {
|
|
dief("failed to generate pprof: %v\n", err)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
if *pprofFlag != "" {
|
|
dief("unknown pprof type %s\n", *pprofFlag)
|
|
}
|
|
|
|
ln, err := net.Listen("tcp", *httpFlag)
|
|
if err != nil {
|
|
dief("failed to create server socket: %v\n", err)
|
|
}
|
|
|
|
log.Print("Parsing trace...")
|
|
res, err := parseTrace()
|
|
if err != nil {
|
|
dief("%v\n", err)
|
|
}
|
|
|
|
if *debugFlag {
|
|
trace.Print(res.Events)
|
|
os.Exit(0)
|
|
}
|
|
reportMemoryUsage("after parsing trace")
|
|
debug.FreeOSMemory()
|
|
|
|
log.Print("Splitting trace...")
|
|
ranges = splitTrace(res)
|
|
reportMemoryUsage("after spliting trace")
|
|
debug.FreeOSMemory()
|
|
|
|
addr := "http://" + ln.Addr().String()
|
|
log.Printf("Opening browser. Trace viewer is listening on %s", addr)
|
|
browser.Open(addr)
|
|
|
|
// Start http server.
|
|
http.HandleFunc("/", httpMain)
|
|
err = http.Serve(ln, nil)
|
|
dief("failed to start http server: %v\n", err)
|
|
}
|
|
|
|
var ranges []Range
|
|
|
|
var loader struct {
|
|
once sync.Once
|
|
res trace.ParseResult
|
|
err error
|
|
}
|
|
|
|
// parseEvents is a compatibility wrapper that returns only
|
|
// the Events part of trace.ParseResult returned by parseTrace.
|
|
func parseEvents() ([]*trace.Event, error) {
|
|
res, err := parseTrace()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.Events, err
|
|
}
|
|
|
|
func parseTrace() (trace.ParseResult, error) {
|
|
loader.once.Do(func() {
|
|
tracef, err := os.Open(traceFile)
|
|
if err != nil {
|
|
loader.err = fmt.Errorf("failed to open trace file: %v", err)
|
|
return
|
|
}
|
|
defer tracef.Close()
|
|
|
|
// Parse and symbolize.
|
|
res, err := trace.Parse(bufio.NewReader(tracef), programBinary)
|
|
if err != nil {
|
|
loader.err = fmt.Errorf("failed to parse trace: %v", err)
|
|
return
|
|
}
|
|
loader.res = res
|
|
})
|
|
return loader.res, loader.err
|
|
}
|
|
|
|
// httpMain serves the starting page.
|
|
func httpMain(w http.ResponseWriter, r *http.Request) {
|
|
if err := templMain.Execute(w, ranges); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
var templMain = template.Must(template.New("").Parse(`
|
|
<html>
|
|
<style>
|
|
/* 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%; }
|
|
</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>
|
|
{{if $}}
|
|
<p>
|
|
Large traces are split into multiple sections of equal data size
|
|
(not duration) to avoid overwhelming the visualizer.
|
|
</p>
|
|
<ul>
|
|
{{range $e := $}}
|
|
<li><a href="{{$e.URL}}">View trace ({{$e.Name}})</a></li>
|
|
{{end}}
|
|
</ul>
|
|
{{else}}
|
|
<ul>
|
|
<li><a href="/trace">View trace</a></li>
|
|
</ul>
|
|
{{end}}
|
|
<p>
|
|
This view displays a timeline for each of the GOMAXPROCS logical
|
|
processors, showing which goroutine (if any) was running on that
|
|
logical processor at each moment.
|
|
|
|
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 blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)</li>
|
|
<li><a href="/sched">Scheduler latency profile</a> (<a href="/sche?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>
|
|
`))
|
|
|
|
func dief(msg string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, msg, args...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var debugMemoryUsage bool
|
|
|
|
func init() {
|
|
v := os.Getenv("DEBUG_MEMORY_USAGE")
|
|
debugMemoryUsage = v != ""
|
|
}
|
|
|
|
func reportMemoryUsage(msg string) {
|
|
if !debugMemoryUsage {
|
|
return
|
|
}
|
|
var s runtime.MemStats
|
|
runtime.ReadMemStats(&s)
|
|
w := os.Stderr
|
|
fmt.Fprintf(w, "%s\n", msg)
|
|
fmt.Fprintf(w, " Alloc:\t%d Bytes\n", s.Alloc)
|
|
fmt.Fprintf(w, " Sys:\t%d Bytes\n", s.Sys)
|
|
fmt.Fprintf(w, " HeapReleased:\t%d Bytes\n", s.HeapReleased)
|
|
fmt.Fprintf(w, " HeapSys:\t%d Bytes\n", s.HeapSys)
|
|
fmt.Fprintf(w, " HeapInUse:\t%d Bytes\n", s.HeapInuse)
|
|
fmt.Fprintf(w, " HeapAlloc:\t%d Bytes\n", s.HeapAlloc)
|
|
var dummy string
|
|
fmt.Printf("Enter to continue...")
|
|
fmt.Scanf("%s", &dummy)
|
|
}
|