2019-06-26 20:46:12 -06:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package debug
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2019-08-14 10:51:42 -06:00
|
|
|
"context"
|
2019-06-26 20:46:12 -06:00
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
2019-12-20 13:04:24 -07:00
|
|
|
"sync"
|
2019-06-26 20:46:12 -06:00
|
|
|
"time"
|
|
|
|
|
2020-04-17 07:32:56 -06:00
|
|
|
"golang.org/x/tools/internal/event/core"
|
|
|
|
"golang.org/x/tools/internal/event/export"
|
2020-04-20 13:44:34 -06:00
|
|
|
"golang.org/x/tools/internal/event/label"
|
2019-06-26 20:46:12 -06:00
|
|
|
)
|
|
|
|
|
2020-01-30 16:49:04 -07:00
|
|
|
var traceTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
|
2019-06-26 20:46:12 -06:00
|
|
|
{{define "title"}}Trace Information{{end}}
|
|
|
|
{{define "body"}}
|
|
|
|
{{range .Traces}}<a href="/trace/{{.Name}}">{{.Name}}</a> last: {{.Last.Duration}}, longest: {{.Longest.Duration}}<br>{{end}}
|
|
|
|
{{if .Selected}}
|
|
|
|
<H2>{{.Selected.Name}}</H2>
|
|
|
|
{{if .Selected.Last}}<H3>Last</H3><ul>{{template "details" .Selected.Last}}</ul>{{end}}
|
|
|
|
{{if .Selected.Longest}}<H3>Longest</H3><ul>{{template "details" .Selected.Longest}}</ul>{{end}}
|
|
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
{{define "details"}}
|
|
|
|
<li>{{.Offset}} {{.Name}} {{.Duration}} {{.Tags}}</li>
|
|
|
|
{{if .Events}}<ul class=events>{{range .Events}}<li>{{.Offset}} {{.Tags}}</li>{{end}}</ul>{{end}}
|
|
|
|
{{if .Children}}<ul>{{range .Children}}{{template "details" .}}{{end}}</ul>{{end}}
|
|
|
|
{{end}}
|
|
|
|
`))
|
|
|
|
|
|
|
|
type traces struct {
|
2019-12-20 13:04:24 -07:00
|
|
|
mu sync.Mutex
|
2019-06-26 20:46:12 -06:00
|
|
|
sets map[string]*traceSet
|
2020-03-01 16:35:55 -07:00
|
|
|
unfinished map[export.SpanContext]*traceData
|
2019-06-26 20:46:12 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type traceResults struct {
|
|
|
|
Traces []*traceSet
|
|
|
|
Selected *traceSet
|
|
|
|
}
|
|
|
|
|
|
|
|
type traceSet struct {
|
|
|
|
Name string
|
|
|
|
Last *traceData
|
|
|
|
Longest *traceData
|
|
|
|
}
|
|
|
|
|
|
|
|
type traceData struct {
|
2020-03-01 16:35:55 -07:00
|
|
|
TraceID export.TraceID
|
|
|
|
SpanID export.SpanID
|
|
|
|
ParentID export.SpanID
|
2019-06-26 20:46:12 -06:00
|
|
|
Name string
|
|
|
|
Start time.Time
|
|
|
|
Finish time.Time
|
|
|
|
Offset time.Duration
|
|
|
|
Duration time.Duration
|
|
|
|
Tags string
|
|
|
|
Events []traceEvent
|
|
|
|
Children []*traceData
|
|
|
|
}
|
|
|
|
|
|
|
|
type traceEvent struct {
|
|
|
|
Time time.Time
|
|
|
|
Offset time.Duration
|
|
|
|
Tags string
|
|
|
|
}
|
|
|
|
|
2020-04-20 13:44:34 -06:00
|
|
|
func (t *traces) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
|
2019-12-20 13:04:24 -07:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2020-03-01 16:35:55 -07:00
|
|
|
span := export.GetSpan(ctx)
|
|
|
|
if span == nil {
|
2020-03-20 06:29:48 -06:00
|
|
|
return ctx
|
2019-06-26 20:46:12 -06:00
|
|
|
}
|
2020-03-07 19:28:21 -07:00
|
|
|
|
|
|
|
switch {
|
|
|
|
case ev.IsStartSpan():
|
2020-03-01 16:35:55 -07:00
|
|
|
if t.sets == nil {
|
|
|
|
t.sets = make(map[string]*traceSet)
|
|
|
|
t.unfinished = make(map[export.SpanContext]*traceData)
|
|
|
|
}
|
|
|
|
// just starting, add it to the unfinished map
|
|
|
|
td := &traceData{
|
|
|
|
TraceID: span.ID.TraceID,
|
|
|
|
SpanID: span.ID.SpanID,
|
|
|
|
ParentID: span.ParentID,
|
|
|
|
Name: span.Name,
|
2020-04-20 21:00:05 -06:00
|
|
|
Start: span.Start().At(),
|
2020-04-20 13:44:34 -06:00
|
|
|
Tags: renderLabels(span.Start()),
|
2020-03-01 16:35:55 -07:00
|
|
|
}
|
|
|
|
t.unfinished[span.ID] = td
|
|
|
|
// and wire up parents if we have them
|
|
|
|
if !span.ParentID.IsValid() {
|
2020-03-20 06:29:48 -06:00
|
|
|
return ctx
|
2020-03-01 16:35:55 -07:00
|
|
|
}
|
|
|
|
parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID}
|
|
|
|
parent, found := t.unfinished[parentID]
|
|
|
|
if !found {
|
|
|
|
// trace had an invalid parent, so it cannot itself be valid
|
2020-03-20 06:29:48 -06:00
|
|
|
return ctx
|
2020-03-01 16:35:55 -07:00
|
|
|
}
|
|
|
|
parent.Children = append(parent.Children, td)
|
2019-06-26 20:46:12 -06:00
|
|
|
|
2020-03-07 19:28:21 -07:00
|
|
|
case ev.IsEndSpan():
|
2020-03-01 16:35:55 -07:00
|
|
|
// finishing, must be already in the map
|
|
|
|
td, found := t.unfinished[span.ID]
|
|
|
|
if !found {
|
2020-03-20 06:29:48 -06:00
|
|
|
return ctx // if this happens we are in a bad place
|
2020-03-01 16:35:55 -07:00
|
|
|
}
|
|
|
|
delete(t.unfinished, span.ID)
|
|
|
|
|
2020-04-20 21:00:05 -06:00
|
|
|
td.Finish = span.Finish().At()
|
|
|
|
td.Duration = span.Finish().At().Sub(span.Start().At())
|
2020-03-27 16:59:18 -06:00
|
|
|
events := span.Events()
|
|
|
|
td.Events = make([]traceEvent, len(events))
|
|
|
|
for i, event := range events {
|
2020-03-01 16:35:55 -07:00
|
|
|
td.Events[i] = traceEvent{
|
2020-04-20 21:00:05 -06:00
|
|
|
Time: event.At(),
|
2020-04-20 13:44:34 -06:00
|
|
|
Tags: renderLabels(event),
|
2020-03-01 16:35:55 -07:00
|
|
|
}
|
2019-06-26 20:46:12 -06:00
|
|
|
}
|
|
|
|
|
2020-03-01 16:35:55 -07:00
|
|
|
set, ok := t.sets[span.Name]
|
|
|
|
if !ok {
|
|
|
|
set = &traceSet{Name: span.Name}
|
|
|
|
t.sets[span.Name] = set
|
|
|
|
}
|
|
|
|
set.Last = td
|
|
|
|
if set.Longest == nil || set.Last.Duration > set.Longest.Duration {
|
|
|
|
set.Longest = set.Last
|
|
|
|
}
|
|
|
|
if !td.ParentID.IsValid() {
|
|
|
|
fillOffsets(td, td.Start)
|
|
|
|
}
|
2019-06-26 20:46:12 -06:00
|
|
|
}
|
2020-03-20 06:29:48 -06:00
|
|
|
return ctx
|
2019-06-26 20:46:12 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *traces) getData(req *http.Request) interface{} {
|
|
|
|
if len(t.sets) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
data := traceResults{}
|
|
|
|
data.Traces = make([]*traceSet, 0, len(t.sets))
|
|
|
|
for _, set := range t.sets {
|
|
|
|
data.Traces = append(data.Traces, set)
|
|
|
|
}
|
|
|
|
sort.Slice(data.Traces, func(i, j int) bool { return data.Traces[i].Name < data.Traces[j].Name })
|
|
|
|
if bits := strings.SplitN(req.URL.Path, "/trace/", 2); len(bits) > 1 {
|
|
|
|
data.Selected = t.sets[bits[1]]
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
func fillOffsets(td *traceData, start time.Time) {
|
|
|
|
td.Offset = td.Start.Sub(start)
|
|
|
|
for i := range td.Events {
|
|
|
|
td.Events[i].Offset = td.Events[i].Time.Sub(start)
|
|
|
|
}
|
|
|
|
for _, child := range td.Children {
|
|
|
|
fillOffsets(child, start)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-20 13:44:34 -06:00
|
|
|
func renderLabels(labels label.List) string {
|
2019-06-26 20:46:12 -06:00
|
|
|
buf := &bytes.Buffer{}
|
2020-04-20 13:44:34 -06:00
|
|
|
for index := 0; labels.Valid(index); index++ {
|
|
|
|
if l := labels.Label(index); l.Valid() {
|
|
|
|
fmt.Fprintf(buf, "%v ", l)
|
2020-04-03 21:06:57 -06:00
|
|
|
}
|
2019-06-26 20:46:12 -06:00
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|