2019-07-09 08:55:22 -06:00
|
|
|
// 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 (
|
2019-08-14 10:51:42 -06:00
|
|
|
"context"
|
2019-07-09 08:55:22 -06:00
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
"sort"
|
2020-03-06 08:28:29 -07:00
|
|
|
"sync"
|
2020-03-26 20:00:12 -06:00
|
|
|
"time"
|
2019-07-09 08:55:22 -06:00
|
|
|
|
2020-04-21 07:41:57 -06:00
|
|
|
"golang.org/x/tools/internal/event"
|
2020-04-17 07:32:56 -06:00
|
|
|
"golang.org/x/tools/internal/event/core"
|
|
|
|
"golang.org/x/tools/internal/event/export"
|
2020-04-20 13:44:34 -06:00
|
|
|
"golang.org/x/tools/internal/event/label"
|
2020-03-10 21:09:39 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/debug/tag"
|
2019-07-09 08:55:22 -06:00
|
|
|
)
|
|
|
|
|
2020-01-30 16:49:04 -07:00
|
|
|
var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
|
2019-07-09 08:55:22 -06:00
|
|
|
{{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}}
|
2020-04-03 13:12:15 -06:00
|
|
|
<i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}}
|
2019-07-09 08:55:22 -06:00
|
|
|
<br>
|
2020-04-03 13:12:15 -06:00
|
|
|
<i>Received</i> {{.Received}} (avg. {{.ReceivedMean}})
|
|
|
|
<i>Sent</i> {{.Sent}} (avg. {{.SentMean}})
|
2019-07-09 08:55:22 -06:00
|
|
|
<br>
|
|
|
|
<i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
|
|
|
|
</P>
|
|
|
|
{{end}}
|
|
|
|
{{end}}
|
|
|
|
`))
|
|
|
|
|
|
|
|
type rpcs struct {
|
2020-03-06 08:28:29 -07:00
|
|
|
mu sync.Mutex
|
2020-04-03 13:12:15 -06:00
|
|
|
Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name
|
|
|
|
Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type rpcStats struct {
|
2020-04-03 13:12:15 -06:00
|
|
|
Method string
|
|
|
|
Started int64
|
|
|
|
Completed int64
|
|
|
|
|
|
|
|
Latency rpcTimeHistogram
|
|
|
|
Received byteUnits
|
|
|
|
Sent byteUnits
|
|
|
|
Codes []*rpcCodeBucket
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-04-20 13:44:34 -06:00
|
|
|
func (r *rpcs) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
|
2020-04-03 13:12:15 -06:00
|
|
|
r.mu.Lock()
|
|
|
|
defer r.mu.Unlock()
|
2020-03-26 20:00:12 -06:00
|
|
|
switch {
|
2020-04-21 07:41:57 -06:00
|
|
|
case event.IsStart(ev):
|
2020-04-03 13:12:15 -06:00
|
|
|
if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
|
|
|
|
stats.Started++
|
|
|
|
}
|
2020-04-21 07:41:57 -06:00
|
|
|
case event.IsEnd(ev):
|
2020-04-03 13:12:15 -06:00
|
|
|
span, stats := r.getRPCSpan(ctx, ev)
|
|
|
|
if stats != nil {
|
|
|
|
endRPC(ctx, ev, span, stats)
|
2020-03-26 20:00:12 -06:00
|
|
|
}
|
2020-04-21 07:41:57 -06:00
|
|
|
case event.IsMetric(ev):
|
2020-04-20 13:44:34 -06:00
|
|
|
sent := byteUnits(tag.SentBytes.Get(lm))
|
|
|
|
rec := byteUnits(tag.ReceivedBytes.Get(lm))
|
2020-04-03 13:12:15 -06:00
|
|
|
if sent != 0 || rec != 0 {
|
|
|
|
if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
|
|
|
|
stats.Sent += sent
|
|
|
|
stats.Received += rec
|
2020-03-26 20:00:12 -06:00
|
|
|
}
|
|
|
|
}
|
2020-03-17 14:00:16 -06:00
|
|
|
}
|
2020-04-03 13:12:15 -06:00
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
2020-04-17 07:32:56 -06:00
|
|
|
func endRPC(ctx context.Context, ev core.Event, span *export.Span, stats *rpcStats) {
|
2020-04-03 13:12:15 -06:00
|
|
|
// 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
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
2020-04-03 13:12:15 -06:00
|
|
|
}
|
|
|
|
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
|
2020-03-17 14:00:16 -06:00
|
|
|
})
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
2020-04-03 13:12:15 -06:00
|
|
|
b.Count++
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
|
|
|
|
2020-04-03 13:12:15 -06:00
|
|
|
// calculate latency if this was an rpc span
|
2020-04-20 21:00:05 -06:00
|
|
|
elapsedTime := span.Finish().At().Sub(span.Start().At())
|
2020-04-03 13:12:15 -06:00
|
|
|
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
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
|
|
|
}
|
2020-04-03 13:12:15 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-17 07:32:56 -06:00
|
|
|
func (r *rpcs) getRPCSpan(ctx context.Context, ev core.Event) (*export.Span, *rpcStats) {
|
2020-04-03 13:12:15 -06:00
|
|
|
// 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
|
2020-04-03 20:59:59 -06:00
|
|
|
return span, r.getRPCStats(span.Start())
|
2020-04-03 13:12:15 -06:00
|
|
|
}
|
|
|
|
|
2020-04-20 13:44:34 -06:00
|
|
|
func (r *rpcs) getRPCStats(lm label.Map) *rpcStats {
|
|
|
|
method := tag.Method.Get(lm)
|
2020-04-03 13:12:15 -06:00
|
|
|
if method == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
set := &r.Inbound
|
2020-04-20 13:44:34 -06:00
|
|
|
if tag.RPCDirection.Get(lm) != tag.Inbound {
|
2020-04-03 13:12:15 -06:00
|
|
|
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() {
|
2020-04-03 20:59:59 -06:00
|
|
|
if status := tag.StatusCode.Get(ev); status != "" {
|
2020-04-03 13:12:15 -06:00
|
|
|
return status
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
2019-07-09 08:55:22 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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"})
|
|
|
|
}
|