golang.org/x/tools/gopls@v0.15.3/internal/telemetry/telemetry_test.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 //go:build go1.21 && !openbsd && !js && !wasip1 && !solaris && !android && !386 6 // +build go1.21,!openbsd,!js,!wasip1,!solaris,!android,!386 7 8 package telemetry_test 9 10 import ( 11 "context" 12 "errors" 13 "os" 14 "strconv" 15 "strings" 16 "testing" 17 "time" 18 19 "golang.org/x/telemetry/counter" 20 "golang.org/x/telemetry/counter/countertest" // requires go1.21+ 21 "golang.org/x/tools/gopls/internal/hooks" 22 "golang.org/x/tools/gopls/internal/protocol" 23 "golang.org/x/tools/gopls/internal/protocol/command" 24 "golang.org/x/tools/gopls/internal/telemetry" 25 . "golang.org/x/tools/gopls/internal/test/integration" 26 "golang.org/x/tools/gopls/internal/util/bug" 27 ) 28 29 func TestMain(m *testing.M) { 30 tmp, err := os.MkdirTemp("", "gopls-telemetry-test") 31 if err != nil { 32 panic(err) 33 } 34 countertest.Open(tmp) 35 defer os.RemoveAll(tmp) 36 Main(m, hooks.Options) 37 } 38 39 func TestTelemetry(t *testing.T) { 40 var ( 41 goversion = "" 42 editor = "vscode" // We set ClientName("Visual Studio Code") below. 43 ) 44 45 // Run gopls once to determine the Go version. 46 WithOptions( 47 Modes(Default), 48 ).Run(t, "", func(_ *testing.T, env *Env) { 49 goversion = strconv.Itoa(env.GoVersion()) 50 }) 51 52 // counters that should be incremented once per session 53 sessionCounters := []*counter.Counter{ 54 counter.New("gopls/client:" + editor), 55 counter.New("gopls/goversion:1." + goversion), 56 counter.New("fwd/vscode/linter:a"), 57 } 58 initialCounts := make([]uint64, len(sessionCounters)) 59 for i, c := range sessionCounters { 60 count, err := countertest.ReadCounter(c) 61 if err != nil { 62 t.Fatalf("ReadCounter(%s): %v", c.Name(), err) 63 } 64 initialCounts[i] = count 65 } 66 67 // Verify that a properly configured session gets notified of a bug on the 68 // server. 69 WithOptions( 70 Modes(Default), // must be in-process to receive the bug report below 71 Settings{"showBugReports": true}, 72 ClientName("Visual Studio Code"), 73 ).Run(t, "", func(_ *testing.T, env *Env) { 74 goversion = strconv.Itoa(env.GoVersion()) 75 addForwardedCounters(env, []string{"vscode/linter:a"}, []int64{1}) 76 const desc = "got a bug" 77 bug.Report(desc) // want a stack counter with the trace starting from here. 78 env.Await(ShownMessage(desc)) 79 }) 80 81 // gopls/editor:client 82 // gopls/goversion:1.x 83 // fwd/vscode/linter:a 84 for i, c := range sessionCounters { 85 want := initialCounts[i] + 1 86 got, err := countertest.ReadCounter(c) 87 if err != nil || got != want { 88 t.Errorf("ReadCounter(%q) = (%v, %v), want (%v, nil)", c.Name(), got, err, want) 89 t.Logf("Current timestamp = %v", time.Now().UTC()) 90 } 91 } 92 93 // gopls/bug 94 bugcount := bug.BugReportCount 95 counts, err := countertest.ReadStackCounter(bugcount) 96 if err != nil { 97 t.Fatalf("ReadStackCounter(bugreportcount) failed - %v", err) 98 } 99 if len(counts) != 1 || !hasEntry(counts, t.Name(), 1) { 100 t.Errorf("read stackcounter(%q) = (%#v, %v), want one entry", "gopls/bug", counts, err) 101 t.Logf("Current timestamp = %v", time.Now().UTC()) 102 } 103 } 104 105 func addForwardedCounters(env *Env, names []string, values []int64) { 106 args, err := command.MarshalArgs(command.AddTelemetryCountersArgs{ 107 Names: names, Values: values, 108 }) 109 if err != nil { 110 env.T.Fatal(err) 111 } 112 var res error 113 env.ExecuteCommand(&protocol.ExecuteCommandParams{ 114 Command: command.AddTelemetryCounters.ID(), 115 Arguments: args, 116 }, res) 117 if res != nil { 118 env.T.Errorf("%v failed - %v", command.AddTelemetryCounters.ID(), res) 119 } 120 } 121 122 func hasEntry(counts map[string]uint64, pattern string, want uint64) bool { 123 for k, v := range counts { 124 if strings.Contains(k, pattern) && v == want { 125 return true 126 } 127 } 128 return false 129 } 130 131 func TestLatencyCounter(t *testing.T) { 132 const operation = "TestLatencyCounter" // a unique operation name 133 134 stop := telemetry.StartLatencyTimer(operation) 135 stop(context.Background(), nil) 136 137 for isError, want := range map[bool]uint64{false: 1, true: 0} { 138 if got := totalLatencySamples(t, operation, isError); got != want { 139 t.Errorf("totalLatencySamples(operation=%v, isError=%v) = %d, want %d", operation, isError, got, want) 140 } 141 } 142 } 143 144 func TestLatencyCounter_Error(t *testing.T) { 145 const operation = "TestLatencyCounter_Error" // a unique operation name 146 147 stop := telemetry.StartLatencyTimer(operation) 148 stop(context.Background(), errors.New("bad")) 149 150 for isError, want := range map[bool]uint64{false: 0, true: 1} { 151 if got := totalLatencySamples(t, operation, isError); got != want { 152 t.Errorf("totalLatencySamples(operation=%v, isError=%v) = %d, want %d", operation, isError, got, want) 153 } 154 } 155 } 156 157 func TestLatencyCounter_Cancellation(t *testing.T) { 158 const operation = "TestLatencyCounter_Cancellation" 159 160 stop := telemetry.StartLatencyTimer(operation) 161 ctx, cancel := context.WithCancel(context.Background()) 162 cancel() 163 stop(ctx, nil) 164 165 for isError, want := range map[bool]uint64{false: 0, true: 0} { 166 if got := totalLatencySamples(t, operation, isError); got != want { 167 t.Errorf("totalLatencySamples(operation=%v, isError=%v) = %d, want %d", operation, isError, got, want) 168 } 169 } 170 } 171 172 func totalLatencySamples(t *testing.T, operation string, isError bool) uint64 { 173 var total uint64 174 telemetry.ForEachLatencyCounter(operation, isError, func(c *counter.Counter) { 175 count, err := countertest.ReadCounter(c) 176 if err != nil { 177 t.Errorf("ReadCounter(%s) failed: %v", c.Name(), err) 178 } else { 179 total += count 180 } 181 }) 182 return total 183 } 184 185 func TestLatencyInstrumentation(t *testing.T) { 186 const files = ` 187 -- go.mod -- 188 module mod.test/a 189 go 1.18 190 -- a.go -- 191 package a 192 193 func _() { 194 x := 0 195 _ = x 196 } 197 ` 198 199 // Verify that a properly configured session gets notified of a bug on the 200 // server. 201 WithOptions( 202 Modes(Default), // must be in-process to receive the bug report below 203 ).Run(t, files, func(_ *testing.T, env *Env) { 204 env.OpenFile("a.go") 205 before := totalLatencySamples(t, "completion", false) 206 loc := env.RegexpSearch("a.go", "x") 207 for i := 0; i < 10; i++ { 208 env.Completion(loc) 209 } 210 after := totalLatencySamples(t, "completion", false) 211 if after-before < 10 { 212 t.Errorf("after 10 completions, completion counter went from %d to %d", before, after) 213 } 214 }) 215 }