From 868dc42ad150a93454469f87c992723167247c01 Mon Sep 17 00:00:00 2001 From: xiami Date: Sat, 20 Feb 2021 22:27:53 -0800 Subject: [PATCH] Batch the metrics with the same dimensions into the same EMF log request --- exporter/awsemfexporter/datapoint.go | 27 +- exporter/awsemfexporter/datapoint_test.go | 106 +- exporter/awsemfexporter/emf_exporter.go | 56 +- exporter/awsemfexporter/go.mod | 1 + exporter/awsemfexporter/go.sum | 163 - exporter/awsemfexporter/grouped_metric.go | 112 + .../awsemfexporter/grouped_metric_test.go | 498 +++ exporter/awsemfexporter/metric_declaration.go | 60 +- .../awsemfexporter/metric_declaration_test.go | 194 +- exporter/awsemfexporter/metric_translator.go | 352 +- .../awsemfexporter/metric_translator_test.go | 3187 +++++++---------- exporter/awsemfexporter/pusher_test.go | 2 +- exporter/awsemfexporter/util.go | 53 + 13 files changed, 2329 insertions(+), 2482 deletions(-) create mode 100644 exporter/awsemfexporter/grouped_metric.go create mode 100644 exporter/awsemfexporter/grouped_metric_test.go diff --git a/exporter/awsemfexporter/datapoint.go b/exporter/awsemfexporter/datapoint.go index f489c6461d69..b345c3b4b053 100644 --- a/exporter/awsemfexporter/datapoint.go +++ b/exporter/awsemfexporter/datapoint.go @@ -62,6 +62,7 @@ type rateKeyParams struct { metricNameKey string logGroupKey string logStreamKey string + timestampKey string labels label.Distinct } @@ -101,12 +102,12 @@ type DoubleSummaryDataPointSlice struct { func (dps IntDataPointSlice) At(i int) DataPoint { metric := dps.IntDataPointSlice.At(i) timestampMs := unixNanoToMilliseconds(metric.Timestamp()) - labels := createLabels(metric.LabelsMap()) + labels := createLabels(metric.LabelsMap(), dps.instrumentationLibraryName) var metricVal float64 metricVal = float64(metric.Value()) if dps.needsCalculateRate { - sortedLabels := getSortedLabels(metric.LabelsMap()) + sortedLabels := getSortedLabels(labels) dps.rateKeyParams.labels = sortedLabels rateKey := dps.rateKeyParams rateTS := dps.timestampMs @@ -127,13 +128,13 @@ func (dps IntDataPointSlice) At(i int) DataPoint { // At retrieves the DoubleDataPoint at the given index and performs rate calculation if necessary. func (dps DoubleDataPointSlice) At(i int) DataPoint { metric := dps.DoubleDataPointSlice.At(i) - labels := createLabels(metric.LabelsMap()) + labels := createLabels(metric.LabelsMap(), dps.instrumentationLibraryName) timestampMs := unixNanoToMilliseconds(metric.Timestamp()) var metricVal float64 metricVal = metric.Value() if dps.needsCalculateRate { - sortedLabels := getSortedLabels(metric.LabelsMap()) + sortedLabels := getSortedLabels(labels) dps.rateKeyParams.labels = sortedLabels rateKey := dps.rateKeyParams rateTS := dps.timestampMs @@ -154,7 +155,7 @@ func (dps DoubleDataPointSlice) At(i int) DataPoint { // At retrieves the DoubleHistogramDataPoint at the given index. func (dps DoubleHistogramDataPointSlice) At(i int) DataPoint { metric := dps.DoubleHistogramDataPointSlice.At(i) - labels := createLabels(metric.LabelsMap()) + labels := createLabels(metric.LabelsMap(), dps.instrumentationLibraryName) timestamp := unixNanoToMilliseconds(metric.Timestamp()) return DataPoint{ @@ -170,7 +171,7 @@ func (dps DoubleHistogramDataPointSlice) At(i int) DataPoint { // At retrieves the DoubleSummaryDataPoint at the given index. func (dps DoubleSummaryDataPointSlice) At(i int) DataPoint { metric := dps.DoubleSummaryDataPointSlice.At(i) - labels := createLabels(metric.LabelsMap()) + labels := createLabels(metric.LabelsMap(), dps.instrumentationLibraryName) timestampMs := unixNanoToMilliseconds(metric.Timestamp()) metricVal := &CWMetricStats{ @@ -190,22 +191,28 @@ func (dps DoubleSummaryDataPointSlice) At(i int) DataPoint { } // createLabels converts OTel StringMap labels to a map -func createLabels(labelsMap pdata.StringMap) map[string]string { +// and optionally adds in the OTel instrumentation library name +func createLabels(labelsMap pdata.StringMap, instrLibName string) map[string]string { labels := make(map[string]string, labelsMap.Len()+1) labelsMap.ForEach(func(k, v string) { labels[k] = v }) + // Add OTel instrumentation lib name as an additional label if it is defined + if instrLibName != noInstrumentationLibraryName { + labels[oTellibDimensionKey] = instrLibName + } + return labels } // getSortedLabels converts OTel StringMap labels to sorted labels as label.Distinct -func getSortedLabels(labelsMap pdata.StringMap) label.Distinct { +func getSortedLabels(labels map[string]string) label.Distinct { var kvs []label.KeyValue var sortable label.Sortable - labelsMap.ForEach(func(k, v string) { + for k, v := range labels { kvs = append(kvs, label.String(k, v)) - }) + } set := label.NewSetWithSortable(kvs, &sortable) return set.Equivalent() diff --git a/exporter/awsemfexporter/datapoint_test.go b/exporter/awsemfexporter/datapoint_test.go index 3c84245be10b..2101cd78b29e 100644 --- a/exporter/awsemfexporter/datapoint_test.go +++ b/exporter/awsemfexporter/datapoint_test.go @@ -16,6 +16,7 @@ package awsemfexporter import ( "reflect" + "strings" "testing" "time" @@ -242,7 +243,6 @@ func generateTestSummary(name string) *metricspb.Metric { } func TestIntDataPointSliceAt(t *testing.T) { - timestamp := time.Now().UnixNano() / int64(time.Millisecond) instrLibName := "cloudwatch-otel" labels := map[string]string{"label1": "value1"} rateKeys := rateKeyParams{ @@ -256,25 +256,35 @@ func TestIntDataPointSliceAt(t *testing.T) { testName string needsCalculateRate bool value interface{} + calculatedValue interface{} }{ { "no rate calculation", false, + int64(-17), float64(-17), }, { - "w/ rate calculation", + "w/ 1st rate calculation", true, + int64(1), float64(0), }, + { + "w/ 2nd rate calculation", + true, + int64(2), + float64(1), + }, } for _, tc := range testCases { t.Run(tc.testName, func(t *testing.T) { + timestamp := time.Now().UnixNano() / int64(time.Millisecond) testDPS := pdata.NewIntDataPointSlice() testDPS.Resize(1) testDP := testDPS.At(0) - testDP.SetValue(int64(-17)) + testDP.SetValue(tc.value.(int64)) testDP.LabelsMap().InitFromMap(labels) dps := IntDataPointSlice{ @@ -288,21 +298,27 @@ func TestIntDataPointSliceAt(t *testing.T) { } expectedDP := DataPoint{ - Value: tc.value, + Value: tc.calculatedValue, Labels: map[string]string{ - "label1": "value1", + oTellibDimensionKey: instrLibName, + "label1": "value1", }, } assert.Equal(t, 1, dps.Len()) dp := dps.At(0) - assert.Equal(t, expectedDP, dp) + if strings.Contains(tc.testName, "2nd rate") { + assert.True(t, (expectedDP.Value.(float64)-dp.Value.(float64)) < 0.01) + } else { + assert.Equal(t, expectedDP, dp) + } + // sleep 1s for verifying the cumulative metric delta rate + time.Sleep(1000 * time.Millisecond) }) } } func TestDoubleDataPointSliceAt(t *testing.T) { - timestamp := time.Now().UnixNano() / int64(time.Millisecond) instrLibName := "cloudwatch-otel" labels := map[string]string{"label1": "value1"} rateKeys := rateKeyParams{ @@ -316,25 +332,35 @@ func TestDoubleDataPointSliceAt(t *testing.T) { testName string needsCalculateRate bool value interface{} + calculatedValue interface{} }{ { "no rate calculation", false, float64(0.3), + float64(0.3), }, { - "w/ rate calculation", + "w/ 1st rate calculation", true, - float64(0), + float64(0.4), + float64(0.0), + }, + { + "w/ 2nd rate calculation", + true, + float64(0.5), + float64(0.1), }, } for _, tc := range testCases { t.Run(tc.testName, func(t *testing.T) { + timestamp := time.Now().UnixNano() / int64(time.Millisecond) testDPS := pdata.NewDoubleDataPointSlice() testDPS.Resize(1) testDP := testDPS.At(0) - testDP.SetValue(float64(0.3)) + testDP.SetValue(tc.value.(float64)) testDP.LabelsMap().InitFromMap(labels) dps := DoubleDataPointSlice{ @@ -348,15 +374,22 @@ func TestDoubleDataPointSliceAt(t *testing.T) { } expectedDP := DataPoint{ - Value: tc.value, + Value: tc.calculatedValue, Labels: map[string]string{ - "label1": "value1", + oTellibDimensionKey: instrLibName, + "label1": "value1", }, } assert.Equal(t, 1, dps.Len()) dp := dps.At(0) - assert.Equal(t, expectedDP, dp) + if strings.Contains(tc.testName, "2nd rate") { + assert.True(t, (expectedDP.Value.(float64)-dp.Value.(float64)) < 0.002) + } else { + assert.Equal(t, expectedDP, dp) + } + // sleep 10ms for verifying the cumulative metric delta rate + time.Sleep(1000 * time.Millisecond) }) } } @@ -385,7 +418,8 @@ func TestDoubleHistogramDataPointSliceAt(t *testing.T) { Count: 17, }, Labels: map[string]string{ - "label1": "value1", + oTellibDimensionKey: instrLibName, + "label1": "value1", }, } @@ -425,7 +459,8 @@ func TestDoubleSummaryDataPointSliceAt(t *testing.T) { Sum: 17.13, }, Labels: map[string]string{ - "label1": "value1", + oTellibDimensionKey: instrLibName, + "label1": "value1", }, } @@ -442,11 +477,12 @@ func TestCreateLabels(t *testing.T) { } labelsMap := pdata.NewStringMap().InitFromMap(expectedLabels) - labels := createLabels(labelsMap) + labels := createLabels(labelsMap, noInstrumentationLibraryName) assert.Equal(t, expectedLabels, labels) // With isntrumentation library name - labels = createLabels(labelsMap) + labels = createLabels(labelsMap, "cloudwatch-otel") + expectedLabels[oTellibDimensionKey] = "cloudwatch-otel" assert.Equal(t, expectedLabels, labels) } @@ -708,13 +744,13 @@ func BenchmarkGetDataPoints(b *testing.B) { } func TestGetSortedLabelsEquals(t *testing.T) { - labelMap1 := pdata.NewStringMap() - labelMap1.Insert("k1", "v1") - labelMap1.Insert("k2", "v2") + labelMap1 := make(map[string]string) + labelMap1["k1"] = "v1" + labelMap1["k2"] = "v2" - labelMap2 := pdata.NewStringMap() - labelMap2.Insert("k2", "v2") - labelMap2.Insert("k1", "v1") + labelMap2 := make(map[string]string) + labelMap2["k2"] = "v2" + labelMap2["k1"] = "v1" sortedLabels1 := getSortedLabels(labelMap1) sortedLabels2 := getSortedLabels(labelMap2) @@ -737,13 +773,13 @@ func TestGetSortedLabelsEquals(t *testing.T) { } func TestGetSortedLabelsNotEqual(t *testing.T) { - labelMap1 := pdata.NewStringMap() - labelMap1.Insert("k1", "v1") - labelMap1.Insert("k2", "v2") + labelMap1 := make(map[string]string) + labelMap1["k1"] = "v1" + labelMap1["k2"] = "v2" - labelMap2 := pdata.NewStringMap() - labelMap2.Insert("k2", "v2") - labelMap2.Insert("k1", "v3") + labelMap2 := make(map[string]string) + labelMap2["k2"] = "v2" + labelMap2["k1"] = "v3" sortedLabels1 := getSortedLabels(labelMap1) sortedLabels2 := getSortedLabels(labelMap2) @@ -766,13 +802,13 @@ func TestGetSortedLabelsNotEqual(t *testing.T) { } func TestGetSortedLabelsNotEqualOnPram(t *testing.T) { - labelMap1 := pdata.NewStringMap() - labelMap1.Insert("k1", "v1") - labelMap1.Insert("k2", "v2") + labelMap1 := make(map[string]string) + labelMap1["k1"] = "v1" + labelMap1["k2"] = "v2" - labelMap2 := pdata.NewStringMap() - labelMap2.Insert("k2", "v2") - labelMap2.Insert("k1", "v1") + labelMap2 := make(map[string]string) + labelMap2["k2"] = "v2" + labelMap2["k1"] = "v1" sortedLabels1 := getSortedLabels(labelMap1) sortedLabels2 := getSortedLabels(labelMap2) diff --git a/exporter/awsemfexporter/emf_exporter.go b/exporter/awsemfexporter/emf_exporter.go index c4204eea665c..7dc028736d94 100644 --- a/exporter/awsemfexporter/emf_exporter.go +++ b/exporter/awsemfexporter/emf_exporter.go @@ -105,61 +105,43 @@ func NewEmfExporter( } func (emf *emfExporter) pushMetricsData(_ context.Context, md pdata.Metrics) (droppedTimeSeries int, err error) { - var cwm []*CWMetrics - var totalDroppedMetrics int + groupedMetrics := make(map[interface{}]*GroupedMetric) expConfig := emf.config.(*Config) - namespace := expConfig.Namespace - logGroup := "/metrics/default" - logStream := fmt.Sprintf("otel-stream-%s", emf.collectorID) + defaultLogStream := fmt.Sprintf("otel-stream-%s", emf.collectorID) rms := md.ResourceMetrics() for i := 0; i < rms.Len(); i++ { rm := rms.At(i) - var droppedMetrics int - cwm, droppedMetrics = emf.metricTranslator.translateOTelToCWMetric(&rm, expConfig) - totalDroppedMetrics = totalDroppedMetrics + droppedMetrics - - if len(cwm) > 0 && len(cwm[0].Measurements) > 0 { - namespace = cwm[0].Measurements[0].Namespace - } - - putLogEvents := translateCWMetricToEMF(cwm, expConfig.logger) + emf.metricTranslator.translateOTelToGroupedMetric(&rm, groupedMetrics, expConfig) + } - if namespace != "" { - logGroup = fmt.Sprintf("/metrics/%s", namespace) - } + for _, groupedMetric := range groupedMetrics { + cWMetric := translateGroupedMetricToCWMetric(groupedMetric, expConfig) + putLogEvent := translateCWMetricToEMF(cWMetric) - // override log group if found it in exp configuration, this configuration has top priority. - // However, in this case, customer won't have correlation experience - if len(expConfig.LogGroupName) > 0 { - logGroup = replacePatterns(expConfig.LogGroupName, rm.Resource().Attributes(), expConfig.logger) - } - if len(expConfig.LogStreamName) > 0 { - logStream = replacePatterns(expConfig.LogStreamName, rm.Resource().Attributes(), expConfig.logger) + logGroup := groupedMetric.Metadata.LogGroup + logStream := groupedMetric.Metadata.LogStream + if logStream == "" { + logStream = defaultLogStream } pusher := emf.getPusher(logGroup, logStream) if pusher != nil { - for _, ple := range putLogEvents { - returnError := pusher.AddLogEntry(ple) - if returnError != nil { - err = wrapErrorIfBadRequest(&returnError) - } - if err != nil { - return totalDroppedMetrics, err - } - } - returnError := pusher.ForceFlush() + returnError := pusher.AddLogEntry(putLogEvent) if returnError != nil { err = wrapErrorIfBadRequest(&returnError) + return } - if err != nil { - return totalDroppedMetrics, err + returnError = pusher.ForceFlush() + if returnError != nil { + err = wrapErrorIfBadRequest(&returnError) + return } } } - return totalDroppedMetrics, nil + + return } func (emf *emfExporter) getPusher(logGroup, logStream string) Pusher { diff --git a/exporter/awsemfexporter/go.mod b/exporter/awsemfexporter/go.mod index 955a1bc8c729..013ca2b30c87 100644 --- a/exporter/awsemfexporter/go.mod +++ b/exporter/awsemfexporter/go.mod @@ -11,4 +11,5 @@ require ( go.opentelemetry.io/collector v0.20.1-0.20210218001603-48151d869607 go.opentelemetry.io/otel v0.16.0 go.uber.org/zap v1.16.0 + google.golang.org/protobuf v1.25.0 ) diff --git a/exporter/awsemfexporter/go.sum b/exporter/awsemfexporter/go.sum index 0d3c90bc231f..01d006cd938b 100644 --- a/exporter/awsemfexporter/go.sum +++ b/exporter/awsemfexporter/go.sum @@ -14,7 +14,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -39,54 +38,39 @@ collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-sdk-for-go v46.4.0+incompatible h1:fCN6Pi+tEiEwFa8RSmtVlFHRXEZ+DJm9gfx/MKqYWw4= github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest v0.11.10 h1:j5sGbX7uj1ieYYkQ3Mpvewd4DCsEQ+ZeJpqnSM9pjnM= github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= -github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/HdrHistogram/hdrhistogram-go v0.9.0 h1:dpujRju0R4M/QZzcnR1LH1qm+TVG3UzkWdp5tH1WMcg= github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -95,13 +79,9 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.22.2-0.20190604114437-cd910a683f9f/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= -github.com/Shopify/sarama v1.27.2 h1:1EyY1dsxNDUQEv0O/4TsjosHI2CgB1uo9H/v56xzTxc= github.com/Shopify/sarama v1.27.2/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II= -github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -114,16 +94,13 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antonmedv/expr v1.8.9 h1:O9stiHmHHww9b4ozhPx7T6BK7fXfOCHJ8ybxf0833zw= github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -139,7 +116,6 @@ github.com/aws/aws-sdk-go v1.37.10/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= @@ -156,10 +132,8 @@ github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -171,15 +145,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI= github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -206,27 +177,19 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.46.0 h1:WRbwjATilgz2NE4NGMeSDpeicy9h4xSKNGuRJ/Nq/fA= github.com/digitalocean/godo v1.46.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible h1:+mzU0jHyjWpYHiD0StRlsVXkCvecWS2hc55M3OlUJSk= github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= @@ -238,20 +201,15 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.7.3/go.mod h1:V1d2J5pfxYH6EjBAgSK7YNXcXlTWxUHdE1sVDXkjnig= -github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -269,16 +227,12 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -341,7 +295,6 @@ github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7 github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -370,7 +323,6 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.3.0 h1:M695OaDJ5ipWvDPcoAg/YL9c3uORAegkEfBqTQF/fTQ= github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -414,10 +366,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -429,12 +379,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -453,21 +400,15 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gophercloud/gophercloud v0.13.0 h1:1XkslZZRm6Ks0bLup+hBNth+KQf+0JA1UeoB7YKw9E8= github.com/gophercloud/gophercloud v0.13.0/go.mod h1:VX0Ibx85B60B5XOrZr6kaNwrmPUzcmMpwxvQ1WQIIWM= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -475,7 +416,6 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -488,49 +428,37 @@ github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/api v1.7.0 h1:tGs8Oep67r8CcA2Ycmb/8BLBcJ70St44mF2X10a/qPg= github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw= github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.0 h1:1X+ga+2ki9aUJJaliI2isvjNLI8rNCGHFkZ1FaVpvCA= github.com/hashicorp/go-hclog v0.14.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8= github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -538,20 +466,16 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM= github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hetznercloud/hcloud-go v1.22.0 h1:CC0jwkaBzwP4ObFE0sdJBTvGh5DE9kB/tuDETnRfOik= github.com/hetznercloud/hcloud-go v1.22.0/go.mod h1:xng8lbDUg+xM1dgc0yGHX5EeqbwIq7UYlMWMTx3SQVg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= @@ -565,28 +489,23 @@ github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1: github.com/jaegertracing/jaeger v1.21.0 h1:Fgre3vTI5E/cmkXKBXK7ksnzul5b/3gXjA3mQzt0+58= github.com/jaegertracing/jaeger v1.21.0/go.mod h1:PCTGGFohQBPQMR4j333V5lt6If7tj8aWJ+pQNgvZ+wU= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -600,28 +519,23 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leoluk/perflib_exporter v0.1.0 h1:fXe/mDaf9jR+Zk8FjFlcCSksACuIj2VNN4GyKHmQqtA= github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -645,14 +559,12 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -660,19 +572,15 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -684,19 +592,15 @@ github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYG github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= @@ -707,14 +611,11 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olivere/elastic v6.2.27+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= @@ -724,7 +625,6 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -732,12 +632,9 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opentracing-contrib/go-grpc v0.0.0-20191001143057-db30781987df/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -747,19 +644,15 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/orijtech/prometheus-go-metrics-exporter v0.0.6 h1:ExkpQsyDDcyp0U3zhoNUQaCQ/o0Ovq7e1jRCL9lQ/4o= github.com/orijtech/prometheus-go-metrics-exporter v0.0.6/go.mod h1:BiTx/ugZex8LheBk3j53tktWaRdFjV5FCfT2o0P7msE= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -772,17 +665,14 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= @@ -791,7 +681,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f h1:JDEmUDtyiLMyMlFwiaDOv2hxUp35497fkwePcLeV7j4= github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -806,14 +695,12 @@ github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -825,7 +712,6 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -837,15 +723,12 @@ github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4 h1:54z99l8Q3TuyyeoNZkyY4Lq7eFht9J2Mynq4T1Hxbzc= github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4/go.mod h1:XYjkJiog7fyQu3puQNivZPI2pNq1C/775EIoHfDvuvY= github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= @@ -855,24 +738,20 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e h1:CGjiMQ0wMH4wtNWrlj6kiTbkPt2F3rbYnhGX6TWLfco= github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.20.12-0.20201210134652-afe0c04c5d5a+incompatible h1:uvFOXu1W/nsxgfbLu0jGTad6j0PbFDsXkwxK/rqT/AA= github.com/shirou/gopsutil v3.20.12-0.20201210134652-afe0c04c5d5a+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -882,17 +761,12 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= @@ -902,7 +776,6 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -937,14 +810,12 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tinylib/msgp v1.1.5 h1:2gXmtWueD2HefZHQe1QOy9HVzmFrLOVvsXwXBQ0ayy0= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= @@ -995,13 +866,11 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -1027,7 +896,6 @@ golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1054,7 +922,6 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -1063,7 +930,6 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1117,7 +983,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1129,7 +994,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1213,7 +1077,6 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1286,12 +1149,10 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201008025239-9df69603baec/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -1316,7 +1177,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts= google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1324,7 +1184,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1388,7 +1247,6 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9 h1:f+/+gfZ/tfaHBXXiv1gWRmCej6wlX3mLY4bnLpI99wk= google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9/go.mod h1:5j1uub0jRGhRiSghIlrThmBUgcgLXOVJQ/l1getT4uo= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1405,35 +1263,25 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg= gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1449,7 +1297,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1459,32 +1306,22 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc= honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= -k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= -k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.3.0 h1:WmkrnW7fdrm0/DMClc+HIxtftvxVIPAhlVwMQo5yLco= k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/exporter/awsemfexporter/grouped_metric.go b/exporter/awsemfexporter/grouped_metric.go new file mode 100644 index 000000000000..c1ad2bf6db51 --- /dev/null +++ b/exporter/awsemfexporter/grouped_metric.go @@ -0,0 +1,112 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsemfexporter + +import ( + "strconv" + + "go.opentelemetry.io/collector/consumer/pdata" + "go.uber.org/zap" +) + +// GroupedMetric defines set of metrics with same namespace, timestamp and labels +type GroupedMetric struct { + Labels map[string]string + Metrics map[string]*MetricInfo + Metadata CWMetricMetadata +} + +// MetricInfo defines value and unit for OT Metrics +type MetricInfo struct { + Value interface{} + Unit string +} + +// addToGroupedMetric processes OT metrics and adds them into GroupedMetric buckets +func addToGroupedMetric(pmd *pdata.Metric, groupedMetrics map[interface{}]*GroupedMetric, metadata CWMetricMetadata, logger *zap.Logger, descriptor map[string]MetricDescriptor) { + if pmd == nil { + return + } + + metricName := pmd.Name() + dps := getDataPoints(pmd, metadata, logger) + if dps == nil || dps.Len() == 0 { + return + } + + for i := 0; i < dps.Len(); i++ { + dp := dps.At(i) + labels := dp.Labels + metric := &MetricInfo{ + Value: dp.Value, + Unit: translateUnit(pmd, descriptor), + } + + if dp.TimestampMs > 0 { + metadata.TimestampMs = dp.TimestampMs + } + + // Extra params to use when grouping metrics + groupKey := rateKeyParams{ + namespaceKey: metadata.Namespace, + timestampKey: strconv.FormatInt(metadata.TimestampMs, 10), + logGroupKey: metadata.LogGroup, + logStreamKey: metadata.LogStream, + } + + sortedLabels := getSortedLabels(labels) + groupKey.labels = sortedLabels + if _, ok := groupedMetrics[groupKey]; ok { + // if metricName already exists in metrics map, print warning log + if _, ok := groupedMetrics[groupKey].Metrics[metricName]; ok { + logger.Warn( + "Duplicate metric found", + zap.String("Name", metricName), + zap.Any("Labels", labels), + ) + } else { + groupedMetrics[groupKey].Metrics[metricName] = metric + } + } else { + groupedMetrics[groupKey] = &GroupedMetric{ + Labels: labels, + Metrics: map[string]*MetricInfo{(metricName): metric}, + Metadata: metadata, + } + } + } +} + +func translateUnit(metric *pdata.Metric, descriptor map[string]MetricDescriptor) string { + unit := metric.Unit() + if descriptor, exists := descriptor[metric.Name()]; exists { + if unit == "" || descriptor.overwrite { + return descriptor.unit + } + } + switch unit { + case "ms": + unit = "Milliseconds" + case "s": + unit = "Seconds" + case "us": + unit = "Microseconds" + case "By": + unit = "Bytes" + case "Bi": + unit = "Bits" + } + return unit +} diff --git a/exporter/awsemfexporter/grouped_metric_test.go b/exporter/awsemfexporter/grouped_metric_test.go new file mode 100644 index 000000000000..8ab6341336be --- /dev/null +++ b/exporter/awsemfexporter/grouped_metric_test.go @@ -0,0 +1,498 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsemfexporter + +import ( + "testing" + "time" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/consumer/consumerdata" + "go.opentelemetry.io/collector/consumer/pdata" + "go.opentelemetry.io/collector/translator/conventions" + "go.opentelemetry.io/collector/translator/internaldata" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestAddToGroupedMetric(t *testing.T) { + namespace := "namespace" + instrumentationLibName := "cloudwatch-otel" + timestamp := time.Now().UnixNano() / int64(time.Millisecond) + logger := zap.NewNop() + + metadata := CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + LogGroup: logGroup, + LogStream: logStreamName, + InstrumentationLibraryName: instrumentationLibName, + } + + testCases := []struct { + testName string + metric *metricspb.Metric + expected map[string]*MetricInfo + }{ + { + "Int gauge", + generateTestIntGauge("foo"), + map[string]*MetricInfo{ + "foo": { + Value: float64(1), + Unit: "Count", + }, + }, + }, + { + "Double gauge", + generateTestDoubleGauge("foo"), + map[string]*MetricInfo{ + "foo": { + Value: 0.1, + Unit: "Count", + }, + }, + }, + { + "Int sum", + generateTestIntSum("foo"), + map[string]*MetricInfo{ + "foo": { + Value: float64(0), + Unit: "Count", + }, + }, + }, + { + "Double sum", + generateTestDoubleSum("foo"), + map[string]*MetricInfo{ + "foo": { + Value: float64(0), + Unit: "Count", + }, + }, + }, + { + "Double histogram", + generateTestDoubleHistogram("foo"), + map[string]*MetricInfo{ + "foo": { + Value: &CWMetricStats{ + Count: 18, + Sum: 35.0, + }, + Unit: "Seconds", + }, + }, + }, + { + "Summary", + generateTestSummary("foo"), + map[string]*MetricInfo{ + "foo": { + Value: &CWMetricStats{ + Min: 1, + Max: 5, + Count: 5, + Sum: 15, + }, + Unit: "Seconds", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + oc := consumerdata.MetricsData{ + Node: &commonpb.Node{}, + Resource: &resourcepb.Resource{ + Labels: map[string]string{ + conventions.AttributeServiceName: "myServiceName", + conventions.AttributeServiceNamespace: "myServiceNS", + }, + }, + Metrics: []*metricspb.Metric{tc.metric}, + } + + // Retrieve *pdata.Metric + rm := internaldata.OCToMetrics(oc) + rms := rm.ResourceMetrics() + assert.Equal(t, 1, rms.Len()) + ilms := rms.At(0).InstrumentationLibraryMetrics() + assert.Equal(t, 1, ilms.Len()) + metrics := ilms.At(0).Metrics() + assert.Equal(t, 1, metrics.Len()) + metric := metrics.At(0) + + addToGroupedMetric(&metric, groupedMetrics, metadata, zap.NewNop(), nil) + expectedLabels := map[string]string{ + oTellibDimensionKey: instrumentationLibName, + "label1": "value1", + } + + for _, v := range groupedMetrics { + assert.Equal(t, len(tc.expected), len(v.Metrics)) + assert.Equal(t, tc.expected, v.Metrics) + assert.Equal(t, 2, len(v.Labels)) + assert.Equal(t, metadata, v.Metadata) + assert.Equal(t, expectedLabels, v.Labels) + } + }) + } + + t.Run("Add multiple different metrics", func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + oc := consumerdata.MetricsData{ + Node: &commonpb.Node{}, + Resource: &resourcepb.Resource{ + Labels: map[string]string{ + conventions.AttributeServiceName: "myServiceName", + conventions.AttributeServiceNamespace: "myServiceNS", + }, + }, + Metrics: []*metricspb.Metric{ + generateTestIntGauge("int-gauge"), + generateTestDoubleGauge("double-gauge"), + generateTestIntSum("int-sum"), + generateTestDoubleSum("double-sum"), + generateTestDoubleHistogram("double-histogram"), + generateTestSummary("summary"), + }, + } + rm := internaldata.OCToMetrics(oc) + rms := rm.ResourceMetrics() + ilms := rms.At(0).InstrumentationLibraryMetrics() + metrics := ilms.At(0).Metrics() + assert.Equal(t, 6, metrics.Len()) + + for i := 0; i < metrics.Len(); i++ { + metric := metrics.At(i) + addToGroupedMetric(&metric, groupedMetrics, metadata, logger, nil) + } + + assert.Equal(t, 1, len(groupedMetrics)) + for _, group := range groupedMetrics { + assert.Equal(t, 6, len(group.Metrics)) + for metricName, metricInfo := range group.Metrics { + if metricName == "double-histogram" || metricName == "summary" { + assert.Equal(t, "Seconds", metricInfo.Unit) + } else { + assert.Equal(t, "Count", metricInfo.Unit) + } + } + expectedLabels := map[string]string{ + oTellibDimensionKey: "cloudwatch-otel", + "label1": "value1", + } + assert.Equal(t, expectedLabels, group.Labels) + assert.Equal(t, metadata, group.Metadata) + } + }) + + t.Run("Add multiple metrics w/ different timestamps", func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + oc := consumerdata.MetricsData{ + Node: &commonpb.Node{}, + Resource: &resourcepb.Resource{ + Labels: map[string]string{ + conventions.AttributeServiceName: "myServiceName", + conventions.AttributeServiceNamespace: "myServiceNS", + }, + }, + Metrics: []*metricspb.Metric{ + generateTestIntGauge("int-gauge"), + generateTestDoubleGauge("double-gauge"), + generateTestIntSum("int-sum"), + generateTestSummary("summary"), + }, + } + + timestamp1 := ×tamppb.Timestamp{ + Seconds: int64(1608068109), + Nanos: 347942000, + } + timestamp2 := ×tamppb.Timestamp{ + Seconds: int64(1608068110), + Nanos: 347942000, + } + + // Give int gauge and int-sum the same timestamp + oc.Metrics[0].Timeseries[0].Points[0].Timestamp = timestamp1 + oc.Metrics[2].Timeseries[0].Points[0].Timestamp = timestamp1 + // Give summary a different timestamp + oc.Metrics[3].Timeseries[0].Points[0].Timestamp = timestamp2 + + rm := internaldata.OCToMetrics(oc) + rms := rm.ResourceMetrics() + ilms := rms.At(0).InstrumentationLibraryMetrics() + metrics := ilms.At(0).Metrics() + assert.Equal(t, 4, metrics.Len()) + + for i := 0; i < metrics.Len(); i++ { + metric := metrics.At(i) + addToGroupedMetric(&metric, groupedMetrics, metadata, logger, nil) + } + + assert.Equal(t, 3, len(groupedMetrics)) + for _, group := range groupedMetrics { + for metricName := range group.Metrics { + if metricName == "int-gauge" || metricName == "int-sum" { + assert.Equal(t, 2, len(group.Metrics)) + assert.Equal(t, int64(1608068109347), group.Metadata.TimestampMs) + } else if metricName == "summary" { + assert.Equal(t, 1, len(group.Metrics)) + assert.Equal(t, int64(1608068110347), group.Metadata.TimestampMs) + } else { + // double-gauge should use the default timestamp + assert.Equal(t, 1, len(group.Metrics)) + assert.Equal(t, "double-gauge", metricName) + assert.Equal(t, timestamp, group.Metadata.TimestampMs) + } + } + expectedLabels := map[string]string{ + oTellibDimensionKey: "cloudwatch-otel", + "label1": "value1", + } + assert.Equal(t, expectedLabels, group.Labels) + } + }) + + t.Run("Add same metric but different log group", func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + oc := consumerdata.MetricsData{ + Metrics: []*metricspb.Metric{ + generateTestIntGauge("int-gauge"), + }, + } + rm := internaldata.OCToMetrics(oc) + ilms := rm.ResourceMetrics().At(0).InstrumentationLibraryMetrics() + metric := ilms.At(0).Metrics().At(0) + + metricMetadata1 := CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + LogGroup: "log-group-1", + LogStream: logStreamName, + InstrumentationLibraryName: instrumentationLibName, + } + addToGroupedMetric(&metric, groupedMetrics, metricMetadata1, logger, nil) + + metricMetadata2 := CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + LogGroup: "log-group-2", + LogStream: logStreamName, + InstrumentationLibraryName: instrumentationLibName, + } + addToGroupedMetric(&metric, groupedMetrics, metricMetadata2, logger, nil) + + assert.Equal(t, 2, len(groupedMetrics)) + seenLogGroup1 := false + seenLogGroup2 := false + for _, group := range groupedMetrics { + assert.Equal(t, 1, len(group.Metrics)) + expectedMetrics := map[string]*MetricInfo{ + "int-gauge": { + Value: float64(1), + Unit: "Count", + }, + } + assert.Equal(t, expectedMetrics, group.Metrics) + expectedLabels := map[string]string{ + oTellibDimensionKey: "cloudwatch-otel", + "label1": "value1", + } + assert.Equal(t, expectedLabels, group.Labels) + + if group.Metadata.LogGroup == "log-group-2" { + seenLogGroup2 = true + } else if group.Metadata.LogGroup == "log-group-1" { + seenLogGroup1 = true + } + } + assert.True(t, seenLogGroup1) + assert.True(t, seenLogGroup2) + }) + + t.Run("Duplicate metric names", func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + oc := consumerdata.MetricsData{ + Resource: &resourcepb.Resource{ + Labels: map[string]string{ + conventions.AttributeServiceName: "myServiceName", + conventions.AttributeServiceNamespace: "myServiceNS", + }, + }, + Metrics: []*metricspb.Metric{ + generateTestIntGauge("foo"), + generateTestDoubleGauge("foo"), + }, + } + rm := internaldata.OCToMetrics(oc) + rms := rm.ResourceMetrics() + ilms := rms.At(0).InstrumentationLibraryMetrics() + metrics := ilms.At(0).Metrics() + assert.Equal(t, 2, metrics.Len()) + + obs, logs := observer.New(zap.WarnLevel) + obsLogger := zap.New(obs) + + for i := 0; i < metrics.Len(); i++ { + metric := metrics.At(i) + addToGroupedMetric(&metric, groupedMetrics, metadata, obsLogger, nil) + } + assert.Equal(t, 1, len(groupedMetrics)) + + labels := map[string]string{ + oTellibDimensionKey: instrumentationLibName, + "label1": "value1", + } + // Test output warning logs + expectedLogs := []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "Duplicate metric found"}, + Context: []zapcore.Field{ + zap.String("Name", "foo"), + zap.Any("Labels", labels), + }, + }, + } + assert.Equal(t, 1, logs.Len()) + assert.Equal(t, expectedLogs, logs.AllUntimed()) + }) + + t.Run("Unhandled metric type", func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + md := pdata.NewMetrics() + rms := md.ResourceMetrics() + rms.Resize(1) + rms.At(0).InstrumentationLibraryMetrics().Resize(1) + rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics().Resize(1) + metric := rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics().At(0) + metric.SetName("foo") + metric.SetUnit("Count") + metric.SetDataType(pdata.MetricDataTypeIntHistogram) + + obs, logs := observer.New(zap.WarnLevel) + obsLogger := zap.New(obs) + addToGroupedMetric(&metric, groupedMetrics, metadata, obsLogger, nil) + assert.Equal(t, 0, len(groupedMetrics)) + + // Test output warning logs + expectedLogs := []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "Unhandled metric data type."}, + Context: []zapcore.Field{ + zap.String("DataType", "IntHistogram"), + zap.String("Name", "foo"), + zap.String("Unit", "Count"), + }, + }, + } + assert.Equal(t, 1, logs.Len()) + assert.Equal(t, expectedLogs, logs.AllUntimed()) + }) + + t.Run("Nil metric", func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + addToGroupedMetric(nil, groupedMetrics, metadata, logger, nil) + assert.Equal(t, 0, len(groupedMetrics)) + }) +} + +func BenchmarkAddToGroupedMetric(b *testing.B) { + oc := consumerdata.MetricsData{ + Metrics: []*metricspb.Metric{ + generateTestIntGauge("int-gauge"), + generateTestDoubleGauge("double-gauge"), + generateTestIntSum("int-sum"), + generateTestDoubleSum("double-sum"), + generateTestDoubleHistogram("double-histogram"), + generateTestSummary("summary"), + }, + } + rms := internaldata.OCToMetrics(oc).ResourceMetrics() + metrics := rms.At(0).InstrumentationLibraryMetrics().At(0).Metrics() + numMetrics := metrics.Len() + + metadata := CWMetricMetadata{ + Namespace: "Namespace", + TimestampMs: int64(1596151098037), + LogGroup: "log-group", + LogStream: "log-stream", + InstrumentationLibraryName: "cloudwatch-otel", + } + + logger := zap.NewNop() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + groupedMetrics := make(map[interface{}]*GroupedMetric) + for i := 0; i < numMetrics; i++ { + metric := metrics.At(i) + addToGroupedMetric(&metric, groupedMetrics, metadata, logger, nil) + } + } +} + +func TestTranslateUnit(t *testing.T) { + metric := pdata.NewMetric() + metric.SetName("writeIfNotExist") + + translator := &metricTranslator{ + metricDescriptor: map[string]MetricDescriptor{ + "writeIfNotExist": { + metricName: "writeIfNotExist", + unit: "Count", + overwrite: false, + }, + "forceOverwrite": { + metricName: "forceOverwrite", + unit: "Count", + overwrite: true, + }, + }, + } + + translateUnitCases := map[string]string{ + "Count": "Count", + "ms": "Milliseconds", + "s": "Seconds", + "us": "Microseconds", + "By": "Bytes", + "Bi": "Bits", + } + for input, output := range translateUnitCases { + t.Run(input, func(tt *testing.T) { + metric.SetUnit(input) + + v := translateUnit(&metric, translator.metricDescriptor) + assert.Equal(t, output, v) + }) + } + + metric.SetName("forceOverwrite") + v := translateUnit(&metric, translator.metricDescriptor) + assert.Equal(t, "Count", v) +} diff --git a/exporter/awsemfexporter/metric_declaration.go b/exporter/awsemfexporter/metric_declaration.go index 5ce8cfcbe092..101e0b894c7d 100644 --- a/exporter/awsemfexporter/metric_declaration.go +++ b/exporter/awsemfexporter/metric_declaration.go @@ -21,7 +21,6 @@ import ( "sort" "strings" - "go.opentelemetry.io/collector/consumer/pdata" "go.uber.org/zap" ) @@ -129,21 +128,20 @@ func (m *MetricDeclaration) Init(logger *zap.Logger) (err error) { return } -// Matches returns true if the given OTLP Metric's name matches any of the Metric -// Declaration's metric name selectors and label matchers. -func (m *MetricDeclaration) Matches(metric *pdata.Metric, labels map[string]string) bool { - // Check if metric matches at least one of the metric name selectors - hasMetricNameMatch := false +// MatchesName returns true if the given OTLP Metric's name matches any of the Metric +// Declaration's metric name selectors. +func (m *MetricDeclaration) MatchesName(metricName string) bool { for _, regex := range m.metricRegexList { - if regex.MatchString(metric.Name()) { - hasMetricNameMatch = true - break + if regex.MatchString(metricName) { + return true } } - if !hasMetricNameMatch { - return false - } + return false +} +// MatchesLabels returns true if the given OTLP Metric's name matches any of the Metric +// Declaration's label matchers. +func (m *MetricDeclaration) MatchesLabels(labels map[string]string) bool { if len(m.LabelMatchers) == 0 { return true } @@ -158,8 +156,8 @@ func (m *MetricDeclaration) Matches(metric *pdata.Metric, labels map[string]stri return false } -// ExtractDimensions extracts dimensions within the MetricDeclaration that only -// contains labels found in `labels`. Returned order of dimensions are preserved. +// ExtractDimensions filters through the dimensions defined in the given metric declaration and +// returns dimensions that only contains labels from in the given label set. func (m *MetricDeclaration) ExtractDimensions(labels map[string]string) (dimensions [][]string) { for _, dimensionSet := range m.Dimensions { if len(dimensionSet) == 0 { @@ -216,37 +214,3 @@ func (lm *LabelMatcher) getConcatenatedLabels(labels map[string]string) string { } return buf.String() } - -// processMetricDeclarations processes a list of MetricDeclarations and creates a -// list of dimension sets that matches the given `metric`. This list is then aggregated -// together with the rolled-up dimensions. Returned dimension sets -// are deduped and the dimensions in each dimension set are sorted. -// Prerequisite: -// 1. metricDeclarations' dimensions are sorted. -func processMetricDeclarations(metricDeclarations []*MetricDeclaration, metric *pdata.Metric, - labels map[string]string, rolledUpDimensions [][]string) (dimensions [][]string) { - seen := make(map[string]bool) - addDimSet := func(dimSet []string) { - key := strings.Join(dimSet, ",") - // Only add dimension set if not a duplicate - if _, ok := seen[key]; !ok { - dimensions = append(dimensions, dimSet) - seen[key] = true - } - } - // Extract and append dimensions from metric declarations - for _, m := range metricDeclarations { - if m.Matches(metric, labels) { - extractedDims := m.ExtractDimensions(labels) - for _, dimSet := range extractedDims { - addDimSet(dimSet) - } - } - } - // Add on rolled-up dimensions - for _, dimSet := range rolledUpDimensions { - sort.Strings(dimSet) - addDimSet(dimSet) - } - return -} diff --git a/exporter/awsemfexporter/metric_declaration_test.go b/exporter/awsemfexporter/metric_declaration_test.go index 2118bc99e5ea..07d7b66f8b31 100644 --- a/exporter/awsemfexporter/metric_declaration_test.go +++ b/exporter/awsemfexporter/metric_declaration_test.go @@ -375,7 +375,7 @@ func TestMetricDeclarationInit(t *testing.T) { }) } -func TestMetricDeclarationMatches(t *testing.T) { +func TestMetricDeclarationMatchesName(t *testing.T) { m := &MetricDeclaration{ MetricNameSelectors: []string{"^a+$", "^b.*$", "^ac+a$"}, } @@ -383,36 +383,18 @@ func TestMetricDeclarationMatches(t *testing.T) { err := m.Init(logger) assert.Nil(t, err) - metric := pdata.NewMetric() - metric.SetName("a") - assert.True(t, m.Matches(&metric, nil)) - - metric.SetName("aa") - assert.True(t, m.Matches(&metric, nil)) - - metric.SetName("aaaa") - assert.True(t, m.Matches(&metric, nil)) - - metric.SetName("aaab") - assert.False(t, m.Matches(&metric, nil)) - - metric.SetName("b") - assert.True(t, m.Matches(&metric, nil)) - - metric.SetName("ba") - assert.True(t, m.Matches(&metric, nil)) - - metric.SetName("c") - assert.False(t, m.Matches(&metric, nil)) - - metric.SetName("aca") - assert.True(t, m.Matches(&metric, nil)) - - metric.SetName("accca") - assert.True(t, m.Matches(&metric, nil)) + assert.True(t, m.MatchesName("a")) + assert.True(t, m.MatchesName("aa")) + assert.True(t, m.MatchesName("aaaa")) + assert.False(t, m.MatchesName("aaab")) + assert.True(t, m.MatchesName("b")) + assert.True(t, m.MatchesName("ba")) + assert.False(t, m.MatchesName("c")) + assert.True(t, m.MatchesName("aca")) + assert.True(t, m.MatchesName("accca")) } -func TestMetricDeclarationMatchesWithLabelMatchers(t *testing.T) { +func TestMetricDeclarationMatchesLabels(t *testing.T) { labels := map[string]string{ "label1": "foo", "label2": "bar", @@ -525,7 +507,7 @@ func TestMetricDeclarationMatchesWithLabelMatchers(t *testing.T) { t.Run(tc.testName, func(t *testing.T) { err := m.Init(logger) assert.Nil(t, err) - matches := m.Matches(&metric, labels) + matches := m.MatchesLabels(labels) assert.Equal(t, tc.expected, matches) }) } @@ -613,155 +595,3 @@ func TestExtractDimensions(t *testing.T) { }) } } - -func TestProcessMetricDeclarations(t *testing.T) { - metricDeclarations := []*MetricDeclaration{ - { - Dimensions: [][]string{{"dim1", "dim2"}}, - MetricNameSelectors: []string{"a", "b", "c"}, - }, - { - Dimensions: [][]string{{"dim1"}}, - MetricNameSelectors: []string{"aa", "b"}, - }, - { - Dimensions: [][]string{{"dim2", "dim1"}, {"dim1"}}, - MetricNameSelectors: []string{"a"}, - }, - } - logger := zap.NewNop() - for _, m := range metricDeclarations { - err := m.Init(logger) - assert.Nil(t, err) - } - testCases := []struct { - testName string - metricName string - labels map[string]string - rollUpDims [][]string - expectedDims [][]string - }{ - { - "Matching single declaration", - "c", - map[string]string{ - "dim1": "foo", - "dim2": "bar", - }, - nil, - [][]string{ - {"dim1", "dim2"}, - }, - }, - { - "Match single dimension set", - "a", - map[string]string{ - "dim1": "foo", - }, - nil, - [][]string{ - {"dim1"}, - }, - }, - { - "Match single dimension set w/ rolled-up dims", - "a", - map[string]string{ - "dim1": "foo", - "dim3": "car", - }, - [][]string{{"dim1"}, {"dim3"}}, - [][]string{ - {"dim1"}, - {"dim3"}, - }, - }, - { - "Matching multiple declarations", - "b", - map[string]string{ - "dim1": "foo", - "dim2": "bar", - }, - nil, - [][]string{ - {"dim1", "dim2"}, - {"dim1"}, - }, - }, - { - "Matching multiple declarations w/ duplicate", - "a", - map[string]string{ - "dim1": "foo", - "dim2": "bar", - }, - nil, - [][]string{ - {"dim1", "dim2"}, - {"dim1"}, - }, - }, - { - "Matching multiple declarations w/ duplicate + rolled-up dims", - "a", - map[string]string{ - "dim1": "foo", - "dim2": "bar", - "dim3": "car", - }, - [][]string{{"dim2", "dim1"}, {"dim3"}}, - [][]string{ - {"dim1", "dim2"}, - {"dim1"}, - {"dim3"}, - }, - }, - { - "No matching dimension set", - "a", - map[string]string{ - "dim2": "bar", - }, - nil, - nil, - }, - { - "No matching dimension set w/ rolled-up dims", - "a", - map[string]string{ - "dim2": "bar", - }, - [][]string{{"dim2"}}, - [][]string{{"dim2"}}, - }, - { - "No matching metric name", - "c", - map[string]string{ - "dim1": "foo", - }, - nil, - nil, - }, - { - "No matching metric name w/ rolled-up dims", - "c", - map[string]string{ - "dim1": "foo", - }, - [][]string{{"dim1"}}, - [][]string{{"dim1"}}, - }, - } - - for _, tc := range testCases { - metric := pdata.NewMetric() - metric.SetName(tc.metricName) - t.Run(tc.testName, func(t *testing.T) { - dimensions := processMetricDeclarations(metricDeclarations, &metric, tc.labels, tc.rollUpDims) - assert.Equal(t, tc.expectedDims, dimensions) - }) - } -} diff --git a/exporter/awsemfexporter/metric_translator.go b/exporter/awsemfexporter/metric_translator.go index d227ae64cafa..c1bc4fdafae0 100644 --- a/exporter/awsemfexporter/metric_translator.go +++ b/exporter/awsemfexporter/metric_translator.go @@ -16,6 +16,7 @@ package awsemfexporter import ( "encoding/json" + "fmt" "time" "go.opentelemetry.io/collector/consumer/pdata" @@ -28,9 +29,6 @@ const ( defaultNamespace = "default" noInstrumentationLibraryName = "Undefined" - // See: http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html - maximumLogEventsPerPut = 10000 - // DimensionRollupOptions zeroAndSingleDimensionRollup = "ZeroAndSingleDimensionRollup" singleDimensionRollupOnly = "SingleDimensionRollupOnly" @@ -38,13 +36,13 @@ const ( // CWMetrics defines type CWMetrics struct { - Measurements []CwMeasurement + Measurements []CWMeasurement TimestampMs int64 Fields map[string]interface{} } // CwMeasurement defines -type CwMeasurement struct { +type CWMeasurement struct { Namespace string Dimensions [][]string Metrics []map[string]string @@ -81,14 +79,12 @@ func newMetricTranslator(config Config) metricTranslator { } } -// translateOTelToCWMetric converts OT metrics to CloudWatch Metric format -func (mt metricTranslator) translateOTelToCWMetric(rm *pdata.ResourceMetrics, config *Config) ([]*CWMetrics, int) { - var cwMetricList []*CWMetrics +// translateOTelToGroupedMetric converts OT metrics to Grouped Metric format. +func (mt metricTranslator) translateOTelToGroupedMetric(rm *pdata.ResourceMetrics, groupedMetrics map[interface{}]*GroupedMetric, config *Config) { + timestamp := time.Now().UnixNano() / int64(time.Millisecond) var instrumentationLibName string - cWNamespace := getNamespace(rm, config.Namespace) logGroup, logStream := getLogInfo(rm, cWNamespace, config) - timestampMs := time.Now().UnixNano() / int64(time.Millisecond) ilms := rm.InstrumentationLibraryMetrics() for j := 0; j < ilms.Len(); j++ { @@ -104,208 +100,226 @@ func (mt metricTranslator) translateOTelToCWMetric(rm *pdata.ResourceMetrics, co metric := metrics.At(k) metadata := CWMetricMetadata{ Namespace: cWNamespace, - TimestampMs: timestampMs, + TimestampMs: timestamp, LogGroup: logGroup, LogStream: logStream, InstrumentationLibraryName: instrumentationLibName, } - cwMetrics := mt.getCWMetrics(&metric, metadata, instrumentationLibName, config) - cwMetricList = append(cwMetricList, cwMetrics...) + addToGroupedMetric(&metric, groupedMetrics, metadata, config.logger, mt.metricDescriptor) } } - return cwMetricList, 0 } -// translateCWMetricToEMF converts CloudWatch Metric format to EMF. -func translateCWMetricToEMF(cwMetricLists []*CWMetrics, logger *zap.Logger) []*LogEvent { - // convert CWMetric into map format for compatible with PLE input - ples := make([]*LogEvent, 0, maximumLogEventsPerPut) - for _, met := range cwMetricLists { - cwmMap := make(map[string]interface{}) - fieldMap := met.Fields - - if len(met.Measurements) > 0 { - // Create `_aws` section only if there are measurements - cwmMap["CloudWatchMetrics"] = met.Measurements - cwmMap["Timestamp"] = met.TimestampMs - fieldMap["_aws"] = cwmMap - } else { - str, _ := json.Marshal(fieldMap) - logger.Debug("Dropped metric due to no matching metric declarations", zap.String("labels", string(str))) - } +// translateGroupedMetricToCWMetric converts Grouped Metric format to CloudWatch Metric format. +func translateGroupedMetricToCWMetric(groupedMetric *GroupedMetric, config *Config) *CWMetrics { + labels := groupedMetric.Labels + fields := make(map[string]interface{}, len(labels)+len(groupedMetric.Metrics)) - pleMsg, err := json.Marshal(fieldMap) - if err != nil { - continue - } - metricCreationTime := met.TimestampMs + // Add labels to fields + for k, v := range labels { + fields[k] = v + } - logEvent := NewLogEvent( - metricCreationTime, - string(pleMsg), - ) - logEvent.LogGeneratedTime = time.Unix(0, metricCreationTime*int64(time.Millisecond)) - ples = append(ples, logEvent) + // Add metrics to fields + for metricName, metricInfo := range groupedMetric.Metrics { + fields[metricName] = metricInfo.Value } - return ples -} -// getCWMetrics translates OTLP Metric to a list of CW Metrics -func (mt metricTranslator) getCWMetrics(metric *pdata.Metric, metadata CWMetricMetadata, instrumentationLibName string, config *Config) (cwMetrics []*CWMetrics) { - if metric == nil { - return + var cWMeasurements []CWMeasurement + if len(config.MetricDeclarations) == 0 { + // If there are no metric declarations defined, translate grouped metric + // into the corresponding CW Measurement + cwm := groupedMetricToCWMeasurement(groupedMetric, config) + cWMeasurements = []CWMeasurement{cwm} + } else { + // If metric declarations are defined, filter grouped metric's metrics using + // metric declarations and translate into the corresponding list of CW Measurements + cWMeasurements = groupedMetricToCWMeasurementsWithFilters(groupedMetric, config) } - dps := getDataPoints(metric, metadata, config.logger) - if dps == nil || dps.Len() == 0 { - return + return &CWMetrics{ + Measurements: cWMeasurements, + TimestampMs: groupedMetric.Metadata.TimestampMs, + Fields: fields, } +} - // metric measure data from OT - metricMeasure := make(map[string]string) - metricMeasure["Name"] = metric.Name() - metricMeasure["Unit"] = mt.translateUnit(metric) - // metric measure slice could include multiple metric measures - metricSlice := []map[string]string{metricMeasure} - - for m := 0; m < dps.Len(); m++ { - dp := dps.At(m) - cwMetric := buildCWMetric(dp, metric, metadata.Namespace, metricSlice, instrumentationLibName, config) - if cwMetric != nil { - cwMetrics = append(cwMetrics, cwMetric) +// groupedMetricToCWMeasurement creates a single CW Measurement from a grouped metric. +func groupedMetricToCWMeasurement(groupedMetric *GroupedMetric, config *Config) CWMeasurement { + labels := groupedMetric.Labels + dimensionRollupOption := config.DimensionRollupOption + + // Create a dimension set containing list of label names + dimSet := make([]string, len(labels)) + idx := 0 + for labelName := range labels { + dimSet[idx] = labelName + idx++ + } + dimensions := [][]string{dimSet} + + // Apply single/zero dimension rollup to labels + rollupDimensionArray := dimensionRollup(dimensionRollupOption, labels) + + if len(rollupDimensionArray) > 0 { + // Perform duplication check for edge case with a single label and single dimension roll-up + _, hasOTelLibKey := labels[oTellibDimensionKey] + isSingleLabel := len(dimSet) <= 1 || (len(dimSet) == 2 && hasOTelLibKey) + singleDimRollup := dimensionRollupOption == singleDimensionRollupOnly || + dimensionRollupOption == zeroAndSingleDimensionRollup + if isSingleLabel && singleDimRollup { + // Remove duplicated dimension set before adding on rolled-up dimensions + dimensions = nil } } - return -} -func (mt metricTranslator) translateUnit(metric *pdata.Metric) string { - unit := metric.Unit() - if descriptor, exists := mt.metricDescriptor[metric.Name()]; exists { - if unit == "" || descriptor.overwrite { - return descriptor.unit + // Add on rolled-up dimensions + dimensions = append(dimensions, rollupDimensionArray...) + + metrics := make([]map[string]string, len(groupedMetric.Metrics)) + idx = 0 + for metricName, metricInfo := range groupedMetric.Metrics { + metrics[idx] = map[string]string{ + "Name": metricName, + "Unit": metricInfo.Unit, } + idx++ } - switch unit { - case "ms": - unit = "Milliseconds" - case "s": - unit = "Seconds" - case "us": - unit = "Microseconds" - case "By": - unit = "Bytes" - case "Bi": - unit = "Bits" + + return CWMeasurement{ + Namespace: groupedMetric.Metadata.Namespace, + Dimensions: dimensions, + Metrics: metrics, } - return unit } -// buildCWMetric builds CWMetric from DataPoint -func buildCWMetric(dp DataPoint, pmd *pdata.Metric, namespace string, metricSlice []map[string]string, instrumentationLibName string, config *Config) *CWMetrics { - dimensionRollupOption := config.DimensionRollupOption - metricDeclarations := config.MetricDeclarations - - labelsMap := dp.Labels - labelsSlice := make([]string, len(labelsMap), len(labelsMap)+1) - // `labels` contains label key/value pairs - labels := make(map[string]string, len(labelsMap)+1) - // `fields` contains metric and dimensions key/value pairs - fields := make(map[string]interface{}, len(labelsMap)+2) - idx := 0 - for k, v := range labelsMap { - fields[k] = v - labels[k] = v - labelsSlice[idx] = k - idx++ +// groupedMetricToCWMeasurementsWithFilters filters the grouped metric using the given list of metric +// declarations and returns the corresponding list of CW Measurements. +func groupedMetricToCWMeasurementsWithFilters(groupedMetric *GroupedMetric, config *Config) (cWMeasurements []CWMeasurement) { + labels := groupedMetric.Labels + + // Filter metric declarations by labels + metricDeclarations := make([]*MetricDeclaration, 0, len(config.MetricDeclarations)) + for _, metricDeclaration := range config.MetricDeclarations { + if metricDeclaration.MatchesLabels(labels) { + metricDeclarations = append(metricDeclarations, metricDeclaration) + } } - // Apply single/zero dimension rollup to labels - rollupDimensionArray := dimensionRollup(dimensionRollupOption, labelsSlice, instrumentationLibName) + // If the whole batch of metrics don't match any metric declarations, drop them + if len(metricDeclarations) == 0 { + labelsStr, _ := json.Marshal(labels) + metricNames := make([]string, 0) + for metricName := range groupedMetric.Metrics { + metricNames = append(metricNames, metricName) + } + config.logger.Debug( + "Dropped batch of metrics: no metric declaration matched labels", + zap.String("Labels", string(labelsStr)), + zap.Strings("Metric Names", metricNames), + ) + return + } - // Add OTel instrumentation lib name as an additional dimension if it is defined - if instrumentationLibName != noInstrumentationLibraryName { - labels[oTellibDimensionKey] = instrumentationLibName - fields[oTellibDimensionKey] = instrumentationLibName + // Group metrics by matched metric declarations + type metricDeclarationGroup struct { + metricDeclIdxList []int + metrics []map[string]string } - // Create list of dimension sets - var dimensions [][]string - if len(metricDeclarations) > 0 { - // If metric declarations are defined, extract dimension sets from them - dimensions = processMetricDeclarations(metricDeclarations, pmd, labels, rollupDimensionArray) - } else { - // If no metric declarations defined, create a single dimension set containing - // the list of labels - dims := labelsSlice - if instrumentationLibName != noInstrumentationLibraryName { - // If OTel instrumentation lib name is defined, add instrumentation lib - // name as a dimension - dims = append(dims, oTellibDimensionKey) + metricDeclGroups := make(map[string]*metricDeclarationGroup) + for metricName, metricInfo := range groupedMetric.Metrics { + // Filter metric declarations by metric name + var metricDeclIdx []int + for i, metricDeclaration := range metricDeclarations { + if metricDeclaration.MatchesName(metricName) { + metricDeclIdx = append(metricDeclIdx, i) + } } - if len(rollupDimensionArray) > 0 { - // Perform de-duplication check for edge case with a single label and single roll-up - // is activated - if len(labelsSlice) > 1 || (dimensionRollupOption != singleDimensionRollupOnly && - dimensionRollupOption != zeroAndSingleDimensionRollup) { - dimensions = [][]string{dims} - } - dimensions = append(dimensions, rollupDimensionArray...) - } else { - dimensions = [][]string{dims} + if len(metricDeclIdx) == 0 { + config.logger.Debug( + "Dropped metric: no metric declaration matched metric name", + zap.String("Metric name", metricName), + ) + continue } - } - // Build list of CW Measurements - var cwMeasurements []CwMeasurement - if len(dimensions) > 0 { - cwMeasurements = []CwMeasurement{ - { - Namespace: namespace, - Dimensions: dimensions, - Metrics: metricSlice, - }, + metric := map[string]string{ + "Name": metricName, + "Unit": metricInfo.Unit, + } + metricDeclKey := fmt.Sprint(metricDeclIdx) + if group, ok := metricDeclGroups[metricDeclKey]; ok { + group.metrics = append(group.metrics, metric) + } else { + metricDeclGroups[metricDeclKey] = &metricDeclarationGroup{ + metricDeclIdxList: metricDeclIdx, + metrics: []map[string]string{metric}, + } } } - timestampMs := time.Now().UnixNano() / int64(time.Millisecond) - if dp.TimestampMs > 0 { - timestampMs = dp.TimestampMs + if len(metricDeclGroups) == 0 { + return } - metricVal := dp.Value - if metricVal == nil { - return nil - } - fields[pmd.Name()] = metricVal + // Apply single/zero dimension rollup to labels + rollupDimensionArray := dimensionRollup(config.DimensionRollupOption, labels) + + // Translate each group into a CW Measurement + cWMeasurements = make([]CWMeasurement, 0, len(metricDeclGroups)) + for _, group := range metricDeclGroups { + var dimensions [][]string + // Extract dimensions from matched metric declarations + for _, metricDeclIdx := range group.metricDeclIdxList { + dims := metricDeclarations[metricDeclIdx].ExtractDimensions(labels) + dimensions = append(dimensions, dims...) + } + dimensions = append(dimensions, rollupDimensionArray...) - cwMetric := &CWMetrics{ - Measurements: cwMeasurements, - TimestampMs: timestampMs, - Fields: fields, + // De-duplicate dimensions + dimensions = dedupDimensions(dimensions) + + // Export metrics only with non-empty dimensions list + if len(dimensions) > 0 { + cwm := CWMeasurement{ + Namespace: groupedMetric.Metadata.Namespace, + Dimensions: dimensions, + Metrics: group.metrics, + } + cWMeasurements = append(cWMeasurements, cwm) + } } - return cwMetric + + return } -// dimensionRollup creates rolled-up dimensions from the metric's label set. -func dimensionRollup(dimensionRollupOption string, originalDimensionSlice []string, instrumentationLibName string) [][]string { - var rollupDimensionArray [][]string - dimensionZero := make([]string, 0) - if instrumentationLibName != noInstrumentationLibraryName { - dimensionZero = append(dimensionZero, oTellibDimensionKey) - } - if dimensionRollupOption == zeroAndSingleDimensionRollup { - //"Zero" dimension rollup - if len(originalDimensionSlice) > 0 { - rollupDimensionArray = append(rollupDimensionArray, dimensionZero) - } +// translateCWMetricToEMF converts CloudWatch Metric format to EMF. +func translateCWMetricToEMF(cWMetric *CWMetrics) *LogEvent { + // convert CWMetric into map format for compatible with PLE input + cWMetricMap := make(map[string]interface{}) + fieldMap := cWMetric.Fields + + // Create `_aws` section only if there are measurements + if len(cWMetric.Measurements) > 0 { + // Create `_aws` section only if there are measurements + cWMetricMap["CloudWatchMetrics"] = cWMetric.Measurements + cWMetricMap["Timestamp"] = cWMetric.TimestampMs + fieldMap["_aws"] = cWMetricMap } - if dimensionRollupOption == zeroAndSingleDimensionRollup || dimensionRollupOption == singleDimensionRollupOnly { - //"One" dimension rollup - for _, dimensionKey := range originalDimensionSlice { - rollupDimensionArray = append(rollupDimensionArray, append(dimensionZero, dimensionKey)) - } + + pleMsg, err := json.Marshal(fieldMap) + if err != nil { + return nil } - return rollupDimensionArray + metricCreationTime := cWMetric.TimestampMs + logEvent := NewLogEvent( + metricCreationTime, + string(pleMsg), + ) + logEvent.LogGeneratedTime = time.Unix(0, metricCreationTime*int64(time.Millisecond)) + + return logEvent } diff --git a/exporter/awsemfexporter/metric_translator_test.go b/exporter/awsemfexporter/metric_translator_test.go index 6e49c5d77386..a99f2e8fba90 100644 --- a/exporter/awsemfexporter/metric_translator_test.go +++ b/exporter/awsemfexporter/metric_translator_test.go @@ -15,12 +15,10 @@ package awsemfexporter import ( - "encoding/json" "io/ioutil" "sort" "strings" "testing" - "time" commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1" @@ -229,7 +227,7 @@ func createMetricTestData() consumerdata.MetricsData { }, { MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanTimerSummary", + Name: "spanTimer", Description: "How long the spans take", Unit: "Seconds", Type: metricspb.MetricDescriptor_SUMMARY, @@ -240,7 +238,7 @@ func createMetricTestData() consumerdata.MetricsData { Timeseries: []*metricspb.TimeSeries{ { LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan"}, + {Value: "testSpan", HasValue: true}, }, Points: []*metricspb.Point{ { @@ -276,1713 +274,814 @@ func createMetricTestData() consumerdata.MetricsData { } } -func createMetricTestDataSecondBatch() consumerdata.MetricsData { - return consumerdata.MetricsData{ - Node: &commonpb.Node{ - LibraryInfo: &commonpb.LibraryInfo{ExporterVersion: "SomeVersion"}, +func stringSlicesEqual(expected, actual []string) bool { + if len(expected) != len(actual) { + return false + } + for i, expectedStr := range expected { + if expectedStr != actual[i] { + return false + } + } + return true +} + +// hashDimensions hashes dimensions for equality checking. +func hashDimensions(dims [][]string) []string { + // Convert to string for easier sorting + stringified := make([]string, len(dims)) + for i, v := range dims { + sort.Strings(v) + stringified[i] = strings.Join(v, ",") + } + // Sort across dimension sets for equality checking + sort.Strings(stringified) + return stringified +} + +// hashMetricSlice hashes a metrics slice for equality checking. +func hashMetricSlice(metricSlice []map[string]string) []string { + // Convert to string for easier sorting + stringified := make([]string, len(metricSlice)) + for i, v := range metricSlice { + stringified[i] = v["Name"] + "," + v["Unit"] + } + // Sort across metrics for equality checking + sort.Strings(stringified) + return stringified +} + +// assertDimsEqual asserts whether dimension sets are equal +// (i.e. has same sets of dimensions), regardless of order. +func assertDimsEqual(t *testing.T, expected, actual [][]string) { + assert.Equal(t, len(expected), len(actual)) + expectedHashedDimensions := hashDimensions(expected) + actualHashedDimensions := hashDimensions(actual) + assert.Equal(t, expectedHashedDimensions, actualHashedDimensions) +} + +// cWMeasurementEqual returns true if CW Measurements are equal. +func cWMeasurementEqual(expected, actual CWMeasurement) bool { + // Check namespace + if expected.Namespace != actual.Namespace { + return false + } + + // Check metrics + if len(expected.Metrics) != len(actual.Metrics) { + return false + } + expectedHashedMetrics := hashMetricSlice(expected.Metrics) + actualHashedMetrics := hashMetricSlice(actual.Metrics) + if !stringSlicesEqual(expectedHashedMetrics, actualHashedMetrics) { + return false + } + + // Check dimensions + if len(expected.Dimensions) != len(actual.Dimensions) { + return false + } + expectedHashedDimensions := hashDimensions(expected.Dimensions) + actualHashedDimensions := hashDimensions(actual.Dimensions) + return stringSlicesEqual(expectedHashedDimensions, actualHashedDimensions) +} + +// assertCWMeasurementEqual asserts whether CW Measurements are equal. +func assertCWMeasurementEqual(t *testing.T, expected, actual CWMeasurement) { + // Check namespace + assert.Equal(t, expected.Namespace, actual.Namespace) + + // Check metrics + assert.Equal(t, len(expected.Metrics), len(actual.Metrics)) + expectedHashSlice := hashMetricSlice(expected.Metrics) + actualHashSlice := hashMetricSlice(actual.Metrics) + assert.Equal(t, expectedHashSlice, actualHashSlice) + + // Check dimensions + assertDimsEqual(t, expected.Dimensions, actual.Dimensions) +} + +// assertCWMeasurementSliceEqual asserts whether CW Measurements are equal, regardless of order. +func assertCWMeasurementSliceEqual(t *testing.T, expected, actual []CWMeasurement) { + assert.Equal(t, len(expected), len(actual)) + seen := make([]bool, len(expected)) + for _, actualMeasurement := range actual { + hasMatch := false + for i, expectedMeasurement := range expected { + if !seen[i] { + if cWMeasurementEqual(actualMeasurement, expectedMeasurement) { + seen[i] = true + hasMatch = true + } + } + } + assert.True(t, hasMatch) + } +} + +// assertCWMetricsEqual asserts whether CW Metrics are equal. +func assertCWMetricsEqual(t *testing.T, expected, actual *CWMetrics) { + assert.Equal(t, expected.TimestampMs, actual.TimestampMs) + assert.Equal(t, expected.Fields, actual.Fields) + assert.Equal(t, len(expected.Measurements), len(actual.Measurements)) + assertCWMeasurementSliceEqual(t, expected.Measurements, actual.Measurements) +} + +func TestTranslateOtToGroupedMetric(t *testing.T) { + config := &Config{ + Namespace: "", + DimensionRollupOption: zeroAndSingleDimensionRollup, + logger: zap.NewNop(), + } + md := createMetricTestData() + + translator := newMetricTranslator(*config) + + noInstrLibMetric := internaldata.OCToMetrics(md).ResourceMetrics().At(0) + instrLibMetric := internaldata.OCToMetrics(md).ResourceMetrics().At(0) + ilm := instrLibMetric.InstrumentationLibraryMetrics().At(0) + ilm.InstrumentationLibrary().SetName("cloudwatch-lib") + + noNamespaceMetric := internaldata.OCToMetrics(md).ResourceMetrics().At(0) + noNamespaceMetric.Resource().Attributes().Delete(conventions.AttributeServiceNamespace) + noNamespaceMetric.Resource().Attributes().Delete(conventions.AttributeServiceName) + + counterMetrics := map[string]*MetricInfo{ + "spanCounter": { + Value: float64(0), + Unit: "Count", }, - Resource: &resourcepb.Resource{ - Labels: map[string]string{ - conventions.AttributeServiceName: "myServiceName", - conventions.AttributeServiceNamespace: "myServiceNS", + "spanDoubleCounter": { + Value: float64(0), + Unit: "Count", + }, + "spanGaugeCounter": { + Value: float64(1), + Unit: "Count", + }, + "spanGaugeDoubleCounter": { + Value: float64(0.1), + Unit: "Count", + }, + } + timerMetrics := map[string]*MetricInfo{ + "spanTimer": { + Value: &CWMetricStats{ + Count: 5, + Sum: 15, }, + Unit: "Seconds", }, - Metrics: []*metricspb.Metric{ - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - {Key: "isItAnError"}, + } + + testCases := []struct { + testName string + metric *pdata.ResourceMetrics + counterLabels map[string]string + timerLabels map[string]string + expectedNamespace string + }{ + { + "w/ instrumentation library and namespace", + &instrLibMetric, + map[string]string{ + (oTellibDimensionKey): "cloudwatch-lib", + "isItAnError": "false", + "spanName": "testSpan", + }, + map[string]string{ + (oTellibDimensionKey): "cloudwatch-lib", + "spanName": "testSpan", + }, + "myServiceNS/myServiceName", + }, + { + "w/o instrumentation library, w/ namespace", + &noInstrLibMetric, + map[string]string{ + "isItAnError": "false", + "spanName": "testSpan", + }, + map[string]string{ + "spanName": "testSpan", + }, + "myServiceNS/myServiceName", + }, + { + "w/o instrumentation library and namespace", + &noNamespaceMetric, + map[string]string{ + "isItAnError": "false", + "spanName": "testSpan", + }, + map[string]string{ + "spanName": "testSpan", + }, + defaultNamespace, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + groupedMetrics := make(map[interface{}]*GroupedMetric) + translator.translateOTelToGroupedMetric(tc.metric, groupedMetrics, config) + assert.NotNil(t, groupedMetrics) + assert.Equal(t, 2, len(groupedMetrics)) + + for _, v := range groupedMetrics { + assert.Equal(t, tc.expectedNamespace, v.Metadata.Namespace) + if len(v.Metrics) == 4 { + assert.Equal(t, tc.counterLabels, v.Labels) + assert.Equal(t, counterMetrics, v.Metrics) + } else { + assert.Equal(t, 1, len(v.Metrics)) + assert.Equal(t, tc.timerLabels, v.Labels) + assert.Equal(t, timerMetrics, v.Metrics) + } + } + }) + } + + t.Run("No metrics", func(t *testing.T) { + md = consumerdata.MetricsData{ + Node: &commonpb.Node{ + LibraryInfo: &commonpb.LibraryInfo{ExporterVersion: "SomeVersion"}, + }, + Resource: &resourcepb.Resource{ + Labels: map[string]string{ + conventions.AttributeServiceName: "myServiceName", + }, + }, + Metrics: []*metricspb.Metric{}, + } + rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) + groupedMetrics := make(map[interface{}]*GroupedMetric) + translator.translateOTelToGroupedMetric(&rm, groupedMetrics, config) + assert.Equal(t, 0, len(groupedMetrics)) + }) +} + +func TestTranslateCWMetricToEMF(t *testing.T) { + cwMeasurement := CWMeasurement{ + Namespace: "test-emf", + Dimensions: [][]string{{oTellibDimensionKey}, {oTellibDimensionKey, "spanName"}}, + Metrics: []map[string]string{{ + "Name": "spanCounter", + "Unit": "Count", + }}, + } + timestamp := int64(1596151098037) + fields := make(map[string]interface{}) + fields[oTellibDimensionKey] = "cloudwatch-otel" + fields["spanName"] = "test" + fields["spanCounter"] = 0 + + met := &CWMetrics{ + TimestampMs: timestamp, + Fields: fields, + Measurements: []CWMeasurement{cwMeasurement}, + } + inputLogEvent := translateCWMetricToEMF(met) + + assert.Equal(t, readFromFile("testdata/testTranslateCWMetricToEMF.json"), *inputLogEvent.InputLogEvent.Message, "Expect to be equal") +} + +func TestTranslateGroupedMetricToCWMetric(t *testing.T) { + timestamp := int64(1596151098037) + namespace := "Namespace" + testCases := []struct { + testName string + groupedMetric *GroupedMetric + metricDeclarations []*MetricDeclaration + expectedCWMetric *CWMetrics + }{ + { + "single metric w/o metric declarations", + &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", + }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", }, }, - Timeseries: []*metricspb.TimeSeries{ + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + nil, + &CWMetrics{ + Measurements: []CWMeasurement{ { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - {Value: "false", HasValue: true}, - }, - Points: []*metricspb.Point{ + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: []map[string]string{ { - Timestamp: ×tamp.Timestamp{ - Seconds: 105, - }, - Value: &metricspb.Point_Int64Value{ - Int64Value: 6, - }, + "Name": "metric1", + "Unit": "Count", }, }, }, }, + TimestampMs: timestamp, + Fields: map[string]interface{}{ + "label1": "value1", + "metric1": 1, + }, }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanGaugeCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_GAUGE_INT64, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - {Key: "isItAnError"}, + }, + { + "single metric w/ metric declarations", + &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", + }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", }, }, - Timeseries: []*metricspb.TimeSeries{ + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + []*MetricDeclaration{ + { + Dimensions: [][]string{{"label1"}, {"label1", "label2"}}, + MetricNameSelectors: []string{"metric.*"}, + }, + }, + &CWMetrics{ + Measurements: []CWMeasurement{ { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - {Value: "false", HasValue: true}, - }, - Points: []*metricspb.Point{ + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: []map[string]string{ { - Timestamp: ×tamp.Timestamp{ - Seconds: 105, - }, - Value: &metricspb.Point_Int64Value{ - Int64Value: 6, - }, + "Name": "metric1", + "Unit": "Count", }, }, }, }, + TimestampMs: timestamp, + Fields: map[string]interface{}{ + "label1": "value1", + "metric1": 1, + }, }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanDoubleCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - {Key: "isItAnError"}, + }, + { + "multiple metrics w/o metric declarations", + &GroupedMetric{ + Labels: map[string]string{ + "label2": "value2", + "label1": "value1", + }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", + }, + "metric2": { + Value: 200, + Unit: "Count", + }, + "metric3": { + Value: 3.14, + Unit: "Seconds", }, }, - Timeseries: []*metricspb.TimeSeries{ + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + nil, + &CWMetrics{ + Measurements: []CWMeasurement{ { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - {Value: "false", HasValue: true}, - }, - Points: []*metricspb.Point{ + Namespace: namespace, + Dimensions: [][]string{{"label1", "label2"}}, + Metrics: []map[string]string{ { - Timestamp: ×tamp.Timestamp{ - Seconds: 105, - }, - Value: &metricspb.Point_DoubleValue{ - DoubleValue: 0.6, - }, + "Name": "metric1", + "Unit": "Count", + }, + { + "Name": "metric2", + "Unit": "Count", + }, + { + "Name": "metric3", + "Unit": "Seconds", }, }, }, }, + TimestampMs: timestamp, + Fields: map[string]interface{}{ + "label1": "value1", + "label2": "value2", + "metric1": 1, + "metric2": 200, + "metric3": 3.14, + }, }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanGaugeDoubleCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - {Key: "isItAnError"}, + }, + { + "multiple metrics w/ metric declarations", + &GroupedMetric{ + Labels: map[string]string{ + "label2": "value2", + "label1": "value1", + }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", + }, + "metric2": { + Value: 200, + Unit: "Count", + }, + "metric3": { + Value: 3.14, + Unit: "Seconds", }, }, - Timeseries: []*metricspb.TimeSeries{ + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + []*MetricDeclaration{ + { + Dimensions: [][]string{ + {"label1"}, + {"label1", "label3"}, + }, + MetricNameSelectors: []string{"metric1"}, + }, + { + Dimensions: [][]string{ + {"label1", "label2"}, + {"label1", "label3"}, + }, + MetricNameSelectors: []string{"metric2"}, + }, + }, + &CWMetrics{ + Measurements: []CWMeasurement{ { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - {Value: "false", HasValue: true}, + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, }, - Points: []*metricspb.Point{ + }, + { + Namespace: namespace, + Dimensions: [][]string{{"label1", "label2"}}, + Metrics: []map[string]string{ { - Timestamp: ×tamp.Timestamp{ - Seconds: 105, - }, - Value: &metricspb.Point_DoubleValue{ - DoubleValue: 0.6, - }, + "Name": "metric2", + "Unit": "Count", }, }, }, }, + TimestampMs: timestamp, + Fields: map[string]interface{}{ + "label1": "value1", + "label2": "value2", + "metric1": 1, + "metric2": 200, + "metric3": 3.14, + }, }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanTimer", - Description: "How long the spans take", - Unit: "Seconds", - Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Timestamp: ×tamp.Timestamp{ - Seconds: 100, - }, - Value: &metricspb.Point_DistributionValue{ - DistributionValue: &metricspb.DistributionValue{ - Sum: 15.0, - Count: 5, - BucketOptions: &metricspb.DistributionValue_BucketOptions{ - Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ - Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ - Bounds: []float64{0, 10}, - }, - }, - }, - Buckets: []*metricspb.DistributionValue_Bucket{ - { - Count: 0, - }, - { - Count: 4, - }, - { - Count: 1, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanTimerSummary", - Description: "How long the spans take", - Unit: "Seconds", - Type: metricspb.MetricDescriptor_SUMMARY, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan"}, - }, - Points: []*metricspb.Point{ - { - Timestamp: ×tamp.Timestamp{ - Seconds: 100, - }, - Value: &metricspb.Point_SummaryValue{ - SummaryValue: &metricspb.SummaryValue{ - Sum: &wrappers.DoubleValue{ - Value: 15.0, - }, - Count: &wrappers.Int64Value{ - Value: 5, - }, - Snapshot: &metricspb.SummaryValue_Snapshot{ - PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{{ - Percentile: 0, - Value: 1, - }, - { - Percentile: 100, - Value: 5, - }}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -// Asserts whether dimension sets are equal (i.e. has same sets of dimensions) -func assertDimsEqual(t *testing.T, expected, actual [][]string) { - // Convert to string for easier sorting - expectedStringified := make([]string, len(expected)) - actualStringified := make([]string, len(actual)) - for i, v := range expected { - sort.Strings(v) - expectedStringified[i] = strings.Join(v, ",") - } - for i, v := range actual { - assert.NotNil(t, v) - sort.Strings(v) - actualStringified[i] = strings.Join(v, ",") - } - // Sort across dimension sets for equality checking - sort.Strings(expectedStringified) - sort.Strings(actualStringified) - assert.Equal(t, expectedStringified, actualStringified) -} - -// Asserts whether CW Measurements are equal. -func assertCwMeasurementEqual(t *testing.T, expected, actual CwMeasurement) { - assert.Equal(t, expected.Namespace, actual.Namespace) - assert.Equal(t, expected.Metrics, actual.Metrics) - assertDimsEqual(t, expected.Dimensions, actual.Dimensions) -} - -func assertCwStatsEqual(t *testing.T, expected, actual *CWMetricStats) { - assert.Equal(t, expected.Min, actual.Min) - assert.Equal(t, expected.Max, actual.Max) - assert.Equal(t, expected.Count, actual.Count) - assert.Equal(t, expected.Sum, actual.Sum) -} - -func TestTranslateOTelToCWMetricWithInstrLibrary(t *testing.T) { - config := &Config{ - Namespace: "", - DimensionRollupOption: zeroAndSingleDimensionRollup, - logger: zap.NewNop(), - } - md := createMetricTestData() - rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) - ilms := rm.InstrumentationLibraryMetrics() - ilm := ilms.At(0) - ilm.InstrumentationLibrary().SetName("cloudwatch-lib") - cwm, totalDroppedMetrics := translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.NotNil(t, cwm) - assert.Equal(t, 6, len(cwm)) - assert.Equal(t, 1, len(cwm[0].Measurements)) - - met := cwm[0] - - assert.Equal(t, met.Fields["spanCounter"], float64(0)) - - expectedMeasurement := CwMeasurement{ - Namespace: "myServiceNS/myServiceName", - Dimensions: [][]string{ - {oTellibDimensionKey, "isItAnError", "spanName"}, - {oTellibDimensionKey}, - {oTellibDimensionKey, "spanName"}, - {oTellibDimensionKey, "isItAnError"}, - }, - Metrics: []map[string]string{ - { - "Name": "spanCounter", - "Unit": "Count", - }, - }, - } - assertCwMeasurementEqual(t, expectedMeasurement, met.Measurements[0]) - - assert.Equal(t, float64(1), cwm[1].Fields["spanGaugeCounter"]) - assert.Equal(t, float64(0), cwm[2].Fields["spanDoubleCounter"]) - assert.Equal(t, 0.1, cwm[3].Fields["spanGaugeDoubleCounter"]) - expectedCwStats := &CWMetricStats{ - Count: 5, - Sum: 15, - } - assertCwStatsEqual(t, expectedCwStats, cwm[4].Fields["spanTimer"].(*CWMetricStats)) - expectedCwStats = &CWMetricStats{ - Count: 5, - Sum: 15, - Min: 1, - Max: 5, - } - assertCwStatsEqual(t, expectedCwStats, cwm[5].Fields["spanTimerSummary"].(*CWMetricStats)) - - time.Sleep(5 * time.Second) - md = createMetricTestDataSecondBatch() - rm = internaldata.OCToMetrics(md).ResourceMetrics().At(0) - ilms = rm.InstrumentationLibraryMetrics() - ilm = ilms.At(0) - ilm.InstrumentationLibrary().SetName("cloudwatch-lib") - cwm, totalDroppedMetrics = translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.NotNil(t, cwm) - assert.Equal(t, 6, len(cwm)) - assert.Equal(t, 1, len(cwm[0].Measurements)) - - assert.True(t, 1-cwm[0].Fields["spanCounter"].(float64) < 0.01) - assert.True(t, 0.1-cwm[2].Fields["spanDoubleCounter"].(float64) < 0.001) -} - -func TestTranslateOTelToCWMetricWithoutInstrLibrary(t *testing.T) { - config := &Config{ - Namespace: "", - DimensionRollupOption: zeroAndSingleDimensionRollup, - logger: zap.NewNop(), - } - md := createMetricTestData() - rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) - cwm, totalDroppedMetrics := translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.NotNil(t, cwm) - assert.Equal(t, 6, len(cwm)) - assert.Equal(t, 1, len(cwm[0].Measurements)) - - met := cwm[0] - assert.NotContains(t, met.Fields, oTellibDimensionKey) - assert.Equal(t, met.Fields["spanCounter"], float64(0)) - - expectedMeasurement := CwMeasurement{ - Namespace: "myServiceNS/myServiceName", - Dimensions: [][]string{ - {"isItAnError", "spanName"}, - {}, - {"spanName"}, - {"isItAnError"}, - }, - Metrics: []map[string]string{ - { - "Name": "spanCounter", - "Unit": "Count", - }, - }, - } - assertCwMeasurementEqual(t, expectedMeasurement, met.Measurements[0]) - - assert.Equal(t, float64(1), cwm[1].Fields["spanGaugeCounter"]) - assert.Equal(t, float64(0), cwm[2].Fields["spanDoubleCounter"]) - assert.Equal(t, 0.1, cwm[3].Fields["spanGaugeDoubleCounter"]) - expectedCwStats := &CWMetricStats{ - Count: 5, - Sum: 15, - } - assertCwStatsEqual(t, expectedCwStats, cwm[4].Fields["spanTimer"].(*CWMetricStats)) - expectedCwStats = &CWMetricStats{ - Count: 5, - Sum: 15, - Min: 1, - Max: 5, - } - assertCwStatsEqual(t, expectedCwStats, cwm[5].Fields["spanTimerSummary"].(*CWMetricStats)) - - time.Sleep(5 * time.Second) - md = createMetricTestDataSecondBatch() - rm = internaldata.OCToMetrics(md).ResourceMetrics().At(0) - cwm, totalDroppedMetrics = translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.NotNil(t, cwm) - assert.Equal(t, 6, len(cwm)) - assert.Equal(t, 1, len(cwm[0].Measurements)) - - assert.True(t, 1-cwm[0].Fields["spanCounter"].(float64) < 0.01) - assert.True(t, 0.1-cwm[2].Fields["spanDoubleCounter"].(float64) < 0.001) -} - -func TestTranslateOTelToCWMetricWithNamespace(t *testing.T) { - config := &Config{ - Namespace: "", - DimensionRollupOption: zeroAndSingleDimensionRollup, - } - md := consumerdata.MetricsData{ - Node: &commonpb.Node{ - LibraryInfo: &commonpb.LibraryInfo{ExporterVersion: "SomeVersion"}, - }, - Resource: &resourcepb.Resource{ - Labels: map[string]string{ - conventions.AttributeServiceName: "myServiceName", - }, - }, - Metrics: []*metricspb.Metric{}, - } - rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) - cwm, totalDroppedMetrics := translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.Nil(t, cwm) - assert.Equal(t, 0, len(cwm)) - md = consumerdata.MetricsData{ - Node: &commonpb.Node{ - LibraryInfo: &commonpb.LibraryInfo{ExporterVersion: "SomeVersion"}, - }, - Resource: &resourcepb.Resource{ - Labels: map[string]string{ - conventions.AttributeServiceNamespace: "myServiceNS", - }, - }, - Metrics: []*metricspb.Metric{ - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - {Key: "isItAnError"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - {Value: "false", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Timestamp: ×tamp.Timestamp{ - Seconds: 100, - }, - Value: &metricspb.Point_Int64Value{ - Int64Value: 1, - }, - }, - }, - }, - }, - }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, - }, - Timeseries: []*metricspb.TimeSeries{}, - }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanGaugeCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_GAUGE_INT64, - }, - Timeseries: []*metricspb.TimeSeries{}, - }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanGaugeDoubleCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, - }, - Timeseries: []*metricspb.TimeSeries{}, - }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanDoubleCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, - }, - Timeseries: []*metricspb.TimeSeries{}, - }, - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanDoubleCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, - }, - Timeseries: []*metricspb.TimeSeries{}, - }, - }, - } - rm = internaldata.OCToMetrics(md).ResourceMetrics().At(0) - cwm, totalDroppedMetrics = translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.NotNil(t, cwm) - assert.Equal(t, 1, len(cwm)) - - met := cwm[0] - assert.Equal(t, "myServiceNS", met.Measurements[0].Namespace) -} - -func TestTranslateUnit(t *testing.T) { - metric := pdata.NewMetric() - metric.SetName("writeIfNotExist") - - translator := &metricTranslator{ - metricDescriptor: map[string]MetricDescriptor{ - "writeIfNotExist": { - metricName: "writeIfNotExist", - unit: "Count", - overwrite: false, - }, - "forceOverwrite": { - metricName: "forceOverwrite", - unit: "Count", - overwrite: true, - }, - }, - } - - translateUnitCases := map[string]string{ - "Count": "Count", - "ms": "Milliseconds", - "s": "Seconds", - "us": "Microseconds", - "By": "Bytes", - "Bi": "Bits", - } - for input, output := range translateUnitCases { - t.Run(input, func(tt *testing.T) { - metric.SetUnit(input) - - v := translator.translateUnit(&metric) - assert.Equal(t, output, v) - }) - } - - metric.SetName("forceOverwrite") - v := translator.translateUnit(&metric) - assert.Equal(t, "Count", v) -} - -func TestTranslateOTelToCWMetricWithFiltering(t *testing.T) { - md := consumerdata.MetricsData{ - Node: &commonpb.Node{ - LibraryInfo: &commonpb.LibraryInfo{ExporterVersion: "SomeVersion"}, - }, - Resource: &resourcepb.Resource{ - Labels: map[string]string{ - conventions.AttributeServiceName: "myServiceName", - conventions.AttributeServiceNamespace: "myServiceNS", - }, - }, - Metrics: []*metricspb.Metric{ - { - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "spanCounter", - Description: "Counting all the spans", - Unit: "Count", - Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, - LabelKeys: []*metricspb.LabelKey{ - {Key: "spanName"}, - {Key: "isItAnError"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "testSpan", HasValue: true}, - {Value: "false", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Timestamp: ×tamp.Timestamp{ - Seconds: 100, - }, - Value: &metricspb.Point_Int64Value{ - Int64Value: 1, - }, - }, - }, - }, - }, - }, - }, - } - - rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) - ilm := rm.InstrumentationLibraryMetrics().At(0) - ilm.InstrumentationLibrary().SetName("cloudwatch-lib") - - testCases := []struct { - testName string - metricNameSelectors []string - labelMatchers []*LabelMatcher - dimensionRollupOption string - expectedDimensions [][]string - numMeasurements int - }{ - { - "has match w/ Zero + Single dim rollup", - []string{"spanCounter"}, - nil, - zeroAndSingleDimensionRollup, - [][]string{ - {"spanName", "isItAnError"}, - {"spanName", oTellibDimensionKey}, - {oTellibDimensionKey, "isItAnError"}, - {oTellibDimensionKey}, - }, - 1, }, { - "has match w/ no dim rollup", - []string{"spanCounter"}, - nil, - "", - [][]string{ - {"spanName", "isItAnError"}, - {"spanName", oTellibDimensionKey}, - }, - 1, - }, - { - "has label match w/ no dim rollup", - []string{"spanCounter"}, - []*LabelMatcher{ - { - LabelNames: []string{"isItAnError", "spanName"}, - Regex: "false;testSpan", + "no metrics", + &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", }, - }, - "", - [][]string{ - {"spanName", "isItAnError"}, - {"spanName", oTellibDimensionKey}, - }, - 1, - }, - { - "no label match w/ no dim rollup", - []string{"spanCounter"}, - []*LabelMatcher{ - { - LabelNames: []string{"isItAnError", "spanName"}, - Regex: "true;testSpan", + Metrics: nil, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, }, }, - "", - nil, - 0, - }, - { - "No match w/ rollup", - []string{"invalid"}, nil, - zeroAndSingleDimensionRollup, - [][]string{ - {oTellibDimensionKey, "spanName"}, - {oTellibDimensionKey, "isItAnError"}, - {oTellibDimensionKey}, + &CWMetrics{ + Measurements: []CWMeasurement{ + { + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: nil, + }, + }, + TimestampMs: timestamp, + Fields: map[string]interface{}{ + "label1": "value1", + }, }, - 1, - }, - { - "No match w/ no rollup", - []string{"invalid"}, - nil, - "", - nil, - 0, }, } + logger := zap.NewNop() for _, tc := range testCases { - m := MetricDeclaration{ - Dimensions: [][]string{{"isItAnError", "spanName"}, {"spanName", oTellibDimensionKey}}, - MetricNameSelectors: tc.metricNameSelectors, - LabelMatchers: tc.labelMatchers, - } - config := &Config{ - Namespace: "", - DimensionRollupOption: tc.dimensionRollupOption, - MetricDeclarations: []*MetricDeclaration{&m}, - } t.Run(tc.testName, func(t *testing.T) { - err := m.Init(logger) - assert.Nil(t, err) - cwm, totalDroppedMetrics := translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.Equal(t, 1, len(cwm)) - assert.NotNil(t, cwm) - - assert.Equal(t, tc.numMeasurements, len(cwm[0].Measurements)) - - if tc.numMeasurements > 0 { - dimensions := cwm[0].Measurements[0].Dimensions - assertDimsEqual(t, tc.expectedDimensions, dimensions) + config := &Config{ + MetricDeclarations: tc.metricDeclarations, + DimensionRollupOption: "", + logger: logger, + } + for _, decl := range tc.metricDeclarations { + decl.Init(logger) } + cWMetric := translateGroupedMetricToCWMetric(tc.groupedMetric, config) + assert.NotNil(t, cWMetric) + assertCWMetricsEqual(t, tc.expectedCWMetric, cWMetric) }) } - - t.Run("No instrumentation library name w/ no dim rollup", func(t *testing.T) { - rm = internaldata.OCToMetrics(md).ResourceMetrics().At(0) - m := MetricDeclaration{ - Dimensions: [][]string{{"isItAnError", "spanName"}, {"spanName", oTellibDimensionKey}}, - MetricNameSelectors: []string{"spanCounter"}, - } - config := &Config{ - Namespace: "", - DimensionRollupOption: "", - MetricDeclarations: []*MetricDeclaration{&m}, - } - err := m.Init(logger) - assert.Nil(t, err) - cwm, totalDroppedMetrics := translateOTelToCWMetric(&rm, config) - assert.Equal(t, 0, totalDroppedMetrics) - assert.Equal(t, 1, len(cwm)) - assert.NotNil(t, cwm) - - assert.Equal(t, 1, len(cwm[0].Measurements)) - - // No OTelLib present - expectedDims := [][]string{ - {"spanName", "isItAnError"}, - } - dimensions := cwm[0].Measurements[0].Dimensions - assertDimsEqual(t, expectedDims, dimensions) - }) -} - -func TestTranslateCWMetricToEMF(t *testing.T) { - cwMeasurement := CwMeasurement{ - Namespace: "test-emf", - Dimensions: [][]string{{oTellibDimensionKey}, {oTellibDimensionKey, "spanName"}}, - Metrics: []map[string]string{{ - "Name": "spanCounter", - "Unit": "Count", - }}, - } - timestamp := int64(1596151098037) - fields := make(map[string]interface{}) - fields[oTellibDimensionKey] = "cloudwatch-otel" - fields["spanName"] = "test" - fields["spanCounter"] = 0 - - met := &CWMetrics{ - TimestampMs: timestamp, - Fields: fields, - Measurements: []CwMeasurement{cwMeasurement}, - } - logger := zap.NewNop() - inputLogEvent := translateCWMetricToEMF([]*CWMetrics{met}, logger) - - assert.Equal(t, readFromFile("testdata/testTranslateCWMetricToEMF.json"), *inputLogEvent[0].InputLogEvent.Message, "Expect to be equal") } -func TestTranslateCWMetricToEMFNoMeasurements(t *testing.T) { +func TestGroupedMetricToCWMeasurement(t *testing.T) { timestamp := int64(1596151098037) - fields := make(map[string]interface{}) - fields[oTellibDimensionKey] = "cloudwatch-otel" - fields["spanName"] = "test" - fields["spanCounter"] = 0 - - met := &CWMetrics{ - TimestampMs: timestamp, - Fields: fields, - Measurements: nil, - } - obs, logs := observer.New(zap.DebugLevel) - logger := zap.New(obs) - inputLogEvent := translateCWMetricToEMF([]*CWMetrics{met}, logger) - expected := "{\"OTelLib\":\"cloudwatch-otel\",\"spanCounter\":0,\"spanName\":\"test\"}" - - assert.Equal(t, expected, *inputLogEvent[0].InputLogEvent.Message) - - // Check logged warning message - fieldsStr, _ := json.Marshal(fields) - expectedLogs := []observer.LoggedEntry{{ - Entry: zapcore.Entry{Level: zap.DebugLevel, Message: "Dropped metric due to no matching metric declarations"}, - Context: []zapcore.Field{zap.String("labels", string(fieldsStr))}, - }} - assert.Equal(t, 1, logs.Len()) - assert.Equal(t, expectedLogs, logs.AllUntimed()) -} - -func TestGetCWMetrics(t *testing.T) { namespace := "Namespace" - OTelLib := oTellibDimensionKey - instrumentationLibName := "InstrLibName" - config := &Config{ - Namespace: "", - DimensionRollupOption: "", - MetricDescriptors: []MetricDescriptor{ - { - metricName: "foo", - unit: "Count", - overwrite: false, - }, - }, - } - - testCases := []struct { - testName string - metric *metricspb.Metric - expected []*CWMetrics - }{ - { - "Int gauge", - &metricspb.Metric{ - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "foo", - Type: metricspb.MetricDescriptor_GAUGE_INT64, - Unit: "Count", - LabelKeys: []*metricspb.LabelKey{ - {Key: "label1"}, - {Key: "label2"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "value1", HasValue: true}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_Int64Value{ - Int64Value: 1, - }, - }, - }, - }, - { - LabelValues: []*metricspb.LabelValue{ - {HasValue: false}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_Int64Value{ - Int64Value: 3, - }, - }, - }, - }, - }, - }, - []*CWMetrics{ - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label1", "label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": float64(1), - "label1": "value1", - "label2": "value2", - }, - }, - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": float64(3), - "label2": "value2", - }, - }, - }, - }, - { - "Double gauge", - &metricspb.Metric{ - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "foo", - Type: metricspb.MetricDescriptor_GAUGE_DOUBLE, - Unit: "Count", - LabelKeys: []*metricspb.LabelKey{ - {Key: "label1"}, - {Key: "label2"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "value1", HasValue: true}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_DoubleValue{ - DoubleValue: 0.1, - }, - }, - }, - }, - { - LabelValues: []*metricspb.LabelValue{ - {HasValue: false}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_DoubleValue{ - DoubleValue: 0.3, - }, - }, - }, - }, - }, - }, - []*CWMetrics{ - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label1", "label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": 0.1, - "label1": "value1", - "label2": "value2", - }, - }, - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": 0.3, - "label2": "value2", - }, - }, - }, - }, - { - "Int sum", - &metricspb.Metric{ - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "foo", - Type: metricspb.MetricDescriptor_CUMULATIVE_INT64, - Unit: "Count", - LabelKeys: []*metricspb.LabelKey{ - {Key: "label1"}, - {Key: "label2"}, - }, - }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "value1", HasValue: true}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_Int64Value{ - Int64Value: 1, - }, - }, - }, - }, - { - LabelValues: []*metricspb.LabelValue{ - {HasValue: false}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_Int64Value{ - Int64Value: 3, - }, - }, - }, - }, - }, - }, - []*CWMetrics{ - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label1", "label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": float64(0), - "label1": "value1", - "label2": "value2", - }, + testCases := []struct { + testName string + dimensionRollupOption string + groupedMetric *GroupedMetric + expectedMeasurement CWMeasurement + }{ + { + "single metric, no dim rollup", + "", + &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", }, - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": float64(0), - "label2": "value2", + }, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + CWMeasurement{ + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", }, }, }, }, { - "Double sum", - &metricspb.Metric{ - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "foo", - Type: metricspb.MetricDescriptor_CUMULATIVE_DOUBLE, - Unit: "Count", - LabelKeys: []*metricspb.LabelKey{ - {Key: "label1"}, - {Key: "label2"}, - }, + "multiple metrics, no dim rollup", + "", + &GroupedMetric{ + Labels: map[string]string{ + "label2": "value2", + "label1": "value1", }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "value1", HasValue: true}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_DoubleValue{ - DoubleValue: 0.1, - }, - }, - }, - }, - { - LabelValues: []*metricspb.LabelValue{ - {HasValue: false}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_DoubleValue{ - DoubleValue: 0.3, - }, - }, - }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", }, - }, - }, - []*CWMetrics{ - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label1", "label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, + "metric2": { + Value: 200, + Unit: "Count", }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": float64(0), - "label1": "value1", - "label2": "value2", + "metric3": { + Value: 3.14, + Unit: "Seconds", }, }, - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Count"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": float64(0), - "label2": "value2", - }, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, }, }, - }, - { - "Double histogram", - &metricspb.Metric{ - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "foo", - Type: metricspb.MetricDescriptor_CUMULATIVE_DISTRIBUTION, - Unit: "Seconds", - LabelKeys: []*metricspb.LabelKey{ - {Key: "label1"}, - {Key: "label2"}, + CWMeasurement{ + Namespace: namespace, + Dimensions: [][]string{{"label1", "label2"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", }, - }, - Timeseries: []*metricspb.TimeSeries{ { - LabelValues: []*metricspb.LabelValue{ - {Value: "value1", HasValue: true}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_DistributionValue{ - DistributionValue: &metricspb.DistributionValue{ - Sum: 15.0, - Count: 5, - BucketOptions: &metricspb.DistributionValue_BucketOptions{ - Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ - Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ - Bounds: []float64{0, 10}, - }, - }, - }, - Buckets: []*metricspb.DistributionValue_Bucket{ - { - Count: 0, - }, - { - Count: 4, - }, - { - Count: 1, - }, - }, - }, - }, - }, - }, + "Name": "metric2", + "Unit": "Count", }, { - LabelValues: []*metricspb.LabelValue{ - {HasValue: false}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_DistributionValue{ - DistributionValue: &metricspb.DistributionValue{ - Sum: 35.0, - Count: 18, - BucketOptions: &metricspb.DistributionValue_BucketOptions{ - Type: &metricspb.DistributionValue_BucketOptions_Explicit_{ - Explicit: &metricspb.DistributionValue_BucketOptions_Explicit{ - Bounds: []float64{0, 10}, - }, - }, - }, - Buckets: []*metricspb.DistributionValue_Bucket{ - { - Count: 5, - }, - { - Count: 6, - }, - { - Count: 7, - }, - }, - }, - }, - }, - }, + "Name": "metric3", + "Unit": "Seconds", }, }, }, - []*CWMetrics{ - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label1", "label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Seconds"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": &CWMetricStats{ - Sum: 15.0, - Count: 5, - }, - "label1": "value1", - "label2": "value2", - }, + }, + { + "single metric, single dim rollup", + singleDimensionRollupOnly, + &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", }, - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Seconds"}, - }, - }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": &CWMetricStats{ - Sum: 35.0, - Count: 18, - }, - "label2": "value2", + }, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + CWMeasurement{ + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", }, }, }, }, { - "Double summary", - &metricspb.Metric{ - MetricDescriptor: &metricspb.MetricDescriptor{ - Name: "foo", - Type: metricspb.MetricDescriptor_SUMMARY, - Unit: "Seconds", - LabelKeys: []*metricspb.LabelKey{ - {Key: "label1"}, - {Key: "label2"}, - }, + "multiple metrics, zero & single dim rollup", + zeroAndSingleDimensionRollup, + &GroupedMetric{ + Labels: map[string]string{ + "label2": "value2", + "label1": "value1", }, - Timeseries: []*metricspb.TimeSeries{ - { - LabelValues: []*metricspb.LabelValue{ - {Value: "value1", HasValue: true}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_SummaryValue{ - SummaryValue: &metricspb.SummaryValue{ - Sum: &wrappers.DoubleValue{ - Value: 15.0, - }, - Count: &wrappers.Int64Value{ - Value: 5, - }, - Snapshot: &metricspb.SummaryValue_Snapshot{ - Count: &wrappers.Int64Value{ - Value: 5, - }, - Sum: &wrappers.DoubleValue{ - Value: 15.0, - }, - PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ - { - Percentile: 0.0, - Value: 1, - }, - { - Percentile: 100.0, - Value: 5, - }, - }, - }, - }, - }, - }, - }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", }, - { - LabelValues: []*metricspb.LabelValue{ - {HasValue: false}, - {Value: "value2", HasValue: true}, - }, - Points: []*metricspb.Point{ - { - Value: &metricspb.Point_SummaryValue{ - SummaryValue: &metricspb.SummaryValue{ - Sum: &wrappers.DoubleValue{ - Value: 35.0, - }, - Count: &wrappers.Int64Value{ - Value: 18, - }, - Snapshot: &metricspb.SummaryValue_Snapshot{ - Count: &wrappers.Int64Value{ - Value: 18, - }, - Sum: &wrappers.DoubleValue{ - Value: 35.0, - }, - PercentileValues: []*metricspb.SummaryValue_Snapshot_ValueAtPercentile{ - { - Percentile: 0.0, - Value: 0, - }, - { - Percentile: 100.0, - Value: 5, - }, - }, - }, - }, - }, - }, - }, + "metric2": { + Value: 200, + Unit: "Count", + }, + "metric3": { + Value: 3.14, + Unit: "Seconds", }, }, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, }, - []*CWMetrics{ - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label1", "label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Seconds"}, - }, - }, - }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": &CWMetricStats{ - Min: 1, - Max: 5, - Sum: 15.0, - Count: 5, - }, - "label1": "value1", - "label2": "value2", - }, + CWMeasurement{ + Namespace: namespace, + Dimensions: [][]string{ + {"label1", "label2"}, + {"label1"}, + {"label2"}, + {}, }, - { - Measurements: []CwMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{ - {"label2", OTelLib}, - }, - Metrics: []map[string]string{ - {"Name": "foo", "Unit": "Seconds"}, - }, - }, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", }, - Fields: map[string]interface{}{ - OTelLib: instrumentationLibName, - "foo": &CWMetricStats{ - Min: 0, - Max: 5, - Sum: 35.0, - Count: 18, - }, - "label2": "value2", + { + "Name": "metric2", + "Unit": "Count", }, - }, - }, - }, - } - - metadata := CWMetricMetadata{ - Namespace: "Namespace", - TimestampMs: time.Now().UnixNano() / int64(time.Millisecond), - LogGroup: "log-group", - LogStream: "log-stream", - InstrumentationLibraryName: "cloudwatch-otel", - } - - translator := newMetricTranslator(*config) - - for _, tc := range testCases { - t.Run(tc.testName, func(t *testing.T) { - oc := consumerdata.MetricsData{ - Node: &commonpb.Node{}, - Resource: &resourcepb.Resource{ - Labels: map[string]string{ - conventions.AttributeServiceName: "myServiceName", - conventions.AttributeServiceNamespace: "myServiceNS", + { + "Name": "metric3", + "Unit": "Seconds", }, }, - Metrics: []*metricspb.Metric{tc.metric}, - } - - // Retrieve *pdata.Metric - rms := internaldata.OCToMetrics(oc).ResourceMetrics() - assert.Equal(t, 1, rms.Len()) - ilms := rms.At(0).InstrumentationLibraryMetrics() - assert.Equal(t, 1, ilms.Len()) - metrics := ilms.At(0).Metrics() - assert.Equal(t, 1, metrics.Len()) - metric := metrics.At(0) - - cwMetrics := translator.getCWMetrics(&metric, metadata, instrumentationLibName, config) - assert.Equal(t, len(tc.expected), len(cwMetrics)) - - for i, expected := range tc.expected { - cwMetric := cwMetrics[i] - assert.Equal(t, len(expected.Measurements), len(cwMetric.Measurements)) - for i, expectedMeasurement := range expected.Measurements { - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[i]) - } - assert.Equal(t, len(expected.Fields), len(cwMetric.Fields)) - assert.Equal(t, expected.Fields, cwMetric.Fields) - } - }) - } - - t.Run("Unhandled metric type", func(t *testing.T) { - metric := pdata.NewMetric() - metric.SetName("foo") - metric.SetUnit("Count") - metric.SetDataType(pdata.MetricDataTypeIntHistogram) - - obs, logs := observer.New(zap.WarnLevel) - obsConfig := &Config{ - DimensionRollupOption: "", - logger: zap.New(obs), - } - - cwMetrics := translator.getCWMetrics(&metric, metadata, instrumentationLibName, obsConfig) - assert.Nil(t, cwMetrics) - - // Test output warning logs - expectedLogs := []observer.LoggedEntry{ - { - Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "Unhandled metric data type."}, - Context: []zapcore.Field{ - zap.String("DataType", "IntHistogram"), - zap.String("Name", "foo"), - zap.String("Unit", "Count"), - }, }, - } - assert.Equal(t, 1, logs.Len()) - assert.Equal(t, expectedLogs, logs.AllUntimed()) - }) - - t.Run("Nil metric", func(t *testing.T) { - cwMetrics := translator.getCWMetrics(nil, metadata, instrumentationLibName, config) - assert.Nil(t, cwMetrics) - }) -} - -func TestBuildCWMetric(t *testing.T) { - namespace := "Namespace" - instrLibName := "InstrLibName" - OTelLib := oTellibDimensionKey - config := &Config{ - Namespace: "", - DimensionRollupOption: "", - } - metricSlice := []map[string]string{ + }, { - "Name": "foo", - "Unit": "", + "no metrics", + "", + &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", + }, + Metrics: nil, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + }, + CWMeasurement{ + Namespace: namespace, + Dimensions: [][]string{{"label1"}}, + Metrics: nil, + }, }, } - // Test data types - metric := pdata.NewMetric() - metric.SetName("foo") - - t.Run("Int gauge", func(t *testing.T) { - metric.SetDataType(pdata.MetricDataTypeIntGauge) - dp := DataPoint{} - dp.Value = int64(-17) - dp.Labels = map[string]string{ - "label1": "value1", - } - - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrLibName, config) - - assert.NotNil(t, cwMetric) - assert.Equal(t, 1, len(cwMetric.Measurements)) - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: [][]string{{"label1", OTelLib}}, - Metrics: metricSlice, - } - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) - expectedFields := map[string]interface{}{ - OTelLib: instrLibName, - "foo": int64(-17), - "label1": "value1", - } - assert.Equal(t, expectedFields, cwMetric.Fields) - }) - - t.Run("Double gauge", func(t *testing.T) { - metric.SetDataType(pdata.MetricDataTypeDoubleGauge) - - dp := DataPoint{} - dp.Value = 0.3 - dp.Labels = map[string]string{ - "label1": "value1", - } - - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrLibName, config) - - assert.NotNil(t, cwMetric) - assert.Equal(t, 1, len(cwMetric.Measurements)) - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: [][]string{{"label1", OTelLib}}, - Metrics: metricSlice, - } - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) - expectedFields := map[string]interface{}{ - OTelLib: instrLibName, - "foo": 0.3, - "label1": "value1", - } - assert.Equal(t, expectedFields, cwMetric.Fields) - }) - - t.Run("Int sum", func(t *testing.T) { - metric.SetDataType(pdata.MetricDataTypeIntSum) - metric.IntSum().SetAggregationTemporality(pdata.AggregationTemporalityCumulative) - - dp := DataPoint{} - dp.Value = float64(17) - dp.Labels = map[string]string{ - "label1": "value1", - } - - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrLibName, config) - - assert.NotNil(t, cwMetric) - assert.Equal(t, 1, len(cwMetric.Measurements)) - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: [][]string{{"label1", OTelLib}}, - Metrics: metricSlice, - } - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) - expectedFields := map[string]interface{}{ - OTelLib: instrLibName, - "foo": float64(17), - "label1": "value1", - } - assert.Equal(t, expectedFields, cwMetric.Fields) - }) - - t.Run("Double sum", func(t *testing.T) { - metric.SetDataType(pdata.MetricDataTypeDoubleSum) - metric.DoubleSum().SetAggregationTemporality(pdata.AggregationTemporalityCumulative) - - dp := DataPoint{} - dp.Value = 0.3 - dp.Labels = map[string]string{ - "label1": "value1", - } - - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrLibName, config) - - assert.NotNil(t, cwMetric) - assert.Equal(t, 1, len(cwMetric.Measurements)) - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: [][]string{{"label1", OTelLib}}, - Metrics: metricSlice, - } - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) - expectedFields := map[string]interface{}{ - OTelLib: instrLibName, - "foo": float64(0.3), - "label1": "value1", - } - assert.Equal(t, expectedFields, cwMetric.Fields) - }) - - t.Run("Double histogram", func(t *testing.T) { - metric.SetDataType(pdata.MetricDataTypeDoubleHistogram) - - cWMetricStats := &CWMetricStats{ - Count: uint64(17), - Sum: 17.13, - } - - dp := DataPoint{} - dp.Value = cWMetricStats - dp.Labels = map[string]string{ - "label1": "value1", - } - - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrLibName, config) - - assert.NotNil(t, cwMetric) - assert.Equal(t, 1, len(cwMetric.Measurements)) - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: [][]string{{"label1", OTelLib}}, - Metrics: metricSlice, - } - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) - expectedFields := map[string]interface{}{ - OTelLib: instrLibName, - "foo": &CWMetricStats{ - Sum: 17.13, - Count: 17, - }, - "label1": "value1", - } - assert.Equal(t, expectedFields, cwMetric.Fields) - }) - - t.Run("Invalid datapoint type", func(t *testing.T) { - metric.SetDataType(pdata.MetricDataTypeIntGauge) - dp := DataPoint{} - - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrLibName, config) - assert.Nil(t, cwMetric) - }) + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + config := &Config{ + MetricDeclarations: nil, + DimensionRollupOption: tc.dimensionRollupOption, + } + cWMeasurement := groupedMetricToCWMeasurement(tc.groupedMetric, config) + assertCWMeasurementEqual(t, tc.expectedMeasurement, cWMeasurement) + }) + } // Test rollup options and labels - testCases := []struct { + instrLibName := "cloudwatch-otel" + rollUpTestCases := []struct { testName string labels map[string]string dimensionRollupOption string expectedDims [][]string }{ { - "Single label w/ no rollup", + "Single label, no rollup, no otel dim", map[string]string{"a": "foo"}, "", [][]string{ - {"a", OTelLib}, + {"a"}, }, }, { - "Single label w/ single rollup", + "Single label, no rollup, w/ otel dim", + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, + "", + [][]string{ + {"a", oTellibDimensionKey}, + }, + }, + { + "Single label, single rollup, no otel dim", map[string]string{"a": "foo"}, singleDimensionRollupOnly, [][]string{ - {"a", OTelLib}, + {"a"}, + }, + }, + { + "Single label, single rollup, w/ otel dim", + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, + singleDimensionRollupOnly, + [][]string{ + {"a", oTellibDimensionKey}, }, }, { - "Single label w/ zero + single rollup", + "Single label, zero + single rollup, no otel dim", map[string]string{"a": "foo"}, zeroAndSingleDimensionRollup, [][]string{ - {"a", OTelLib}, - {OTelLib}, + {"a"}, + {}, + }, + }, + { + "Single label, zero + single rollup, w/ otel dim", + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, + zeroAndSingleDimensionRollup, + [][]string{ + {"a", oTellibDimensionKey}, + {oTellibDimensionKey}, }, }, { - "Multiple label w/ no rollup", + "Multiple label, no rollup, no otel dim", map[string]string{ "a": "foo", "b": "bar", @@ -1990,11 +1089,24 @@ func TestBuildCWMetric(t *testing.T) { }, "", [][]string{ - {"a", "b", "c", OTelLib}, + {"a", "b", "c"}, + }, + }, + { + "Multiple label, no rollup, w/ otel dim", + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, + "", + [][]string{ + {"a", "b", "c", oTellibDimensionKey}, }, }, { - "Multiple label w/ rollup", + "Multiple label, rollup, no otel dim", map[string]string{ "a": "foo", "b": "bar", @@ -2002,61 +1114,454 @@ func TestBuildCWMetric(t *testing.T) { }, zeroAndSingleDimensionRollup, [][]string{ - {"a", "b", "c", OTelLib}, - {OTelLib, "a"}, - {OTelLib, "b"}, - {OTelLib, "c"}, - {OTelLib}, + {"a", "b", "c"}, + {"a"}, + {"b"}, + {"c"}, + {}, + }, + }, + { + "Multiple label, rollup, w/ otel dim", + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, + zeroAndSingleDimensionRollup, + [][]string{ + {"a", "b", "c", oTellibDimensionKey}, + {oTellibDimensionKey, "a"}, + {oTellibDimensionKey, "b"}, + {oTellibDimensionKey, "c"}, + {oTellibDimensionKey}, + }, + }, + } + + for _, tc := range rollUpTestCases { + t.Run(tc.testName, func(t *testing.T) { + groupedMetric := &GroupedMetric{ + Labels: tc.labels, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", + }, + }, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + } + config := &Config{ + DimensionRollupOption: tc.dimensionRollupOption, + } + cWMeasurement := groupedMetricToCWMeasurement(groupedMetric, config) + assertDimsEqual(t, tc.expectedDims, cWMeasurement.Dimensions) + }) + } +} + +func TestGroupedMetricToCWMeasurementsWithFilters(t *testing.T) { + timestamp := int64(1596151098037) + namespace := "Namespace" + + labels := map[string]string{ + "a": "A", + "b": "B", + "c": "C", + } + metrics := map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", + }, + "metric2": { + Value: 200, + Unit: "Count", + }, + "metric3": { + Value: 3.14, + Unit: "Seconds", + }, + } + testCases := []struct { + testName string + metricDeclarations []*MetricDeclaration + expectedMeasurements []CWMeasurement + }{ + { + "single metric declaration", + []*MetricDeclaration{ + { + Dimensions: [][]string{{"a"}, {"a", "c"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + }, + }, + []CWMeasurement{ + { + Namespace: namespace, + Dimensions: [][]string{{"a"}, {"a", "c"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, + { + "Name": "metric2", + "Unit": "Count", + }, + { + "Name": "metric3", + "Unit": "Seconds", + }, + }, + }, + }, + }, + { + "multiple metric declarations, all unique", + []*MetricDeclaration{ + { + Dimensions: [][]string{{"a"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + }, + { + Dimensions: [][]string{{"a", "c"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric1"}, + }, + { + Dimensions: [][]string{{"a"}, {"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric(1|2)"}, + }, + }, + []CWMeasurement{ + { + Namespace: namespace, + Dimensions: [][]string{{"a"}, {"b"}, {"a", "c"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, + }, + }, + { + Namespace: namespace, + Dimensions: [][]string{{"a"}, {"b"}}, + Metrics: []map[string]string{ + { + "Name": "metric2", + "Unit": "Count", + }, + }, + }, + { + Namespace: namespace, + Dimensions: [][]string{{"a"}}, + Metrics: []map[string]string{ + { + "Name": "metric3", + "Unit": "Seconds", + }, + }, + }, + }, + }, + { + "multiple metric declarations, hybrid", + []*MetricDeclaration{ + { + Dimensions: [][]string{{"a"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + }, + { + Dimensions: [][]string{{"a"}, {"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric(1|2)"}, + }, + }, + []CWMeasurement{ + { + Namespace: namespace, + Dimensions: [][]string{{"a"}, {"b"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, + { + "Name": "metric2", + "Unit": "Count", + }, + }, + }, + { + Namespace: namespace, + Dimensions: [][]string{{"a"}}, + Metrics: []map[string]string{ + { + "Name": "metric3", + "Unit": "Seconds", + }, + }, + }, + }, + }, + { + "some dimensions match", + []*MetricDeclaration{ + { + Dimensions: [][]string{{"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + }, + { + Dimensions: [][]string{{"a"}, {"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric(1|2)"}, + }, + }, + []CWMeasurement{ + { + Namespace: namespace, + Dimensions: [][]string{{"a"}, {"b"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, + { + "Name": "metric2", + "Unit": "Count", + }, + }, + }, + }, + }, + { + "no dimension match", + []*MetricDeclaration{ + { + Dimensions: [][]string{{"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + }, + }, + nil, + }, + { + "label matchers", + []*MetricDeclaration{ + { + Dimensions: [][]string{{"a"}, {"a", "c"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + LabelMatchers: []*LabelMatcher{ + { + LabelNames: []string{"a", "b", "d"}, + Regex: "A;B;D", + }, + }, + }, + { + Dimensions: [][]string{{"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + LabelMatchers: []*LabelMatcher{ + { + LabelNames: []string{"a", "b"}, + Regex: "A;B", + }, + }, + }, + }, + []CWMeasurement{ + { + Namespace: namespace, + Dimensions: [][]string{{"b"}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, + { + "Name": "metric2", + "Unit": "Count", + }, + { + "Name": "metric3", + "Unit": "Seconds", + }, + }, + }, }, }, } + logger := zap.NewNop() + for _, tc := range testCases { t.Run(tc.testName, func(t *testing.T) { - dp := DataPoint{} - dp.Value = int64(-17) - dp.Labels = tc.labels - - config = &Config{ - Namespace: namespace, - DimensionRollupOption: tc.dimensionRollupOption, + groupedMetric := &GroupedMetric{ + Labels: labels, + Metrics: metrics, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, } - - expectedFields := map[string]interface{}{ - oTellibDimensionKey: OTelLib, - "foo": int64(-17), + config := &Config{ + DimensionRollupOption: "", + MetricDeclarations: tc.metricDeclarations, + logger: logger, } - for k, v := range tc.labels { - expectedFields[k] = v + for _, decl := range tc.metricDeclarations { + err := decl.Init(logger) + assert.Nil(t, err) } - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: tc.expectedDims, - Metrics: metricSlice, + + cWMeasurements := groupedMetricToCWMeasurementsWithFilters(groupedMetric, config) + assert.NotNil(t, cWMeasurements) + assert.Equal(t, len(tc.expectedMeasurements), len(cWMeasurements)) + assertCWMeasurementSliceEqual(t, tc.expectedMeasurements, cWMeasurements) + }) + } + + t.Run("No label match", func(t *testing.T) { + groupedMetric := &GroupedMetric{ + Labels: labels, + Metrics: metrics, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + } + metricDeclarations := []*MetricDeclaration{ + { + Dimensions: [][]string{{"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + LabelMatchers: []*LabelMatcher{ + { + LabelNames: []string{"a", "b"}, + Regex: "A;C", + }, + }, + }, + { + Dimensions: [][]string{{"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric.*"}, + LabelMatchers: []*LabelMatcher{ + { + LabelNames: []string{"a", "b"}, + Regex: "a;B", + }, + }, + }, + } + for _, decl := range metricDeclarations { + err := decl.Init(zap.NewNop()) + assert.Nil(t, err) + } + obs, logs := observer.New(zap.DebugLevel) + logger := zap.New(obs) + config := &Config{ + DimensionRollupOption: "", + MetricDeclarations: metricDeclarations, + logger: logger, + } + + cWMeasurements := groupedMetricToCWMeasurementsWithFilters(groupedMetric, config) + assert.Nil(t, cWMeasurements) + + // Test output warning logs + expectedLog := observer.LoggedEntry{ + Entry: zapcore.Entry{Level: zap.DebugLevel, Message: "Dropped batch of metrics: no metric declaration matched labels"}, + Context: []zapcore.Field{ + zap.String("Labels", "{\"a\":\"A\",\"b\":\"B\",\"c\":\"C\"}"), + zap.Strings("Metric Names", []string{"metric1", "metric2", "metric3"}), + }, + } + assert.Equal(t, 1, logs.Len()) + log := logs.AllUntimed()[0] + // Have to perform this hacky equality check because the metric names might not + // be in the right order due to map iteration + assert.Equal(t, expectedLog.Entry, log.Entry) + assert.Equal(t, 2, len(log.Context)) + assert.Equal(t, expectedLog.Context[0], log.Context[0]) + isMatch := false + possibleOrders := []zapcore.Field{ + zap.Strings("Metric Names", []string{"metric1", "metric2", "metric3"}), + zap.Strings("Metric Names", []string{"metric1", "metric3", "metric2"}), + zap.Strings("Metric Names", []string{"metric2", "metric1", "metric3"}), + zap.Strings("Metric Names", []string{"metric2", "metric3", "metric1"}), + zap.Strings("Metric Names", []string{"metric3", "metric2", "metric1"}), + zap.Strings("Metric Names", []string{"metric3", "metric1", "metric2"}), + } + for _, field := range possibleOrders { + if field.Equals(log.Context[1]) { + isMatch = true + break } + } + assert.True(t, isMatch) + }) - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, OTelLib, config) + t.Run("No metric name match", func(t *testing.T) { + groupedMetric := &GroupedMetric{ + Labels: labels, + Metrics: metrics, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, + } + metricDeclarations := []*MetricDeclaration{ + { + Dimensions: [][]string{{"b"}, {"b", "d"}}, + MetricNameSelectors: []string{"metric4"}, + }, + } + for _, decl := range metricDeclarations { + err := decl.Init(zap.NewNop()) + assert.Nil(t, err) + } + obs, logs := observer.New(zap.DebugLevel) + logger := zap.New(obs) + config := &Config{ + DimensionRollupOption: "", + MetricDeclarations: metricDeclarations, + logger: logger, + } - // Check fields - assert.Equal(t, expectedFields, cwMetric.Fields) + cWMeasurements := groupedMetricToCWMeasurementsWithFilters(groupedMetric, config) + assert.Nil(t, cWMeasurements) - // Check CW measurement - assert.Equal(t, 1, len(cwMetric.Measurements)) - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) - }) - } -} + // Test output warning logs + expectedEntry := zapcore.Entry{Level: zap.DebugLevel, Message: "Dropped metric: no metric declaration matched metric name"} + expectedContexts := []zapcore.Field{ + zap.String("Metric name", "metric1"), + zap.String("Metric name", "metric2"), + zap.String("Metric name", "metric3"), + } + assert.Equal(t, 3, logs.Len()) + // Match logs (possibly out of order) + seen := make([]bool, 3) + for _, log := range logs.AllUntimed() { + assert.Equal(t, expectedEntry, log.Entry) + assert.Equal(t, 1, len(log.Context)) + hasMatch := false + for i, expectedCtx := range expectedContexts { + if !seen[i] && log.Context[0].Equals(expectedCtx) { + hasMatch = true + seen[i] = true + break + } + } + assert.True(t, hasMatch) + } + }) -func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { - namespace := "Namespace" - OTelLib := oTellibDimensionKey - instrumentationLibName := "cloudwatch-otel" + // Test metric filtering with various roll-up options metricName := "metric1" - metricValue := int64(-17) - metric := pdata.NewMetric() - metric.SetName(metricName) - metricSlice := []map[string]string{{"Name": metricName}} - testCases := []struct { + instrLibName := "cloudwatch-otel" + rollupTestCases := []struct { testName string labels map[string]string metricDeclarations []*MetricDeclaration @@ -2065,7 +1570,10 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }{ { "Single label w/ no rollup", - map[string]string{"a": "foo"}, + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a"}}, @@ -2077,19 +1585,25 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, { "Single label + OTelLib w/ no rollup", - map[string]string{"a": "foo"}, + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { - Dimensions: [][]string{{"a", OTelLib}}, + Dimensions: [][]string{{"a", oTellibDimensionKey}}, MetricNameSelectors: []string{metricName}, }, }, "", - [][]string{{"a", OTelLib}}, + [][]string{{"a", oTellibDimensionKey}}, }, { "Single label w/ single rollup", - map[string]string{"a": "foo"}, + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a"}}, @@ -2097,11 +1611,14 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, }, singleDimensionRollupOnly, - [][]string{{"a"}, {"a", OTelLib}}, + [][]string{{"a"}, {"a", oTellibDimensionKey}}, }, { "Single label w/ zero/single rollup", - map[string]string{"a": "foo"}, + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a"}}, @@ -2109,23 +1626,30 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, }, zeroAndSingleDimensionRollup, - [][]string{{"a"}, {"a", OTelLib}, {OTelLib}}, + [][]string{{"a"}, {"a", oTellibDimensionKey}, {oTellibDimensionKey}}, }, { - "No matching metric name", - map[string]string{"a": "foo"}, + "Single label + Otel w/ zero/single rollup", + map[string]string{ + "a": "foo", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { - Dimensions: [][]string{{"a"}}, - MetricNameSelectors: []string{"invalid"}, + Dimensions: [][]string{{"a", oTellibDimensionKey}}, + MetricNameSelectors: []string{metricName}, }, }, - "", - nil, + zeroAndSingleDimensionRollup, + [][]string{{"a", oTellibDimensionKey}, {oTellibDimensionKey}}, }, { "multiple labels w/ no rollup", - map[string]string{"a": "foo", "b": "bar"}, + map[string]string{ + "a": "foo", + "b": "bar", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a"}}, @@ -2137,7 +1661,11 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, { "multiple labels w/ rollup", - map[string]string{"a": "foo", "b": "bar"}, + map[string]string{ + "a": "foo", + "b": "bar", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a"}}, @@ -2147,14 +1675,18 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { zeroAndSingleDimensionRollup, [][]string{ {"a"}, - {OTelLib, "a"}, - {OTelLib, "b"}, - {OTelLib}, + {oTellibDimensionKey, "a"}, + {oTellibDimensionKey, "b"}, + {oTellibDimensionKey}, }, }, { "multiple labels + multiple dimensions w/ no rollup", - map[string]string{"a": "foo", "b": "bar"}, + map[string]string{ + "a": "foo", + "b": "bar", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b"}, {"b"}}, @@ -2165,20 +1697,28 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { [][]string{{"a", "b"}, {"b"}}, }, { - "multiple labels + multiple dimensions + OTelLib w/ no rollup", - map[string]string{"a": "foo", "b": "bar"}, + "multiple labels + multiple dimensions + oTellibDimensionKey w/ no rollup", + map[string]string{ + "a": "foo", + "b": "bar", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { - Dimensions: [][]string{{"a", "b"}, {"b", OTelLib}, {OTelLib}}, + Dimensions: [][]string{{"a", "b"}, {"b", oTellibDimensionKey}, {oTellibDimensionKey}}, MetricNameSelectors: []string{metricName}, }, }, "", - [][]string{{"a", "b"}, {"b", OTelLib}, {OTelLib}}, + [][]string{{"a", "b"}, {"b", oTellibDimensionKey}, {oTellibDimensionKey}}, }, { "multiple labels + multiple dimensions w/ rollup", - map[string]string{"a": "foo", "b": "bar"}, + map[string]string{ + "a": "foo", + "b": "bar", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b"}, {"b"}}, @@ -2189,14 +1729,18 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { [][]string{ {"a", "b"}, {"b"}, - {OTelLib, "a"}, - {OTelLib, "b"}, - {OTelLib}, + {oTellibDimensionKey, "a"}, + {oTellibDimensionKey, "b"}, + {oTellibDimensionKey}, }, }, { "multiple labels, multiple dimensions w/ invalid dimension", - map[string]string{"a": "foo", "b": "bar"}, + map[string]string{ + "a": "foo", + "b": "bar", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b", "c"}, {"b"}}, @@ -2206,14 +1750,19 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { zeroAndSingleDimensionRollup, [][]string{ {"b"}, - {OTelLib, "a"}, - {OTelLib, "b"}, - {OTelLib}, + {oTellibDimensionKey, "a"}, + {oTellibDimensionKey, "b"}, + {oTellibDimensionKey}, }, }, { "multiple labels, multiple dimensions w/ missing dimension", - map[string]string{"a": "foo", "b": "bar", "c": "car"}, + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b"}, {"b"}}, @@ -2224,15 +1773,20 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { [][]string{ {"a", "b"}, {"b"}, - {OTelLib, "a"}, - {OTelLib, "b"}, - {OTelLib, "c"}, - {OTelLib}, + {oTellibDimensionKey, "a"}, + {oTellibDimensionKey, "b"}, + {oTellibDimensionKey, "c"}, + {oTellibDimensionKey}, }, }, { "multiple metric declarations w/ no rollup", - map[string]string{"a": "foo", "b": "bar", "c": "car"}, + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b"}, {"b"}}, @@ -2258,7 +1812,12 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, { "multiple metric declarations w/ rollup", - map[string]string{"a": "foo", "b": "bar", "c": "car"}, + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b"}, {"b"}}, @@ -2277,10 +1836,10 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { [][]string{ {"a", "b"}, {"b"}, - {OTelLib, "a"}, - {OTelLib, "b"}, - {OTelLib, "c"}, - {OTelLib}, + {oTellibDimensionKey, "a"}, + {oTellibDimensionKey, "b"}, + {oTellibDimensionKey, "c"}, + {oTellibDimensionKey}, {"a", "c"}, {"c"}, {"b", "c"}, @@ -2288,7 +1847,12 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, { "remove measurements with no dimensions", - map[string]string{"a": "foo", "b": "bar", "c": "car"}, + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "b"}, {"b"}}, @@ -2307,7 +1871,12 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, { "multiple declarations w/ no dimensions", - map[string]string{"a": "foo", "b": "bar", "c": "car"}, + map[string]string{ + "a": "foo", + "b": "bar", + "c": "car", + (oTellibDimensionKey): instrLibName, + }, []*MetricDeclaration{ { Dimensions: [][]string{{"a", "e"}, {"d"}}, @@ -2335,159 +1904,62 @@ func TestBuildCWMetricWithMetricDeclarations(t *testing.T) { }, } - for _, tc := range testCases { + for _, tc := range rollupTestCases { t.Run(tc.testName, func(t *testing.T) { - dp := DataPoint{} - dp.Labels = tc.labels - dp.Value = metricValue - config := &Config{ - Namespace: namespace, - DimensionRollupOption: tc.dimensionRollupOption, - MetricDeclarations: tc.metricDeclarations, + groupedMetric := &GroupedMetric{ + Labels: tc.labels, + Metrics: map[string]*MetricInfo{ + (metricName): { + Value: int64(5), + Unit: "Count", + }, + }, + Metadata: CWMetricMetadata{ + Namespace: namespace, + TimestampMs: timestamp, + }, } - logger := zap.NewNop() - for _, m := range tc.metricDeclarations { - err := m.Init(logger) + for _, decl := range tc.metricDeclarations { + err := decl.Init(zap.NewNop()) assert.Nil(t, err) } - - expectedFields := map[string]interface{}{ - oTellibDimensionKey: instrumentationLibName, - metricName: metricValue, - } - for k, v := range tc.labels { - expectedFields[k] = v + config := &Config{ + DimensionRollupOption: tc.dimensionRollupOption, + MetricDeclarations: tc.metricDeclarations, + logger: zap.NewNop(), } - cwMetric := buildCWMetric(dp, &metric, namespace, metricSlice, instrumentationLibName, config) - - // Check fields - assert.Equal(t, expectedFields, cwMetric.Fields) - - // Check CW measurement - if tc.expectedDims == nil { - assert.Equal(t, 0, len(cwMetric.Measurements)) + cWMeasurements := groupedMetricToCWMeasurementsWithFilters(groupedMetric, config) + if len(tc.expectedDims) == 0 { + assert.Equal(t, 0, len(cWMeasurements)) } else { - assert.Equal(t, 1, len(cwMetric.Measurements)) - expectedMeasurement := CwMeasurement{ - Namespace: namespace, - Dimensions: tc.expectedDims, - Metrics: metricSlice, - } - assertCwMeasurementEqual(t, expectedMeasurement, cwMetric.Measurements[0]) + assert.Equal(t, 1, len(cWMeasurements)) + dims := cWMeasurements[0].Dimensions + assertDimsEqual(t, tc.expectedDims, dims) } }) } } -func TestDimensionRollup(t *testing.T) { - testCases := []struct { - testName string - dimensionRollupOption string - dims []string - instrumentationLibName string - expected [][]string - }{ - { - "no rollup w/o instrumentation library name", - "", - []string{"a", "b", "c"}, - noInstrumentationLibraryName, - nil, - }, - { - "no rollup w/ instrumentation library name", - "", - []string{"a", "b", "c"}, - "cloudwatch-otel", - nil, - }, - { - "single dim w/o instrumentation library name", - singleDimensionRollupOnly, - []string{"a", "b", "c"}, - noInstrumentationLibraryName, - [][]string{ - {"a"}, - {"b"}, - {"c"}, - }, - }, - { - "single dim w/ instrumentation library name", - singleDimensionRollupOnly, - []string{"a", "b", "c"}, - "cloudwatch-otel", - [][]string{ - {oTellibDimensionKey, "a"}, - {oTellibDimensionKey, "b"}, - {oTellibDimensionKey, "c"}, - }, - }, - { - "single dim w/o instrumentation library name and only one label", - singleDimensionRollupOnly, - []string{"a"}, - noInstrumentationLibraryName, - [][]string{{"a"}}, - }, - { - "single dim w/ instrumentation library name and only one label", - singleDimensionRollupOnly, - []string{"a"}, - "cloudwatch-otel", - [][]string{{oTellibDimensionKey, "a"}}, - }, - { - "zero + single dim w/o instrumentation library name", - zeroAndSingleDimensionRollup, - []string{"a", "b", "c"}, - noInstrumentationLibraryName, - [][]string{ - {}, - {"a"}, - {"b"}, - {"c"}, - }, - }, - { - "zero + single dim w/ instrumentation library name", - zeroAndSingleDimensionRollup, - []string{"a", "b", "c", "A"}, - "cloudwatch-otel", - [][]string{ - {oTellibDimensionKey}, - {oTellibDimensionKey, "a"}, - {oTellibDimensionKey, "b"}, - {oTellibDimensionKey, "c"}, - {oTellibDimensionKey, "A"}, - }, - }, - { - "zero dim rollup w/o instrumentation library name and no labels", - zeroAndSingleDimensionRollup, - []string{}, - noInstrumentationLibraryName, - nil, - }, - { - "zero dim rollup w/ instrumentation library name and no labels", - zeroAndSingleDimensionRollup, - []string{}, - "cloudwatch-otel", - nil, - }, - } +func TestTranslateCWMetricToEMFNoMeasurements(t *testing.T) { + timestamp := int64(1596151098037) + fields := make(map[string]interface{}) + fields[oTellibDimensionKey] = "cloudwatch-otel" + fields["spanName"] = "test" + fields["spanCounter"] = 0 - for _, tc := range testCases { - t.Run(tc.testName, func(t *testing.T) { - rolledUp := dimensionRollup(tc.dimensionRollupOption, tc.dims, tc.instrumentationLibName) - assertDimsEqual(t, tc.expected, rolledUp) - }) + met := &CWMetrics{ + TimestampMs: timestamp, + Fields: fields, + Measurements: nil, } + inputLogEvent := translateCWMetricToEMF(met) + expected := "{\"OTelLib\":\"cloudwatch-otel\",\"spanCounter\":0,\"spanName\":\"test\"}" + + assert.Equal(t, expected, *inputLogEvent.InputLogEvent.Message) } -func BenchmarkTranslateOTelToCWMetricWithInstrLibrary(b *testing.B) { +func BenchmarkTranslateOtToGroupedMetricWithInstrLibrary(b *testing.B) { md := createMetricTestData() rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) ilms := rm.InstrumentationLibraryMetrics() @@ -2496,59 +1968,108 @@ func BenchmarkTranslateOTelToCWMetricWithInstrLibrary(b *testing.B) { config := &Config{ Namespace: "", DimensionRollupOption: zeroAndSingleDimensionRollup, + logger: zap.NewNop(), } + translator := newMetricTranslator(*config) b.ResetTimer() for n := 0; n < b.N; n++ { - translateOTelToCWMetric(&rm, config) + groupedMetric := make(map[interface{}]*GroupedMetric) + translator.translateOTelToGroupedMetric(&rm, groupedMetric, config) } } -func BenchmarkTranslateOTelToCWMetricWithoutInstrLibrary(b *testing.B) { +func BenchmarkTranslateOtToGroupedMetricWithoutInstrLibrary(b *testing.B) { md := createMetricTestData() rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) config := &Config{ Namespace: "", DimensionRollupOption: zeroAndSingleDimensionRollup, + logger: zap.NewNop(), } + translator := newMetricTranslator(*config) b.ResetTimer() for n := 0; n < b.N; n++ { - translateOTelToCWMetric(&rm, config) + groupedMetrics := make(map[interface{}]*GroupedMetric) + translator.translateOTelToGroupedMetric(&rm, groupedMetrics, config) } } -func BenchmarkTranslateOTelToCWMetricWithFiltering(b *testing.B) { - md := createMetricTestData() - rm := internaldata.OCToMetrics(md).ResourceMetrics().At(0) - ilms := rm.InstrumentationLibraryMetrics() - ilm := ilms.At(0) - ilm.InstrumentationLibrary().SetName("cloudwatch-lib") - m := MetricDeclaration{ - Dimensions: [][]string{{"spanName"}}, - MetricNameSelectors: []string{"spanCounter", "spanGaugeCounter"}, +func BenchmarkTranslateGroupedMetricToCWMetric(b *testing.B) { + groupedMetric := &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + "label3": "value3", + }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", + }, + "metric2": { + Value: 200, + Unit: "Seconds", + }, + }, + Metadata: CWMetricMetadata{ + Namespace: "Namespace", + TimestampMs: int64(1596151098037), + }, } - logger := zap.NewNop() - m.Init(logger) config := &Config{ - Namespace: "", + MetricDeclarations: nil, DimensionRollupOption: zeroAndSingleDimensionRollup, - MetricDeclarations: []*MetricDeclaration{&m}, } b.ResetTimer() for n := 0; n < b.N; n++ { - translateOTelToCWMetric(&rm, config) + translateGroupedMetricToCWMetric(groupedMetric, config) } } -func translateOTelToCWMetric(rm *pdata.ResourceMetrics, config *Config) ([]*CWMetrics, int) { - translator := newMetricTranslator(*config) - return translator.translateOTelToCWMetric(rm, config) +func BenchmarkTranslateGroupedMetricToCWMetricWithFiltering(b *testing.B) { + groupedMetric := &GroupedMetric{ + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + "label3": "value3", + }, + Metrics: map[string]*MetricInfo{ + "metric1": { + Value: 1, + Unit: "Count", + }, + "metric2": { + Value: 200, + Unit: "Seconds", + }, + }, + Metadata: CWMetricMetadata{ + Namespace: "Namespace", + TimestampMs: int64(1596151098037), + }, + } + m := &MetricDeclaration{ + Dimensions: [][]string{{"label1"}, {"label2"}}, + MetricNameSelectors: []string{"metric1", "metric2"}, + } + logger := zap.NewNop() + m.Init(logger) + config := &Config{ + MetricDeclarations: []*MetricDeclaration{m}, + DimensionRollupOption: zeroAndSingleDimensionRollup, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + translateGroupedMetricToCWMetric(groupedMetric, config) + } } func BenchmarkTranslateCWMetricToEMF(b *testing.B) { - cwMeasurement := CwMeasurement{ + cwMeasurement := CWMeasurement{ Namespace: "test-emf", Dimensions: [][]string{{oTellibDimensionKey}, {oTellibDimensionKey, "spanName"}}, Metrics: []map[string]string{{ @@ -2565,19 +2086,11 @@ func BenchmarkTranslateCWMetricToEMF(b *testing.B) { met := &CWMetrics{ TimestampMs: timestamp, Fields: fields, - Measurements: []CwMeasurement{cwMeasurement}, + Measurements: []CWMeasurement{cwMeasurement}, } - logger := zap.NewNop() b.ResetTimer() for n := 0; n < b.N; n++ { - translateCWMetricToEMF([]*CWMetrics{met}, logger) - } -} - -func BenchmarkDimensionRollup(b *testing.B) { - dimensions := []string{"a", "b", "c"} - for n := 0; n < b.N; n++ { - dimensionRollup(zeroAndSingleDimensionRollup, dimensions, "cloudwatch-otel") + translateCWMetricToEMF(met) } } diff --git a/exporter/awsemfexporter/pusher_test.go b/exporter/awsemfexporter/pusher_test.go index 79af0dfa75c5..b61a975d3720 100644 --- a/exporter/awsemfexporter/pusher_test.go +++ b/exporter/awsemfexporter/pusher_test.go @@ -79,7 +79,7 @@ func TestLogEventBatch_sortLogEvents(t *testing.T) { logEvent := NewLogEvent( int64(timestamp), fmt.Sprintf("message%v", timestamp)) - fmt.Printf("logEvents[%d].Timetsmap=%d.\n", i, timestamp) + fmt.Printf("logEvents[%d].Timestamp=%d.\n", i, timestamp) logEventBatch.PutLogEventsInput.LogEvents = append(logEventBatch.PutLogEventsInput.LogEvents, logEvent.InputLogEvent) } diff --git a/exporter/awsemfexporter/util.go b/exporter/awsemfexporter/util.go index 7435d4cea38b..13c396da856a 100644 --- a/exporter/awsemfexporter/util.go +++ b/exporter/awsemfexporter/util.go @@ -16,6 +16,7 @@ package awsemfexporter import ( "fmt" + "sort" "strings" "time" @@ -96,6 +97,58 @@ func getLogInfo(rm *pdata.ResourceMetrics, cWNamespace string, config *Config) ( return } +// dedupDimensions removes duplicated dimension sets from the given dimensions. +// Prerequisite: each dimension set is already sorted +func dedupDimensions(dimensions [][]string) (deduped [][]string) { + seen := make(map[string]bool) + for _, dimSet := range dimensions { + key := strings.Join(dimSet, ",") + // Only add dimension set if not a duplicate + if _, ok := seen[key]; !ok { + deduped = append(deduped, dimSet) + seen[key] = true + } + } + return +} + +// dimensionRollup creates rolled-up dimensions from the metric's label set. +// The returned dimensions are sorted in alphabetical order within each dimension set +func dimensionRollup(dimensionRollupOption string, labels map[string]string) [][]string { + var rollupDimensionArray [][]string + dimensionZero := make([]string, 0) + + instrLibName, hasOTelKey := labels[oTellibDimensionKey] + if hasOTelKey { + // If OTel key exists in labels, add it as a zero dimension but remove it + // temporarily from labels as it is not an original label + dimensionZero = []string{oTellibDimensionKey} + delete(labels, oTellibDimensionKey) + } + + if dimensionRollupOption == zeroAndSingleDimensionRollup { + //"Zero" dimension rollup + if len(labels) > 0 { + rollupDimensionArray = append(rollupDimensionArray, dimensionZero) + } + } + if dimensionRollupOption == zeroAndSingleDimensionRollup || dimensionRollupOption == singleDimensionRollupOnly { + //"One" dimension rollup + for labelName := range labels { + dimSet := append(dimensionZero, labelName) + sort.Strings(dimSet) + rollupDimensionArray = append(rollupDimensionArray, dimSet) + } + } + + // Add back OTel key to labels if it was removed + if hasOTelKey { + labels[oTellibDimensionKey] = instrLibName + } + + return rollupDimensionArray +} + // unixNanoToMilliseconds converts a timestamp in nanoseconds to milliseconds. func unixNanoToMilliseconds(timestamp pdata.TimestampUnixNano) int64 { return int64(uint64(timestamp) / uint64(time.Millisecond))