github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/tests/integration/metrics_registry_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package integration 5 6 import ( 7 "bytes" 8 "fmt" 9 "sort" 10 "strconv" 11 "sync" 12 "sync/atomic" 13 "testing" 14 "time" 15 16 "github.com/ydb-platform/ydb-go-sdk/v3" 17 "github.com/ydb-platform/ydb-go-sdk/v3/metrics" 18 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 19 ) 20 21 func movingAverage[T interface { 22 time.Duration | float64 23 }]( 24 oldValue T, newValue T, α float64, 25 ) T { 26 return T(α*float64(newValue) + (1-α)*float64(oldValue)) 27 } 28 29 func nameLabelValuesToString(name string, labelValues map[string]string) string { 30 var buffer bytes.Buffer 31 fmt.Fprintf(&buffer, name) 32 fmt.Fprintf(&buffer, "{") 33 labels := make([]string, 0, len(labelValues)) 34 for k := range labelValues { 35 labels = append(labels, k) 36 } 37 sort.Strings(labels) 38 for i, l := range labels { 39 if i != 0 { 40 fmt.Fprintf(&buffer, ",") 41 } 42 fmt.Fprintf(&buffer, "%s=%s", l, labelValues[l]) 43 } 44 fmt.Fprintf(&buffer, "}") 45 return buffer.String() 46 } 47 48 func nameLabelNamesToString(name string, labelNames []string) string { 49 var buffer bytes.Buffer 50 fmt.Fprintf(&buffer, name) 51 fmt.Fprintf(&buffer, "{") 52 sort.Strings(labelNames) 53 for i, name := range labelNames { 54 if i != 0 { 55 fmt.Fprintf(&buffer, ",") 56 } 57 fmt.Fprintf(&buffer, name) 58 } 59 fmt.Fprintf(&buffer, "}") 60 return buffer.String() 61 } 62 63 type counter struct { 64 name string 65 value uint64 66 m sync.RWMutex 67 } 68 69 func (c *counter) Inc() { 70 c.m.Lock() 71 defer c.m.Unlock() 72 c.value += 1 73 } 74 75 func (c *counter) Name() string { 76 return c.name 77 } 78 79 func (c *counter) Value() string { 80 c.m.RLock() 81 defer c.m.RUnlock() 82 return fmt.Sprintf("%d", c.value) 83 } 84 85 // define custom counterVec 86 type counterVec struct { 87 name string 88 labelNames []string 89 counters map[string]*counter 90 m sync.RWMutex 91 } 92 93 func (vec *counterVec) With(labels map[string]string) metrics.Counter { 94 name := nameLabelValuesToString(vec.name, labels) 95 vec.m.RLock() 96 c, has := vec.counters[name] 97 vec.m.RUnlock() 98 if has { 99 return c 100 } 101 if has { 102 return c 103 } 104 c = &counter{ 105 name: name, 106 } 107 vec.m.Lock() 108 vec.counters[name] = c 109 vec.m.Unlock() 110 return c 111 } 112 113 func (vec *counterVec) Name() string { 114 return vec.name 115 } 116 117 func (vec *counterVec) Values() (values []string) { 118 vec.m.RLock() 119 defer vec.m.RUnlock() 120 for _, g := range vec.counters { 121 values = append(values, fmt.Sprintf("%s = %s", g.Name(), g.Value())) 122 } 123 return values 124 } 125 126 type timer struct { 127 name string 128 value time.Duration 129 m sync.RWMutex 130 } 131 132 func (t *timer) Record(value time.Duration) { 133 t.m.Lock() 134 defer t.m.Unlock() 135 t.value = movingAverage(t.value, value, 0.9) 136 } 137 138 func (t *timer) Name() string { 139 return t.name 140 } 141 142 func (t *timer) Value() string { 143 t.m.RLock() 144 defer t.m.RUnlock() 145 return fmt.Sprintf("%v", t.value) 146 } 147 148 // define custom timerVec 149 type timerVec struct { 150 name string 151 labelNames []string 152 timers map[string]*timer 153 m sync.RWMutex 154 } 155 156 func (vec *timerVec) With(labels map[string]string) metrics.Timer { 157 name := nameLabelValuesToString(vec.name, labels) 158 vec.m.RLock() 159 t, has := vec.timers[name] 160 vec.m.RUnlock() 161 if has { 162 return t 163 } 164 t = &timer{ 165 name: name, 166 } 167 vec.m.Lock() 168 vec.timers[name] = t 169 vec.m.Unlock() 170 return t 171 } 172 173 func (vec *timerVec) Name() string { 174 return vec.name 175 } 176 177 func (vec *timerVec) Values() (values []string) { 178 vec.m.RLock() 179 defer vec.m.RUnlock() 180 for _, t := range vec.timers { 181 values = append(values, fmt.Sprintf("%s = %s", t.Name(), t.Value())) 182 } 183 return values 184 } 185 186 type bin struct { 187 value float64 // left corner of bucket 188 count uint64 189 } 190 191 type histogram struct { 192 name string 193 buckets []*bin 194 m sync.RWMutex 195 } 196 197 func (h *histogram) Record(value float64) { 198 h.m.Lock() 199 defer h.m.Unlock() 200 for i := len(h.buckets) - 1; i >= 0; i-- { 201 if h.buckets[i].value < value { 202 atomic.AddUint64(&h.buckets[i].count, 1) 203 return 204 } 205 } 206 } 207 208 func (h *histogram) Name() string { 209 return h.name 210 } 211 212 func (h *histogram) Value() string { 213 var buffer bytes.Buffer 214 h.m.RLock() 215 defer h.m.RUnlock() 216 buffer.WriteByte('[') 217 for i := 0; i < len(h.buckets); i++ { 218 if i > 0 { 219 buffer.WriteByte(',') 220 } 221 buffer.WriteByte('[') 222 buffer.WriteString(strconv.FormatFloat(h.buckets[i].value, 'f', -1, 64)) 223 buffer.WriteString("..") 224 if i == len(h.buckets)-1 { 225 buffer.WriteString(".") 226 } else { 227 buffer.WriteString(strconv.FormatFloat(h.buckets[i+1].value, 'f', -1, 64)) 228 } 229 buffer.WriteString("]:") 230 buffer.WriteString(strconv.FormatUint(atomic.LoadUint64(&h.buckets[i].count), 10)) 231 } 232 buffer.WriteByte(']') 233 return buffer.String() 234 } 235 236 // define custom histogramVec 237 type histogramVec struct { 238 name string 239 labelNames []string 240 histograms map[string]*histogram 241 buckets []float64 242 m sync.RWMutex 243 } 244 245 func (vec *histogramVec) With(labels map[string]string) metrics.Histogram { 246 name := nameLabelValuesToString(vec.name, labels) 247 vec.m.RLock() 248 h, has := vec.histograms[name] 249 vec.m.RUnlock() 250 if has { 251 return h 252 } 253 h = &histogram{ 254 name: name, 255 buckets: make([]*bin, len(vec.buckets)), 256 } 257 for i, v := range vec.buckets { 258 h.buckets[i] = &bin{ 259 value: v, 260 } 261 } 262 vec.m.Lock() 263 vec.histograms[name] = h 264 vec.m.Unlock() 265 return h 266 } 267 268 func (vec *histogramVec) Name() string { 269 return vec.name 270 } 271 272 func (vec *histogramVec) Values() (values []string) { 273 vec.m.RLock() 274 defer vec.m.RUnlock() 275 for _, t := range vec.histograms { 276 values = append(values, fmt.Sprintf("%s = %s", t.Name(), t.Value())) 277 } 278 return values 279 } 280 281 // define custom gaugeVec 282 type gaugeVec struct { 283 name string 284 labelNames []string 285 gauges map[string]*gauge 286 m sync.RWMutex 287 } 288 289 type gauge struct { 290 name string 291 value float64 292 m sync.RWMutex 293 } 294 295 func (g *gauge) Name() string { 296 return g.name 297 } 298 299 func (g *gauge) Value() string { 300 g.m.RLock() 301 defer g.m.RUnlock() 302 return fmt.Sprintf("%f", g.value) 303 } 304 305 func (vec *gaugeVec) With(labels map[string]string) metrics.Gauge { 306 name := nameLabelValuesToString(vec.name, labels) 307 vec.m.RLock() 308 g, has := vec.gauges[name] 309 vec.m.RUnlock() 310 if has { 311 return g 312 } 313 g = &gauge{ 314 name: name, 315 } 316 vec.m.Lock() 317 vec.gauges[name] = g 318 vec.m.Unlock() 319 return g 320 } 321 322 func (g *gauge) Add(value float64) { 323 g.m.Lock() 324 defer g.m.Unlock() 325 g.value += value 326 } 327 328 func (g *gauge) Set(value float64) { 329 g.m.Lock() 330 defer g.m.Unlock() 331 g.value = value 332 } 333 334 func (vec *gaugeVec) Name() string { 335 return vec.name 336 } 337 338 func (vec *gaugeVec) Values() (values []string) { 339 vec.m.RLock() 340 defer vec.m.RUnlock() 341 for _, g := range vec.gauges { 342 values = append(values, fmt.Sprintf("%s = %s", g.Name(), g.Value())) 343 } 344 return values 345 } 346 347 type vec[T any] struct { 348 mtx sync.RWMutex 349 data map[string]*T 350 } 351 352 func newVec[T any]() *vec[T] { 353 return &vec[T]{ 354 data: make(map[string]*T, 0), 355 } 356 } 357 358 func (v *vec[T]) add(key string, element *T) *T { 359 v.mtx.Lock() 360 defer v.mtx.Unlock() 361 if _, has := v.data[key]; has { 362 panic(fmt.Sprintf("key = %s already exists", key)) 363 } 364 v.data[key] = element 365 return element 366 } 367 368 func (v *vec[T]) iterate(f func(element *T)) { 369 v.mtx.RLock() 370 defer v.mtx.RUnlock() 371 for _, element := range v.data { 372 f(element) 373 } 374 } 375 376 // define custom registryConfig 377 type registryConfig struct { 378 prefix string 379 details trace.Details 380 gauges *vec[gaugeVec] 381 counters *vec[counterVec] 382 timers *vec[timerVec] 383 histograms *vec[histogramVec] 384 } 385 386 func (registry *registryConfig) CounterVec(name string, labelNames ...string) metrics.CounterVec { 387 return registry.counters.add(nameLabelNamesToString(registry.prefix+"."+name, labelNames), &counterVec{ 388 name: registry.prefix + "." + name, 389 labelNames: labelNames, 390 counters: make(map[string]*counter), 391 }) 392 } 393 394 func (registry *registryConfig) GaugeVec(name string, labelNames ...string) metrics.GaugeVec { 395 return registry.gauges.add(nameLabelNamesToString(registry.prefix+"."+name, labelNames), &gaugeVec{ 396 name: registry.prefix + "." + name, 397 labelNames: labelNames, 398 gauges: make(map[string]*gauge), 399 }) 400 } 401 402 func (registry *registryConfig) TimerVec(name string, labelNames ...string) metrics.TimerVec { 403 return registry.timers.add(nameLabelNamesToString(registry.prefix+"."+name, labelNames), &timerVec{ 404 name: registry.prefix + "." + name, 405 labelNames: labelNames, 406 timers: make(map[string]*timer), 407 }) 408 } 409 410 func (registry *registryConfig) HistogramVec(name string, buckets []float64, labelNames ...string) metrics.HistogramVec { 411 return registry.histograms.add(nameLabelNamesToString(registry.prefix+"."+name, labelNames), &histogramVec{ 412 name: registry.prefix + "." + name, 413 labelNames: labelNames, 414 histograms: make(map[string]*histogram), 415 buckets: buckets, 416 }) 417 } 418 419 func (registry *registryConfig) WithSystem(subsystem string) metrics.Config { 420 copy := *registry 421 if len(copy.prefix) == 0 { 422 copy.prefix = subsystem 423 } else { 424 copy.prefix = registry.prefix + "." + subsystem 425 } 426 return © 427 } 428 429 func (registry *registryConfig) Details() trace.Details { 430 return registry.details 431 } 432 433 func withMetrics(t *testing.T, details trace.Details, interval time.Duration) ydb.Option { 434 registry := ®istryConfig{ 435 details: details, 436 gauges: newVec[gaugeVec](), 437 counters: newVec[counterVec](), 438 timers: newVec[timerVec](), 439 histograms: newVec[histogramVec](), 440 } 441 var ( 442 done = make(chan struct{}) 443 printState = func(log func(format string, args ...interface{}), header string) { 444 log(time.Now().Format("[2006-01-02 15:04:05] ") + header + ":\n") 445 var ( 446 gaugesOnce sync.Once 447 countersOnce sync.Once 448 timersOnce sync.Once 449 histogramsOnce sync.Once 450 ) 451 registry.gauges.iterate(func(element *gaugeVec) { 452 for _, v := range element.Values() { 453 gaugesOnce.Do(func() { 454 log("- gauges:\n") 455 }) 456 log(" - %s\n", v) 457 } 458 }) 459 registry.counters.iterate(func(element *counterVec) { 460 for _, v := range element.Values() { 461 countersOnce.Do(func() { 462 log("- counters:\n") 463 }) 464 log(" - %s\n", v) 465 } 466 }) 467 registry.timers.iterate(func(element *timerVec) { 468 for _, v := range element.Values() { 469 timersOnce.Do(func() { 470 log("- timers:\n") 471 }) 472 log(" - %s\n", v) 473 } 474 }) 475 registry.histograms.iterate(func(element *histogramVec) { 476 for _, v := range element.Values() { 477 histogramsOnce.Do(func() { 478 log("- histograms:\n") 479 }) 480 log(" - %s\n", v) 481 } 482 }) 483 } 484 ) 485 if interval > 0 { 486 go func() { 487 for { 488 select { 489 case <-done: 490 return 491 case <-time.After(interval): 492 printState(t.Logf, "registry state") 493 } 494 } 495 }() 496 } 497 t.Cleanup(func() { 498 close(done) 499 printState(func(format string, args ...interface{}) { 500 _, _ = fmt.Printf(format, args...) 501 }, "final registry state") 502 }) 503 return metrics.WithTraces(registry) 504 }