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 5package debug 6 7import ( 8 "bytes" 9 "context" 10 "fmt" 11 "html/template" 12 "net/http" 13 "sort" 14 "strings" 15 "time" 16 17 "golang.org/x/tools/internal/telemetry" 18) 19 20var traceTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` 21{{define "title"}}Trace Information{{end}} 22{{define "body"}} 23 {{range .Traces}}<a href="/trace/{{.Name}}">{{.Name}}</a> last: {{.Last.Duration}}, longest: {{.Longest.Duration}}<br>{{end}} 24 {{if .Selected}} 25 <H2>{{.Selected.Name}}</H2> 26 {{if .Selected.Last}}<H3>Last</H3><ul>{{template "details" .Selected.Last}}</ul>{{end}} 27 {{if .Selected.Longest}}<H3>Longest</H3><ul>{{template "details" .Selected.Longest}}</ul>{{end}} 28 {{end}} 29{{end}} 30{{define "details"}} 31 <li>{{.Offset}} {{.Name}} {{.Duration}} {{.Tags}}</li> 32 {{if .Events}}<ul class=events>{{range .Events}}<li>{{.Offset}} {{.Tags}}</li>{{end}}</ul>{{end}} 33 {{if .Children}}<ul>{{range .Children}}{{template "details" .}}{{end}}</ul>{{end}} 34{{end}} 35`)) 36 37type traces struct { 38 sets map[string]*traceSet 39 unfinished map[telemetry.SpanContext]*traceData 40} 41 42type traceResults struct { 43 Traces []*traceSet 44 Selected *traceSet 45} 46 47type traceSet struct { 48 Name string 49 Last *traceData 50 Longest *traceData 51} 52 53type traceData struct { 54 TraceID telemetry.TraceID 55 SpanID telemetry.SpanID 56 ParentID telemetry.SpanID 57 Name string 58 Start time.Time 59 Finish time.Time 60 Offset time.Duration 61 Duration time.Duration 62 Tags string 63 Events []traceEvent 64 Children []*traceData 65} 66 67type traceEvent struct { 68 Time time.Time 69 Offset time.Duration 70 Tags string 71} 72 73func (t *traces) StartSpan(ctx context.Context, span *telemetry.Span) { 74 if t.sets == nil { 75 t.sets = make(map[string]*traceSet) 76 t.unfinished = make(map[telemetry.SpanContext]*traceData) 77 } 78 // just starting, add it to the unfinished map 79 td := &traceData{ 80 TraceID: span.ID.TraceID, 81 SpanID: span.ID.SpanID, 82 ParentID: span.ParentID, 83 Name: span.Name, 84 Start: span.Start, 85 Tags: renderTags(span.Tags), 86 } 87 t.unfinished[span.ID] = td 88 // and wire up parents if we have them 89 if !span.ParentID.IsValid() { 90 return 91 } 92 parentID := telemetry.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID} 93 parent, found := t.unfinished[parentID] 94 if !found { 95 // trace had an invalid parent, so it cannot itself be valid 96 return 97 } 98 parent.Children = append(parent.Children, td) 99} 100 101func (t *traces) FinishSpan(ctx context.Context, span *telemetry.Span) { 102 // finishing, must be already in the map 103 td, found := t.unfinished[span.ID] 104 if !found { 105 return // if this happens we are in a bad place 106 } 107 delete(t.unfinished, span.ID) 108 109 td.Finish = span.Finish 110 td.Duration = span.Finish.Sub(span.Start) 111 td.Events = make([]traceEvent, len(span.Events)) 112 for i, event := range span.Events { 113 td.Events[i] = traceEvent{ 114 Time: event.At, 115 Tags: renderTags(event.Tags), 116 } 117 } 118 119 set, ok := t.sets[span.Name] 120 if !ok { 121 set = &traceSet{Name: span.Name} 122 t.sets[span.Name] = set 123 } 124 set.Last = td 125 if set.Longest == nil || set.Last.Duration > set.Longest.Duration { 126 set.Longest = set.Last 127 } 128 if !td.ParentID.IsValid() { 129 fillOffsets(td, td.Start) 130 } 131} 132 133func (t *traces) Log(ctx context.Context, event telemetry.Event) {} 134 135func (t *traces) Metric(ctx context.Context, data telemetry.MetricData) {} 136 137func (t *traces) Flush() {} 138 139func (t *traces) getData(req *http.Request) interface{} { 140 if len(t.sets) == 0 { 141 return nil 142 } 143 data := traceResults{} 144 data.Traces = make([]*traceSet, 0, len(t.sets)) 145 for _, set := range t.sets { 146 data.Traces = append(data.Traces, set) 147 } 148 sort.Slice(data.Traces, func(i, j int) bool { return data.Traces[i].Name < data.Traces[j].Name }) 149 if bits := strings.SplitN(req.URL.Path, "/trace/", 2); len(bits) > 1 { 150 data.Selected = t.sets[bits[1]] 151 } 152 return data 153} 154 155func fillOffsets(td *traceData, start time.Time) { 156 td.Offset = td.Start.Sub(start) 157 for i := range td.Events { 158 td.Events[i].Offset = td.Events[i].Time.Sub(start) 159 } 160 for _, child := range td.Children { 161 fillOffsets(child, start) 162 } 163} 164 165func renderTags(tags telemetry.TagList) string { 166 buf := &bytes.Buffer{} 167 for _, tag := range tags { 168 fmt.Fprintf(buf, "%v=%q ", tag.Key, tag.Value) 169 } 170 return buf.String() 171} 172