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