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