aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/debug/rpc.go
diff options
context:
space:
mode:
Diffstat (limited to 'gopls/internal/lsp/debug/rpc.go')
-rw-r--r--gopls/internal/lsp/debug/rpc.go239
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"})
+}