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  }