...

Source file src/github.com/go-kit/kit/metrics/cloudwatch/cloudwatch_test.go

Documentation: github.com/go-kit/kit/metrics/cloudwatch

     1  package cloudwatch
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/aws/aws-sdk-go/service/cloudwatch"
    11  	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
    12  
    13  	"github.com/go-kit/kit/metrics"
    14  	"github.com/go-kit/kit/metrics/teststat"
    15  	"github.com/go-kit/log"
    16  )
    17  
    18  const metricNameToGenerateError = "metric_name_used_to_throw_an_error"
    19  
    20  var errTest = errors.New("test error")
    21  
    22  type mockCloudWatch struct {
    23  	cloudwatchiface.CloudWatchAPI
    24  	mtx                sync.RWMutex
    25  	valuesReceived     map[string][]float64
    26  	dimensionsReceived map[string][]*cloudwatch.Dimension
    27  }
    28  
    29  func newMockCloudWatch() *mockCloudWatch {
    30  	return &mockCloudWatch{
    31  		valuesReceived:     map[string][]float64{},
    32  		dimensionsReceived: map[string][]*cloudwatch.Dimension{},
    33  	}
    34  }
    35  
    36  func (mcw *mockCloudWatch) PutMetricData(input *cloudwatch.PutMetricDataInput) (*cloudwatch.PutMetricDataOutput, error) {
    37  	mcw.mtx.Lock()
    38  	defer mcw.mtx.Unlock()
    39  	for _, datum := range input.MetricData {
    40  		if *datum.MetricName == metricNameToGenerateError {
    41  			return nil, errTest
    42  		}
    43  
    44  		if len(datum.Values) > 0 {
    45  			for _, v := range datum.Values {
    46  				mcw.valuesReceived[*datum.MetricName] = append(mcw.valuesReceived[*datum.MetricName], *v)
    47  			}
    48  		} else {
    49  			mcw.valuesReceived[*datum.MetricName] = append(mcw.valuesReceived[*datum.MetricName], *datum.Value)
    50  		}
    51  		mcw.dimensionsReceived[*datum.MetricName] = datum.Dimensions
    52  	}
    53  	return nil, nil
    54  }
    55  
    56  func (mcw *mockCloudWatch) testDimensions(name string, labelValues ...string) error {
    57  	mcw.mtx.RLock()
    58  	_, hasValue := mcw.valuesReceived[name]
    59  	if !hasValue {
    60  		return nil // nothing to check; 0 samples were received
    61  	}
    62  	dimensions, ok := mcw.dimensionsReceived[name]
    63  	mcw.mtx.RUnlock()
    64  
    65  	if !ok {
    66  		if len(labelValues) > 0 {
    67  			return errors.New("Expected dimensions to be available, but none were")
    68  		}
    69  	}
    70  LabelValues:
    71  	for i, j := 0, 0; i < len(labelValues); i, j = i+2, j+1 {
    72  		name, value := labelValues[i], labelValues[i+1]
    73  		for _, dimension := range dimensions {
    74  			if *dimension.Name == name {
    75  				if *dimension.Value == value {
    76  					break LabelValues
    77  				}
    78  			}
    79  		}
    80  		return fmt.Errorf("could not find dimension with name %s and value %s", name, value)
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  func TestCounter(t *testing.T) {
    87  	namespace, name := "abc", "def"
    88  	label, value := "label", "value"
    89  	svc := newMockCloudWatch()
    90  	cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
    91  	counter := cw.NewCounter(name).With(label, value)
    92  	valuef := func() float64 {
    93  		if err := cw.Send(); err != nil {
    94  			t.Fatal(err)
    95  		}
    96  		svc.mtx.RLock()
    97  		defer svc.mtx.RUnlock()
    98  		value := svc.valuesReceived[name][len(svc.valuesReceived[name])-1]
    99  		delete(svc.valuesReceived, name)
   100  
   101  		return value
   102  	}
   103  	if err := teststat.TestCounter(counter, valuef); err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	if err := teststat.TestCounter(counter, valuef); err != nil {
   107  		t.Fatal("Fill and flush counter 2nd time: ", err)
   108  	}
   109  	if err := svc.testDimensions(name, label, value); err != nil {
   110  		t.Fatal(err)
   111  	}
   112  }
   113  
   114  func TestCounterLowSendConcurrency(t *testing.T) {
   115  	namespace := "abc"
   116  	var names, labels, values []string
   117  	for i := 1; i <= 45; i++ {
   118  		num := strconv.Itoa(i)
   119  		names = append(names, "name"+num)
   120  		labels = append(labels, "label"+num)
   121  		values = append(values, "value"+num)
   122  	}
   123  	svc := newMockCloudWatch()
   124  	cw := New(namespace, svc,
   125  		WithLogger(log.NewNopLogger()),
   126  		WithConcurrentRequests(2),
   127  	)
   128  
   129  	counters := make(map[string]metrics.Counter)
   130  	var wants []float64
   131  	for i, name := range names {
   132  		counters[name] = cw.NewCounter(name).With(labels[i], values[i])
   133  		wants = append(wants, teststat.FillCounter(counters[name]))
   134  	}
   135  
   136  	if err := cw.Send(); err != nil {
   137  		t.Fatal(err)
   138  	}
   139  
   140  	for i, name := range names {
   141  		if l := len(svc.valuesReceived[name]); l == 0 && wants[i] == 0 {
   142  			continue
   143  		} else if l != 1 {
   144  			t.Fatalf("one value expected, got %d", l)
   145  		}
   146  
   147  		if svc.valuesReceived[name][0] != wants[i] {
   148  			t.Fatalf("want %f, have %f", wants[i], svc.valuesReceived[name])
   149  		}
   150  		if err := svc.testDimensions(name, labels[i], values[i]); err != nil {
   151  			t.Fatal(err)
   152  		}
   153  	}
   154  }
   155  
   156  func TestGauge(t *testing.T) {
   157  	namespace, name := "abc", "def"
   158  	label, value := "label", "value"
   159  	svc := newMockCloudWatch()
   160  	cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
   161  	gauge := cw.NewGauge(name).With(label, value)
   162  	valuef := func() []float64 {
   163  		if err := cw.Send(); err != nil {
   164  			t.Fatal(err)
   165  		}
   166  		svc.mtx.RLock()
   167  		defer svc.mtx.RUnlock()
   168  		res := svc.valuesReceived[name]
   169  		delete(svc.valuesReceived, name)
   170  		return res
   171  	}
   172  
   173  	if err := teststat.TestGauge(gauge, valuef); err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	if err := svc.testDimensions(name, label, value); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  }
   180  
   181  func TestHistogram(t *testing.T) {
   182  	namespace, name := "abc", "def"
   183  	label, value := "label", "value"
   184  	svc := newMockCloudWatch()
   185  	cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
   186  	histogram := cw.NewHistogram(name).With(label, value)
   187  	n50 := fmt.Sprintf("%s_50", name)
   188  	n90 := fmt.Sprintf("%s_90", name)
   189  	n95 := fmt.Sprintf("%s_95", name)
   190  	n99 := fmt.Sprintf("%s_99", name)
   191  	quantiles := func() (p50, p90, p95, p99 float64) {
   192  		err := cw.Send()
   193  		if err != nil {
   194  			t.Fatal(err)
   195  		}
   196  
   197  		svc.mtx.RLock()
   198  		defer svc.mtx.RUnlock()
   199  		if len(svc.valuesReceived[n50]) > 0 {
   200  			p50 = svc.valuesReceived[n50][0]
   201  			delete(svc.valuesReceived, n50)
   202  		}
   203  
   204  		if len(svc.valuesReceived[n90]) > 0 {
   205  			p90 = svc.valuesReceived[n90][0]
   206  			delete(svc.valuesReceived, n90)
   207  		}
   208  
   209  		if len(svc.valuesReceived[n95]) > 0 {
   210  			p95 = svc.valuesReceived[n95][0]
   211  			delete(svc.valuesReceived, n95)
   212  		}
   213  
   214  		if len(svc.valuesReceived[n99]) > 0 {
   215  			p99 = svc.valuesReceived[n99][0]
   216  			delete(svc.valuesReceived, n99)
   217  		}
   218  		return
   219  	}
   220  	if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	if err := svc.testDimensions(n50, label, value); err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	if err := svc.testDimensions(n90, label, value); err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	if err := svc.testDimensions(n95, label, value); err != nil {
   230  		t.Fatal(err)
   231  	}
   232  	if err := svc.testDimensions(n99, label, value); err != nil {
   233  		t.Fatal(err)
   234  	}
   235  
   236  	// now test with only 2 custom percentiles
   237  	//
   238  	svc = newMockCloudWatch()
   239  	cw = New(namespace, svc, WithLogger(log.NewNopLogger()), WithPercentiles(0.50, 0.90))
   240  	histogram = cw.NewHistogram(name).With(label, value)
   241  
   242  	customQuantiles := func() (p50, p90, p95, p99 float64) {
   243  		err := cw.Send()
   244  		if err != nil {
   245  			t.Fatal(err)
   246  		}
   247  		svc.mtx.RLock()
   248  		defer svc.mtx.RUnlock()
   249  		if len(svc.valuesReceived[n50]) > 0 {
   250  			p50 = svc.valuesReceived[n50][0]
   251  			delete(svc.valuesReceived, n50)
   252  		}
   253  		if len(svc.valuesReceived[n90]) > 0 {
   254  			p90 = svc.valuesReceived[n90][0]
   255  			delete(svc.valuesReceived, n90)
   256  		}
   257  
   258  		// our teststat.TestHistogram wants us to give p95 and p99,
   259  		// but with custom percentiles we don't have those.
   260  		// So fake them. Maybe we should make teststat.nvq() public and use that?
   261  		p95 = 541.121341
   262  		p99 = 558.158697
   263  
   264  		// but fail if they are actually set (because that would mean the
   265  		// WithPercentiles() is not respected)
   266  		if _, isSet := svc.valuesReceived[n95]; isSet {
   267  			t.Fatal("p95 should not be set")
   268  		}
   269  		if _, isSet := svc.valuesReceived[n99]; isSet {
   270  			t.Fatal("p99 should not be set")
   271  		}
   272  		return
   273  	}
   274  	if err := teststat.TestHistogram(histogram, customQuantiles, 0.01); err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	if err := svc.testDimensions(n50, label, value); err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	if err := svc.testDimensions(n90, label, value); err != nil {
   281  		t.Fatal(err)
   282  	}
   283  	if err := svc.testDimensions(n95, label, value); err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	if err := svc.testDimensions(n99, label, value); err != nil {
   287  		t.Fatal(err)
   288  	}
   289  }
   290  
   291  func TestErrorLog(t *testing.T) {
   292  	namespace := "abc"
   293  	svc := newMockCloudWatch()
   294  	cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
   295  	cw.NewGauge(metricNameToGenerateError).Set(123)
   296  	if err := cw.Send(); err != errTest {
   297  		t.Fatal("Expected error, but didn't get one")
   298  	}
   299  }
   300  

View as plain text