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 }