golang.org/x/tools/gopls@v0.15.3/internal/telemetry/latency.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package telemetry 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "sort" 12 "sync" 13 "time" 14 15 "golang.org/x/telemetry/counter" 16 ) 17 18 // latencyKey is used for looking up latency counters. 19 type latencyKey struct { 20 operation, bucket string 21 isError bool 22 } 23 24 var ( 25 latencyBuckets = []struct { 26 end time.Duration 27 name string 28 }{ 29 {10 * time.Millisecond, "<10ms"}, 30 {50 * time.Millisecond, "<50ms"}, 31 {100 * time.Millisecond, "<100ms"}, 32 {200 * time.Millisecond, "<200ms"}, 33 {500 * time.Millisecond, "<500ms"}, 34 {1 * time.Second, "<1s"}, 35 {5 * time.Second, "<5s"}, 36 {24 * time.Hour, "<24h"}, 37 } 38 39 latencyCounterMu sync.Mutex 40 latencyCounters = make(map[latencyKey]*counter.Counter) // lazily populated 41 ) 42 43 // ForEachLatencyCounter runs the provided function for each current latency 44 // counter measuring the given operation. 45 // 46 // Exported for testing. 47 func ForEachLatencyCounter(operation string, isError bool, f func(*counter.Counter)) { 48 latencyCounterMu.Lock() 49 defer latencyCounterMu.Unlock() 50 51 for k, v := range latencyCounters { 52 if k.operation == operation && k.isError == isError { 53 f(v) 54 } 55 } 56 } 57 58 // getLatencyCounter returns the counter used to record latency of the given 59 // operation in the given bucket. 60 func getLatencyCounter(operation, bucket string, isError bool) *counter.Counter { 61 latencyCounterMu.Lock() 62 defer latencyCounterMu.Unlock() 63 64 key := latencyKey{operation, bucket, isError} 65 c, ok := latencyCounters[key] 66 if !ok { 67 var name string 68 if isError { 69 name = fmt.Sprintf("gopls/%s/error-latency:%s", operation, bucket) 70 } else { 71 name = fmt.Sprintf("gopls/%s/latency:%s", operation, bucket) 72 } 73 c = counter.New(name) 74 latencyCounters[key] = c 75 } 76 return c 77 } 78 79 // StartLatencyTimer starts a timer for the gopls operation with the given 80 // name, and returns a func to stop the timer and record the latency sample. 81 // 82 // If the context provided to the the resulting func is done, no observation is 83 // recorded. 84 func StartLatencyTimer(operation string) func(context.Context, error) { 85 start := time.Now() 86 return func(ctx context.Context, err error) { 87 if errors.Is(ctx.Err(), context.Canceled) { 88 // Ignore timing where the operation is cancelled, it may be influenced 89 // by client behavior. 90 return 91 } 92 latency := time.Since(start) 93 bucketIdx := sort.Search(len(latencyBuckets), func(i int) bool { 94 bucket := latencyBuckets[i] 95 return latency < bucket.end 96 }) 97 if bucketIdx < len(latencyBuckets) { // ignore latency longer than a day :) 98 bucketName := latencyBuckets[bucketIdx].name 99 getLatencyCounter(operation, bucketName, err != nil).Inc() 100 } 101 } 102 }