internal/telemetry/telemetry.go (95 lines of code) (raw):
package telemetry
import (
"context"
"fmt"
"time"
lsp "github.com/Azure/azapi-lsp/internal/protocol"
)
var _ Sender = &Telemetry{}
type Telemetry struct {
version int
notifier Notifier
}
type Notifier interface {
Notify(ctx context.Context, method string, params interface{}) error
}
func NewSender(version int, notifier Notifier) (*Telemetry, error) {
if version != lsp.TelemetryFormatVersion {
return nil, fmt.Errorf("unsupported telemetry format version: %d", version)
}
return &Telemetry{
version: version,
notifier: notifier,
}, nil
}
func (t *Telemetry) SendEvent(ctx context.Context, name string, properties map[string]interface{}) {
for _, limiter := range telemetryRateLimiters {
if !limiter.Allow(name, properties) {
return
}
}
_ = t.notifier.Notify(ctx, "telemetry/event", lsp.TelemetryEvent{
Version: t.version,
Name: name,
Properties: properties,
})
}
var telemetryRateLimiters []TelemetryRateLimiter
func init() {
telemetryRateLimiters = make([]TelemetryRateLimiter, 0)
telemetryRateLimiters = append(telemetryRateLimiters, &HoverTelemetryRateLimiter{})
telemetryRateLimiters = append(telemetryRateLimiters, &CompletionTelemetryRateLimiter{})
}
type TelemetryRateLimiter interface {
Allow(name string, properties map[string]interface{}) bool
}
var _ TelemetryRateLimiter = &HoverTelemetryRateLimiter{}
type HoverTelemetryRateLimiter struct {
lastSentTimeStamp int64
}
func (h *HoverTelemetryRateLimiter) Allow(name string, properties map[string]interface{}) bool {
if name != "textDocument/hover" {
return true
}
// always allow the resource-definition hover
if properties != nil && properties["kind"] != nil {
if kind, ok := properties["kind"].(string); ok && kind == "resource-definition" {
return true
}
}
timeStampNow := time.Now().Unix()
if h.lastSentTimeStamp == 0 {
h.lastSentTimeStamp = timeStampNow
return true
}
// minimum interval between two hover telemetry events is 1 hour
if timeStampNow-h.lastSentTimeStamp < 3600 {
return false
}
h.lastSentTimeStamp = timeStampNow
return true
}
var _ TelemetryRateLimiter = &CompletionTelemetryRateLimiter{}
type CompletionTelemetryRateLimiter struct {
lastSentTimeStamp int64
}
func (c *CompletionTelemetryRateLimiter) Allow(name string, properties map[string]interface{}) bool {
if name != "textDocument/completion" {
return true
}
// always allow code-sample and template completions
if properties != nil && properties["kind"] != nil {
if kind, ok := properties["kind"].(string); ok {
if kind == "code-sample" || kind == "template" {
return true
}
}
}
timeStampNow := time.Now().Unix()
if c.lastSentTimeStamp == 0 {
c.lastSentTimeStamp = timeStampNow
return true
}
// minimum interval between two completion telemetry events is 1 hour
if timeStampNow-c.lastSentTimeStamp < 3600 {
return false
}
c.lastSentTimeStamp = timeStampNow
return true
}