github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/trace/v2/jsontrace.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 "cmp" 9 "log" 10 "math" 11 "net/http" 12 "slices" 13 "strconv" 14 "time" 15 16 "github.com/go-asm/go/trace" 17 "github.com/go-asm/go/trace/traceviewer" 18 tracev2 "github.com/go-asm/go/trace/v2" 19 ) 20 21 func JSONTraceHandler(parsed *parsedTrace) http.Handler { 22 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 opts := defaultGenOpts() 24 25 switch r.FormValue("view") { 26 case "thread": 27 opts.mode = traceviewer.ModeThreadOriented 28 } 29 if goids := r.FormValue("goid"); goids != "" { 30 // Render trace focused on a particular goroutine. 31 32 id, err := strconv.ParseUint(goids, 10, 64) 33 if err != nil { 34 log.Printf("failed to parse goid parameter %q: %v", goids, err) 35 return 36 } 37 goid := tracev2.GoID(id) 38 g, ok := parsed.summary.Goroutines[goid] 39 if !ok { 40 log.Printf("failed to find goroutine %d", goid) 41 return 42 } 43 opts.mode = traceviewer.ModeGoroutineOriented 44 if g.StartTime != 0 { 45 opts.startTime = g.StartTime.Sub(parsed.startTime()) 46 } else { 47 opts.startTime = 0 48 } 49 if g.EndTime != 0 { 50 opts.endTime = g.EndTime.Sub(parsed.startTime()) 51 } else { // The goroutine didn't end. 52 opts.endTime = parsed.endTime().Sub(parsed.startTime()) 53 } 54 opts.focusGoroutine = goid 55 opts.goroutines = trace.RelatedGoroutinesV2(parsed.events, goid) 56 } else if taskids := r.FormValue("focustask"); taskids != "" { 57 taskid, err := strconv.ParseUint(taskids, 10, 64) 58 if err != nil { 59 log.Printf("failed to parse focustask parameter %q: %v", taskids, err) 60 return 61 } 62 task, ok := parsed.summary.Tasks[tracev2.TaskID(taskid)] 63 if !ok || (task.Start == nil && task.End == nil) { 64 log.Printf("failed to find task with id %d", taskid) 65 return 66 } 67 opts.setTask(parsed, task) 68 } else if taskids := r.FormValue("taskid"); taskids != "" { 69 taskid, err := strconv.ParseUint(taskids, 10, 64) 70 if err != nil { 71 log.Printf("failed to parse taskid parameter %q: %v", taskids, err) 72 return 73 } 74 task, ok := parsed.summary.Tasks[tracev2.TaskID(taskid)] 75 if !ok { 76 log.Printf("failed to find task with id %d", taskid) 77 return 78 } 79 // This mode is goroutine-oriented. 80 opts.mode = traceviewer.ModeGoroutineOriented 81 opts.setTask(parsed, task) 82 83 // Pick the goroutine to orient ourselves around by just 84 // trying to pick the earliest event in the task that makes 85 // any sense. Though, we always want the start if that's there. 86 var firstEv *tracev2.Event 87 if task.Start != nil { 88 firstEv = task.Start 89 } else { 90 for _, logEv := range task.Logs { 91 if firstEv == nil || logEv.Time() < firstEv.Time() { 92 firstEv = logEv 93 } 94 } 95 if task.End != nil && (firstEv == nil || task.End.Time() < firstEv.Time()) { 96 firstEv = task.End 97 } 98 } 99 if firstEv == nil || firstEv.Goroutine() == tracev2.NoGoroutine { 100 log.Printf("failed to find task with id %d", taskid) 101 return 102 } 103 104 // Set the goroutine filtering options. 105 goid := firstEv.Goroutine() 106 opts.focusGoroutine = goid 107 goroutines := make(map[tracev2.GoID]struct{}) 108 for _, task := range opts.tasks { 109 // Find only directly involved goroutines. 110 for id := range task.Goroutines { 111 goroutines[id] = struct{}{} 112 } 113 } 114 opts.goroutines = goroutines 115 } 116 117 // Parse start and end options. Both or none must be present. 118 start := int64(0) 119 end := int64(math.MaxInt64) 120 if startStr, endStr := r.FormValue("start"), r.FormValue("end"); startStr != "" && endStr != "" { 121 var err error 122 start, err = strconv.ParseInt(startStr, 10, 64) 123 if err != nil { 124 log.Printf("failed to parse start parameter %q: %v", startStr, err) 125 return 126 } 127 128 end, err = strconv.ParseInt(endStr, 10, 64) 129 if err != nil { 130 log.Printf("failed to parse end parameter %q: %v", endStr, err) 131 return 132 } 133 } 134 135 c := traceviewer.ViewerDataTraceConsumer(w, start, end) 136 if err := generateTrace(parsed, opts, c); err != nil { 137 log.Printf("failed to generate trace: %v", err) 138 } 139 }) 140 } 141 142 // traceContext is a wrapper around a traceviewer.Emitter with some additional 143 // information that's useful to most parts of trace viewer JSON emission. 144 type traceContext struct { 145 *traceviewer.Emitter 146 startTime tracev2.Time 147 endTime tracev2.Time 148 } 149 150 // elapsed returns the elapsed time between the trace time and the start time 151 // of the trace. 152 func (ctx *traceContext) elapsed(now tracev2.Time) time.Duration { 153 return now.Sub(ctx.startTime) 154 } 155 156 type genOpts struct { 157 mode traceviewer.Mode 158 startTime time.Duration 159 endTime time.Duration 160 161 // Used if mode != 0. 162 focusGoroutine tracev2.GoID 163 goroutines map[tracev2.GoID]struct{} // Goroutines to be displayed for goroutine-oriented or task-oriented view. goroutines[0] is the main goroutine. 164 tasks []*trace.UserTaskSummary 165 } 166 167 // setTask sets a task to focus on. 168 func (opts *genOpts) setTask(parsed *parsedTrace, task *trace.UserTaskSummary) { 169 opts.mode |= traceviewer.ModeTaskOriented 170 if task.Start != nil { 171 opts.startTime = task.Start.Time().Sub(parsed.startTime()) 172 } else { // The task started before the trace did. 173 opts.startTime = 0 174 } 175 if task.End != nil { 176 opts.endTime = task.End.Time().Sub(parsed.startTime()) 177 } else { // The task didn't end. 178 opts.endTime = parsed.endTime().Sub(parsed.startTime()) 179 } 180 opts.tasks = task.Descendents() 181 slices.SortStableFunc(opts.tasks, func(a, b *trace.UserTaskSummary) int { 182 aStart, bStart := parsed.startTime(), parsed.startTime() 183 if a.Start != nil { 184 aStart = a.Start.Time() 185 } 186 if b.Start != nil { 187 bStart = b.Start.Time() 188 } 189 if a.Start != b.Start { 190 return cmp.Compare(aStart, bStart) 191 } 192 // Break ties with the end time. 193 aEnd, bEnd := parsed.endTime(), parsed.endTime() 194 if a.End != nil { 195 aEnd = a.End.Time() 196 } 197 if b.End != nil { 198 bEnd = b.End.Time() 199 } 200 return cmp.Compare(aEnd, bEnd) 201 }) 202 } 203 204 func defaultGenOpts() *genOpts { 205 return &genOpts{ 206 startTime: time.Duration(0), 207 endTime: time.Duration(math.MaxInt64), 208 } 209 } 210 211 func generateTrace(parsed *parsedTrace, opts *genOpts, c traceviewer.TraceConsumer) error { 212 ctx := &traceContext{ 213 Emitter: traceviewer.NewEmitter(c, opts.startTime, opts.endTime), 214 startTime: parsed.events[0].Time(), 215 endTime: parsed.events[len(parsed.events)-1].Time(), 216 } 217 defer ctx.Flush() 218 219 var g generator 220 if opts.mode&traceviewer.ModeGoroutineOriented != 0 { 221 g = newGoroutineGenerator(ctx, opts.focusGoroutine, opts.goroutines) 222 } else if opts.mode&traceviewer.ModeThreadOriented != 0 { 223 g = newThreadGenerator() 224 } else { 225 g = newProcGenerator() 226 } 227 runGenerator(ctx, g, parsed, opts) 228 return nil 229 }