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  }