1
0
mirror of https://github.com/golang/go synced 2024-11-05 11:56:12 -07:00

internal/lsp: rewrite the rpc debug page

Now it only uses the telemetry messages directly rather than the metric system.
This is much faster and more direct, and removes a blocker on improving the
metrics support.
Also fixes the fact that recieced and sent were the wrong way round before,
probably as an artifact of the old protocol logging code, and also removes
the bytes histogram which was a lie (it was a histogram of write sizes that
was presented as a histogram of message sizes)

fixes golang/go#38168

Change-Id: Ib1c3459c0ff1cf0c6087a828981e80c1ce1c5c1b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/227139
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Ian Cottrell 2020-04-03 15:12:15 -04:00
parent ccaaa5c26f
commit 7db14c95bf
2 changed files with 138 additions and 138 deletions

View File

@ -112,7 +112,7 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (e
event.Record(ctx, tag.Started.Of(1))
n, err := c.stream.Write(ctx, data)
event.Record(ctx, tag.ReceivedBytes.Of(n))
event.Record(ctx, tag.SentBytes.Of(n))
return err
}
@ -161,7 +161,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
}()
// now we are ready to send
n, err := c.stream.Write(ctx, data)
event.Record(ctx, tag.ReceivedBytes.Of(n))
event.Record(ctx, tag.SentBytes.Of(n))
if err != nil {
// sending failed, we will never get a response, so don't leave it pending
return err
@ -239,7 +239,7 @@ func (r *Request) Reply(ctx context.Context, result interface{}, err error) erro
return err
}
n, err := r.conn.stream.Write(ctx, data)
event.Record(ctx, tag.ReceivedBytes.Of(n))
event.Record(ctx, tag.SentBytes.Of(n))
if err != nil {
// TODO(iancottrell): if a stream write fails, we really need to shut down
@ -302,7 +302,7 @@ func (c *Conn) Run(runCtx context.Context, handler Handler) error {
)
event.Record(reqCtx,
tag.Started.Of(1),
tag.SentBytes.Of(n))
tag.ReceivedBytes.Of(n))
req := &Request{
conn: c,

View File

@ -16,7 +16,6 @@ import (
"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(`
@ -32,10 +31,10 @@ var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
<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}}
<i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}}
<br>
<i>Received</i> {{with .Received}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
<i>Sent</i> {{with .Sent}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
<i>Received</i> {{.Received}} (avg. {{.ReceivedMean}})
<i>Sent</i> {{.Sent}} (avg. {{.SentMean}})
<br>
<i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
</P>
@ -45,25 +44,24 @@ var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
type rpcs struct {
mu sync.Mutex
Inbound []*rpcStats
Outbound []*rpcStats
Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name
Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name
}
type rpcStats struct {
Method string
Started int64
Completed int64
InProgress int64
Latency rpcTimeHistogram
Received rpcBytesHistogram
Sent rpcBytesHistogram
Received byteUnits
Sent byteUnits
Codes []*rpcCodeBucket
}
type rpcTimeHistogram struct {
Sum timeUnits
Count int64
Mean timeUnits
Min timeUnits
Max timeUnits
Values []rpcTimeBucket
@ -74,78 +72,43 @@ type rpcTimeBucket struct {
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
switch {
case ev.IsStartSpan():
if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
stats.Started++
}
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}
case ev.IsEndSpan():
span, stats := r.getRPCSpan(ctx, ev)
if stats != nil {
endRPC(ctx, ev, span, stats)
}
stats := (*set)[index]
switch data.Handle() {
case started.Name:
stats.Started = data.(*metric.Int64Data).Rows[i]
case completed.Name:
status := tag.StatusCode.Get(groupTags)
case ev.IsRecord():
sent := byteUnits(tag.SentBytes.Get(tagMap))
rec := byteUnits(tag.ReceivedBytes.Get(tagMap))
if sent != 0 || rec != 0 {
if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
stats.Sent += sent
stats.Received += rec
}
}
}
return ctx
}
func endRPC(ctx context.Context, ev event.Event, span *export.Span, stats *rpcStats) {
// update the basic counts
stats.Completed++
// get and record the status code
if status := getStatusCode(span); status != "" {
var b *rpcCodeBucket
for c, entry := range stats.Codes {
if entry.Key == status {
@ -160,52 +123,89 @@ func (r *rpcs) ProcessEvent(ctx context.Context, ev event.Event, tagMap event.Ta
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]
b.Count++
}
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)
// calculate latency if this was an rpc span
elapsedTime := span.Finish().At.Sub(span.Start().At)
latencyMillis := timeUnits(elapsedTime) / timeUnits(time.Millisecond)
if stats.Latency.Count == 0 {
stats.Latency.Min = latencyMillis
stats.Latency.Max = latencyMillis
} else {
if stats.Latency.Min > latencyMillis {
stats.Latency.Min = latencyMillis
}
if stats.Latency.Max < latencyMillis {
stats.Latency.Max = latencyMillis
}
}
stats.Latency.Count++
stats.Latency.Sum += latencyMillis
for i := range stats.Latency.Values {
if stats.Latency.Values[i].Limit > latencyMillis {
stats.Latency.Values[i].Count++
break
}
}
}
for _, set := range [][]*rpcStats{r.Inbound, r.Outbound} {
for _, stats := range set {
stats.Completed = 0
for _, b := range stats.Codes {
stats.Completed += b.Count
func (r *rpcs) getRPCSpan(ctx context.Context, ev event.Event) (*export.Span, *rpcStats) {
// get the span
span := export.GetSpan(ctx)
if span == nil {
return nil, nil
}
stats.InProgress = stats.Started - stats.Completed
// use the span start event look up the correct stats block
// we do this because it prevents us matching a sub span
startMap := span.Start().Map()
return span, r.getRPCStats(startMap)
}
func (r *rpcs) getRPCStats(tagMap event.TagMap) *rpcStats {
method := tag.Method.Get(tagMap)
if method == "" {
return nil
}
set := &r.Inbound
if tag.RPCDirection.Get(tagMap) != tag.Inbound {
set = &r.Outbound
}
// get the record for this method
index := sort.Search(len(*set), func(i int) bool {
return (*set)[i].Method >= method
})
if index < len(*set) && (*set)[index].Method == method {
return (*set)[index]
}
old := *set
*set = make([]*rpcStats, len(old)+1)
copy(*set, old[:index])
copy((*set)[index+1:], old[index:])
stats := &rpcStats{Method: method}
stats.Latency.Values = make([]rpcTimeBucket, len(millisecondsDistribution))
for i, m := range millisecondsDistribution {
stats.Latency.Values[i].Limit = timeUnits(m)
}
(*set)[index] = stats
return stats
}
func (s *rpcStats) InProgress() int64 { return s.Started - s.Completed }
func (s *rpcStats) SentMean() byteUnits { return s.Sent / byteUnits(s.Started) }
func (s *rpcStats) ReceivedMean() byteUnits { return s.Received / byteUnits(s.Started) }
func (h *rpcTimeHistogram) Mean() timeUnits { return h.Sum / timeUnits(h.Count) }
func getStatusCode(span *export.Span) string {
for _, ev := range span.Events() {
if status := tag.StatusCode.Get(ev.Map()); status != "" {
return status
}
}
return ctx
return ""
}
func (r *rpcs) getData(req *http.Request) interface{} {