github.com/rudderlabs/rudder-go-kit@v0.30.0/stats/memstats/stats_test.go (about)

     1  package memstats_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/rudderlabs/rudder-go-kit/stats"
    11  	"github.com/rudderlabs/rudder-go-kit/stats/memstats"
    12  	"github.com/rudderlabs/rudder-go-kit/stats/testhelper/tracemodel"
    13  )
    14  
    15  func TestStats(t *testing.T) {
    16  	now := time.Now()
    17  
    18  	commonTags := stats.Tags{"tag1": "value1"}
    19  
    20  	t.Run("test Count", func(t *testing.T) {
    21  		name := "testCount"
    22  		store, err := memstats.New(
    23  			memstats.WithNow(func() time.Time {
    24  				return now
    25  			}),
    26  		)
    27  		require.NoError(t, err)
    28  
    29  		m := store.NewTaggedStat(name, stats.CountType, commonTags)
    30  
    31  		m.Increment()
    32  
    33  		require.Equal(t, 1.0, store.Get(name, commonTags).LastValue())
    34  		require.Equal(t, []float64{1.0}, store.Get(name, commonTags).Values())
    35  
    36  		m.Count(2)
    37  
    38  		require.Equal(t, 3.0, store.Get(name, commonTags).LastValue())
    39  		require.Equal(t, []float64{1.0, 3.0}, store.Get(name, commonTags).Values())
    40  
    41  		require.Equal(t, []memstats.Metric{{
    42  			Name:  name,
    43  			Tags:  commonTags,
    44  			Value: 3.0,
    45  		}}, store.GetAll())
    46  
    47  		require.Equal(t, []memstats.Metric{{
    48  			Name:  name,
    49  			Tags:  commonTags,
    50  			Value: 3.0,
    51  		}}, store.GetByName(name))
    52  	})
    53  
    54  	t.Run("test Gauge", func(t *testing.T) {
    55  		name := "testGauge"
    56  		store, err := memstats.New(
    57  			memstats.WithNow(func() time.Time {
    58  				return now
    59  			}),
    60  		)
    61  		require.NoError(t, err)
    62  
    63  		m := store.NewTaggedStat(name, stats.GaugeType, commonTags)
    64  
    65  		m.Gauge(1.0)
    66  
    67  		require.Equal(t, 1.0, store.Get(name, commonTags).LastValue())
    68  		require.Equal(t, []float64{1.0}, store.Get(name, commonTags).Values())
    69  
    70  		m.Gauge(2.0)
    71  
    72  		require.Equal(t, 2.0, store.Get(name, commonTags).LastValue())
    73  		require.Equal(t, []float64{1.0, 2.0}, store.Get(name, commonTags).Values())
    74  
    75  		require.Equal(t, []memstats.Metric{{
    76  			Name:  name,
    77  			Tags:  commonTags,
    78  			Value: 2.0,
    79  		}}, store.GetAll())
    80  
    81  		require.Equal(t, []memstats.Metric{{
    82  			Name:  name,
    83  			Tags:  commonTags,
    84  			Value: 2.0,
    85  		}}, store.GetByName(name))
    86  	})
    87  
    88  	t.Run("test Histogram", func(t *testing.T) {
    89  		name := "testHistogram"
    90  		store, err := memstats.New(
    91  			memstats.WithNow(func() time.Time {
    92  				return now
    93  			}),
    94  		)
    95  		require.NoError(t, err)
    96  
    97  		m := store.NewTaggedStat(name, stats.HistogramType, commonTags)
    98  
    99  		m.Observe(1.0)
   100  
   101  		require.Equal(t, 1.0, store.Get(name, commonTags).LastValue())
   102  		require.Equal(t, []float64{1.0}, store.Get(name, commonTags).Values())
   103  
   104  		m.Observe(2.0)
   105  
   106  		require.Equal(t, 2.0, store.Get(name, commonTags).LastValue())
   107  		require.Equal(t, []float64{1.0, 2.0}, store.Get(name, commonTags).Values())
   108  
   109  		require.Equal(t, []memstats.Metric{{
   110  			Name:   name,
   111  			Tags:   commonTags,
   112  			Values: []float64{1.0, 2.0},
   113  		}}, store.GetAll())
   114  
   115  		require.Equal(t, []memstats.Metric{{
   116  			Name:   name,
   117  			Tags:   commonTags,
   118  			Values: []float64{1.0, 2.0},
   119  		}}, store.GetByName(name))
   120  	})
   121  
   122  	t.Run("test Timer", func(t *testing.T) {
   123  		name := "testTimer"
   124  		store, err := memstats.New(
   125  			memstats.WithNow(func() time.Time {
   126  				return now
   127  			}),
   128  		)
   129  		require.NoError(t, err)
   130  
   131  		m := store.NewTaggedStat(name, stats.TimerType, commonTags)
   132  
   133  		m.SendTiming(time.Second)
   134  		require.Equal(t, time.Second, store.Get(name, commonTags).LastDuration())
   135  		require.Equal(t, []time.Duration{time.Second}, store.Get(name, commonTags).Durations())
   136  
   137  		m.SendTiming(time.Minute)
   138  		require.Equal(t, time.Minute, store.Get(name, commonTags).LastDuration())
   139  		require.Equal(t,
   140  			[]time.Duration{time.Second, time.Minute},
   141  			store.Get(name, commonTags).Durations(),
   142  		)
   143  
   144  		func() {
   145  			defer m.RecordDuration()()
   146  			now = now.Add(time.Second)
   147  		}()
   148  		require.Equal(t, time.Second, store.Get(name, commonTags).LastDuration())
   149  		require.Equal(t,
   150  			[]time.Duration{time.Second, time.Minute, time.Second},
   151  			store.Get(name, commonTags).Durations(),
   152  		)
   153  
   154  		m.Since(now.Add(-time.Minute))
   155  		require.Equal(t, time.Minute, store.Get(name, commonTags).LastDuration())
   156  		require.Equal(t,
   157  			[]time.Duration{time.Second, time.Minute, time.Second, time.Minute},
   158  			store.Get(name, commonTags).Durations(),
   159  		)
   160  
   161  		require.Equal(t, []memstats.Metric{{
   162  			Name:      name,
   163  			Tags:      commonTags,
   164  			Durations: []time.Duration{time.Second, time.Minute, time.Second, time.Minute},
   165  		}}, store.GetAll())
   166  
   167  		require.Equal(t, []memstats.Metric{{
   168  			Name:      name,
   169  			Tags:      commonTags,
   170  			Durations: []time.Duration{time.Second, time.Minute, time.Second, time.Minute},
   171  		}}, store.GetByName(name))
   172  	})
   173  
   174  	t.Run("invalid operations", func(t *testing.T) {
   175  		store, err := memstats.New(
   176  			memstats.WithNow(func() time.Time {
   177  				return now
   178  			}),
   179  		)
   180  		require.NoError(t, err)
   181  
   182  		require.PanicsWithValue(t, "operation Count not supported for measurement type:gauge", func() {
   183  			store.NewTaggedStat("invalid_count", stats.GaugeType, commonTags).Count(1)
   184  		})
   185  		require.PanicsWithValue(t, "operation Increment not supported for measurement type:gauge", func() {
   186  			store.NewTaggedStat("invalid_inc", stats.GaugeType, commonTags).Increment()
   187  		})
   188  		require.PanicsWithValue(t, "operation Gauge not supported for measurement type:count", func() {
   189  			store.NewTaggedStat("invalid_gauge", stats.CountType, commonTags).Gauge(1)
   190  		})
   191  		require.PanicsWithValue(t, "operation SendTiming not supported for measurement type:histogram", func() {
   192  			store.NewTaggedStat("invalid_send_timing", stats.HistogramType, commonTags).SendTiming(time.Second)
   193  		})
   194  		require.PanicsWithValue(t, "operation RecordDuration not supported for measurement type:histogram", func() {
   195  			store.NewTaggedStat("invalid_record_duration", stats.HistogramType, commonTags).RecordDuration()
   196  		})
   197  		require.PanicsWithValue(t, "operation Since not supported for measurement type:histogram", func() {
   198  			store.NewTaggedStat("invalid_since", stats.HistogramType, commonTags).Since(time.Now())
   199  		})
   200  		require.PanicsWithValue(t, "operation Observe not supported for measurement type:timer", func() {
   201  			store.NewTaggedStat("invalid_observe", stats.TimerType, commonTags).Observe(1)
   202  		})
   203  
   204  		require.PanicsWithValue(t, "name cannot be empty", func() {
   205  			store.GetByName("")
   206  		})
   207  	})
   208  
   209  	t.Run("no op", func(t *testing.T) {
   210  		store, err := memstats.New(
   211  			memstats.WithNow(func() time.Time {
   212  				return now
   213  			}),
   214  		)
   215  		require.NoError(t, err)
   216  
   217  		require.NoError(t, store.Start(context.Background(), stats.DefaultGoRoutineFactory))
   218  		store.Stop()
   219  
   220  		require.Equal(t, []memstats.Metric{}, store.GetAll())
   221  	})
   222  
   223  	t.Run("no tags", func(t *testing.T) {
   224  		name := "no_tags"
   225  		store, err := memstats.New(
   226  			memstats.WithNow(func() time.Time {
   227  				return now
   228  			}),
   229  		)
   230  		require.NoError(t, err)
   231  
   232  		m := store.NewStat(name, stats.CountType)
   233  
   234  		m.Increment()
   235  
   236  		require.Equal(t, 1.0, store.Get(name, nil).LastValue())
   237  
   238  		require.Equal(t, []memstats.Metric{{
   239  			Name:  name,
   240  			Value: 1.0,
   241  		}}, store.GetAll())
   242  
   243  		require.Equal(t, []memstats.Metric{{
   244  			Name:  name,
   245  			Value: 1.0,
   246  		}}, store.GetByName(name))
   247  	})
   248  
   249  	t.Run("get by name", func(t *testing.T) {
   250  		name1 := "name_1"
   251  		name2 := "name_2"
   252  
   253  		store, err := memstats.New(
   254  			memstats.WithNow(func() time.Time {
   255  				return now
   256  			}),
   257  		)
   258  		require.NoError(t, err)
   259  
   260  		m1 := store.NewStat(name1, stats.CountType)
   261  		m1.Increment()
   262  		m2 := store.NewStat(name2, stats.TimerType)
   263  		m2.SendTiming(time.Second)
   264  
   265  		require.Equal(t, []memstats.Metric{{
   266  			Name:  name1,
   267  			Value: 1.0,
   268  		}}, store.GetByName(name1))
   269  
   270  		require.Equal(t, []memstats.Metric{{
   271  			Name:      name2,
   272  			Durations: []time.Duration{time.Second},
   273  		}}, store.GetByName(name2))
   274  
   275  		require.Equal(t, []memstats.Metric{{
   276  			Name:  name1,
   277  			Value: 1.0,
   278  		}, {
   279  			Name:      name2,
   280  			Durations: []time.Duration{time.Second},
   281  		}}, store.GetAll())
   282  	})
   283  
   284  	t.Run("with tracing", func(t *testing.T) {
   285  		store, err := memstats.New(
   286  			memstats.WithNow(func() time.Time {
   287  				return now
   288  			}),
   289  			memstats.WithTracing(),
   290  		)
   291  		require.NoError(t, err)
   292  
   293  		// we haven't done anything yet, so there should be no spans
   294  		spans, err := store.Spans()
   295  		require.NoError(t, err)
   296  		require.Nil(t, spans)
   297  
   298  		tracer := store.NewTracer("my-tracer")
   299  		ctx, span1 := tracer.Start(context.Background(), "span1", stats.SpanKindInternal, stats.SpanWithTags(stats.Tags{
   300  			"tag1": "value1",
   301  			"tag2": "value2",
   302  		}))
   303  
   304  		_, span2 := tracer.Start(ctx, "span2", stats.SpanKindInternal, stats.SpanWithTags(stats.Tags{"tag3": "value3"}))
   305  		time.Sleep(time.Millisecond)
   306  		span2.End()
   307  		time.Sleep(time.Millisecond)
   308  		span1.End()
   309  
   310  		_, unrelatedSpan := tracer.Start(
   311  			context.Background(), "unrelatedSpan", stats.SpanKindInternal, stats.SpanWithTags(stats.Tags{
   312  				"tag4": "value4",
   313  			}),
   314  		)
   315  		time.Sleep(time.Millisecond)
   316  		unrelatedSpan.End()
   317  
   318  		spans, err = store.Spans()
   319  		require.NoError(t, err)
   320  
   321  		require.Len(t, spans, 3)
   322  		require.Equal(t, "span2", spans[0].Name)
   323  		require.Equal(t, "span1", spans[1].Name)
   324  		require.Equal(t, "unrelatedSpan", spans[2].Name)
   325  		require.True(t, spans[0].StartTime.IsZero())
   326  		require.True(t, spans[1].StartTime.IsZero())
   327  		require.True(t, spans[2].StartTime.IsZero())
   328  		// checking hierarchy
   329  		require.Equal(t, spans[1].SpanContext.SpanID, spans[0].Parent.SpanID)
   330  		require.NotEqual(t, spans[2].SpanContext.SpanID, spans[0].Parent.SpanID)
   331  		require.NotEqual(t, spans[2].SpanContext.SpanID, spans[1].Parent.SpanID)
   332  		// checking attributes
   333  		require.ElementsMatchf(t, []tracemodel.Attributes{{
   334  			Key: "tag3",
   335  			Value: tracemodel.Value{
   336  				Type:  "STRING",
   337  				Value: "value3",
   338  			},
   339  		}}, spans[0].Attributes, "span2 attributes: %+v", spans[0].Attributes)
   340  		require.ElementsMatchf(t, []tracemodel.Attributes{{
   341  			Key: "tag1",
   342  			Value: tracemodel.Value{
   343  				Type:  "STRING",
   344  				Value: "value1",
   345  			},
   346  		}, {
   347  			Key: "tag2",
   348  			Value: tracemodel.Value{
   349  				Type:  "STRING",
   350  				Value: "value2",
   351  			},
   352  		}}, spans[1].Attributes, "span1 attributes: %+v", spans[1].Attributes)
   353  		require.ElementsMatchf(t, []tracemodel.Attributes{{
   354  			Key: "tag4",
   355  			Value: tracemodel.Value{
   356  				Type:  "STRING",
   357  				Value: "value4",
   358  			},
   359  		}}, spans[2].Attributes, "unrelatedSpan attributes: %+v", spans[2].Attributes)
   360  	})
   361  
   362  	t.Run("with tracing timestamps", func(t *testing.T) {
   363  		store, err := memstats.New(
   364  			memstats.WithNow(func() time.Time {
   365  				return now
   366  			}),
   367  			memstats.WithTracing(),
   368  			memstats.WithTracingTimestamps(),
   369  		)
   370  		require.NoError(t, err)
   371  
   372  		tracer := store.NewTracer("my-tracer")
   373  		ctx, span1 := tracer.Start(context.Background(), "span1", stats.SpanKindInternal, stats.SpanWithTags(stats.Tags{
   374  			"tag1": "value1",
   375  			"tag2": "value2",
   376  		}))
   377  
   378  		_, span2 := tracer.Start(ctx, "span2", stats.SpanKindInternal, stats.SpanWithTags(stats.Tags{"tag3": "value3"}))
   379  		span2.End()
   380  		span1.End()
   381  
   382  		spans, err := store.Spans()
   383  		require.NoError(t, err)
   384  
   385  		require.Len(t, spans, 2)
   386  		// The data is extracted from stdout so the order is reversed
   387  		require.Equal(t, "span2", spans[0].Name)
   388  		require.Equal(t, "span1", spans[1].Name)
   389  		require.False(t, spans[0].StartTime.IsZero())
   390  		require.False(t, spans[1].StartTime.IsZero())
   391  		// checking hierarchy
   392  		require.Equal(t, spans[1].SpanContext.SpanID, spans[0].Parent.SpanID)
   393  	})
   394  }