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}}
`))