trpc.group/trpc-go/trpc-go@v1.0.3/metrics/metrics_test.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package metrics_test
    15  
    16  import (
    17  	"errors"
    18  	"reflect"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  	"trpc.group/trpc-go/trpc-go/metrics"
    26  )
    27  
    28  func TestNewCounter(t *testing.T) {
    29  	// create expected counters
    30  	type args struct {
    31  		name string
    32  	}
    33  	tests := []struct {
    34  		name  string
    35  		args  args
    36  		comp  metrics.ICounter
    37  		match bool
    38  	}{
    39  		{"same-Name-same-counter", args{"req.total.num"}, metrics.Counter("req.total.num"), true},
    40  		{"diff-Name-diff-counter", args{"req.total.num"}, metrics.Counter("req.total.fail"), false},
    41  	}
    42  	for _, tt := range tests {
    43  		t.Run(tt.name, func(t *testing.T) {
    44  			if got := metrics.Counter(tt.args.name); reflect.DeepEqual(got, tt.comp) != tt.match {
    45  				t.Errorf("Counter() = %v, comp %v, match should be %v", got, tt.comp, tt.match)
    46  			}
    47  		})
    48  	}
    49  }
    50  
    51  func TestNewGauge(t *testing.T) {
    52  	type args struct {
    53  		name string
    54  	}
    55  	tests := []struct {
    56  		name  string
    57  		args  args
    58  		comp  metrics.IGauge
    59  		match bool
    60  	}{
    61  		{"same-Name-same-gauge", args{"cpu.load.average"}, metrics.Gauge("cpu.load.average"), true},
    62  		{"diff-Name-diff-gauge", args{"cpu.load.average"}, metrics.Gauge("cpu.load.max"), false},
    63  	}
    64  	for _, tt := range tests {
    65  		t.Run(tt.name, func(t *testing.T) {
    66  			if got := metrics.Gauge(tt.args.name); reflect.DeepEqual(got, tt.comp) != tt.match {
    67  				t.Errorf("Gauge() = %v, comp %v, match should be %v", got, tt.comp, tt.match)
    68  			}
    69  		})
    70  	}
    71  }
    72  
    73  func TestNewTimer(t *testing.T) {
    74  	type args struct {
    75  		name string
    76  	}
    77  	tests := []struct {
    78  		name  string
    79  		args  args
    80  		comp  metrics.ITimer
    81  		match bool
    82  	}{
    83  		{"same-Name-same-timer", args{"req.1.timecost"}, metrics.Timer("req.1.timecost"), true},
    84  		{"diff-Name-diff-timer", args{"req.1.timecost"}, metrics.Timer("req.2.timecost"), false},
    85  	}
    86  	for _, tt := range tests {
    87  		t.Run(tt.name, func(t *testing.T) {
    88  			if got := metrics.Timer(tt.args.name); reflect.DeepEqual(got, tt.comp) != tt.match {
    89  				t.Errorf("Timer() = %v, compared with %v, match should be %v", got, tt.comp, tt.match)
    90  			}
    91  		})
    92  	}
    93  }
    94  
    95  func TestNewHistogram(t *testing.T) {
    96  	buckets := metrics.NewDurationBounds(0*time.Millisecond,
    97  		100*time.Millisecond, 500*time.Millisecond, 1000*time.Millisecond)
    98  
    99  	type args struct {
   100  		name    string
   101  		buckets metrics.BucketBounds
   102  	}
   103  	tests := []struct {
   104  		name  string
   105  		args  args
   106  		comp  metrics.IHistogram
   107  		match bool
   108  	}{
   109  		{"same-Name-same-histogram", args{"cmd.1.timecost", buckets},
   110  			metrics.Histogram("cmd.1.timecost", buckets), true},
   111  		{"diff-Name-diff-histogram", args{"cmd.1.timecost", buckets},
   112  			metrics.Histogram("cmd.2.timecost", buckets), false},
   113  	}
   114  
   115  	for _, tt := range tests {
   116  		t.Run(tt.name, func(t *testing.T) {
   117  			if got := metrics.Histogram(tt.args.name, tt.args.buckets); reflect.
   118  				DeepEqual(got, tt.comp) != tt.match {
   119  
   120  				t.Errorf("Histogram() = %v, comp %v, match should be %v", got, tt.comp, tt.match)
   121  			}
   122  		})
   123  	}
   124  }
   125  
   126  func TestRegisterMetricsSink(t *testing.T) {
   127  	type args struct {
   128  		sink metrics.Sink
   129  	}
   130  	tests := []struct {
   131  		name string
   132  		args args
   133  	}{
   134  		{"noop", args{&metrics.NoopSink{}}},
   135  		{"console", args{metrics.NewConsoleSink()}},
   136  	}
   137  	for _, tt := range tests {
   138  		t.Run(tt.name, func(t *testing.T) {
   139  			metrics.RegisterMetricsSink(tt.args.sink)
   140  			err := metrics.Report(metrics.Record{})
   141  			require.Nil(t, err)
   142  		})
   143  	}
   144  }
   145  
   146  func TestIncrCounter(t *testing.T) {
   147  	type args struct {
   148  		key   string
   149  		value float64
   150  	}
   151  	tests := []struct {
   152  		name string
   153  		args args
   154  	}{
   155  		{"counter-1", args{"req.total", 100}},
   156  		{"counter-2", args{"req.fail", 1}},
   157  		{"counter-3", args{"req.succ", 99}},
   158  	}
   159  	for _, tt := range tests {
   160  		t.Run(tt.name, func(t *testing.T) {
   161  			require.NotNil(t, metrics.Counter(tt.args.key))
   162  			metrics.IncrCounter(tt.args.key, tt.args.value)
   163  		})
   164  	}
   165  }
   166  
   167  func TestSetGauge(t *testing.T) {
   168  	type args struct {
   169  		key   string
   170  		value float64
   171  	}
   172  	tests := []struct {
   173  		name string
   174  		args args
   175  	}{
   176  		{"gauge-1", args{"cpu.avgload", 70.1}},
   177  		{"gauge-2", args{"mem.avgload", 80.0}},
   178  	}
   179  	for _, tt := range tests {
   180  		t.Run(tt.name, func(t *testing.T) {
   181  			require.NotNil(t, metrics.Gauge(tt.args.key))
   182  			metrics.SetGauge(tt.args.key, tt.args.value)
   183  		})
   184  	}
   185  }
   186  
   187  func TestRecordTimer(t *testing.T) {
   188  	type args struct {
   189  		key      string
   190  		duration time.Duration
   191  	}
   192  	tests := []struct {
   193  		name string
   194  		args args
   195  	}{
   196  		{"timer-1", args{"timer.cmd.1", time.Second}},
   197  		{"timer-2", args{"timer.cmd.2", time.Second * 2}},
   198  	}
   199  	for _, tt := range tests {
   200  		t.Run(tt.name, func(t *testing.T) {
   201  			require.NotNil(t, metrics.Timer(tt.args.key))
   202  			metrics.RecordTimer(tt.args.key, tt.args.duration)
   203  		})
   204  	}
   205  }
   206  
   207  func TestAddSample(t *testing.T) {
   208  	metrics.Histogram("timecost.dist", metrics.NewDurationBounds(time.Second,
   209  		time.Second*2, time.Second*3, time.Second*4))
   210  	metrics.RegisterMetricsSink(metrics.NewConsoleSink())
   211  	type args struct {
   212  		key     string
   213  		buckets metrics.BucketBounds
   214  		value   float64
   215  	}
   216  	buckets := metrics.NewDurationBounds(time.Second, time.Second*2, time.Second*5)
   217  	tests := []struct {
   218  		name string
   219  		args args
   220  	}{
   221  		{"histogram-1", args{"timecost.dist", buckets, float64(time.Second)}},
   222  		{"histogram-2", args{"timecost.dist", buckets, float64(time.Second * 2)}},
   223  		{"histogram-2", args{"timecost.dist", buckets, float64(time.Second * 3)}},
   224  	}
   225  	for _, tt := range tests {
   226  		t.Run(tt.name, func(t *testing.T) {
   227  			metrics.AddSample(tt.args.key, tt.args.buckets, tt.args.value)
   228  			h, _ := metrics.GetHistogram(tt.args.key)
   229  			require.NotNil(t, h)
   230  		})
   231  	}
   232  }
   233  
   234  func TestHistogramSink(t *testing.T) {
   235  	var histNameBucketBounds sync.Map
   236  	metrics.RegisterMetricsSink(&histSink{
   237  		name: "histSink1",
   238  		register: func(name string, o metrics.HistogramOption) {
   239  			histNameBucketBounds.LoadOrStore(name, o.BucketBounds)
   240  		},
   241  	})
   242  	metrics.Histogram("hist1", metrics.BucketBounds{1, 2})
   243  	bb, ok := histNameBucketBounds.Load("hist1")
   244  	require.True(t, ok)
   245  	require.True(t, reflect.DeepEqual(bb, metrics.BucketBounds{1, 2}))
   246  
   247  	histNameBucketBounds.Delete("hist1")
   248  	metrics.RegisterMetricsSink(&histSink{
   249  		name: "histSink2",
   250  		register: func(name string, o metrics.HistogramOption) {
   251  			histNameBucketBounds.LoadOrStore(name, o.BucketBounds)
   252  		},
   253  	})
   254  	bb, ok = histNameBucketBounds.Load("hist1")
   255  	require.True(t, ok)
   256  	require.True(t, reflect.DeepEqual(bb, metrics.BucketBounds{1, 2}))
   257  
   258  	metrics.RegisterHistogram("hist2",
   259  		metrics.HistogramOption{BucketBounds: metrics.BucketBounds{1, 2}})
   260  	bb, ok = histNameBucketBounds.Load("hist2")
   261  	require.True(t, ok)
   262  	require.True(t, reflect.DeepEqual(bb, metrics.BucketBounds{1, 2}))
   263  }
   264  
   265  type unhealthySink struct{}
   266  
   267  func (u *unhealthySink) Name() string {
   268  	return "unhealthy"
   269  }
   270  
   271  func (u *unhealthySink) Report(_ metrics.Record, _ ...metrics.Option) error {
   272  	time.Sleep(time.Millisecond * 100)
   273  	return errors.New("timeout")
   274  }
   275  
   276  type unstableSink struct{}
   277  
   278  func (p *unstableSink) Name() string {
   279  	return "unstable"
   280  }
   281  
   282  func (p *unstableSink) Report(_ metrics.Record, _ ...metrics.Option) error {
   283  	time.Sleep(time.Millisecond * 100)
   284  	return errors.New("backend error")
   285  }
   286  
   287  func TestReport(t *testing.T) {
   288  
   289  	metrics.RegisterMetricsSink(metrics.NewConsoleSink())
   290  	metrics.RegisterMetricsSink(&metrics.NoopSink{})
   291  	metrics.RegisterMetricsSink(&unhealthySink{})
   292  	metrics.RegisterMetricsSink(&unstableSink{})
   293  
   294  	rec := metrics.NewSingleDimensionMetrics("total.req", float64(100), metrics.PolicySUM)
   295  	tests := []struct {
   296  		name    string
   297  		rec     metrics.Record
   298  		wantErr bool
   299  	}{
   300  		{"reportHasError", rec, true},
   301  	}
   302  	for _, tt := range tests {
   303  		t.Run(tt.name, func(t *testing.T) {
   304  			if err := metrics.Report(rec); (err != nil) != tt.wantErr {
   305  				t.Errorf("Report() error = %v, wantErr %v", err, tt.wantErr)
   306  			}
   307  		})
   308  	}
   309  }
   310  
   311  type histSink struct {
   312  	name     string
   313  	register func(name string, o metrics.HistogramOption)
   314  }
   315  
   316  func (hs *histSink) Name() string { return hs.name }
   317  
   318  func (*histSink) Report(_ metrics.Record, _ ...metrics.Option) error {
   319  	return nil
   320  }
   321  
   322  func (hs *histSink) Register(name string, o metrics.HistogramOption) {
   323  	hs.register(name, o)
   324  }
   325  
   326  func TestGetMetricsSink(t *testing.T) {
   327  	metrics.RegisterMetricsSink(metrics.NewConsoleSink())
   328  	type args struct {
   329  		name string
   330  	}
   331  	tests := []struct {
   332  		name  string
   333  		args  args
   334  		want  metrics.Sink
   335  		want1 bool
   336  	}{
   337  		{"GetSuccess", args{"console"}, metrics.NewConsoleSink(), true},
   338  		{"GetFailed", args{"not_exit_key"}, nil, false},
   339  	}
   340  	for _, tt := range tests {
   341  		t.Run(tt.name, func(t *testing.T) {
   342  			got, got1 := metrics.GetMetricsSink(tt.args.name)
   343  			assert.Equalf(t, tt.want, got, "GetMetricsSink(%v)", tt.args.name)
   344  			assert.Equalf(t, tt.want1, got1, "GetMetricsSink(%v)", tt.args.name)
   345  		})
   346  	}
   347  }