1
0
mirror of https://github.com/golang/go synced 2024-11-18 20:04:52 -07:00

internal/lsp: add new stats library

This is the basic library that allows for recording of stats about the program
operation.

Change-Id: I09f7e3de5fc37aaf29bc0db46f15b15056fc0eb2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/185338
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2019-07-08 15:15:11 -04:00
parent 75a6f91e26
commit f82b303b69
4 changed files with 112 additions and 79 deletions

View File

@ -16,7 +16,6 @@ import (
"time" "time"
"golang.org/x/tools/internal/lsp/telemetry" "golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/lsp/telemetry/stats"
"golang.org/x/tools/internal/lsp/telemetry/tag" "golang.org/x/tools/internal/lsp/telemetry/tag"
"golang.org/x/tools/internal/lsp/telemetry/trace" "golang.org/x/tools/internal/lsp/telemetry/trace"
) )
@ -81,18 +80,12 @@ type Handler func(context.Context, *Request)
type Canceler func(context.Context, *Conn, ID) type Canceler func(context.Context, *Conn, ID)
type rpcStats struct { type rpcStats struct {
server bool server bool
method string method string
span trace.Span span trace.Span
start time.Time start time.Time
received int64
sent int64
} }
type statsKeyType string
const rpcStatsKey = statsKeyType("rpcStatsKey")
func start(ctx context.Context, server bool, method string, id *ID) (context.Context, *rpcStats) { func start(ctx context.Context, server bool, method string, id *ID) (context.Context, *rpcStats) {
if method == "" { if method == "" {
panic("no method in rpc stats") panic("no method in rpc stats")
@ -102,7 +95,6 @@ func start(ctx context.Context, server bool, method string, id *ID) (context.Con
method: method, method: method,
start: time.Now(), start: time.Now(),
} }
ctx = context.WithValue(ctx, rpcStatsKey, s)
mode := telemetry.Outbound mode := telemetry.Outbound
if server { if server {
mode = telemetry.Inbound mode = telemetry.Inbound
@ -112,7 +104,7 @@ func start(ctx context.Context, server bool, method string, id *ID) (context.Con
tag.Tag{Key: telemetry.RPCDirection, Value: mode}, tag.Tag{Key: telemetry.RPCDirection, Value: mode},
tag.Tag{Key: telemetry.RPCID, Value: id}, tag.Tag{Key: telemetry.RPCID, Value: id},
) )
stats.Record(ctx, telemetry.Started.M(1)) telemetry.Started.Record(ctx, 1)
return ctx, s return ctx, s
} }
@ -124,13 +116,7 @@ func (s *rpcStats) end(ctx context.Context, err *error) {
} }
elapsedTime := time.Since(s.start) elapsedTime := time.Since(s.start)
latencyMillis := float64(elapsedTime) / float64(time.Millisecond) latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
telemetry.Latency.Record(ctx, latencyMillis)
stats.Record(ctx,
telemetry.ReceivedBytes.M(s.received),
telemetry.SentBytes.M(s.sent),
telemetry.Latency.M(latencyMillis),
)
s.span.End() s.span.End()
} }
@ -199,7 +185,7 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (e
} }
c.Logger(Send, nil, -1, request.Method, request.Params, nil) c.Logger(Send, nil, -1, request.Method, request.Params, nil)
n, err := c.stream.Write(ctx, data) n, err := c.stream.Write(ctx, data)
rpcStats.sent += n telemetry.SentBytes.Record(ctx, n)
return err return err
} }
@ -241,7 +227,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
before := time.Now() before := time.Now()
c.Logger(Send, request.ID, -1, request.Method, request.Params, nil) c.Logger(Send, request.ID, -1, request.Method, request.Params, nil)
n, err := c.stream.Write(ctx, data) n, err := c.stream.Write(ctx, data)
rpcStats.sent += n telemetry.SentBytes.Record(ctx, n)
if err != nil { if err != nil {
// sending failed, we will never get a response, so don't leave it pending // sending failed, we will never get a response, so don't leave it pending
return err return err
@ -336,13 +322,7 @@ func (r *Request) Reply(ctx context.Context, result interface{}, err error) erro
} }
r.conn.Logger(Send, response.ID, elapsed, r.Method, response.Result, response.Error) r.conn.Logger(Send, response.ID, elapsed, r.Method, response.Result, response.Error)
n, err := r.conn.stream.Write(ctx, data) n, err := r.conn.stream.Write(ctx, data)
telemetry.SentBytes.Record(ctx, n)
v := ctx.Value(rpcStatsKey)
if v != nil {
v.(*rpcStats).sent += n
} else {
panic("no stats available in reply")
}
if err != nil { if err != nil {
// TODO(iancottrell): if a stream write fails, we really need to shut down // TODO(iancottrell): if a stream write fails, we really need to shut down
@ -407,7 +387,7 @@ func (c *Conn) Run(ctx context.Context) error {
// if method is set it must be a request // if method is set it must be a request
reqCtx, cancelReq := context.WithCancel(ctx) reqCtx, cancelReq := context.WithCancel(ctx)
reqCtx, rpcStats := start(reqCtx, true, msg.Method, msg.ID) reqCtx, rpcStats := start(reqCtx, true, msg.Method, msg.ID)
rpcStats.received += n telemetry.ReceivedBytes.Record(ctx, n)
thisRequest := nextRequest thisRequest := nextRequest
nextRequest = make(chan struct{}) nextRequest = make(chan struct{})
req := &Request{ req := &Request{

View File

@ -19,7 +19,6 @@ import (
"strconv" "strconv"
"sync" "sync"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/span" "golang.org/x/tools/internal/span"
) )
@ -217,7 +216,6 @@ func Serve(ctx context.Context, addr string) error {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data })) mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data }))
mux.HandleFunc("/debug/", Render(debugTmpl, nil)) mux.HandleFunc("/debug/", Render(debugTmpl, nil))
telemetry.Handle(mux)
mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/profile", pprof.Profile)

