github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/telemetry/features.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package telemetry 12 13 import ( 14 "fmt" 15 "sync/atomic" 16 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroach/pkg/util/metric" 20 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 21 "github.com/cockroachdb/errors" 22 ) 23 24 // Bucket10 buckets a number by order of magnitude base 10, eg 637 -> 100. 25 // This can be used in telemetry to get ballpark ideas of how users use a given 26 // feature, such as file sizes, qps, etc, without being as revealing as the 27 // raw numbers. 28 // The numbers 0-10 are reported unchanged. 29 func Bucket10(num int64) int64 { 30 if num <= 0 { 31 return 0 32 } 33 if num < 10 { 34 return num 35 } 36 res := int64(10) 37 for ; res < 1000000000000000000 && res*10 < num; res *= 10 { 38 } 39 return res 40 } 41 42 // CountBucketed counts the feature identified by prefix and the value, using 43 // the bucketed value to pick a feature bucket to increment, e.g. a prefix of 44 // "foo.bar" and value of 632 would be counted as "foo.bar.100". 45 func CountBucketed(prefix string, value int64) { 46 Count(fmt.Sprintf("%s.%d", prefix, Bucket10(value))) 47 } 48 49 // Count retrieves and increments the usage counter for the passed feature. 50 // High-volume callers may want to instead use `GetCounter` and hold on to the 51 // returned Counter between calls to Inc, to avoid contention in the registry. 52 func Count(feature string) { 53 Inc(GetCounter(feature)) 54 } 55 56 // Counter represents the usage counter for a given 'feature'. 57 type Counter *int32 58 59 // Inc increments the counter. 60 func Inc(c Counter) { 61 atomic.AddInt32(c, 1) 62 } 63 64 // Read reads the current value of the counter. 65 func Read(c Counter) int32 { 66 return atomic.LoadInt32(c) 67 } 68 69 // GetCounterOnce returns a counter from the global registry, 70 // and asserts it didn't exist previously. 71 func GetCounterOnce(feature string) Counter { 72 counters.RLock() 73 _, ok := counters.m[feature] 74 counters.RUnlock() 75 if ok { 76 panic("counter already exists: " + feature) 77 } 78 return GetCounter(feature) 79 } 80 81 // GetCounter returns a counter from the global registry. 82 func GetCounter(feature string) Counter { 83 counters.RLock() 84 i, ok := counters.m[feature] 85 counters.RUnlock() 86 if ok { 87 return i 88 } 89 90 counters.Lock() 91 defer counters.Unlock() 92 i, ok = counters.m[feature] 93 if !ok { 94 i = new(int32) 95 counters.m[feature] = i 96 } 97 return i 98 } 99 100 // CounterWithMetric combines a telemetry and a metrics counter. 101 type CounterWithMetric struct { 102 telemetry Counter 103 metric *metric.Counter 104 } 105 106 // Necessary for metric metadata registration. 107 var _ metric.Iterable = CounterWithMetric{} 108 109 // NewCounterWithMetric creates a CounterWithMetric. 110 func NewCounterWithMetric(metadata metric.Metadata) CounterWithMetric { 111 return CounterWithMetric{ 112 telemetry: GetCounter(metadata.Name), 113 metric: metric.NewCounter(metadata), 114 } 115 } 116 117 // Inc increments both counters. 118 func (c CounterWithMetric) Inc() { 119 Inc(c.telemetry) 120 c.metric.Inc(1) 121 } 122 123 // Forward the metric.Iterable interface to the metric counter. We 124 // don't just embed the counter because our Inc() interface is a bit 125 // different. 126 127 // GetName implements metric.Iterable 128 func (c CounterWithMetric) GetName() string { 129 return c.metric.GetName() 130 } 131 132 // GetHelp implements metric.Iterable 133 func (c CounterWithMetric) GetHelp() string { 134 return c.metric.GetHelp() 135 } 136 137 // GetMeasurement implements metric.Iterable 138 func (c CounterWithMetric) GetMeasurement() string { 139 return c.metric.GetMeasurement() 140 } 141 142 // GetUnit implements metric.Iterable 143 func (c CounterWithMetric) GetUnit() metric.Unit { 144 return c.metric.GetUnit() 145 } 146 147 // GetMetadata implements metric.Iterable 148 func (c CounterWithMetric) GetMetadata() metric.Metadata { 149 return c.metric.GetMetadata() 150 } 151 152 // Inspect implements metric.Iterable 153 func (c CounterWithMetric) Inspect(f func(interface{})) { 154 c.metric.Inspect(f) 155 } 156 157 func init() { 158 counters.m = make(map[string]Counter, approxFeatureCount) 159 } 160 161 var approxFeatureCount = 1500 162 163 // counters stores the registry of feature-usage counts. 164 // TODO(dt): consider a lock-free map. 165 var counters struct { 166 syncutil.RWMutex 167 m map[string]Counter 168 } 169 170 // QuantizeCounts controls if counts are quantized when fetched. 171 type QuantizeCounts bool 172 173 // ResetCounters controls if counts are reset when fetched. 174 type ResetCounters bool 175 176 const ( 177 // Quantized returns counts quantized to order of magnitude. 178 Quantized QuantizeCounts = true 179 // Raw returns the raw, unquantized counter values. 180 Raw QuantizeCounts = false 181 // ResetCounts resets the counter to zero after fetching its value. 182 ResetCounts ResetCounters = true 183 // ReadOnly leaves the counter value unchanged when reading it. 184 ReadOnly ResetCounters = false 185 ) 186 187 // GetRawFeatureCounts returns current raw, un-quanitzed feature counter values. 188 func GetRawFeatureCounts() map[string]int32 { 189 return GetFeatureCounts(Raw, ReadOnly) 190 } 191 192 // GetFeatureCounts returns the current feature usage counts. 193 // 194 // It optionally quantizes quantizes the returned counts to just order of 195 // magnitude using the `Bucket10` helper, and optionally resets the counters to 196 // zero i.e. if flushing accumulated counts during a report. 197 func GetFeatureCounts(quantize QuantizeCounts, reset ResetCounters) map[string]int32 { 198 counters.RLock() 199 m := make(map[string]int32, len(counters.m)) 200 for k, cnt := range counters.m { 201 var val int32 202 if reset { 203 val = atomic.SwapInt32(cnt, 0) 204 } else { 205 val = atomic.LoadInt32(cnt) 206 } 207 if val != 0 { 208 m[k] = val 209 } 210 } 211 counters.RUnlock() 212 if quantize { 213 for k := range m { 214 m[k] = int32(Bucket10(int64(m[k]))) 215 } 216 } 217 return m 218 } 219 220 // RecordError takes an error and increments the corresponding count 221 // for its error code, and, if it is an unimplemented or internal 222 // error, the count for that feature or the internal error's shortened 223 // stack trace. 224 func RecordError(err error) { 225 if err == nil { 226 return 227 } 228 229 code := pgerror.GetPGCode(err) 230 Count("errorcodes." + code) 231 232 tkeys := errors.GetTelemetryKeys(err) 233 if len(tkeys) > 0 { 234 var prefix string 235 switch code { 236 case pgcode.FeatureNotSupported: 237 prefix = "unimplemented." 238 case pgcode.Internal: 239 prefix = "internalerror." 240 default: 241 prefix = "othererror." + code + "." 242 } 243 244 for _, tk := range tkeys { 245 Count(prefix + tk) 246 } 247 } 248 }