mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
internal/lsp: add an rpc summary debug page using the metrics
Change-Id: I92481b0e7996747d70081d575a47768222219506 Reviewed-on: https://go-review.googlesource.com/c/tools/+/185986 Run-TryBot: Ian Cottrell <iancottrell@google.com> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
565492930f
commit
ef8e083144
209
internal/lsp/debug/rpc.go
Normal file
209
internal/lsp/debug/rpc.go
Normal file
@ -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"}}
|
||||
<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 {
|
||||
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"})
|
||||
}
|
@ -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 {
|
||||
<a href="/info">Info</a>
|
||||
<a href="/memory">Memory</a>
|
||||
<a href="/metrics">Metrics</a>
|
||||
<a href="/rpc">RPC</a>
|
||||
<hr>
|
||||
<h1>{{template "title" .}}</h1>
|
||||
{{block "body" .}}
|
||||
@ -358,8 +368,6 @@ var debugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
|
||||
{{define "title"}}GoPls Debug pages{{end}}
|
||||
{{define "body"}}
|
||||
<a href="/debug/pprof">Profiling</a>
|
||||
<a href="/debug/rpcz">RPCz</a>
|
||||
<a href="/debug/tracez">Tracez</a>
|
||||
{{end}}
|
||||
`))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user