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 &copy
   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 := &registryConfig{
   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  }