mirror of
https://github.com/golang/go
synced 2024-09-30 22:58:34 -06:00
7db14c95bf
Now it only uses the telemetry messages directly rather than the metric system. This is much faster and more direct, and removes a blocker on improving the metrics support. Also fixes the fact that recieced and sent were the wrong way round before, probably as an artifact of the old protocol logging code, and also removes the bytes histogram which was a lie (it was a histogram of write sizes that was presented as a histogram of message sizes) fixes golang/go#38168 Change-Id: Ib1c3459c0ff1cf0c6087a828981e80c1ce1c5c1b Reviewed-on: https://go-review.googlesource.com/c/tools/+/227139 Run-TryBot: Ian Cottrell <iancottrell@google.com> Reviewed-by: Robert Findley <rfindley@google.com>
239 lines
5.7 KiB
Go
239 lines
5.7 KiB
Go
// 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/lsp/debug/tag"
|
|
"golang.org/x/tools/internal/telemetry/event"
|
|
"golang.org/x/tools/internal/telemetry/export"
|
|
)
|
|
|
|
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 {
|
|
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 event.Event, tagMap event.TagMap) context.Context {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
switch {
|
|
case ev.IsStartSpan():
|
|
if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
|
|
stats.Started++
|
|
}
|
|
case ev.IsEndSpan():
|
|
span, stats := r.getRPCSpan(ctx, ev)
|
|
if stats != nil {
|
|
endRPC(ctx, ev, span, stats)
|
|
}
|
|
case ev.IsRecord():
|
|
sent := byteUnits(tag.SentBytes.Get(tagMap))
|
|
rec := byteUnits(tag.ReceivedBytes.Get(tagMap))
|
|
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 event.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 event.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
|
|
startMap := span.Start().Map()
|
|
return span, r.getRPCStats(startMap)
|
|
}
|
|
|
|
func (r *rpcs) getRPCStats(tagMap event.TagMap) *rpcStats {
|
|
method := tag.Method.Get(tagMap)
|
|
if method == "" {
|
|
return nil
|
|
}
|
|
set := &r.Inbound
|
|
if tag.RPCDirection.Get(tagMap) != 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.Map()); 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"})
|
|
}
|