File: latency.go

package info (click to toggle)
golang-github-cue-lang-cue 0.12.0.-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 19,072 kB
  • sloc: sh: 57; makefile: 17
file content (102 lines) | stat: -rw-r--r-- 2,795 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Copyright 2023 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 telemetry

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"

	"cuelang.org/go/internal/golangorgx/telemetry/counter"
)

// latencyKey is used for looking up latency counters.
type latencyKey struct {
	operation, bucket string
	isError           bool
}

var (
	latencyBuckets = []struct {
		end  time.Duration
		name string
	}{
		{10 * time.Millisecond, "<10ms"},
		{50 * time.Millisecond, "<50ms"},
		{100 * time.Millisecond, "<100ms"},
		{200 * time.Millisecond, "<200ms"},
		{500 * time.Millisecond, "<500ms"},
		{1 * time.Second, "<1s"},
		{5 * time.Second, "<5s"},
		{24 * time.Hour, "<24h"},
	}

	latencyCounterMu sync.Mutex
	latencyCounters  = make(map[latencyKey]*counter.Counter) // lazily populated
)

// ForEachLatencyCounter runs the provided function for each current latency
// counter measuring the given operation.
//
// Exported for testing.
func ForEachLatencyCounter(operation string, isError bool, f func(*counter.Counter)) {
	latencyCounterMu.Lock()
	defer latencyCounterMu.Unlock()

	for k, v := range latencyCounters {
		if k.operation == operation && k.isError == isError {
			f(v)
		}
	}
}

// getLatencyCounter returns the counter used to record latency of the given
// operation in the given bucket.
func getLatencyCounter(operation, bucket string, isError bool) *counter.Counter {
	latencyCounterMu.Lock()
	defer latencyCounterMu.Unlock()

	key := latencyKey{operation, bucket, isError}
	c, ok := latencyCounters[key]
	if !ok {
		var name string
		if isError {
			name = fmt.Sprintf("gopls/%s/error-latency:%s", operation, bucket)
		} else {
			name = fmt.Sprintf("gopls/%s/latency:%s", operation, bucket)
		}
		c = counter.New(name)
		latencyCounters[key] = c
	}
	return c
}

// StartLatencyTimer starts a timer for the gopls operation with the given
// name, and returns a func to stop the timer and record the latency sample.
//
// If the context provided to the the resulting func is done, no observation is
// recorded.
func StartLatencyTimer(operation string) func(context.Context, error) {
	start := time.Now()
	return func(ctx context.Context, err error) {
		if errors.Is(ctx.Err(), context.Canceled) {
			// Ignore timing where the operation is cancelled, it may be influenced
			// by client behavior.
			return
		}
		latency := time.Since(start)
		bucketIdx := sort.Search(len(latencyBuckets), func(i int) bool {
			bucket := latencyBuckets[i]
			return latency < bucket.end
		})
		if bucketIdx < len(latencyBuckets) { // ignore latency longer than a day :)
			bucketName := latencyBuckets[bucketIdx].name
			getLatencyCounter(operation, bucketName, err != nil).Inc()
		}
	}
}