Skip to content

Commit 748952f

Browse files
mknyszekgopherbot
authored andcommittedNov 21, 2023
cmd/trace: add almost full support for v2 traces in the trace viewer
This change refactors the cmd/trace package and adds most of the support for v2 traces. The following features of note are missing in this CL and will be implemented in follow-up CLs: - The focustask filter for the trace viewer - The taskid filter for the trace viewer - The goid filter for the trace viewer - Pprof profiles - The MMU graph - The goroutine analysis pages - The task analysis pages - The region analysis pages This CL makes one notable change to the trace CLI: it makes the -d flag accept an integer to set the debug mode. For old traces -d != 0 works just like -d. For new traces -d=1 means the high-level events and -d=2 means the low-level events. Thanks to Felix Geisendörfer ([email protected]) for doing a lot of work on this CL; I picked this up from him and got a massive headstart as a result. For #60773. For #63960. Change-Id: I3626e22473227c5980134a85f1bb6a845f567b1b Reviewed-on: https://go-review.googlesource.com/c/go/+/542218 Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> TryBot-Bypass: Michael Knyszek <[email protected]>
1 parent 3548f9a commit 748952f

26 files changed

+7196
-907
lines changed
 

‎src/cmd/go/internal/trace/trace.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
package trace
66

77
import (
8-
"cmd/internal/traceviewer"
98
"context"
109
"encoding/json"
1110
"errors"
11+
"internal/trace/traceviewer/format"
1212
"os"
1313
"strings"
1414
"sync/atomic"
@@ -47,7 +47,7 @@ func StartSpan(ctx context.Context, name string) (context.Context, *Span) {
4747
return ctx, nil
4848
}
4949
childSpan := &Span{t: tc.t, name: name, tid: tc.tid, start: time.Now()}
50-
tc.t.writeEvent(&traceviewer.Event{
50+
tc.t.writeEvent(&format.Event{
5151
Name: childSpan.name,
5252
Time: float64(childSpan.start.UnixNano()) / float64(time.Microsecond),
5353
TID: childSpan.tid,
@@ -77,15 +77,15 @@ func Flow(ctx context.Context, from *Span, to *Span) {
7777
}
7878

7979
id := tc.t.getNextFlowID()
80-
tc.t.writeEvent(&traceviewer.Event{
80+
tc.t.writeEvent(&format.Event{
8181
Name: from.name + " -> " + to.name,
8282
Category: "flow",
8383
ID: id,
8484
Time: float64(from.end.UnixNano()) / float64(time.Microsecond),
8585
Phase: phaseFlowStart,
8686
TID: from.tid,
8787
})
88-
tc.t.writeEvent(&traceviewer.Event{
88+
tc.t.writeEvent(&format.Event{
8989
Name: from.name + " -> " + to.name,
9090
Category: "flow", // TODO(matloob): Add Category to Flow?
9191
ID: id,
@@ -110,7 +110,7 @@ func (s *Span) Done() {
110110
return
111111
}
112112
s.end = time.Now()
113-
s.t.writeEvent(&traceviewer.Event{
113+
s.t.writeEvent(&format.Event{
114114
Name: s.name,
115115
Time: float64(s.end.UnixNano()) / float64(time.Microsecond),
116116
TID: s.tid,
@@ -125,7 +125,7 @@ type tracer struct {
125125
nextFlowID atomic.Uint64
126126
}
127127

128-
func (t *tracer) writeEvent(ev *traceviewer.Event) error {
128+
func (t *tracer) writeEvent(ev *format.Event) error {
129129
f := <-t.file
130130
defer func() { t.file <- f }()
131131
var err error

‎src/cmd/trace/main.go

Lines changed: 29 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ package main
77
import (
88
"bufio"
99
"cmd/internal/browser"
10+
cmdv2 "cmd/trace/v2"
1011
"flag"
1112
"fmt"
12-
"html/template"
1313
"internal/trace"
14+
"internal/trace/traceviewer"
1415
"io"
1516
"log"
1617
"net"
@@ -46,7 +47,7 @@ Supported profile types are:
4647
Flags:
4748
-http=addr: HTTP service address (e.g., ':6060')
4849
-pprof=type: print a pprof-like profile instead
49-
-d: print debug info such as parsed events
50+
-d=int: print debug info such as parsed events (1 for high-level, 2 for low-level)
5051
5152
Note that while the various profiles available when launching
5253
'go tool trace' work on every browser, the trace viewer itself
@@ -57,7 +58,7 @@ and is only actively tested on that browser.
5758
var (
5859
httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
5960
pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead")
60-
debugFlag = flag.Bool("d", false, "print debug information such as parsed events list")
61+
debugFlag = flag.Int("d", 0, "print debug information (1 for basic debug info, 2 for lower-level info)")
6162

6263
// The binary file name, left here for serveSVGProfile.
6364
programBinary string
@@ -83,6 +84,13 @@ func main() {
8384
flag.Usage()
8485
}
8586

87+
if isTraceV2(traceFile) {
88+
if err := cmdv2.Main(traceFile, *httpFlag, *pprofFlag, *debugFlag); err != nil {
89+
dief("%s\n", err)
90+
}
91+
return
92+
}
93+
8694
var pprofFunc func(io.Writer, *http.Request) error
8795
switch *pprofFlag {
8896
case "net":
@@ -115,7 +123,7 @@ func main() {
115123
dief("%v\n", err)
116124
}
117125

118-
if *debugFlag {
126+
if *debugFlag != 0 {
119127
trace.Print(res.Events)
120128
os.Exit(0)
121129
}
@@ -132,12 +140,27 @@ func main() {
132140
browser.Open(addr)
133141

134142
// Start http server.
135-
http.HandleFunc("/", httpMain)
143+
http.Handle("/", traceviewer.MainHandler(ranges))
136144
err = http.Serve(ln, nil)
137145
dief("failed to start http server: %v\n", err)
138146
}
139147

140-
var ranges []Range
148+
// isTraceV2 returns true if filename holds a v2 trace.
149+
func isTraceV2(filename string) bool {
150+
file, err := os.Open(filename)
151+
if err != nil {
152+
return false
153+
}
154+
defer file.Close()
155+
156+
ver, _, err := trace.ReadVersion(file)
157+
if err != nil {
158+
return false
159+
}
160+
return ver >= 1022
161+
}
162+
163+
var ranges []traceviewer.Range
141164

142165
var loader struct {
143166
once sync.Once
@@ -175,209 +198,6 @@ func parseTrace() (trace.ParseResult, error) {
175198
return loader.res, loader.err
176199
}
177200

178-
// httpMain serves the starting page.
179-
func httpMain(w http.ResponseWriter, r *http.Request) {
180-
if err := templMain.Execute(w, ranges); err != nil {
181-
http.Error(w, err.Error(), http.StatusInternalServerError)
182-
return
183-
}
184-
}
185-
186-
var templMain = template.Must(template.New("").Parse(`
187-
<html>
188-
<style>
189-
/* See https://github.com/golang/pkgsite/blob/master/static/shared/typography/typography.css */
190-
body {
191-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
192-
font-size: 1rem;
193-
line-height: normal;
194-
max-width: 9in;
195-
margin: 1em;
196-
}
197-
h1 { font-size: 1.5rem; }
198-
h2 { font-size: 1.375rem; }
199-
h1,h2 {
200-
font-weight: 600;
201-
line-height: 1.25em;
202-
word-break: break-word;
203-
}
204-
p { color: grey85; font-size:85%; }
205-
</style>
206-
<body>
207-
<h1>cmd/trace: the Go trace event viewer</h1>
208-
<p>
209-
This web server provides various visualizations of an event log gathered during
210-
the execution of a Go program that uses the <a href='https://pkg.go.dev/runtime/trace'>runtime/trace</a> package.
211-
</p>
212-
213-
<h2>Event timelines for running goroutines</h2>
214-
{{if $}}
215-
<p>
216-
Large traces are split into multiple sections of equal data size
217-
(not duration) to avoid overwhelming the visualizer.
218-
</p>
219-
<ul>
220-
{{range $e := $}}
221-
<li><a href="{{$e.URL}}">View trace ({{$e.Name}})</a></li>
222-
{{end}}
223-
</ul>
224-
{{else}}
225-
<ul>
226-
<li><a href="/trace">View trace</a></li>
227-
</ul>
228-
{{end}}
229-
<p>
230-
This view displays a timeline for each of the GOMAXPROCS logical
231-
processors, showing which goroutine (if any) was running on that
232-
logical processor at each moment.
233-
234-
Each goroutine has an identifying number (e.g. G123), main function,
235-
and color.
236-
237-
A colored bar represents an uninterrupted span of execution.
238-
239-
Execution of a goroutine may migrate from one logical processor to another,
240-
causing a single colored bar to be horizontally continuous but
241-
vertically displaced.
242-
</p>
243-
<p>
244-
Clicking on a span reveals information about it, such as its
245-
duration, its causal predecessors and successors, and the stack trace
246-
at the final moment when it yielded the logical processor, for example
247-
because it made a system call or tried to acquire a mutex.
248-
249-
Directly underneath each bar, a smaller bar or more commonly a fine
250-
vertical line indicates an event occurring during its execution.
251-
Some of these are related to garbage collection; most indicate that
252-
a goroutine yielded its logical processor but then immediately resumed execution
253-
on the same logical processor. Clicking on the event displays the stack trace
254-
at the moment it occurred.
255-
</p>
256-
<p>
257-
The causal relationships between spans of goroutine execution
258-
can be displayed by clicking the Flow Events button at the top.
259-
</p>
260-
<p>
261-
At the top ("STATS"), there are three additional timelines that
262-
display statistical information.
263-
264-
"Goroutines" is a time series of the count of existing goroutines;
265-
clicking on it displays their breakdown by state at that moment:
266-
running, runnable, or waiting.
267-
268-
"Heap" is a time series of the amount of heap memory allocated (in orange)
269-
and (in green) the allocation limit at which the next GC cycle will begin.
270-
271-
"Threads" shows the number of kernel threads in existence: there is
272-
always one kernel thread per logical processor, and additional threads
273-
are created for calls to non-Go code such as a system call or a
274-
function written in C.
275-
</p>
276-
<p>
277-
Above the event trace for the first logical processor are
278-
traces for various runtime-internal events.
279-
280-
The "GC" bar shows when the garbage collector is running, and in which stage.
281-
Garbage collection may temporarily affect all the logical processors
282-
and the other metrics.
283-
284-
The "Network", "Timers", and "Syscalls" traces indicate events in
285-
the runtime that cause goroutines to wake up.
286-
</p>
287-
<p>
288-
The visualization allows you to navigate events at scales ranging from several
289-
seconds to a handful of nanoseconds.
290-
291-
Consult the documentation for the Chromium <a href='https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/'>Trace Event Profiling Tool<a/>
292-
for help navigating the view.
293-
</p>
294-
295-
<ul>
296-
<li><a href="/goroutines">Goroutine analysis</a></li>
297-
</ul>
298-
<p>
299-
This view displays information about each set of goroutines that
300-
shares the same main function.
301-
302-
Clicking on a main function shows links to the four types of
303-
blocking profile (see below) applied to that subset of goroutines.
304-
305-
It also shows a table of specific goroutine instances, with various
306-
execution statistics and a link to the event timeline for each one.
307-
308-
The timeline displays only the selected goroutine and any others it
309-
interacts with via block/unblock events. (The timeline is
310-
goroutine-oriented rather than logical processor-oriented.)
311-
</p>
312-
313-
<h2>Profiles</h2>
314-
<p>
315-
Each link below displays a global profile in zoomable graph form as
316-
produced by <a href='https://go.dev/blog/pprof'>pprof</a>'s "web" command.
317-
318-
In addition there is a link to download the profile for offline
319-
analysis with pprof.
320-
321-
All four profiles represent causes of delay that prevent a goroutine
322-
from running on a logical processor: because it was waiting for the network,
323-
for a synchronization operation on a mutex or channel, for a system call,
324-
or for a logical processor to become available.
325-
</p>
326-
<ul>
327-
<li><a href="/io">Network blocking profile</a> (<a href="/io?raw=1" download="io.profile">⬇</a>)</li>
328-
<li><a href="/block">Synchronization blocking profile</a> (<a href="/block?raw=1" download="block.profile">⬇</a>)</li>
329-
<li><a href="/syscall">Syscall blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)</li>
330-
<li><a href="/sched">Scheduler latency profile</a> (<a href="/sche?raw=1" download="sched.profile">⬇</a>)</li>
331-
</ul>
332-
333-
<h2>User-defined tasks and regions</h2>
334-
<p>
335-
The trace API allows a target program to annotate a <a
336-
href='https://pkg.go.dev/runtime/trace#Region'>region</a> of code
337-
within a goroutine, such as a key function, so that its performance
338-
can be analyzed.
339-
340-
<a href='https://pkg.go.dev/runtime/trace#Log'>Log events</a> may be
341-
associated with a region to record progress and relevant values.
342-
343-
The API also allows annotation of higher-level
344-
<a href='https://pkg.go.dev/runtime/trace#Task'>tasks</a>,
345-
which may involve work across many goroutines.
346-
</p>
347-
<p>
348-
The links below display, for each region and task, a histogram of its execution times.
349-
350-
Each histogram bucket contains a sample trace that records the
351-
sequence of events such as goroutine creations, log events, and
352-
subregion start/end times.
353-
354-
For each task, you can click through to a logical-processor or
355-
goroutine-oriented view showing the tasks and regions on the
356-
timeline.
357-
358-
Such information may help uncover which steps in a region are
359-
unexpectedly slow, or reveal relationships between the data values
360-
logged in a request and its running time.
361-
</p>
362-
<ul>
363-
<li><a href="/usertasks">User-defined tasks</a></li>
364-
<li><a href="/userregions">User-defined regions</a></li>
365-
</ul>
366-
367-
<h2>Garbage collection metrics</h2>
368-
<ul>
369-
<li><a href="/mmu">Minimum mutator utilization</a></li>
370-
</ul>
371-
<p>
372-
This chart indicates the maximum GC pause time (the largest x value
373-
for which y is zero), and more generally, the fraction of time that
374-
the processors are available to application goroutines ("mutators"),
375-
for any time window of a specified size, in the worst case.
376-
</p>
377-
</body>
378-
</html>
379-
`))
380-
381201
func dief(msg string, args ...any) {
382202
fmt.Fprintf(os.Stderr, msg, args...)
383203
os.Exit(1)

‎src/cmd/trace/mmu.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"encoding/json"
3030
"fmt"
3131
"internal/trace"
32+
"internal/trace/traceviewer"
3233
"log"
3334
"math"
3435
"net/http"
@@ -393,7 +394,7 @@ type linkedUtilWindow struct {
393394

394395
func newLinkedUtilWindow(ui trace.UtilWindow, window time.Duration) linkedUtilWindow {
395396
// Find the range containing this window.
396-
var r Range
397+
var r traceviewer.Range
397398
for _, r = range ranges {
398399
if r.EndTime > ui.Time {
399400
break

0 commit comments

Comments
 (0)
Please sign in to comment.