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>
diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go
index 52b0188..4e9cd70 100644
--- a/internal/jsonrpc2/jsonrpc2.go
+++ b/internal/jsonrpc2/jsonrpc2.go
@@ -16,7 +16,6 @@
"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 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 @@
method: method,
start: time.Now(),
}
- ctx = context.WithValue(ctx, rpcStatsKey, s)
mode := telemetry.Outbound
if server {
mode = telemetry.Inbound
@@ -112,7 +104,7 @@
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 @@
}
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 @@
}
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 @@
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 @@
}
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 @@
// 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 73cbcd1..c7c6a26 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -19,7 +19,6 @@
"strconv"
"sync"
- "golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/span"
)
@@ -217,7 +216,6 @@
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 cade415..6da3ed3 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 f97c1e5..6b56c07 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 @@
)
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 (