1
2
3
4
5 package main
6
7 import (
8 "cmd/internal/browser"
9 "cmd/internal/telemetry/counter"
10 "cmp"
11 "flag"
12 "fmt"
13 "internal/trace"
14 "internal/trace/event"
15 "internal/trace/raw"
16 "internal/trace/traceviewer"
17 "io"
18 "log"
19 "net"
20 "net/http"
21 _ "net/http/pprof"
22 "os"
23 "slices"
24 "sync/atomic"
25 "text/tabwriter"
26 "time"
27 )
28
29 const usageMessage = "" +
30 `Usage of 'go tool trace':
31 Given a trace file produced by 'go test':
32 go test -trace=trace.out pkg
33
34 Open a web browser displaying trace:
35 go tool trace [flags] [pkg.test] trace.out
36
37 Generate a pprof-like profile from the trace:
38 go tool trace -pprof=TYPE [pkg.test] trace.out
39
40 [pkg.test] argument is required for traces produced by Go 1.6 and below.
41 Go 1.7 does not require the binary argument.
42
43 Supported profile types are:
44 - net: network blocking profile
45 - sync: synchronization blocking profile
46 - syscall: syscall blocking profile
47 - sched: scheduler latency profile
48
49 Flags:
50 -http=addr: HTTP service address (e.g., ':6060')
51 -pprof=type: print a pprof-like profile instead
52 -d=mode: print debug info and exit (modes: wire, parsed, footprint)
53
54 Note that while the various profiles available when launching
55 'go tool trace' work on every browser, the trace viewer itself
56 (the 'view trace' page) comes from the Chrome/Chromium project
57 and is only actively tested on that browser.
58 `
59
60 var (
61 httpFlag = flag.String("http", "localhost:0", "HTTP service address (e.g., ':6060')")
62 pprofFlag = flag.String("pprof", "", "print a pprof-like profile instead")
63 debugFlag = flag.String("d", "", "print debug info and exit (modes: wire, parsed, footprint)")
64
65
66 programBinary string
67 traceFile string
68 )
69
70 func main() {
71 counter.Open()
72 flag.Usage = func() {
73 fmt.Fprint(os.Stderr, usageMessage)
74 os.Exit(2)
75 }
76 flag.Parse()
77 counter.Inc("trace/invocations")
78 counter.CountFlags("trace/flag:", *flag.CommandLine)
79
80
81
82 switch flag.NArg() {
83 case 1:
84 traceFile = flag.Arg(0)
85 case 2:
86 programBinary = flag.Arg(0)
87 traceFile = flag.Arg(1)
88 default:
89 flag.Usage()
90 }
91
92 tracef, err := os.Open(traceFile)
93 if err != nil {
94 logAndDie(fmt.Errorf("failed to read trace file: %w", err))
95 }
96 defer tracef.Close()
97
98
99 fi, err := tracef.Stat()
100 if err != nil {
101 logAndDie(fmt.Errorf("failed to stat trace file: %v", err))
102 }
103 traceSize := fi.Size()
104
105
106 if *pprofFlag != "" {
107 parsed, err := parseTrace(tracef, traceSize)
108 if err != nil {
109 logAndDie(err)
110 }
111 var f traceviewer.ProfileFunc
112 switch *pprofFlag {
113 case "net":
114 f = pprofByGoroutine(computePprofIO(), parsed)
115 case "sync":
116 f = pprofByGoroutine(computePprofBlock(), parsed)
117 case "syscall":
118 f = pprofByGoroutine(computePprofSyscall(), parsed)
119 case "sched":
120 f = pprofByGoroutine(computePprofSched(), parsed)
121 default:
122 logAndDie(fmt.Errorf("unknown pprof type %s\n", *pprofFlag))
123 }
124 records, err := f(&http.Request{})
125 if err != nil {
126 logAndDie(fmt.Errorf("failed to generate pprof: %v\n", err))
127 }
128 if err := traceviewer.BuildProfile(records).Write(os.Stdout); err != nil {
129 logAndDie(fmt.Errorf("failed to generate pprof: %v\n", err))
130 }
131 logAndDie(nil)
132 }
133
134
135 if *debugFlag != "" {
136 switch *debugFlag {
137 case "parsed":
138 logAndDie(debugProcessedEvents(tracef))
139 case "wire":
140 logAndDie(debugRawEvents(tracef))
141 case "footprint":
142 logAndDie(debugEventsFootprint(tracef))
143 default:
144 logAndDie(fmt.Errorf("invalid debug mode %s, want one of: parsed, wire, footprint", *debugFlag))
145 }
146 }
147
148 ln, err := net.Listen("tcp", *httpFlag)
149 if err != nil {
150 logAndDie(fmt.Errorf("failed to create server socket: %w", err))
151 }
152 addr := "http://" + ln.Addr().String()
153
154 log.Print("Preparing trace for viewer...")
155 parsed, err := parseTraceInteractive(tracef, traceSize)
156 if err != nil {
157 logAndDie(err)
158 }
159
160
161 tracef.Close()
162
163
164 if parsed.err != nil {
165 log.Printf("Encountered error, but able to proceed. Error: %v", parsed.err)
166
167 lost := parsed.size - parsed.valid
168 pct := float64(lost) / float64(parsed.size) * 100
169 log.Printf("Lost %.2f%% of the latest trace data due to error (%s of %s)", pct, byteCount(lost), byteCount(parsed.size))
170 }
171
172 log.Print("Splitting trace for viewer...")
173 ranges, err := splitTrace(parsed)
174 if err != nil {
175 logAndDie(err)
176 }
177
178 log.Printf("Opening browser. Trace viewer is listening on %s", addr)
179 browser.Open(addr)
180
181 mutatorUtil := func(flags trace.UtilFlags) ([][]trace.MutatorUtil, error) {
182 return trace.MutatorUtilizationV2(parsed.events, flags), nil
183 }
184
185 mux := http.NewServeMux()
186
187
188 mux.Handle("/", traceviewer.MainHandler([]traceviewer.View{
189 {Type: traceviewer.ViewProc, Ranges: ranges},
190
191
192
193 {Type: traceviewer.ViewThread, Ranges: ranges},
194 }))
195
196
197 mux.Handle("/trace", traceviewer.TraceHandler())
198 mux.Handle("/jsontrace", JSONTraceHandler(parsed))
199 mux.Handle("/static/", traceviewer.StaticHandler())
200
201
202 mux.HandleFunc("/goroutines", GoroutinesHandlerFunc(parsed.summary.Goroutines))
203 mux.HandleFunc("/goroutine", GoroutineHandler(parsed.summary.Goroutines))
204
205
206 mux.HandleFunc("/mmu", traceviewer.MMUHandlerFunc(ranges, mutatorUtil))
207
208
209 mux.HandleFunc("/io", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofIO(), parsed)))
210 mux.HandleFunc("/block", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofBlock(), parsed)))
211 mux.HandleFunc("/syscall", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSyscall(), parsed)))
212 mux.HandleFunc("/sched", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSched(), parsed)))
213
214
215 mux.HandleFunc("/regionio", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofIO(), parsed)))
216 mux.HandleFunc("/regionblock", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofBlock(), parsed)))
217 mux.HandleFunc("/regionsyscall", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSyscall(), parsed)))
218 mux.HandleFunc("/regionsched", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSched(), parsed)))
219
220
221 mux.HandleFunc("/userregions", UserRegionsHandlerFunc(parsed))
222 mux.HandleFunc("/userregion", UserRegionHandlerFunc(parsed))
223
224
225 mux.HandleFunc("/usertasks", UserTasksHandlerFunc(parsed))
226 mux.HandleFunc("/usertask", UserTaskHandlerFunc(parsed))
227
228 err = http.Serve(ln, mux)
229 logAndDie(fmt.Errorf("failed to start http server: %w", err))
230 }
231
232 func logAndDie(err error) {
233 if err == nil {
234 os.Exit(0)
235 }
236 fmt.Fprintf(os.Stderr, "%s\n", err)
237 os.Exit(1)
238 }
239
240 func parseTraceInteractive(tr io.Reader, size int64) (parsed *parsedTrace, err error) {
241 done := make(chan struct{})
242 cr := countingReader{r: tr}
243 go func() {
244 parsed, err = parseTrace(&cr, size)
245 done <- struct{}{}
246 }()
247 ticker := time.NewTicker(5 * time.Second)
248 progressLoop:
249 for {
250 select {
251 case <-ticker.C:
252 case <-done:
253 ticker.Stop()
254 break progressLoop
255 }
256 progress := cr.bytesRead.Load()
257 pct := float64(progress) / float64(size) * 100
258 log.Printf("%s of %s (%.1f%%) processed...", byteCount(progress), byteCount(size), pct)
259 }
260 return
261 }
262
263 type parsedTrace struct {
264 events []trace.Event
265 summary *trace.Summary
266 size, valid int64
267 err error
268 }
269
270 func parseTrace(rr io.Reader, size int64) (*parsedTrace, error) {
271
272 cr := countingReader{r: rr}
273 r, err := trace.NewReader(&cr)
274 if err != nil {
275 return nil, fmt.Errorf("failed to create trace reader: %w", err)
276 }
277
278
279 s := trace.NewSummarizer()
280 t := new(parsedTrace)
281 var validBytes int64
282 var validEvents int
283 for {
284 ev, err := r.ReadEvent()
285 if err == io.EOF {
286 validBytes = cr.bytesRead.Load()
287 validEvents = len(t.events)
288 break
289 }
290 if err != nil {
291 t.err = err
292 break
293 }
294 t.events = append(t.events, ev)
295 s.Event(&t.events[len(t.events)-1])
296
297 if ev.Kind() == trace.EventSync {
298 validBytes = cr.bytesRead.Load()
299 validEvents = len(t.events)
300 }
301 }
302
303
304 if validEvents == 0 {
305 return nil, fmt.Errorf("failed to parse any useful part of the trace: %v", t.err)
306 }
307
308
309 t.summary = s.Finalize()
310 t.valid = validBytes
311 t.size = size
312 t.events = t.events[:validEvents]
313 return t, nil
314 }
315
316 func (t *parsedTrace) startTime() trace.Time {
317 return t.events[0].Time()
318 }
319
320 func (t *parsedTrace) endTime() trace.Time {
321 return t.events[len(t.events)-1].Time()
322 }
323
324
325
326 func splitTrace(parsed *parsedTrace) ([]traceviewer.Range, error) {
327
328
329 s, c := traceviewer.SplittingTraceConsumer(100 << 20)
330 if err := generateTrace(parsed, defaultGenOpts(), c); err != nil {
331 return nil, err
332 }
333 return s.Ranges, nil
334 }
335
336 func debugProcessedEvents(trc io.Reader) error {
337 tr, err := trace.NewReader(trc)
338 if err != nil {
339 return err
340 }
341 for {
342 ev, err := tr.ReadEvent()
343 if err == io.EOF {
344 return nil
345 } else if err != nil {
346 return err
347 }
348 fmt.Println(ev.String())
349 }
350 }
351
352 func debugRawEvents(trc io.Reader) error {
353 rr, err := raw.NewReader(trc)
354 if err != nil {
355 return err
356 }
357 for {
358 ev, err := rr.ReadEvent()
359 if err == io.EOF {
360 return nil
361 } else if err != nil {
362 return err
363 }
364 fmt.Println(ev.String())
365 }
366 }
367
368 func debugEventsFootprint(trc io.Reader) error {
369 cr := countingReader{r: trc}
370 tr, err := raw.NewReader(&cr)
371 if err != nil {
372 return err
373 }
374 type eventStats struct {
375 typ event.Type
376 count int
377 bytes int
378 }
379 var stats [256]eventStats
380 for i := range stats {
381 stats[i].typ = event.Type(i)
382 }
383 eventsRead := 0
384 for {
385 e, err := tr.ReadEvent()
386 if err == io.EOF {
387 break
388 }
389 if err != nil {
390 return err
391 }
392 s := &stats[e.Ev]
393 s.count++
394 s.bytes += e.EncodedSize()
395 eventsRead++
396 }
397 slices.SortFunc(stats[:], func(a, b eventStats) int {
398 return cmp.Compare(b.bytes, a.bytes)
399 })
400 specs := tr.Version().Specs()
401 w := tabwriter.NewWriter(os.Stdout, 3, 8, 2, ' ', 0)
402 fmt.Fprintf(w, "Event\tBytes\t%%\tCount\t%%\n")
403 fmt.Fprintf(w, "-\t-\t-\t-\t-\n")
404 for i := range stats {
405 stat := &stats[i]
406 name := ""
407 if int(stat.typ) >= len(specs) {
408 name = fmt.Sprintf("<unknown (%d)>", stat.typ)
409 } else {
410 name = specs[stat.typ].Name
411 }
412 bytesPct := float64(stat.bytes) / float64(cr.bytesRead.Load()) * 100
413 countPct := float64(stat.count) / float64(eventsRead) * 100
414 fmt.Fprintf(w, "%s\t%d\t%.2f%%\t%d\t%.2f%%\n", name, stat.bytes, bytesPct, stat.count, countPct)
415 }
416 w.Flush()
417 return nil
418 }
419
420 type countingReader struct {
421 r io.Reader
422 bytesRead atomic.Int64
423 }
424
425 func (c *countingReader) Read(buf []byte) (n int, err error) {
426 n, err = c.r.Read(buf)
427 c.bytesRead.Add(int64(n))
428 return n, err
429 }
430
431 type byteCount int64
432
433 func (b byteCount) String() string {
434 var suffix string
435 var divisor int64
436 switch {
437 case b < 1<<10:
438 suffix = "B"
439 divisor = 1
440 case b < 1<<20:
441 suffix = "KiB"
442 divisor = 1 << 10
443 case b < 1<<30:
444 suffix = "MiB"
445 divisor = 1 << 20
446 case b < 1<<40:
447 suffix = "GiB"
448 divisor = 1 << 30
449 }
450 if divisor == 1 {
451 return fmt.Sprintf("%d %s", b, suffix)
452 }
453 return fmt.Sprintf("%.1f %s", float64(b)/float64(divisor), suffix)
454 }
455
View as plain text