github.com/hashicorp/go-metrics@v0.5.3/inmem_endpoint_test.go (about)

     1  package metrics
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/pascaldekloe/goe/verify"
    13  )
    14  
    15  func TestDisplayMetrics(t *testing.T) {
    16  	interval := 10 * time.Millisecond
    17  	inm := NewInmemSink(interval, 50*time.Millisecond)
    18  
    19  	// Add data points
    20  	inm.SetGauge([]string{"foo", "bar"}, 42)
    21  	inm.SetGaugeWithLabels([]string{"foo", "bar"}, 23, []Label{{"a", "b"}})
    22  	inm.EmitKey([]string{"foo", "bar"}, 42)
    23  	inm.IncrCounter([]string{"foo", "bar"}, 20)
    24  	inm.IncrCounter([]string{"foo", "bar"}, 22)
    25  	inm.IncrCounterWithLabels([]string{"foo", "bar"}, 20, []Label{{"a", "b"}})
    26  	inm.IncrCounterWithLabels([]string{"foo", "bar"}, 40, []Label{{"a", "b"}})
    27  	inm.AddSample([]string{"foo", "bar"}, 20)
    28  	inm.AddSample([]string{"foo", "bar"}, 24)
    29  	inm.AddSampleWithLabels([]string{"foo", "bar"}, 23, []Label{{"a", "b"}})
    30  	inm.AddSampleWithLabels([]string{"foo", "bar"}, 33, []Label{{"a", "b"}})
    31  
    32  	data := inm.Data()
    33  	if len(data) != 1 {
    34  		t.Fatalf("bad: %v", data)
    35  	}
    36  
    37  	expected := MetricsSummary{
    38  		Timestamp: data[0].Interval.Round(time.Second).UTC().String(),
    39  		Gauges: []GaugeValue{
    40  			{
    41  				Name:          "foo.bar",
    42  				Hash:          "foo.bar",
    43  				Value:         float32(42),
    44  				DisplayLabels: map[string]string{},
    45  			},
    46  			{
    47  				Name:          "foo.bar",
    48  				Hash:          "foo.bar;a=b",
    49  				Value:         float32(23),
    50  				DisplayLabels: map[string]string{"a": "b"},
    51  			},
    52  		},
    53  		Points: []PointValue{
    54  			{
    55  				Name:   "foo.bar",
    56  				Points: []float32{42},
    57  			},
    58  		},
    59  		Counters: []SampledValue{
    60  			{
    61  				Name: "foo.bar",
    62  				Hash: "foo.bar",
    63  				AggregateSample: &AggregateSample{
    64  					Count: 2,
    65  					Min:   20,
    66  					Max:   22,
    67  					Sum:   42,
    68  					SumSq: 884,
    69  					Rate:  4200,
    70  				},
    71  				Mean:   21,
    72  				Stddev: 1.4142135623730951,
    73  			},
    74  			{
    75  				Name: "foo.bar",
    76  				Hash: "foo.bar;a=b",
    77  				AggregateSample: &AggregateSample{
    78  					Count: 2,
    79  					Min:   20,
    80  					Max:   40,
    81  					Sum:   60,
    82  					SumSq: 2000,
    83  					Rate:  6000,
    84  				},
    85  				Mean:          30,
    86  				Stddev:        14.142135623730951,
    87  				DisplayLabels: map[string]string{"a": "b"},
    88  			},
    89  		},
    90  		Samples: []SampledValue{
    91  			{
    92  				Name: "foo.bar",
    93  				Hash: "foo.bar",
    94  				AggregateSample: &AggregateSample{
    95  					Count: 2,
    96  					Min:   20,
    97  					Max:   24,
    98  					Sum:   44,
    99  					SumSq: 976,
   100  					Rate:  4400,
   101  				},
   102  				Mean:   22,
   103  				Stddev: 2.8284271247461903,
   104  			},
   105  			{
   106  				Name: "foo.bar",
   107  				Hash: "foo.bar;a=b",
   108  				AggregateSample: &AggregateSample{
   109  					Count: 2,
   110  					Min:   23,
   111  					Max:   33,
   112  					Sum:   56,
   113  					SumSq: 1618,
   114  					Rate:  5600,
   115  				},
   116  				Mean:          28,
   117  				Stddev:        7.0710678118654755,
   118  				DisplayLabels: map[string]string{"a": "b"},
   119  			},
   120  		},
   121  	}
   122  
   123  	raw, err := inm.DisplayMetrics(nil, nil)
   124  	if err != nil {
   125  		t.Fatalf("err: %v", err)
   126  	}
   127  	result := raw.(MetricsSummary)
   128  
   129  	// Ignore the LastUpdated field, we don't export that anyway
   130  	for i, got := range result.Counters {
   131  		expected.Counters[i].LastUpdated = got.LastUpdated
   132  	}
   133  	for i, got := range result.Samples {
   134  		expected.Samples[i].LastUpdated = got.LastUpdated
   135  	}
   136  
   137  	verify.Values(t, "all", result, expected)
   138  }
   139  
   140  func TestDisplayMetrics_RaceSetGauge(t *testing.T) {
   141  	interval := 200 * time.Millisecond
   142  	inm := NewInmemSink(interval, 10*interval)
   143  	result := make(chan float32)
   144  
   145  	go func() {
   146  		for {
   147  			time.Sleep(150 * time.Millisecond)
   148  			inm.SetGauge([]string{"foo", "bar"}, float32(42))
   149  		}
   150  	}()
   151  
   152  	go func() {
   153  		start := time.Now()
   154  		var summary MetricsSummary
   155  		// test for twenty intervals
   156  		for time.Now().Sub(start) < 20*interval {
   157  			time.Sleep(100 * time.Millisecond)
   158  			raw, _ := inm.DisplayMetrics(nil, nil)
   159  			summary = raw.(MetricsSummary)
   160  		}
   161  		// save result
   162  		for _, g := range summary.Gauges {
   163  			if g.Name == "foo.bar" {
   164  				result <- g.Value
   165  			}
   166  		}
   167  		close(result)
   168  	}()
   169  
   170  	got := <-result
   171  	verify.Values(t, "all", got, float32(42))
   172  }
   173  
   174  func TestDisplayMetrics_RaceAddSample(t *testing.T) {
   175  	interval := 200 * time.Millisecond
   176  	inm := NewInmemSink(interval, 10*interval)
   177  	result := make(chan float32)
   178  
   179  	go func() {
   180  		for {
   181  			time.Sleep(75 * time.Millisecond)
   182  			inm.AddSample([]string{"foo", "bar"}, float32(0.0))
   183  		}
   184  	}()
   185  
   186  	go func() {
   187  		start := time.Now()
   188  		var summary MetricsSummary
   189  		// test for twenty intervals
   190  		for time.Now().Sub(start) < 20*interval {
   191  			time.Sleep(100 * time.Millisecond)
   192  			raw, _ := inm.DisplayMetrics(nil, nil)
   193  			summary = raw.(MetricsSummary)
   194  		}
   195  		// save result
   196  		for _, g := range summary.Gauges {
   197  			if g.Name == "foo.bar" {
   198  				result <- g.Value
   199  			}
   200  		}
   201  		close(result)
   202  	}()
   203  
   204  	got := <-result
   205  	verify.Values(t, "all", got, float32(0.0))
   206  }
   207  
   208  func TestDisplayMetrics_RaceIncrCounter(t *testing.T) {
   209  	interval := 200 * time.Millisecond
   210  	inm := NewInmemSink(interval, 10*interval)
   211  	result := make(chan float32)
   212  
   213  	go func() {
   214  		for {
   215  			time.Sleep(75 * time.Millisecond)
   216  			inm.IncrCounter([]string{"foo", "bar"}, float32(0.0))
   217  		}
   218  	}()
   219  
   220  	go func() {
   221  		start := time.Now()
   222  		var summary MetricsSummary
   223  		// test for twenty intervals
   224  		for time.Now().Sub(start) < 20*interval {
   225  			time.Sleep(30 * time.Millisecond)
   226  			raw, _ := inm.DisplayMetrics(nil, nil)
   227  			summary = raw.(MetricsSummary)
   228  		}
   229  		// save result for testing
   230  		for _, g := range summary.Gauges {
   231  			if g.Name == "foo.bar" {
   232  				result <- g.Value
   233  			}
   234  		}
   235  		close(result)
   236  	}()
   237  
   238  	got := <-result
   239  	verify.Values(t, "all", got, float32(0.0))
   240  }
   241  
   242  func TestDisplayMetrics_RaceMetricsSetGauge(t *testing.T) {
   243  	interval := 200 * time.Millisecond
   244  	inm := NewInmemSink(interval, 10*interval)
   245  	met := &Metrics{Config: Config{FilterDefault: true}, sink: inm}
   246  	result := make(chan float32)
   247  	labels := []Label{
   248  		{"name1", "value1"},
   249  		{"name2", "value2"},
   250  	}
   251  
   252  	go func() {
   253  		for {
   254  			time.Sleep(75 * time.Millisecond)
   255  			met.SetGaugeWithLabels([]string{"foo", "bar"}, float32(42), labels)
   256  		}
   257  	}()
   258  
   259  	go func() {
   260  		start := time.Now()
   261  		var summary MetricsSummary
   262  		// test for twenty intervals
   263  		for time.Now().Sub(start) < 40*interval {
   264  			time.Sleep(150 * time.Millisecond)
   265  			raw, _ := inm.DisplayMetrics(nil, nil)
   266  			summary = raw.(MetricsSummary)
   267  		}
   268  		// save result
   269  		for _, g := range summary.Gauges {
   270  			if g.Name == "foo.bar" {
   271  				result <- g.Value
   272  			}
   273  		}
   274  		close(result)
   275  	}()
   276  
   277  	got := <-result
   278  	verify.Values(t, "all", got, float32(42))
   279  }
   280  
   281  func TestInmemSink_Stream(t *testing.T) {
   282  	interval := 10 * time.Millisecond
   283  	total := 50 * time.Millisecond
   284  	inm := NewInmemSink(interval, total)
   285  
   286  	ctx, cancel := context.WithTimeout(context.Background(), total*2)
   287  	defer cancel()
   288  
   289  	chDone := make(chan struct{})
   290  
   291  	go func() {
   292  		for i := float32(0); ctx.Err() == nil; i++ {
   293  			inm.SetGaugeWithLabels([]string{"gauge", "foo"}, 20+i, []Label{{"a", "b"}})
   294  			inm.EmitKey([]string{"key", "foo"}, 30+i)
   295  			inm.IncrCounterWithLabels([]string{"counter", "bar"}, 40+i, []Label{{"a", "b"}})
   296  			inm.IncrCounterWithLabels([]string{"counter", "bar"}, 50+i, []Label{{"a", "b"}})
   297  			inm.AddSampleWithLabels([]string{"sample", "bar"}, 60+i, []Label{{"a", "b"}})
   298  			inm.AddSampleWithLabels([]string{"sample", "bar"}, 70+i, []Label{{"a", "b"}})
   299  			time.Sleep(interval / 3)
   300  		}
   301  		close(chDone)
   302  	}()
   303  
   304  	resp := httptest.NewRecorder()
   305  	enc := encoder{
   306  		encoder: json.NewEncoder(resp),
   307  		flusher: resp,
   308  	}
   309  	inm.Stream(ctx, enc)
   310  
   311  	<-chDone
   312  
   313  	decoder := json.NewDecoder(resp.Body)
   314  	var prevGaugeValue float32
   315  	for i := 0; i < 8; i++ {
   316  		var summary MetricsSummary
   317  		if err := decoder.Decode(&summary); err != nil {
   318  			t.Fatalf("expected no error while decoding response %d, got %v", i, err)
   319  		}
   320  		if count := len(summary.Gauges); count != 1 {
   321  			t.Fatalf("expected at least one gauge in response %d, got %v", i, count)
   322  		}
   323  		value := summary.Gauges[0].Value
   324  		// The upper bound of the gauge value is not known, but we can expect it
   325  		// to be less than 50 because it increments by 3 every interval and we run
   326  		// for ~10 intervals.
   327  		if value < 20 || value > 50 {
   328  			t.Fatalf("expected interval %d guage value between 20 and 50, got %v", i, value)
   329  		}
   330  		if value <= prevGaugeValue {
   331  			t.Fatalf("expected interval %d guage value to be greater than previous, %v == %v", i, value, prevGaugeValue)
   332  		}
   333  		prevGaugeValue = value
   334  	}
   335  }
   336  
   337  type encoder struct {
   338  	flusher http.Flusher
   339  	encoder *json.Encoder
   340  }
   341  
   342  func (e encoder) Encode(metrics interface{}) error {
   343  	if err := e.encoder.Encode(metrics); err != nil {
   344  		fmt.Println("failed to encode metrics summary", "error", err)
   345  		return err
   346  	}
   347  	e.flusher.Flush()
   348  	return nil
   349  }