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.
16 "golang.org/x/tools/internal/event"
17 "golang.org/x/tools/internal/event/core"
18 "golang.org/x/tools/internal/event/export"
19 "golang.org/x/tools/internal/event/label"
20 "golang.org/x/tools/internal/lsp/debug/tag"
23 var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
24 {{define "title"}}RPC Information{{end}}
27 {{template "rpcSection" .Inbound}}
29 {{template "rpcSection" .Outbound}}
31 {{define "rpcSection"}}
33 <b>{{.Method}}</b> {{.Started}} <a href="/trace/{{.Method}}">traces</a> ({{.InProgress}} in progress)
35 <i>Latency</i> {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
36 <i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}}
38 <i>Received</i> {{.Received}} (avg. {{.ReceivedMean}})
39 <i>Sent</i> {{.Sent}} (avg. {{.SentMean}})
41 <i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
49 Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name
50 Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name
53 type rpcStats struct {
58 Latency rpcTimeHistogram
61 Codes []*rpcCodeBucket
64 type rpcTimeHistogram struct {
69 Values []rpcTimeBucket
72 type rpcTimeBucket struct {
77 type rpcCodeBucket struct {
82 func (r *rpcs) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
86 case event.IsStart(ev):
87 if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
91 span, stats := r.getRPCSpan(ctx, ev)
93 endRPC(ctx, ev, span, stats)
95 case event.IsMetric(ev):
96 sent := byteUnits(tag.SentBytes.Get(lm))
97 rec := byteUnits(tag.ReceivedBytes.Get(lm))
98 if sent != 0 || rec != 0 {
99 if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
101 stats.Received += rec
108 func endRPC(ctx context.Context, ev core.Event, span *export.Span, stats *rpcStats) {
109 // update the basic counts
112 // get and record the status code
113 if status := getStatusCode(span); status != "" {
115 for c, entry := range stats.Codes {
116 if entry.Key == status {
122 b = &rpcCodeBucket{Key: status}
123 stats.Codes = append(stats.Codes, b)
124 sort.Slice(stats.Codes, func(i int, j int) bool {
125 return stats.Codes[i].Key < stats.Codes[j].Key
131 // calculate latency if this was an rpc span
132 elapsedTime := span.Finish().At().Sub(span.Start().At())
133 latencyMillis := timeUnits(elapsedTime) / timeUnits(time.Millisecond)
134 if stats.Latency.Count == 0 {
135 stats.Latency.Min = latencyMillis
136 stats.Latency.Max = latencyMillis
138 if stats.Latency.Min > latencyMillis {
139 stats.Latency.Min = latencyMillis
141 if stats.Latency.Max < latencyMillis {
142 stats.Latency.Max = latencyMillis
145 stats.Latency.Count++
146 stats.Latency.Sum += latencyMillis
147 for i := range stats.Latency.Values {
148 if stats.Latency.Values[i].Limit > latencyMillis {
149 stats.Latency.Values[i].Count++
155 func (r *rpcs) getRPCSpan(ctx context.Context, ev core.Event) (*export.Span, *rpcStats) {
157 span := export.GetSpan(ctx)
161 // use the span start event look up the correct stats block
162 // we do this because it prevents us matching a sub span
163 return span, r.getRPCStats(span.Start())
166 func (r *rpcs) getRPCStats(lm label.Map) *rpcStats {
167 method := tag.Method.Get(lm)
172 if tag.RPCDirection.Get(lm) != tag.Inbound {
175 // get the record for this method
176 index := sort.Search(len(*set), func(i int) bool {
177 return (*set)[i].Method >= method
180 if index < len(*set) && (*set)[index].Method == method {
185 *set = make([]*rpcStats, len(old)+1)
186 copy(*set, old[:index])
187 copy((*set)[index+1:], old[index:])
188 stats := &rpcStats{Method: method}
189 stats.Latency.Values = make([]rpcTimeBucket, len(millisecondsDistribution))
190 for i, m := range millisecondsDistribution {
191 stats.Latency.Values[i].Limit = timeUnits(m)
193 (*set)[index] = stats
197 func (s *rpcStats) InProgress() int64 { return s.Started - s.Completed }
198 func (s *rpcStats) SentMean() byteUnits { return s.Sent / byteUnits(s.Started) }
199 func (s *rpcStats) ReceivedMean() byteUnits { return s.Received / byteUnits(s.Started) }
201 func (h *rpcTimeHistogram) Mean() timeUnits { return h.Sum / timeUnits(h.Count) }
203 func getStatusCode(span *export.Span) string {
204 for _, ev := range span.Events() {
205 if status := tag.StatusCode.Get(ev); status != "" {
212 func (r *rpcs) getData(req *http.Request) interface{} {
216 func units(v float64, suffixes []string) string {
218 for _, s = range suffixes {
225 return fmt.Sprintf("%.2f%s", v, s)
228 type timeUnits float64
230 func (v timeUnits) String() string {
232 return units(float64(v), []string{"ns", "μs", "ms", "s"})
235 type byteUnits float64
237 func (v byteUnits) String() string {
238 return units(float64(v), []string{"B", "KB", "MB", "GB", "TB"})