1
0
mirror of https://github.com/golang/go synced 2024-11-18 20:24:41 -07:00
go/internal/lsp/debug/rpc.go
Ian Cottrell 80f63e2b9b internal/lsp: rewrite the stats using the newer telemetry features
This allows us to reduce the handler interface and delete the telemetry handler.
It is also safer and faster, and can be easily disabled along with the rest of
the telemetry system.

Change-Id: Ia4961d7f2e374f7dc22360d6a4020a065bfeae6f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/225957
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-03-31 00:50:33 +00:00

239 lines
6.1 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"
"golang.org/x/tools/internal/telemetry/export/metric"
)
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}}<b>{{.Count}}</b> {{.Limit}} {{end}}
<br>
<i>Received</i> {{with .Received}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
<i>Sent</i> {{with .Sent}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
<br>
<i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
</P>
{{end}}
{{end}}
`))
type rpcs struct {
mu sync.Mutex
Inbound []*rpcStats
Outbound []*rpcStats
}
type rpcStats struct {
Method string
Started int64
Completed int64
InProgress int64
Latency rpcTimeHistogram
Received rpcBytesHistogram
Sent rpcBytesHistogram
Codes []*rpcCodeBucket
}
type rpcTimeHistogram struct {
Sum timeUnits
Count int64
Mean timeUnits
Min timeUnits
Max timeUnits
Values []rpcTimeBucket
}
type rpcTimeBucket struct {
Limit timeUnits
Count int64
}
type rpcBytesHistogram struct {
Sum byteUnits
Count int64
Mean byteUnits
Min byteUnits
Max byteUnits
Values []rpcBytesBucket
}
type rpcBytesBucket struct {
Limit byteUnits
Count int64
}
type rpcCodeBucket struct {
Key string
Count int64
}
func (r *rpcs) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.TagMap) context.Context {
switch {
case ev.IsEndSpan():
// calculate latency if this was an rpc span
span := export.GetSpan(ctx)
if span == nil {
return ctx
}
// is this a finished rpc span, if so it will have a status code record
for _, ev := range span.Events() {
code := tag.StatusCode.Get(ev.Map())
if code != "" {
elapsedTime := span.Finish().At.Sub(span.Start().At)
latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
statsCtx := event.Label1(ctx, tag.StatusCode.Of(code))
event.Record1(statsCtx, tag.Latency.Of(latencyMillis))
}
}
return ctx
case ev.IsRecord():
// fall through to the metrics handling logic
default:
// ignore all other event types
return ctx
}
r.mu.Lock()
defer r.mu.Unlock()
//TODO(38168): we should just deal with the events here and not use metrics
metrics := metric.Entries.Get(tagMap).([]metric.Data)
for _, data := range metrics {
for i, group := range data.Groups() {
set := &r.Inbound
groupTags := event.NewTagMap(group...)
if tag.RPCDirection.Get(groupTags) == tag.Outbound {
set = &r.Outbound
}
method := tag.Method.Get(groupTags)
index := sort.Search(len(*set), func(i int) bool {
return (*set)[i].Method >= method
})
if index >= len(*set) || (*set)[index].Method != method {
old := *set
*set = make([]*rpcStats, len(old)+1)
copy(*set, old[:index])
copy((*set)[index+1:], old[index:])
(*set)[index] = &rpcStats{Method: method}
}
stats := (*set)[index]
switch data.Handle() {
case started.Name:
stats.Started = data.(*metric.Int64Data).Rows[i]
case completed.Name:
status := tag.StatusCode.Get(groupTags)
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 = data.(*metric.Int64Data).Rows[i]
case latency.Name:
data := data.(*metric.HistogramFloat64Data)
row := data.Rows[i]
stats.Latency.Count = row.Count
stats.Latency.Sum = timeUnits(row.Sum)
stats.Latency.Min = timeUnits(row.Min)
stats.Latency.Max = timeUnits(row.Max)
stats.Latency.Mean = timeUnits(row.Sum) / timeUnits(row.Count)
stats.Latency.Values = make([]rpcTimeBucket, len(data.Info.Buckets))
last := int64(0)
for i, b := range data.Info.Buckets {
stats.Latency.Values[i].Limit = timeUnits(b)
stats.Latency.Values[i].Count = row.Values[i] - last
last = row.Values[i]
}
case sentBytes.Name:
data := data.(*metric.HistogramInt64Data)
row := data.Rows[i]
stats.Sent.Count = row.Count
stats.Sent.Sum = byteUnits(row.Sum)
stats.Sent.Min = byteUnits(row.Min)
stats.Sent.Max = byteUnits(row.Max)
stats.Sent.Mean = byteUnits(row.Sum) / byteUnits(row.Count)
case receivedBytes.Name:
data := data.(*metric.HistogramInt64Data)
row := data.Rows[i]
stats.Received.Count = row.Count
stats.Received.Sum = byteUnits(row.Sum)
stats.Sent.Min = byteUnits(row.Min)
stats.Sent.Max = byteUnits(row.Max)
stats.Received.Mean = byteUnits(row.Sum) / byteUnits(row.Count)
}
}
}
for _, set := range [][]*rpcStats{r.Inbound, r.Outbound} {
for _, stats := range set {
stats.Completed = 0
for _, b := range stats.Codes {
stats.Completed += b.Count
}
stats.InProgress = stats.Started - stats.Completed
}
}
return ctx
}
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"})
}