mirror of
https://github.com/golang/go
synced 2024-11-18 20:24:41 -07:00
80f63e2b9b
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>
239 lines
6.1 KiB
Go
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"})
|
|
}
|