// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package lsprpc import ( "context" "encoding/json" "time" "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/lsp/debug/tag" "golang.org/x/tools/internal/telemetry/event" ) type telemetryHandler struct{} func (h telemetryHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool { stats := h.getStats(ctx) if stats != nil { stats.delivering() } return false } func (h telemetryHandler) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool { return false } func (h telemetryHandler) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context { if r.Method == "" { panic("no method in rpc stats") } stats := &rpcStats{ method: r.Method, id: r.ID, start: time.Now(), direction: direction, payload: r.Params, } ctx = context.WithValue(ctx, statsKey, stats) mode := tag.Outbound if direction == jsonrpc2.Receive { mode = tag.Inbound } ctx, stats.close = event.StartSpan(ctx, r.Method, tag.Method.Of(r.Method), tag.RPCDirection.Of(mode), tag.RPCID.Of(r.ID.String()), ) event.Record(ctx, tag.Started.Of(1)) _, stats.delivering = event.StartSpan(ctx, "queued") return ctx } func (h telemetryHandler) Response(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context { return ctx } func (h telemetryHandler) Done(ctx context.Context, err error) { stats := h.getStats(ctx) if err != nil { ctx = event.Label(ctx, tag.StatusCode.Of("ERROR")) } else { ctx = event.Label(ctx, tag.StatusCode.Of("OK")) } elapsedTime := time.Since(stats.start) latencyMillis := float64(elapsedTime) / float64(time.Millisecond) event.Record(ctx, tag.Latency.Of(latencyMillis)) stats.close() } func (h telemetryHandler) Read(ctx context.Context, bytes int64) context.Context { event.Record(ctx, tag.SentBytes.Of(bytes)) return ctx } func (h telemetryHandler) Wrote(ctx context.Context, bytes int64) context.Context { event.Record(ctx, tag.ReceivedBytes.Of(bytes)) return ctx } func (h telemetryHandler) Error(ctx context.Context, err error) { } func (h telemetryHandler) getStats(ctx context.Context) *rpcStats { stats, ok := ctx.Value(statsKey).(*rpcStats) if !ok || stats == nil { method, ok := ctx.Value(tag.Method).(string) if !ok { method = "???" } stats = &rpcStats{ method: method, close: func() {}, } } return stats } type rpcStats struct { method string direction jsonrpc2.Direction id *jsonrpc2.ID payload *json.RawMessage start time.Time delivering func() close func() } type statsKeyType int const statsKey = statsKeyType(0)