diff options
Diffstat (limited to 'gopls/internal/lsp/debug/rpc.go')
-rw-r--r-- | gopls/internal/lsp/debug/rpc.go | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/gopls/internal/lsp/debug/rpc.go b/gopls/internal/lsp/debug/rpc.go new file mode 100644 index 000000000..561002147 --- /dev/null +++ b/gopls/internal/lsp/debug/rpc.go @@ -0,0 +1,239 @@ +// 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 ( + "context" + "fmt" + "html/template" + "net/http" + "sort" + "sync" + "time" + + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/export" + "golang.org/x/tools/internal/event/label" + "golang.org/x/tools/internal/event/tag" +) + +var RPCTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}RPC Information{{end}} +{{define "body"}} + <H2>Inbound</H2> + {{template "rpcSection" .Inbound}} + <H2>Outbound</H2> + {{template "rpcSection" .Outbound}} +{{end}} +{{define "rpcSection"}} + {{range .}}<P> + <b>{{.Method}}</b> {{.Started}} <a href="/trace/{{.Method}}">traces</a> ({{.InProgress}} in progress) + <br> + <i>Latency</i> {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}} + <i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}} + <br> + <i>Received</i> {{.Received}} (avg. {{.ReceivedMean}}) + <i>Sent</i> {{.Sent}} (avg. {{.SentMean}}) + <br> + <i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}} + </P> + {{end}} +{{end}} +`)) + +type Rpcs struct { // exported for testing + mu sync.Mutex + Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name + Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name +} + +type rpcStats struct { + Method string + Started int64 + Completed int64 + + Latency rpcTimeHistogram + Received byteUnits + Sent byteUnits + Codes []*rpcCodeBucket +} + +type rpcTimeHistogram struct { + Sum timeUnits + Count int64 + Min timeUnits + Max timeUnits + Values []rpcTimeBucket +} + +type rpcTimeBucket struct { + Limit timeUnits + Count int64 +} + +type rpcCodeBucket struct { + Key string + Count int64 +} + +func (r *Rpcs) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { + r.mu.Lock() + defer r.mu.Unlock() + switch { + case event.IsStart(ev): + if _, stats := r.getRPCSpan(ctx, ev); stats != nil { + stats.Started++ + } + case event.IsEnd(ev): + span, stats := r.getRPCSpan(ctx, ev) + if stats != nil { + endRPC(ctx, ev, span, stats) + } + case event.IsMetric(ev): + sent := byteUnits(tag.SentBytes.Get(lm)) + rec := byteUnits(tag.ReceivedBytes.Get(lm)) + if sent != 0 || rec != 0 { + if _, stats := r.getRPCSpan(ctx, ev); stats != nil { + stats.Sent += sent + stats.Received += rec + } + } + } + return ctx +} + +func endRPC(ctx context.Context, ev core.Event, span *export.Span, stats *rpcStats) { + // update the basic counts + stats.Completed++ + + // get and record the status code + if status := getStatusCode(span); status != "" { + var b *rpcCodeBucket + for c, entry := range stats.Codes { + if entry.Key == status { + b = stats.Codes[c] + break + } + } + if b == nil { + b = &rpcCodeBucket{Key: status} + stats.Codes = append(stats.Codes, b) + sort.Slice(stats.Codes, func(i int, j int) bool { + return stats.Codes[i].Key < stats.Codes[j].Key + }) + } + b.Count++ + } + + // calculate latency if this was an rpc span + elapsedTime := span.Finish().At().Sub(span.Start().At()) + latencyMillis := timeUnits(elapsedTime) / timeUnits(time.Millisecond) + if stats.Latency.Count == 0 { + stats.Latency.Min = latencyMillis + stats.Latency.Max = latencyMillis + } else { + if stats.Latency.Min > latencyMillis { + stats.Latency.Min = latencyMillis + } + if stats.Latency.Max < latencyMillis { + stats.Latency.Max = latencyMillis + } + } + stats.Latency.Count++ + stats.Latency.Sum += latencyMillis + for i := range stats.Latency.Values { + if stats.Latency.Values[i].Limit > latencyMillis { + stats.Latency.Values[i].Count++ + break + } + } +} + +func (r *Rpcs) getRPCSpan(ctx context.Context, ev core.Event) (*export.Span, *rpcStats) { + // get the span + span := export.GetSpan(ctx) + if span == nil { + return nil, nil + } + // use the span start event look up the correct stats block + // we do this because it prevents us matching a sub span + return span, r.getRPCStats(span.Start()) +} + +func (r *Rpcs) getRPCStats(lm label.Map) *rpcStats { + method := tag.Method.Get(lm) + if method == "" { + return nil + } + set := &r.Inbound + if tag.RPCDirection.Get(lm) != tag.Inbound { + set = &r.Outbound + } + // get the record for this method + index := sort.Search(len(*set), func(i int) bool { + return (*set)[i].Method >= method + }) + + if index < len(*set) && (*set)[index].Method == method { + return (*set)[index] + } + + old := *set + *set = make([]*rpcStats, len(old)+1) + copy(*set, old[:index]) + copy((*set)[index+1:], old[index:]) + stats := &rpcStats{Method: method} + stats.Latency.Values = make([]rpcTimeBucket, len(millisecondsDistribution)) + for i, m := range millisecondsDistribution { + stats.Latency.Values[i].Limit = timeUnits(m) + } + (*set)[index] = stats + return stats +} + +func (s *rpcStats) InProgress() int64 { return s.Started - s.Completed } +func (s *rpcStats) SentMean() byteUnits { return s.Sent / byteUnits(s.Started) } +func (s *rpcStats) ReceivedMean() byteUnits { return s.Received / byteUnits(s.Started) } + +func (h *rpcTimeHistogram) Mean() timeUnits { return h.Sum / timeUnits(h.Count) } + +func getStatusCode(span *export.Span) string { + for _, ev := range span.Events() { + if status := tag.StatusCode.Get(ev); status != "" { + return status + } + } + return "" +} + +func (r *Rpcs) getData(req *http.Request) interface{} { + return r +} + +func units(v float64, suffixes []string) string { + s := "" + for _, s = range suffixes { + n := v / 1000 + if n < 1 { + break + } + v = n + } + return fmt.Sprintf("%.2f%s", v, s) +} + +type timeUnits float64 + +func (v timeUnits) String() string { + v = v * 1000 * 1000 + return units(float64(v), []string{"ns", "μs", "ms", "s"}) +} + +type byteUnits float64 + +func (v byteUnits) String() string { + return units(float64(v), []string{"B", "KB", "MB", "GB", "TB"}) +} |