diff --git a/internal/lsp/debug/rpc.go b/internal/lsp/debug/rpc.go new file mode 100644 index 0000000000..87624aef4a --- /dev/null +++ b/internal/lsp/debug/rpc.go @@ -0,0 +1,209 @@ +// 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 ( + "fmt" + "html/template" + "log" + "net/http" + "sort" + + "golang.org/x/tools/internal/lsp/telemetry" + "golang.org/x/tools/internal/lsp/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) observeMetric(data metric.Data) { + for i, group := range data.Groups() { + set := &r.Inbound + if group.Get(telemetry.RPCDirection) == telemetry.Outbound { + set = &r.Outbound + } + method, ok := group.Get(telemetry.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(telemetry.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[i].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"}) +} diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go index 26d8b55957..2427cdce4a 100644 --- a/internal/lsp/debug/serve.go +++ b/internal/lsp/debug/serve.go @@ -20,6 +20,7 @@ import ( "sync" "golang.org/x/tools/internal/lsp/telemetry/metric" + "golang.org/x/tools/internal/lsp/telemetry/worker" "golang.org/x/tools/internal/span" ) @@ -215,6 +216,8 @@ func Serve(ctx context.Context, addr string) error { log.Printf("Debug serving on port: %d", listener.Addr().(*net.TCPAddr).Port) prometheus := prometheus{} metric.RegisterObservers(prometheus.observeMetric) + rpcs := rpcs{} + metric.RegisterObservers(rpcs.observeMetric) go func() { mux := http.NewServeMux() mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data })) @@ -225,6 +228,7 @@ func Serve(ctx context.Context, addr string) error { mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) mux.HandleFunc("/metrics/", prometheus.serve) + mux.HandleFunc("/rpc/", Render(rpcTmpl, rpcs.getData)) mux.HandleFunc("/cache/", Render(cacheTmpl, getCache)) mux.HandleFunc("/session/", Render(sessionTmpl, getSession)) mux.HandleFunc("/view/", Render(viewTmpl, getView)) @@ -242,13 +246,18 @@ func Serve(ctx context.Context, addr string) error { func Render(tmpl *template.Template, fun func(*http.Request) interface{}) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - var data interface{} - if fun != nil { - data = fun(r) - } - if err := tmpl.Execute(w, data); err != nil { - log.Print(err) - } + done := make(chan struct{}) + worker.Do(func() { + defer close(done) + var data interface{} + if fun != nil { + data = fun(r) + } + if err := tmpl.Execute(w, data); err != nil { + log.Print(err) + } + }) + <-done } } @@ -288,6 +297,7 @@ td.value { Info Memory Metrics +RPC

{{template "title" .}}

{{block "body" .}} @@ -358,8 +368,6 @@ var debugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` {{define "title"}}GoPls Debug pages{{end}} {{define "body"}} Profiling -RPCz -Tracez {{end}} `))