From f82b303b69d78b21fdc1f53a547848b19117caf1 Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Mon, 8 Jul 2019 15:15:11 -0400 Subject: [PATCH] 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 Reviewed-by: Rebecca Stambler --- internal/jsonrpc2/jsonrpc2.go | 40 ++------ internal/lsp/debug/serve.go | 2 - internal/lsp/telemetry/stats/stats.go | 136 ++++++++++++++++++-------- internal/lsp/telemetry/telemetry.go | 13 +-- 4 files changed, 112 insertions(+), 79 deletions(-) diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go index 52b0188d..4e9cd709 100644 --- a/internal/jsonrpc2/jsonrpc2.go +++ b/internal/jsonrpc2/jsonrpc2.go @@ -16,7 +16,6 @@ import ( "time" "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/trace" ) @@ -81,18 +80,12 @@ type Handler func(context.Context, *Request) type Canceler func(context.Context, *Conn, ID) type rpcStats struct { - server bool - method string - span trace.Span - start time.Time - received int64 - sent int64 + server bool + method string + span trace.Span + start time.Time } -type statsKeyType string - -const rpcStatsKey = statsKeyType("rpcStatsKey") - func start(ctx context.Context, server bool, method string, id *ID) (context.Context, *rpcStats) { if method == "" { 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, start: time.Now(), } - ctx = context.WithValue(ctx, rpcStatsKey, s) mode := telemetry.Outbound if server { 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.RPCID, Value: id}, ) - stats.Record(ctx, telemetry.Started.M(1)) + telemetry.Started.Record(ctx, 1) return ctx, s } @@ -124,13 +116,7 @@ func (s *rpcStats) end(ctx context.Context, err *error) { } elapsedTime := time.Since(s.start) latencyMillis := float64(elapsedTime) / float64(time.Millisecond) - - stats.Record(ctx, - telemetry.ReceivedBytes.M(s.received), - telemetry.SentBytes.M(s.sent), - telemetry.Latency.M(latencyMillis), - ) - + telemetry.Latency.Record(ctx, latencyMillis) 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) n, err := c.stream.Write(ctx, data) - rpcStats.sent += n + telemetry.SentBytes.Record(ctx, n) return err } @@ -241,7 +227,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface before := time.Now() c.Logger(Send, request.ID, -1, request.Method, request.Params, nil) n, err := c.stream.Write(ctx, data) - rpcStats.sent += n + telemetry.SentBytes.Record(ctx, n) if err != nil { // sending failed, we will never get a response, so don't leave it pending 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) n, err := r.conn.stream.Write(ctx, data) - - v := ctx.Value(rpcStatsKey) - if v != nil { - v.(*rpcStats).sent += n - } else { - panic("no stats available in reply") - } + telemetry.SentBytes.Record(ctx, n) if err != nil { // 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 reqCtx, cancelReq := context.WithCancel(ctx) reqCtx, rpcStats := start(reqCtx, true, msg.Method, msg.ID) - rpcStats.received += n + telemetry.ReceivedBytes.Record(ctx, n) thisRequest := nextRequest nextRequest = make(chan struct{}) req := &Request{ diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go index 73cbcd1d..c7c6a260 100644 --- a/internal/lsp/debug/serve.go +++ b/internal/lsp/debug/serve.go @@ -19,7 +19,6 @@ import ( "strconv" "sync" - "golang.org/x/tools/internal/lsp/telemetry" "golang.org/x/tools/internal/span" ) @@ -217,7 +216,6 @@ func Serve(ctx context.Context, addr string) error { mux := http.NewServeMux() mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data })) mux.HandleFunc("/debug/", Render(debugTmpl, nil)) - telemetry.Handle(mux) mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) diff --git a/internal/lsp/telemetry/stats/stats.go b/internal/lsp/telemetry/stats/stats.go index cade4151..6da3ed39 100644 --- a/internal/lsp/telemetry/stats/stats.go +++ b/internal/lsp/telemetry/stats/stats.go @@ -3,45 +3,103 @@ // license that can be found in the LICENSE file. // 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 -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) {} +import ( + "context" ) + +// 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) + } +} diff --git a/internal/lsp/telemetry/telemetry.go b/internal/lsp/telemetry/telemetry.go index f97c1e51..6b56c070 100644 --- a/internal/lsp/telemetry/telemetry.go +++ b/internal/lsp/telemetry/telemetry.go @@ -7,8 +7,6 @@ package telemetry import ( - "net/http" - "golang.org/x/tools/internal/lsp/telemetry/stats" "golang.org/x/tools/internal/lsp/telemetry/tag" ) @@ -25,12 +23,11 @@ const ( ) var ( - Handle = func(mux *http.ServeMux) {} - - Started = stats.NullInt64Measure() - ReceivedBytes = stats.NullInt64Measure() - SentBytes = stats.NullInt64Measure() - Latency = stats.NullFloat64Measure() + // create the stats we measure + Started = stats.Int64("started", "Count of started RPCs.", stats.UnitDimensionless) + ReceivedBytes = stats.Int64("received_bytes", "Bytes received.", stats.UnitBytes) + SentBytes = stats.Int64("sent_bytes", "Bytes sent.", stats.UnitBytes) + Latency = stats.Float64("latency_ms", "Elapsed time in milliseconds", stats.UnitMilliseconds) ) const (