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