golang.org/x/tools/gopls@v0.15.3/internal/debug/trace.go (about) 1 // Copyright 2019 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 debug 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "html/template" 12 "net/http" 13 "runtime/trace" 14 "sort" 15 "strings" 16 "sync" 17 "time" 18 19 "golang.org/x/tools/internal/event" 20 "golang.org/x/tools/internal/event/core" 21 "golang.org/x/tools/internal/event/export" 22 "golang.org/x/tools/internal/event/label" 23 ) 24 25 // TraceTmpl extends BaseTemplate and renders a TraceResults, e.g. from getData(). 26 var TraceTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 27 {{define "title"}}Trace Information{{end}} 28 {{define "body"}} 29 {{range .Traces}}<a href="/trace/{{.Name}}">{{.Name}}</a> last: {{.Last.Duration}}, longest: {{.Longest.Duration}}<br>{{end}} 30 {{if .Selected}} 31 <H2>{{.Selected.Name}}</H2> 32 {{if .Selected.Last}}<H3>Last</H3><ul class='spans'>{{template "completeSpan" .Selected.Last}}</ul>{{end}} 33 {{if .Selected.Longest}}<H3>Longest</H3><ul class='spans'>{{template "completeSpan" .Selected.Longest}}</ul>{{end}} 34 {{end}} 35 36 <H2>Recent spans (oldest first)</H2> 37 <p> 38 A finite number of recent span start/end times are shown below. 39 The nesting represents the children of a parent span (and the log events within a span). 40 A span may appear twice: chronologically at toplevel, and nested within its parent. 41 </p> 42 <ul class='spans'>{{range .Recent}}{{template "spanStartEnd" .}}{{end}}</ul> 43 {{end}} 44 {{define "spanStartEnd"}} 45 {{if .Start}} 46 <li>{{.Span.Header .Start}}</li> 47 {{else}} 48 {{template "completeSpan" .Span}} 49 {{end}} 50 {{end}} 51 {{define "completeSpan"}} 52 <li>{{.Header false}}</li> 53 {{if .Events}}<ul>{{range .Events}}<li>{{.Header}}</li>{{end}}</ul>{{end}} 54 {{if .ChildStartEnd}}<ul>{{range .ChildStartEnd}}{{template "spanStartEnd" .}}{{end}}</ul>{{end}} 55 {{end}} 56 `)) 57 58 type traces struct { 59 mu sync.Mutex 60 sets map[string]*traceSet 61 unfinished map[export.SpanContext]*traceSpan 62 recent []spanStartEnd 63 recentEvictions int 64 } 65 66 // A spanStartEnd records the start or end of a span. 67 // If Start, the span may be unfinished, so some fields (e.g. Finish) 68 // may be unset and others (e.g. Events) may be being actively populated. 69 type spanStartEnd struct { 70 Start bool 71 Span *traceSpan 72 } 73 74 func (ev spanStartEnd) Time() time.Time { 75 if ev.Start { 76 return ev.Span.Start 77 } else { 78 return ev.Span.Finish 79 } 80 } 81 82 // A TraceResults is the subject for the /trace HTML template. 83 type TraceResults struct { // exported for testing 84 Traces []*traceSet 85 Selected *traceSet 86 Recent []spanStartEnd 87 } 88 89 // A traceSet holds two representative spans of a given span name. 90 type traceSet struct { 91 Name string 92 Last *traceSpan 93 Longest *traceSpan 94 } 95 96 // A traceSpan holds information about a single span. 97 type traceSpan struct { 98 TraceID export.TraceID 99 SpanID export.SpanID 100 ParentID export.SpanID 101 Name string 102 Start time.Time 103 Finish time.Time // set at end 104 Duration time.Duration // set at end 105 Tags string 106 Events []traceEvent // set at end 107 ChildStartEnd []spanStartEnd // populated while active 108 109 parent *traceSpan 110 } 111 112 const timeFormat = "15:04:05.000" 113 114 // Header renders the time, name, tags, and (if !start), 115 // duration of a span start or end event. 116 func (span *traceSpan) Header(start bool) string { 117 if start { 118 return fmt.Sprintf("%s start %s %s", 119 span.Start.Format(timeFormat), span.Name, span.Tags) 120 } else { 121 return fmt.Sprintf("%s end %s (+%s) %s", 122 span.Finish.Format(timeFormat), span.Name, span.Duration, span.Tags) 123 } 124 } 125 126 type traceEvent struct { 127 Time time.Time 128 Offset time.Duration // relative to start of span 129 Tags string 130 } 131 132 func (ev traceEvent) Header() string { 133 return fmt.Sprintf("%s event (+%s) %s", ev.Time.Format(timeFormat), ev.Offset, ev.Tags) 134 } 135 136 func StdTrace(exporter event.Exporter) event.Exporter { 137 return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { 138 span := export.GetSpan(ctx) 139 if span == nil { 140 return exporter(ctx, ev, lm) 141 } 142 switch { 143 case event.IsStart(ev): 144 if span.ParentID.IsValid() { 145 region := trace.StartRegion(ctx, span.Name) 146 ctx = context.WithValue(ctx, traceKey, region) 147 } else { 148 var task *trace.Task 149 ctx, task = trace.NewTask(ctx, span.Name) 150 ctx = context.WithValue(ctx, traceKey, task) 151 } 152 // Log the start event as it may contain useful labels. 153 msg := formatEvent(ev, lm) 154 trace.Log(ctx, "start", msg) 155 case event.IsLog(ev): 156 category := "" 157 if event.IsError(ev) { 158 category = "error" 159 } 160 msg := formatEvent(ev, lm) 161 trace.Log(ctx, category, msg) 162 case event.IsEnd(ev): 163 if v := ctx.Value(traceKey); v != nil { 164 v.(interface{ End() }).End() 165 } 166 } 167 return exporter(ctx, ev, lm) 168 } 169 } 170 171 func formatEvent(ev core.Event, lm label.Map) string { 172 buf := &bytes.Buffer{} 173 p := export.Printer{} 174 p.WriteEvent(buf, ev, lm) 175 return buf.String() 176 } 177 178 func (t *traces) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { 179 span := export.GetSpan(ctx) 180 if span == nil { 181 return ctx 182 } 183 184 switch { 185 case event.IsStart(ev): 186 // Just starting: add it to the unfinished map. 187 // Allocate before the critical section. 188 td := &traceSpan{ 189 TraceID: span.ID.TraceID, 190 SpanID: span.ID.SpanID, 191 ParentID: span.ParentID, 192 Name: span.Name, 193 Start: span.Start().At(), 194 Tags: renderLabels(span.Start()), 195 } 196 197 t.mu.Lock() 198 defer t.mu.Unlock() 199 200 t.addRecentLocked(td, true) // add start event 201 202 if t.sets == nil { 203 t.sets = make(map[string]*traceSet) 204 t.unfinished = make(map[export.SpanContext]*traceSpan) 205 } 206 t.unfinished[span.ID] = td 207 208 // Wire up parents if we have them. 209 if span.ParentID.IsValid() { 210 parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID} 211 if parent, ok := t.unfinished[parentID]; ok { 212 td.parent = parent 213 parent.ChildStartEnd = append(parent.ChildStartEnd, spanStartEnd{true, td}) 214 } 215 } 216 217 case event.IsEnd(ev): 218 // Finishing: must be already in the map. 219 // Allocate events before the critical section. 220 events := span.Events() 221 tdEvents := make([]traceEvent, len(events)) 222 for i, event := range events { 223 tdEvents[i] = traceEvent{ 224 Time: event.At(), 225 Tags: renderLabels(event), 226 } 227 } 228 229 t.mu.Lock() 230 defer t.mu.Unlock() 231 td, found := t.unfinished[span.ID] 232 if !found { 233 return ctx // if this happens we are in a bad place 234 } 235 delete(t.unfinished, span.ID) 236 td.Finish = span.Finish().At() 237 td.Duration = span.Finish().At().Sub(span.Start().At()) 238 td.Events = tdEvents 239 t.addRecentLocked(td, false) // add end event 240 241 set, ok := t.sets[span.Name] 242 if !ok { 243 set = &traceSet{Name: span.Name} 244 t.sets[span.Name] = set 245 } 246 set.Last = td 247 if set.Longest == nil || set.Last.Duration > set.Longest.Duration { 248 set.Longest = set.Last 249 } 250 if td.parent != nil { 251 td.parent.ChildStartEnd = append(td.parent.ChildStartEnd, spanStartEnd{false, td}) 252 } else { 253 fillOffsets(td, td.Start) 254 } 255 } 256 return ctx 257 } 258 259 // addRecentLocked appends a start or end event to the "recent" log, 260 // evicting an old entry if necessary. 261 func (t *traces) addRecentLocked(span *traceSpan, start bool) { 262 t.recent = append(t.recent, spanStartEnd{Start: start, Span: span}) 263 264 const maxRecent = 100 // number of log entries before eviction 265 for len(t.recent) > maxRecent { 266 t.recent[0] = spanStartEnd{} // aid GC 267 t.recent = t.recent[1:] 268 t.recentEvictions++ 269 270 // Using a slice as a FIFO queue leads to unbounded growth 271 // as Go's GC cannot collect the ever-growing unused prefix. 272 // So, compact it periodically. 273 if t.recentEvictions%maxRecent == 0 { 274 t.recent = append([]spanStartEnd(nil), t.recent...) 275 } 276 } 277 } 278 279 // getData returns the TraceResults rendered by TraceTmpl for the /trace[/name] endpoint. 280 func (t *traces) getData(req *http.Request) interface{} { 281 // TODO(adonovan): the HTTP request doesn't acquire the mutex 282 // for t or for each span! Audit and fix. 283 284 // Sort last/longest sets by name. 285 traces := make([]*traceSet, 0, len(t.sets)) 286 for _, set := range t.sets { 287 traces = append(traces, set) 288 } 289 sort.Slice(traces, func(i, j int) bool { 290 return traces[i].Name < traces[j].Name 291 }) 292 293 return TraceResults{ 294 Traces: traces, 295 Selected: t.sets[strings.TrimPrefix(req.URL.Path, "/trace/")], // may be nil 296 Recent: t.recent, 297 } 298 } 299 300 func fillOffsets(td *traceSpan, start time.Time) { 301 for i := range td.Events { 302 td.Events[i].Offset = td.Events[i].Time.Sub(start) 303 } 304 for _, child := range td.ChildStartEnd { 305 if !child.Start { 306 fillOffsets(child.Span, start) 307 } 308 } 309 } 310 311 func renderLabels(labels label.List) string { 312 buf := &bytes.Buffer{} 313 for index := 0; labels.Valid(index); index++ { 314 // The 'start' label duplicates the span name, so discard it. 315 if l := labels.Label(index); l.Valid() && l.Key().Name() != "start" { 316 fmt.Fprintf(buf, "%v ", l) 317 } 318 } 319 return buf.String() 320 }