github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/trace/v2/main.go (about) 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 trace 6 7 import ( 8 "fmt" 9 "io" 10 "log" 11 "net" 12 "net/http" 13 "os" 14 15 "github.com/go-asm/go/trace" 16 "github.com/go-asm/go/trace/traceviewer" 17 tracev2 "github.com/go-asm/go/trace/v2" 18 19 "github.com/go-asm/go/trace/v2/raw" 20 21 "github.com/go-asm/go/cmd/browser" 22 ) 23 24 // Main is the main function for cmd/trace v2. 25 func Main(traceFile, httpAddr, pprof string, debug int) error { 26 tracef, err := os.Open(traceFile) 27 if err != nil { 28 return fmt.Errorf("failed to read trace file: %w", err) 29 } 30 defer tracef.Close() 31 32 // Debug flags. 33 switch debug { 34 case 1: 35 return debugProcessedEvents(tracef) 36 case 2: 37 return debugRawEvents(tracef) 38 } 39 40 ln, err := net.Listen("tcp", httpAddr) 41 if err != nil { 42 return fmt.Errorf("failed to create server socket: %w", err) 43 } 44 addr := "http://" + ln.Addr().String() 45 46 log.Print("Preparing trace for viewer...") 47 parsed, err := parseTrace(tracef) 48 if err != nil { 49 return err 50 } 51 // N.B. tracef not needed after this point. 52 // We might double-close, but that's fine; we ignore the error. 53 tracef.Close() 54 55 log.Print("Splitting trace for viewer...") 56 ranges, err := splitTrace(parsed) 57 if err != nil { 58 return err 59 } 60 61 log.Printf("Opening browser. Trace viewer is listening on %s", addr) 62 browser.Open(addr) 63 64 mutatorUtil := func(flags trace.UtilFlags) ([][]trace.MutatorUtil, error) { 65 return trace.MutatorUtilizationV2(parsed.events, flags), nil 66 } 67 68 mux := http.NewServeMux() 69 70 // Main endpoint. 71 mux.Handle("/", traceviewer.MainHandler([]traceviewer.View{ 72 {Type: traceviewer.ViewProc, Ranges: ranges}, 73 // N.B. Use the same ranges for threads. It takes a long time to compute 74 // the split a second time, but the makeup of the events are similar enough 75 // that this is still a good split. 76 {Type: traceviewer.ViewThread, Ranges: ranges}, 77 })) 78 79 // Catapult handlers. 80 mux.Handle("/trace", traceviewer.TraceHandler()) 81 mux.Handle("/jsontrace", JSONTraceHandler(parsed)) 82 mux.Handle("/static/", traceviewer.StaticHandler()) 83 84 // Goroutines handlers. 85 mux.HandleFunc("/goroutines", GoroutinesHandlerFunc(parsed.summary.Goroutines)) 86 mux.HandleFunc("/goroutine", GoroutineHandler(parsed.summary.Goroutines)) 87 88 // MMU handler. 89 mux.HandleFunc("/mmu", traceviewer.MMUHandlerFunc(ranges, mutatorUtil)) 90 91 // Basic pprof endpoints. 92 mux.HandleFunc("/io", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofIO(), parsed))) 93 mux.HandleFunc("/block", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofBlock(), parsed))) 94 mux.HandleFunc("/syscall", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSyscall(), parsed))) 95 mux.HandleFunc("/sched", traceviewer.SVGProfileHandlerFunc(pprofByGoroutine(computePprofSched(), parsed))) 96 97 // Region-based pprof endpoints. 98 mux.HandleFunc("/regionio", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofIO(), parsed))) 99 mux.HandleFunc("/regionblock", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofBlock(), parsed))) 100 mux.HandleFunc("/regionsyscall", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSyscall(), parsed))) 101 mux.HandleFunc("/regionsched", traceviewer.SVGProfileHandlerFunc(pprofByRegion(computePprofSched(), parsed))) 102 103 // Region endpoints. 104 mux.HandleFunc("/userregions", UserRegionsHandlerFunc(parsed)) 105 mux.HandleFunc("/userregion", UserRegionHandlerFunc(parsed)) 106 107 // Task endpoints. 108 mux.HandleFunc("/usertasks", UserTasksHandlerFunc(parsed)) 109 mux.HandleFunc("/usertask", UserTaskHandlerFunc(parsed)) 110 111 err = http.Serve(ln, mux) 112 return fmt.Errorf("failed to start http server: %w", err) 113 } 114 115 type parsedTrace struct { 116 events []tracev2.Event 117 summary *trace.Summary 118 } 119 120 func parseTrace(tr io.Reader) (*parsedTrace, error) { 121 r, err := tracev2.NewReader(tr) 122 if err != nil { 123 return nil, fmt.Errorf("failed to create trace reader: %w", err) 124 } 125 s := trace.NewSummarizer() 126 t := new(parsedTrace) 127 for { 128 ev, err := r.ReadEvent() 129 if err == io.EOF { 130 break 131 } else if err != nil { 132 return nil, fmt.Errorf("failed to read event: %w", err) 133 } 134 t.events = append(t.events, ev) 135 s.Event(&t.events[len(t.events)-1]) 136 } 137 t.summary = s.Finalize() 138 return t, nil 139 } 140 141 func (t *parsedTrace) startTime() tracev2.Time { 142 return t.events[0].Time() 143 } 144 145 func (t *parsedTrace) endTime() tracev2.Time { 146 return t.events[len(t.events)-1].Time() 147 } 148 149 // splitTrace splits the trace into a number of ranges, each resulting in approx 100 MiB of 150 // json output (the trace viewer can hardly handle more). 151 func splitTrace(parsed *parsedTrace) ([]traceviewer.Range, error) { 152 // TODO(mknyszek): Split traces by generation by doing a quick first pass over the 153 // trace to identify all the generation boundaries. 154 s, c := traceviewer.SplittingTraceConsumer(100 << 20) // 100 MiB 155 if err := generateTrace(parsed, defaultGenOpts(), c); err != nil { 156 return nil, err 157 } 158 return s.Ranges, nil 159 } 160 161 func debugProcessedEvents(trace io.Reader) error { 162 tr, err := tracev2.NewReader(trace) 163 if err != nil { 164 return err 165 } 166 for { 167 ev, err := tr.ReadEvent() 168 if err == io.EOF { 169 return nil 170 } else if err != nil { 171 return err 172 } 173 fmt.Println(ev.String()) 174 } 175 } 176 177 func debugRawEvents(trace io.Reader) error { 178 rr, err := raw.NewReader(trace) 179 if err != nil { 180 return err 181 } 182 for { 183 ev, err := rr.ReadEvent() 184 if err == io.EOF { 185 return nil 186 } else if err != nil { 187 return err 188 } 189 fmt.Println(ev.String()) 190 } 191 }