// 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" "log" "net/http" "sort" tlm "golang.org/x/tools/internal/lsp/telemetry" "golang.org/x/tools/internal/telemetry" "golang.org/x/tools/internal/telemetry/metric" ) var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(` {{define "title"}}RPC Information{{end}} {{define "body"}}

Inbound

{{template "rpcSection" .Inbound}}

Outbound

{{template "rpcSection" .Outbound}} {{end}} {{define "rpcSection"}} {{range .}}

{{.Method}} {{.Started}} traces ({{.InProgress}} in progress)
Latency {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}} By bucket 0s {{range .Latency.Values}}{{.Count}} {{.Limit}} {{end}}
Received {{with .Received}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}} Sent {{with .Sent}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
Result codes {{range .Codes}}{{.Key}}={{.Count}} {{end}}

{{end}} {{end}} `)) type rpcs struct { 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) StartSpan(ctx context.Context, span *telemetry.Span) {} func (r *rpcs) FinishSpan(ctx context.Context, span *telemetry.Span) {} func (r *rpcs) Log(ctx context.Context, event telemetry.Event) {} func (r *rpcs) Flush() {} func (r *rpcs) Metric(ctx context.Context, data telemetry.MetricData) { for i, group := range data.Groups() { set := &r.Inbound if group.Get(tlm.RPCDirection) == tlm.Outbound { set = &r.Outbound } method, ok := group.Get(tlm.Method).(string) if !ok { log.Printf("Not a method... %v", group) continue } 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: stats.Started = data.(*metric.Int64Data).Rows[i] case completed: status, ok := group.Get(tlm.StatusCode).(string) if !ok { log.Printf("Not status... %v", group) continue } 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: 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: 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: 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 } } } 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"}) }