mirror of
https://github.com/golang/go
synced 2024-11-18 17:44:47 -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:
parent
75a6f91e26
commit
f82b303b69
@ -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{
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 (
|
||||||
|
Loading…
Reference in New Issue
Block a user