View File

@ -3,45 +3,103 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package stats provides support for recording telemetry statistics. // Package stats provides support for recording telemetry statistics.
// It acts as a coordination point between things that want to record stats,
// and things that want to aggregate and report stats.
package stats package stats
import "context" import (
"context"
type Measure interface {
Name() string
Description() string
Unit() string
}
type Float64Measure interface {
Measure
M(v float64) Measurement
}
type Int64Measure interface {
Measure
M(v int64) Measurement
}
type Measurement interface {
Measure() Measure
Value() float64
}
type nullMeasure struct{}
type nullFloat64Measure struct{ nullMeasure }
type nullInt64Measure struct{ nullMeasure }
func (nullMeasure) Name() string { return "" }
func (nullMeasure) Description() string { return "" }
func (nullMeasure) Unit() string { return "" }
func (nullFloat64Measure) M(v float64) Measurement { return nil }
func (nullInt64Measure) M(v int64) Measurement { return nil }
func NullFloat64Measure() Float64Measure { return nullFloat64Measure{} }
func NullInt64Measure() Int64Measure { return nullInt64Measure{} }
var (
Record = func(ctx context.Context, ms ...Measurement) {}
) )
// Int64Measure is used to record integer values.
type Int64Measure struct {
name string
description string
unit Unit
subscribers []Int64Subscriber
}
// Int64Measure is used to record floating point values.
type Float64Measure struct {
name string
description string
unit Unit
subscribers []Float64Subscriber
}
// Int64Subscriber is the type for functions that want to listen to
// integer statistic events.
type Int64Subscriber func(context.Context, *Int64Measure, int64)
// Float64Subscriber is the type for functions that want to listen to
// floating point statistic events.
type Float64Subscriber func(context.Context, *Float64Measure, float64)
// Unit is used to specify the units for a given measure.
// This is can used for display purposes.
type Unit int
const (
// UnitDimensionless indicates that a measure has no specified units.
UnitDimensionless = Unit(iota)
// UnitBytes indicates that that a measure is recording number of bytes.
UnitBytes
// UnitMilliseconds indicates that a measure is recording a duration in milliseconds.
UnitMilliseconds
)
// Int64 creates a new Int64Measure and prepares it for use.
func Int64(name string, description string, unit Unit) *Int64Measure {
return &Int64Measure{
name: name,
description: description,
unit: unit,
}
}
// Float64 creates a new Float64Measure and prepares it for use.
func Float64(name string, description string, unit Unit) *Float64Measure {
return &Float64Measure{
name: name,
description: description,
unit: unit,
}
}
// Name returns the name this measure was given on construction.
func (m *Int64Measure) Name() string { return m.name }
// Description returns the description this measure was given on construction.
func (m *Int64Measure) Description() string { return m.description }
// Unit returns the units this measure was given on construction.
func (m *Int64Measure) Unit() Unit { return m.unit }
// Subscribe adds a new subscriber to this measure.
func (m *Int64Measure) Subscribe(s Int64Subscriber) { m.subscribers = append(m.subscribers, s) }
// Record delivers a new value to the subscribers of this measure.
func (m *Int64Measure) Record(ctx context.Context, value int64) {
for _, s := range m.subscribers {
s(ctx, m, value)
}
}
// Name returns the name this measure was given on construction.
func (m *Float64Measure) Name() string { return m.name }
// Description returns the description this measure was given on construction.
func (m *Float64Measure) Description() string { return m.description }
// Unit returns the units this measure was given on construction.
func (m *Float64Measure) Unit() Unit { return m.unit }
// Subscribe adds a new subscriber to this measure.
func (m *Float64Measure) Subscribe(s Float64Subscriber) { m.subscribers = append(m.subscribers, s) }
// Record delivers a new value to the subscribers of this measure.
func (m *Float64Measure) Record(ctx context.Context, value float64) {
for _, s := range m.subscribers {
s(ctx, m, value)
}
}

View File

@ -7,8 +7,6 @@
package telemetry package telemetry
import ( import (
"net/http"
"golang.org/x/tools/internal/lsp/telemetry/stats" "golang.org/x/tools/internal/lsp/telemetry/stats"
"golang.org/x/tools/internal/lsp/telemetry/tag" "golang.org/x/tools/internal/lsp/telemetry/tag"
) )
@ -25,12 +23,11 @@ const (
) )
var ( var (
Handle = func(mux *http.ServeMux) {} // create the stats we measure
Started = stats.Int64("started", "Count of started RPCs.", stats.UnitDimensionless)
Started = stats.NullInt64Measure() ReceivedBytes = stats.Int64("received_bytes", "Bytes received.", stats.UnitBytes)
ReceivedBytes = stats.NullInt64Measure() SentBytes = stats.Int64("sent_bytes", "Bytes sent.", stats.UnitBytes)
SentBytes = stats.NullInt64Measure() Latency = stats.Float64("latency_ms", "Elapsed time in milliseconds", stats.UnitMilliseconds)
Latency = stats.NullFloat64Measure()
) )
const ( const (