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) getData(req *http.Request) interface{} {
134	if len(t.sets) == 0 {
135		return nil
136	}
137	data := traceResults{}
138	data.Traces = make([]*traceSet, 0, len(t.sets))
139	for _, set := range t.sets {
140		data.Traces = append(data.Traces, set)
141	}
142	sort.Slice(data.Traces, func(i, j int) bool { return data.Traces[i].Name < data.Traces[j].Name })
143	if bits := strings.SplitN(req.URL.Path, "/trace/", 2); len(bits) > 1 {
144		data.Selected = t.sets[bits[1]]
145	}
146	return data
147}
148
149func fillOffsets(td *traceData, start time.Time) {
150	td.Offset = td.Start.Sub(start)
151	for i := range td.Events {
152		td.Events[i].Offset = td.Events[i].Time.Sub(start)
153	}
154	for _, child := range td.Children {
155		fillOffsets(child, start)
156	}
157}
158
159func renderTags(tags telemetry.TagList) string {
160	buf := &bytes.Buffer{}
161	for _, tag := range tags {
162		fmt.Fprintf(buf, "%v=%q ", tag.Key, tag.Value)
163	}
164	return buf.String()
165}
166