From a8342057b2fd4f2cac6eb4907f53417b874fab9f Mon Sep 17 00:00:00 2001 From: Alexander Yastrebov Date: Tue, 2 Dec 2025 19:13:34 +0100 Subject: [PATCH 1/2] statsd: remove global cardinality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The change #327 introduced support for tag cardinality with global default value that could be overriden per metric call via parameter. Default cardinality value is stored in a global variable guarded by a mutex which has two downsides: * performance impact - each measure call has to obtain mutex to get the default value * two clients created with different default cardinality override the same global value This change eliminates global default value and uses default value per statsd client specified during client construction with a fallback to environment variables. User supplied cardinality value is validated during client construction and during parameter parsing with a fallback to default value such that all internal components can assume they always receive a valid value. Performance improvement (via `Statsd.*Aggregation` benchmarks): ``` goos: darwin goarch: arm64 pkg: github.com/DataDog/datadog-go/v5/statsd cpu: Apple M4 Max │ master │ HEAD │ │ sec/op │ sec/op vs base │ StatsdUDPSameMetricMutexAggregation-16 248.3n ± 1% 142.3n ± 0% -42.68% (p=0.000 n=10) StatsdUDPSameMetricChannelAggregation-16 252.3n ± 0% 143.8n ± 1% -43.02% (p=0.000 n=10) StatsdUDPDifferentMetricMutexAggregation-16 250.7n ± 1% 148.6n ± 0% -40.71% (p=0.000 n=10) StatsdUDPDifferentMetricChannelAggregation-16 252.5n ± 1% 150.3n ± 0% -40.48% (p=0.000 n=10) StatsdUDSSameMetricMutexAggregation-16 248.9n ± 1% 143.0n ± 1% -42.53% (p=0.000 n=10) StatsdUDSSameMetricChannelAggregation-16 251.9n ± 1% 144.0n ± 1% -42.87% (p=0.000 n=10) StatsdUDPSifferentMetricMutexAggregation-16 252.1n ± 0% 148.8n ± 1% -40.97% (p=0.000 n=10) StatsdUDSDifferentMetricChannelAggregation-16 252.8n ± 1% 149.5n ± 0% -40.85% (p=0.000 n=10) geomean 251.2n 146.3n -41.77% │ master │ HEAD │ │ %_dropRate │ %_dropRate vs base │ StatsdUDPSameMetricMutexAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDPSameMetricChannelAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDPDifferentMetricMutexAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDPDifferentMetricChannelAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDSSameMetricMutexAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDSSameMetricChannelAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDPSifferentMetricMutexAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ StatsdUDSDifferentMetricChannelAggregation-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` --- statsd/aggregator.go | 25 +- statsd/aggregator_test.go | 418 ++++++++---------------------- statsd/buffer.go | 37 ++- statsd/buffer_pool_test.go | 2 +- statsd/buffer_test.go | 268 +++++-------------- statsd/buffered_metric_context.go | 5 +- statsd/format.go | 6 +- statsd/metrics.go | 104 ++++---- statsd/metrics_test.go | 54 ++-- statsd/options.go | 9 +- statsd/options_test.go | 11 +- statsd/statsdex.go | 57 ++-- statsd/tag_cardinality.go | 67 ++--- statsd/tag_cardinality_test.go | 345 ++++-------------------- statsd/worker.go | 18 +- 15 files changed, 409 insertions(+), 1017 deletions(-) diff --git a/statsd/aggregator.go b/statsd/aggregator.go index 03629d29..139e0684 100644 --- a/statsd/aggregator.go +++ b/statsd/aggregator.go @@ -96,11 +96,11 @@ func (a *aggregator) pullMetric() { case m := <-a.inputMetrics: switch m.metricType { case histogram: - a.histogram(m.name, m.fvalue, m.tags, m.rate, m.overrideCard) + a.histogram(m.name, m.fvalue, m.tags, m.rate, m.cardinality) case distribution: - a.distribution(m.name, m.fvalue, m.tags, m.rate, m.overrideCard) + a.distribution(m.name, m.fvalue, m.tags, m.rate, m.cardinality) case timing: - a.timing(m.name, m.fvalue, m.tags, m.rate, m.overrideCard) + a.timing(m.name, m.fvalue, m.tags, m.rate, m.cardinality) } case <-a.stopChannelMode: a.wg.Done() @@ -191,7 +191,7 @@ func getContextAndTags(name string, tags []string, cardinality Cardinality) (str if cardString == "" { return name, "" } - return name + nameSeparatorSymbol + cardinality.String(), "" + return name + nameSeparatorSymbol + cardString, "" } n := len(name) + len(nameSeparatorSymbol) + len(tagSeparatorSymbol)*(len(tags)-1) @@ -224,8 +224,7 @@ func getContextAndTags(name string, tags []string, cardinality Cardinality) (str } func (a *aggregator) count(name string, value int64, tags []string, cardinality Cardinality) error { - resolvedCardinality := resolveCardinality(cardinality) - context := getContext(name, tags, resolvedCardinality) + context := getContext(name, tags, cardinality) a.countsM.RLock() if count, found := a.counts[context]; found { count.sample(value) @@ -234,10 +233,10 @@ func (a *aggregator) count(name string, value int64, tags []string, cardinality } a.countsM.RUnlock() - metric := newCountMetric(name, value, tags, resolvedCardinality) + metric := newCountMetric(name, value, tags, cardinality) a.countsM.Lock() - // Check if another goroutines hasn't created the value betwen the RUnlock and 'Lock' + // Check if another goroutines hasn't created the value between the RUnlock and 'Lock' if count, found := a.counts[context]; found { count.sample(value) a.countsM.Unlock() @@ -250,8 +249,7 @@ func (a *aggregator) count(name string, value int64, tags []string, cardinality } func (a *aggregator) gauge(name string, value float64, tags []string, cardinality Cardinality) error { - resolvedCardinality := resolveCardinality(cardinality) - context := getContext(name, tags, resolvedCardinality) + context := getContext(name, tags, cardinality) a.gaugesM.RLock() if gauge, found := a.gauges[context]; found { gauge.sample(value) @@ -260,7 +258,7 @@ func (a *aggregator) gauge(name string, value float64, tags []string, cardinalit } a.gaugesM.RUnlock() - gauge := newGaugeMetric(name, value, tags, resolvedCardinality) + gauge := newGaugeMetric(name, value, tags, cardinality) a.gaugesM.Lock() // Check if another goroutines hasn't created the value betwen the 'RUnlock' and 'Lock' @@ -275,8 +273,7 @@ func (a *aggregator) gauge(name string, value float64, tags []string, cardinalit } func (a *aggregator) set(name string, value string, tags []string, cardinality Cardinality) error { - resolvedCardinality := resolveCardinality(cardinality) - context := getContext(name, tags, resolvedCardinality) + context := getContext(name, tags, cardinality) a.setsM.RLock() if set, found := a.sets[context]; found { set.sample(value) @@ -285,7 +282,7 @@ func (a *aggregator) set(name string, value string, tags []string, cardinality C } a.setsM.RUnlock() - metric := newSetMetric(name, value, tags, resolvedCardinality) + metric := newSetMetric(name, value, tags, cardinality) a.setsM.Lock() // Check if another goroutines hasn't created the value betwen the 'RUnlock' and 'Lock' diff --git a/statsd/aggregator_test.go b/statsd/aggregator_test.go index cd2faf02..38a27434 100644 --- a/statsd/aggregator_test.go +++ b/statsd/aggregator_test.go @@ -17,31 +17,31 @@ func TestAggregatorSample(t *testing.T) { tags := []string{"tag1", "tag2"} for i := 0; i < 2; i++ { - a.gauge("gaugeTest", 21, tags, defaultTagCardinality) + a.gauge("gaugeTest", 21, tags, CardinalityNotSet) assert.Len(t, a.gauges, 1) assert.Contains(t, a.gauges, "gaugeTest:tag1,tag2") - a.count("countTest", 21, tags, defaultTagCardinality) + a.count("countTest", 21, tags, CardinalityNotSet) assert.Len(t, a.counts, 1) assert.Contains(t, a.counts, "countTest:tag1,tag2") - a.set("setTest", "value1", tags, defaultTagCardinality) + a.set("setTest", "value1", tags, CardinalityNotSet) assert.Len(t, a.sets, 1) assert.Contains(t, a.sets, "setTest:tag1,tag2") - a.set("setTest", "value1", tags, defaultTagCardinality) + a.set("setTest", "value1", tags, CardinalityNotSet) assert.Len(t, a.sets, 1) assert.Contains(t, a.sets, "setTest:tag1,tag2") - a.histogram("histogramTest", 21, tags, 1, defaultTagCardinality) + a.histogram("histogramTest", 21, tags, 1, CardinalityNotSet) assert.Len(t, a.histograms.values, 1) assert.Contains(t, a.histograms.values, "histogramTest:tag1,tag2") - a.distribution("distributionTest", 21, tags, 1, defaultTagCardinality) + a.distribution("distributionTest", 21, tags, 1, CardinalityNotSet) assert.Len(t, a.distributions.values, 1) assert.Contains(t, a.distributions.values, "distributionTest:tag1,tag2") - a.timing("timingTest", 21, tags, 1, defaultTagCardinality) + a.timing("timingTest", 21, tags, 1, CardinalityNotSet) assert.Len(t, a.timings.values, 1) assert.Contains(t, a.timings.values, "timingTest:tag1,tag2") } @@ -102,108 +102,108 @@ func TestAggregatorFlush(t *testing.T) { assert.Equal(t, []metric{ metric{ - metricType: gauge, - name: "gaugeTest1", - tags: tags, - rate: 1, - fvalue: float64(10), - overrideCard: CardinalityLow, + metricType: gauge, + name: "gaugeTest1", + tags: tags, + rate: 1, + fvalue: float64(10), + cardinality: CardinalityLow, }, metric{ - metricType: gauge, - name: "gaugeTest2", - tags: tags, - rate: 1, - fvalue: float64(15), - overrideCard: CardinalityLow, + metricType: gauge, + name: "gaugeTest2", + tags: tags, + rate: 1, + fvalue: float64(15), + cardinality: CardinalityLow, }, metric{ - metricType: count, - name: "countTest1", - tags: tags, - rate: 1, - ivalue: int64(31), - overrideCard: CardinalityLow, + metricType: count, + name: "countTest1", + tags: tags, + rate: 1, + ivalue: int64(31), + cardinality: CardinalityLow, }, metric{ - metricType: count, - name: "countTest2", - tags: tags, - rate: 1, - ivalue: int64(1), - overrideCard: CardinalityLow, + metricType: count, + name: "countTest2", + tags: tags, + rate: 1, + ivalue: int64(1), + cardinality: CardinalityLow, }, metric{ - metricType: histogramAggregated, - name: "histogramTest1", - stags: strings.Join(tags, tagSeparatorSymbol), - rate: 1, - fvalues: []float64{21.0, 22.0}, - overrideCard: CardinalityLow, + metricType: histogramAggregated, + name: "histogramTest1", + stags: strings.Join(tags, tagSeparatorSymbol), + rate: 1, + fvalues: []float64{21.0, 22.0}, + cardinality: CardinalityLow, }, metric{ - metricType: histogramAggregated, - name: "histogramTest2", - stags: strings.Join(tags, tagSeparatorSymbol), - rate: 1, - fvalues: []float64{23.0}, - overrideCard: CardinalityLow, + metricType: histogramAggregated, + name: "histogramTest2", + stags: strings.Join(tags, tagSeparatorSymbol), + rate: 1, + fvalues: []float64{23.0}, + cardinality: CardinalityLow, }, metric{ - metricType: distributionAggregated, - name: "distributionTest1", - stags: strings.Join(tags, tagSeparatorSymbol), - rate: 1, - fvalues: []float64{21.0, 22.0}, - overrideCard: CardinalityLow, + metricType: distributionAggregated, + name: "distributionTest1", + stags: strings.Join(tags, tagSeparatorSymbol), + rate: 1, + fvalues: []float64{21.0, 22.0}, + cardinality: CardinalityLow, }, metric{ - metricType: distributionAggregated, - name: "distributionTest2", - stags: strings.Join(tags, tagSeparatorSymbol), - rate: 1, - fvalues: []float64{23.0}, - overrideCard: CardinalityLow, + metricType: distributionAggregated, + name: "distributionTest2", + stags: strings.Join(tags, tagSeparatorSymbol), + rate: 1, + fvalues: []float64{23.0}, + cardinality: CardinalityLow, }, metric{ - metricType: set, - name: "setTest1", - tags: tags, - rate: 1, - svalue: "value1", - overrideCard: CardinalityLow, + metricType: set, + name: "setTest1", + tags: tags, + rate: 1, + svalue: "value1", + cardinality: CardinalityLow, }, metric{ - metricType: set, - name: "setTest1", - tags: tags, - rate: 1, - svalue: "value2", - overrideCard: CardinalityLow, + metricType: set, + name: "setTest1", + tags: tags, + rate: 1, + svalue: "value2", + cardinality: CardinalityLow, }, metric{ - metricType: set, - name: "setTest2", - tags: tags, - rate: 1, - svalue: "value1", - overrideCard: CardinalityLow, + metricType: set, + name: "setTest2", + tags: tags, + rate: 1, + svalue: "value1", + cardinality: CardinalityLow, }, metric{ - metricType: timingAggregated, - name: "timingTest1", - stags: strings.Join(tags, tagSeparatorSymbol), - rate: 1, - fvalues: []float64{21.0, 22.0}, - overrideCard: CardinalityLow, + metricType: timingAggregated, + name: "timingTest1", + stags: strings.Join(tags, tagSeparatorSymbol), + rate: 1, + fvalues: []float64{21.0, 22.0}, + cardinality: CardinalityLow, }, metric{ - metricType: timingAggregated, - name: "timingTest2", - stags: strings.Join(tags, tagSeparatorSymbol), - rate: 1, - fvalues: []float64{23.0}, - overrideCard: CardinalityLow, + metricType: timingAggregated, + name: "timingTest2", + stags: strings.Join(tags, tagSeparatorSymbol), + rate: 1, + fvalues: []float64{23.0}, + cardinality: CardinalityLow, }, }, metrics) @@ -460,9 +460,9 @@ func TestAggregatorCardinalitySeparation(t *testing.T) { var lowGauge, highGauge metric for _, m := range metrics { if m.metricType == gauge && m.name == "test.metric" { - if m.overrideCard == CardinalityLow { + if m.cardinality == CardinalityLow { lowGauge = m - } else if m.overrideCard == CardinalityHigh { + } else if m.cardinality == CardinalityHigh { highGauge = m } } @@ -475,9 +475,9 @@ func TestAggregatorCardinalitySeparation(t *testing.T) { var lowCount, highCount metric for _, m := range metrics { if m.metricType == count && m.name == "test.count" { - if m.overrideCard == CardinalityLow { + if m.cardinality == CardinalityLow { lowCount = m - } else if m.overrideCard == CardinalityHigh { + } else if m.cardinality == CardinalityHigh { highCount = m } } @@ -490,9 +490,9 @@ func TestAggregatorCardinalitySeparation(t *testing.T) { var lowSetValues, highSetValues []string for _, m := range metrics { if m.metricType == set && m.name == "test.set" { - if m.overrideCard == CardinalityLow { + if m.cardinality == CardinalityLow { lowSetValues = append(lowSetValues, m.svalue) - } else if m.overrideCard == CardinalityHigh { + } else if m.cardinality == CardinalityHigh { highSetValues = append(highSetValues, m.svalue) } } @@ -523,13 +523,13 @@ func TestAggregatorCardinalityPreservation(t *testing.T) { switch m.metricType { case gauge: assert.Equal(t, "test.metric", m.name) - assert.Equal(t, CardinalityLow, m.overrideCard) + assert.Equal(t, CardinalityLow, m.cardinality) case count: assert.Equal(t, "test.count", m.name) - assert.Equal(t, CardinalityHigh, m.overrideCard) + assert.Equal(t, CardinalityHigh, m.cardinality) case set: assert.Equal(t, "test.set", m.name) - assert.Equal(t, CardinalityOrchestrator, m.overrideCard) + assert.Equal(t, CardinalityOrchestrator, m.cardinality) } } } @@ -558,9 +558,9 @@ func TestAggregatorCardinalityWithBufferedMetrics(t *testing.T) { var lowHist, highHist metric for _, m := range metrics { if m.metricType == histogramAggregated && m.name == "test.hist" { - if m.overrideCard == CardinalityLow { + if m.cardinality == CardinalityLow { lowHist = m - } else if m.overrideCard == CardinalityHigh { + } else if m.cardinality == CardinalityHigh { highHist = m } } @@ -576,9 +576,9 @@ func TestAggregatorCardinalityWithBufferedMetrics(t *testing.T) { var lowDist, highDist metric for _, m := range metrics { if m.metricType == distributionAggregated && m.name == "test.dist" { - if m.overrideCard == CardinalityLow { + if m.cardinality == CardinalityLow { lowDist = m - } else if m.overrideCard == CardinalityHigh { + } else if m.cardinality == CardinalityHigh { highDist = m } } @@ -594,9 +594,9 @@ func TestAggregatorCardinalityWithBufferedMetrics(t *testing.T) { var lowTiming, highTiming metric for _, m := range metrics { if m.metricType == timingAggregated && m.name == "test.timing" { - if m.overrideCard == CardinalityLow { + if m.cardinality == CardinalityLow { lowTiming = m - } else if m.overrideCard == CardinalityHigh { + } else if m.cardinality == CardinalityHigh { highTiming = m } } @@ -623,9 +623,9 @@ func TestAggregatorCardinalityEmptyVsNonEmpty(t *testing.T) { var emptyCard, lowCard metric for _, m := range metrics { if m.metricType == gauge && m.name == "test.metric" { - if m.overrideCard == CardinalityNotSet { + if m.cardinality == CardinalityNotSet { emptyCard = m - } else if m.overrideCard == CardinalityLow { + } else if m.cardinality == CardinalityLow { lowCard = m } } @@ -644,6 +644,13 @@ func TestAggregatorCardinalityContextGeneration(t *testing.T) { wantContext string wantTags string }{ + { + name: "test.metric", + tags: []string{"env:prod"}, + cardinality: CardinalityNotSet, + wantContext: "test.metric:env:prod", + wantTags: "env:prod", + }, { name: "test.metric", tags: []string{"env:prod"}, @@ -682,210 +689,3 @@ func TestAggregatorCardinalityContextGeneration(t *testing.T) { }) } } - -func TestAggregatorCardinalityGlobalSettingAggregation(t *testing.T) { - // Test that metrics without explicit cardinality are aggregated with metrics - // that have the same cardinality as the global setting. - - // Set global cardinality to "low" - initTagCardinality(CardinalityLow) - defer resetTagCardinality() - - a := newAggregator(nil, 0) - tags := []string{"env:prod"} - - // Add metrics with different cardinality scenarios - // 1. No explicit cardinality (should use global "low") - a.gauge("test.metric", 10, tags, CardinalityNotSet) - - // 2. Explicit cardinality matching global setting - a.gauge("test.metric", 20, tags, CardinalityLow) - - // 3. Different explicit cardinality - a.gauge("test.metric", 30, tags, CardinalityHigh) - - // 4. Another metric with no explicit cardinality (should aggregate with first two) - a.gauge("test.metric", 40, tags, CardinalityNotSet) - - metrics := a.flushMetrics() - - // Should have 2 metrics: one for "low" cardinality (aggregated), one for "high" - assert.Len(t, metrics, 2) - - // Find the metrics by cardinality - var lowCardMetric, highCardMetric metric - for _, m := range metrics { - if m.metricType == gauge && m.name == "test.metric" { - if m.overrideCard == CardinalityLow { - lowCardMetric = m - } else if m.overrideCard == CardinalityHigh { - highCardMetric = m - } - } - } - - assert.Equal(t, float64(40), lowCardMetric.fvalue) - - assert.Equal(t, float64(30), highCardMetric.fvalue) - - a.count("test.count", 5, tags, CardinalityNotSet) - a.count("test.count", 15, tags, CardinalityLow) - a.count("test.count", 25, tags, CardinalityHigh) - a.count("test.count", 35, tags, CardinalityNotSet) - - metrics = a.flushMetrics() - - assert.Len(t, metrics, 2) - - var lowCardCount, highCardCount metric - for _, m := range metrics { - if m.metricType == count && m.name == "test.count" { - if m.overrideCard == CardinalityLow { - lowCardCount = m - } else if m.overrideCard == CardinalityHigh { - highCardCount = m - } - } - } - - assert.Equal(t, int64(55), lowCardCount.ivalue) - - assert.Equal(t, int64(25), highCardCount.ivalue) - - a.set("test.set", "value1", tags, CardinalityNotSet) - a.set("test.set", "value2", tags, CardinalityLow) - a.set("test.set", "value3", tags, CardinalityHigh) - a.set("test.set", "value4", tags, CardinalityNotSet) - - metrics = a.flushMetrics() - - assert.Len(t, metrics, 4) - - var lowCardSetCount, highCardSetCount int - var lowCardSetValues, highCardSetValues []string - for _, m := range metrics { - if m.metricType == set && m.name == "test.set" { - if m.overrideCard == CardinalityLow { - lowCardSetCount++ - lowCardSetValues = append(lowCardSetValues, m.svalue) - } else if m.overrideCard == CardinalityHigh { - highCardSetCount++ - highCardSetValues = append(highCardSetValues, m.svalue) - } - } - } - - assert.Equal(t, 3, lowCardSetCount) - assert.Contains(t, lowCardSetValues, "value1") - assert.Contains(t, lowCardSetValues, "value2") - assert.Contains(t, lowCardSetValues, "value4") - - assert.Equal(t, 1, highCardSetCount) - assert.Contains(t, highCardSetValues, "value3") -} - -func TestAggregatorCardinalityNoGlobalSetting(t *testing.T) { - // Test behavior when global cardinality is not set. - // Reset to ensure no global setting. - resetTagCardinality() - - a := newAggregator(nil, 0) - tags := []string{"env:prod"} - - // Test case 1: No cardinality specified (should use empty cardinality). - a.gauge("test.metric", 10, tags, CardinalityNotSet) - a.gauge("test.metric", 20, tags, CardinalityNotSet) - - // Test case 2: Explicit cardinality specified. - a.gauge("test.metric", 30, tags, CardinalityLow) - a.gauge("test.metric", 40, tags, CardinalityLow) - - // Test case 3: Different explicit cardinality. - a.gauge("test.metric", 50, tags, CardinalityHigh) - - metrics := a.flushMetrics() - - assert.Len(t, metrics, 3) - - var emptyCardMetric, lowCardMetric, highCardMetric metric - for _, m := range metrics { - if m.metricType == gauge && m.name == "test.metric" { - if m.overrideCard == CardinalityNotSet { - emptyCardMetric = m - } else if m.overrideCard == CardinalityLow { - lowCardMetric = m - } else if m.overrideCard == CardinalityHigh { - highCardMetric = m - } - } - } - - assert.Equal(t, float64(20), emptyCardMetric.fvalue) - assert.Equal(t, float64(40), lowCardMetric.fvalue) - assert.Equal(t, float64(50), highCardMetric.fvalue) - - a.count("test.count", 5, tags, CardinalityNotSet) - a.count("test.count", 15, tags, CardinalityNotSet) - a.count("test.count", 25, tags, CardinalityLow) - a.count("test.count", 35, tags, CardinalityLow) - a.count("test.count", 45, tags, CardinalityHigh) - - metrics = a.flushMetrics() - - assert.Len(t, metrics, 3) - - var emptyCardCount, lowCardCount, highCardCount metric - for _, m := range metrics { - if m.metricType == count && m.name == "test.count" { - if m.overrideCard == CardinalityNotSet { - emptyCardCount = m - } else if m.overrideCard == CardinalityLow { - lowCardCount = m - } else if m.overrideCard == CardinalityHigh { - highCardCount = m - } - } - } - - assert.Equal(t, int64(20), emptyCardCount.ivalue) - assert.Equal(t, int64(60), lowCardCount.ivalue) - assert.Equal(t, int64(45), highCardCount.ivalue) - - a.set("test.set", "value1", tags, CardinalityNotSet) - a.set("test.set", "value2", tags, CardinalityNotSet) - a.set("test.set", "value3", tags, CardinalityLow) - a.set("test.set", "value4", tags, CardinalityLow) - a.set("test.set", "value5", tags, CardinalityHigh) - - metrics = a.flushMetrics() - - assert.Len(t, metrics, 5) - - var emptyCardSetCount, lowCardSetCount, highCardSetCount int - var emptyCardSetValues, lowCardSetValues, highCardSetValues []string - for _, m := range metrics { - if m.metricType == set && m.name == "test.set" { - if m.overrideCard == CardinalityNotSet { - emptyCardSetCount++ - emptyCardSetValues = append(emptyCardSetValues, m.svalue) - } else if m.overrideCard == CardinalityLow { - lowCardSetCount++ - lowCardSetValues = append(lowCardSetValues, m.svalue) - } else if m.overrideCard == CardinalityHigh { - highCardSetCount++ - highCardSetValues = append(highCardSetValues, m.svalue) - } - } - } - - assert.Equal(t, 2, emptyCardSetCount) - assert.Contains(t, emptyCardSetValues, "value1") - assert.Contains(t, emptyCardSetValues, "value2") - - assert.Equal(t, 2, lowCardSetCount) - assert.Contains(t, lowCardSetValues, "value3") - assert.Contains(t, lowCardSetValues, "value4") - - assert.Equal(t, 1, highCardSetCount) - assert.Contains(t, highCardSetValues, "value5") -} diff --git a/statsd/buffer.go b/statsd/buffer.go index ad05a398..2b604090 100644 --- a/statsd/buffer.go +++ b/statsd/buffer.go @@ -39,44 +39,43 @@ func newStatsdBuffer(maxSize, maxElements int) *statsdBuffer { } } -func (b *statsdBuffer) writeGauge(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, timestamp int64, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeGauge(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, timestamp int64, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendGauge(b.buffer, namespace, globalTags, name, value, tags, rate, originDetection) b.buffer = appendTimestamp(b.buffer, timestamp) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } - -func (b *statsdBuffer) writeCount(namespace string, globalTags []string, name string, value int64, tags []string, rate float64, timestamp int64, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeCount(namespace string, globalTags []string, name string, value int64, tags []string, rate float64, timestamp int64, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendCount(b.buffer, namespace, globalTags, name, value, tags, rate, originDetection) b.buffer = appendTimestamp(b.buffer, timestamp) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } -func (b *statsdBuffer) writeHistogram(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeHistogram(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendHistogram(b.buffer, namespace, globalTags, name, value, tags, rate, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } // writeAggregated serialized as many values as possible in the current buffer and return the position in values where it stopped. -func (b *statsdBuffer) writeAggregated(metricSymbol []byte, namespace string, globalTags []string, name string, values []float64, tags string, tagSize int, precision int, rate float64, originDetection bool, overrideCard Cardinality) (int, error) { +func (b *statsdBuffer) writeAggregated(metricSymbol []byte, namespace string, globalTags []string, name string, values []float64, tags string, tagSize int, precision int, rate float64, originDetection bool, cardinality Cardinality) (int, error) { if b.elementCount >= b.maxElements { return 0, errBufferFull } @@ -120,7 +119,7 @@ func (b *statsdBuffer) writeAggregated(metricSymbol []byte, namespace string, gl b.buffer = appendTagsAggregated(b.buffer, globalTags, tags) b.buffer = appendContainerID(b.buffer) b.buffer = appendExternalEnv(b.buffer, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() b.elementCount++ @@ -131,57 +130,57 @@ func (b *statsdBuffer) writeAggregated(metricSymbol []byte, namespace string, gl } -func (b *statsdBuffer) writeDistribution(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeDistribution(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendDistribution(b.buffer, namespace, globalTags, name, value, tags, rate, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } -func (b *statsdBuffer) writeSet(namespace string, globalTags []string, name string, value string, tags []string, rate float64, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeSet(namespace string, globalTags []string, name string, value string, tags []string, rate float64, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendSet(b.buffer, namespace, globalTags, name, value, tags, rate, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } -func (b *statsdBuffer) writeTiming(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeTiming(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendTiming(b.buffer, namespace, globalTags, name, value, tags, rate, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } -func (b *statsdBuffer) writeEvent(event *Event, globalTags []string, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeEvent(event *Event, globalTags []string, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendEvent(b.buffer, event, globalTags, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } -func (b *statsdBuffer) writeServiceCheck(serviceCheck *ServiceCheck, globalTags []string, originDetection bool, overrideCard Cardinality) error { +func (b *statsdBuffer) writeServiceCheck(serviceCheck *ServiceCheck, globalTags []string, originDetection bool, cardinality Cardinality) error { if b.elementCount >= b.maxElements { return errBufferFull } originalBuffer := b.buffer b.buffer = appendServiceCheck(b.buffer, serviceCheck, globalTags, originDetection) - b.buffer = appendTagCardinality(b.buffer, overrideCard) + b.buffer = appendTagCardinality(b.buffer, cardinality) b.writeSeparator() return b.validateNewElement(originalBuffer) } diff --git a/statsd/buffer_pool_test.go b/statsd/buffer_pool_test.go index 0d792560..ac2ff388 100644 --- a/statsd/buffer_pool_test.go +++ b/statsd/buffer_pool_test.go @@ -33,7 +33,7 @@ func TestBufferPoolEmpty(t *testing.T) { func TestBufferReturn(t *testing.T) { bufferPool := newBufferPool(1, 1024, 20) buffer := bufferPool.borrowBuffer() - buffer.writeCount("", nil, "", 1, nil, 1, noTimestamp, true, defaultTagCardinality) + buffer.writeCount("", nil, "", 1, nil, 1, noTimestamp, true, CardinalityNotSet) assert.Equal(t, 0, len(bufferPool.pool)) bufferPool.returnBuffer(buffer) diff --git a/statsd/buffer_test.go b/statsd/buffer_test.go index 4f5a545b..4191d269 100644 --- a/statsd/buffer_test.go +++ b/statsd/buffer_test.go @@ -8,7 +8,7 @@ import ( func TestBufferGauge(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|g|#tag:tag\n", string(buffer.bytes())) @@ -17,13 +17,13 @@ func TestBufferGauge(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|g|#tag:tag|c:container-id\n", string(buffer.bytes())) // with a timestamp buffer = newStatsdBuffer(1024, 1) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, 1658934092, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, 1658934092, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|g|#tag:tag|c:container-id|T1658934092\n", string(buffer.bytes())) @@ -32,37 +32,20 @@ func TestBufferGauge(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|g|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|g|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|g|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|g|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferCount(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err := buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|c|#tag:tag\n", string(buffer.bytes())) @@ -71,13 +54,13 @@ func TestBufferCount(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|c|#tag:tag|c:container-id\n", string(buffer.bytes())) // with a timestamp buffer = newStatsdBuffer(1024, 1) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, 1658934092, true, defaultTagCardinality) + err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, 1658934092, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|c|#tag:tag|c:container-id|T1658934092\n", string(buffer.bytes())) @@ -86,37 +69,20 @@ func TestBufferCount(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|c|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|c|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|c|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|c|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferHistogram(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err := buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|h|#tag:tag\n", string(buffer.bytes())) @@ -125,7 +91,7 @@ func TestBufferHistogram(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|h|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -134,37 +100,20 @@ func TestBufferHistogram(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|h|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|h|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|h|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|h|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferDistribution(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err := buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|d|#tag:tag\n", string(buffer.bytes())) @@ -173,7 +122,7 @@ func TestBufferDistribution(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|d|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -182,36 +131,19 @@ func TestBufferDistribution(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|d|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|d|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|d|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|d|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferSet(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, defaultTagCardinality) + err := buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:value|s|#tag:tag\n", string(buffer.bytes())) @@ -220,7 +152,7 @@ func TestBufferSet(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, defaultTagCardinality) + err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:value|s|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -229,37 +161,20 @@ func TestBufferSet(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, defaultTagCardinality) + err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:value|s|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "1", []string{}, 1, true, defaultTagCardinality) + err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "1", []string{}, 1, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|s|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "1", []string{}, 1, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|s|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "1", []string{}, 1, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1|s|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferTiming(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err := buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1.000000|ms|#tag:tag\n", string(buffer.bytes())) @@ -268,7 +183,7 @@ func TestBufferTiming(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1.000000|ms|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -277,37 +192,20 @@ func TestBufferTiming(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1.000000|ms|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1.000000|ms|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1.000000|ms|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "namespace.metric:1.000000|ms|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferEvent(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, defaultTagCardinality) + err := buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "_e{5,4}:title|text|#tag:tag\n", string(buffer.bytes())) @@ -316,7 +214,7 @@ func TestBufferEvent(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "_e{5,4}:title|text|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -325,37 +223,20 @@ func TestBufferEvent(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "_e{5,4}:title|text|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "_e{5,4}:title|text|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "_e{5,4}:title|text|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "_e{5,4}:title|text|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferServiceCheck(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, defaultTagCardinality) + err := buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "_sc|name|0|#tag:tag\n", string(buffer.bytes())) @@ -364,7 +245,7 @@ func TestBufferServiceCheck(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "_sc|name|0|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -373,68 +254,51 @@ func TestBufferServiceCheck(t *testing.T) { defer resetExternalEnv() buffer = newStatsdBuffer(1024, 1) - err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "_sc|name|0|#tag:tag|c:container-id|e:external-env\n", string(buffer.bytes())) - // with a tag cardinality (global setting) - patchTagCardinality(CardinalityLow, "high", "orchestrator") - defer resetTagCardinality() - + // with a tag cardinality buffer = newStatsdBuffer(1024, 1) - err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityLow) assert.Nil(t, err) assert.Equal(t, "_sc|name|0|#tag:tag|c:container-id|e:external-env|card:low\n", string(buffer.bytes())) - - // with a tag cardinality (valid local override) - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityNone) - assert.Nil(t, err) - assert.Equal(t, "_sc|name|0|#tag:tag|c:container-id|e:external-env|card:none\n", string(buffer.bytes())) - - // with a tag cardinality (invalid local override) - patchTagCardinality(CardinalityInvalid, "high", "orchestrator") - - buffer = newStatsdBuffer(1024, 1) - err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityInvalid) - assert.Nil(t, err) - assert.Equal(t, "_sc|name|0|#tag:tag|c:container-id|e:external-env|card:high\n", string(buffer.bytes())) } func TestBufferFullSize(t *testing.T) { buffer := newStatsdBuffer(30, 10) - err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Len(t, buffer.bytes(), 30) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) } func TestBufferSeparator(t *testing.T) { buffer := newStatsdBuffer(1024, 10) - err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, "namespace.metric:1|g|#tag:tag\nnamespace.metric:1|g|#tag:tag\n", string(buffer.bytes())) } func TestBufferAggregated(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - pos, err := buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err := buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1}, "", 12, -1, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, 1, pos) assert.Equal(t, "namespace.metric:1|h|#tag:tag\n", string(buffer.bytes())) buffer = newStatsdBuffer(1024, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, 4, pos) assert.Equal(t, "namespace.metric:1:2:3:4|h|#tag:tag\n", string(buffer.bytes())) // With a sampling rate buffer = newStatsdBuffer(1024, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 0.33, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 0.33, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, 4, pos) assert.Equal(t, "namespace.metric:1:2:3:4|h|@0.33|#tag:tag\n", string(buffer.bytes())) @@ -442,29 +306,29 @@ func TestBufferAggregated(t *testing.T) { // max element already used buffer = newStatsdBuffer(1024, 1) buffer.elementCount = 1 - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) // not enough size to start serializing (tags and header too big) buffer = newStatsdBuffer(4, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) // not enough size to serializing one message buffer = newStatsdBuffer(29, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) // space for only 1 number buffer = newStatsdBuffer(30, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errPartialWrite, err) assert.Equal(t, 1, pos) assert.Equal(t, "namespace.metric:1|h|#tag:tag\n", string(buffer.bytes())) // first value too big buffer = newStatsdBuffer(30, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{12, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{12, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) assert.Equal(t, 0, pos) assert.Equal(t, "", string(buffer.bytes())) // checking that the buffer was reset @@ -472,14 +336,14 @@ func TestBufferAggregated(t *testing.T) { // not enough space left buffer = newStatsdBuffer(40, 1) buffer.buffer = append(buffer.buffer, []byte("abcdefghij")...) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{12, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{12, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) assert.Equal(t, 0, pos) assert.Equal(t, "abcdefghij", string(buffer.bytes())) // checking that the buffer was reset // space for only 2 number buffer = newStatsdBuffer(32, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1, 2, 3, 4}, "", 12, -1, 1, true, CardinalityNotSet) assert.Equal(t, errPartialWrite, err) assert.Equal(t, 2, pos) assert.Equal(t, "namespace.metric:1:2|h|#tag:tag\n", string(buffer.bytes())) @@ -489,7 +353,7 @@ func TestBufferAggregated(t *testing.T) { defer resetContainerID() buffer = newStatsdBuffer(1024, 1) - pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1}, "", 12, -1, 1, true, defaultTagCardinality) + pos, err = buffer.writeAggregated([]byte("h"), "namespace.", []string{"tag:tag"}, "metric", []float64{1}, "", 12, -1, 1, true, CardinalityNotSet) assert.Nil(t, err) assert.Equal(t, 1, pos) assert.Equal(t, "namespace.metric:1|h|#tag:tag|c:container-id\n", string(buffer.bytes())) @@ -498,30 +362,30 @@ func TestBufferAggregated(t *testing.T) { func TestBufferMaxElement(t *testing.T) { buffer := newStatsdBuffer(1024, 1) - err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err := buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Nil(t, err) - err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeGauge("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, defaultTagCardinality) + err = buffer.writeCount("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, noTimestamp, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeHistogram("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeDistribution("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, defaultTagCardinality) + err = buffer.writeSet("namespace.", []string{"tag:tag"}, "metric", "value", []string{}, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, defaultTagCardinality) + err = buffer.writeTiming("namespace.", []string{"tag:tag"}, "metric", 1, []string{}, 1, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeEvent(&Event{Title: "title", Text: "text"}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) - err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, defaultTagCardinality) + err = buffer.writeServiceCheck(&ServiceCheck{Name: "name", Status: Ok}, []string{"tag:tag"}, true, CardinalityNotSet) assert.Equal(t, errBufferFull, err) } diff --git a/statsd/buffered_metric_context.go b/statsd/buffered_metric_context.go index 9f28d9a5..85cab2a1 100644 --- a/statsd/buffered_metric_context.go +++ b/statsd/buffered_metric_context.go @@ -67,8 +67,7 @@ func (bc *bufferedMetricContexts) sample(name string, value float64, tags []stri return nil } - resolvedCardinality := resolveCardinality(cardinality) - context, stringTags := getContextAndTags(name, tags, resolvedCardinality) + context, stringTags := getContextAndTags(name, tags, cardinality) var v *bufferedMetric bc.mutex.RLock() @@ -82,7 +81,7 @@ func (bc *bufferedMetricContexts) sample(name string, value float64, tags []stri v, _ = bc.values[context] if v == nil { // If we might keep a sample that we should have skipped, but that should not drastically affect performances. - bc.values[context] = bc.newMetric(name, value, stringTags, rate, resolvedCardinality) + bc.values[context] = bc.newMetric(name, value, stringTags, rate, cardinality) // We added a new value, we need to unlock the mutex and quit bc.mutex.Unlock() return nil diff --git a/statsd/format.go b/statsd/format.go index 089c373f..52f90635 100644 --- a/statsd/format.go +++ b/statsd/format.go @@ -296,10 +296,8 @@ func appendExternalEnv(buffer []byte, originDetection bool) []byte { return buffer } -func appendTagCardinality(buffer []byte, overrideCard Cardinality) []byte { - // Check if the user has provided a valid cardinality parameter. If not, use the global setting. - cardString := resolveCardinality(overrideCard).String() - +func appendTagCardinality(buffer []byte, cardinality Cardinality) []byte { + cardString := cardinality.String() if cardString != "" { buffer = append(buffer, "|card:"...) buffer = append(buffer, cardString...) diff --git a/statsd/metrics.go b/statsd/metrics.go index b59de473..ea78730e 100644 --- a/statsd/metrics.go +++ b/statsd/metrics.go @@ -15,18 +15,18 @@ Those are metrics type that can be aggregated on the client side: */ type countMetric struct { - value int64 - name string - tags []string - overrideCard Cardinality + value int64 + name string + tags []string + cardinality Cardinality } func newCountMetric(name string, value int64, tags []string, cardinality Cardinality) *countMetric { return &countMetric{ - value: value, - name: name, - tags: copySlice(tags), - overrideCard: cardinality, + value: value, + name: name, + tags: copySlice(tags), + cardinality: cardinality, } } @@ -36,30 +36,30 @@ func (c *countMetric) sample(v int64) { func (c *countMetric) flushUnsafe() metric { return metric{ - metricType: count, - name: c.name, - tags: c.tags, - rate: 1, - ivalue: c.value, - overrideCard: c.overrideCard, + metricType: count, + name: c.name, + tags: c.tags, + rate: 1, + ivalue: c.value, + cardinality: c.cardinality, } } // Gauge type gaugeMetric struct { - value uint64 - name string - tags []string - overrideCard Cardinality + value uint64 + name string + tags []string + cardinality Cardinality } func newGaugeMetric(name string, value float64, tags []string, cardinality Cardinality) *gaugeMetric { return &gaugeMetric{ - value: math.Float64bits(value), - name: name, - tags: copySlice(tags), - overrideCard: cardinality, + value: math.Float64bits(value), + name: name, + tags: copySlice(tags), + cardinality: cardinality, } } @@ -69,31 +69,31 @@ func (g *gaugeMetric) sample(v float64) { func (g *gaugeMetric) flushUnsafe() metric { return metric{ - metricType: gauge, - name: g.name, - tags: g.tags, - rate: 1, - fvalue: math.Float64frombits(g.value), - overrideCard: g.overrideCard, + metricType: gauge, + name: g.name, + tags: g.tags, + rate: 1, + fvalue: math.Float64frombits(g.value), + cardinality: g.cardinality, } } // Set type setMetric struct { - data map[string]struct{} - name string - tags []string - overrideCard Cardinality + data map[string]struct{} + name string + tags []string + cardinality Cardinality sync.Mutex } func newSetMetric(name string, value string, tags []string, cardinality Cardinality) *setMetric { set := &setMetric{ - data: map[string]struct{}{}, - name: name, - tags: copySlice(tags), - overrideCard: cardinality, + data: map[string]struct{}{}, + name: name, + tags: copySlice(tags), + cardinality: cardinality, } set.data[value] = struct{}{} return set @@ -116,12 +116,12 @@ func (s *setMetric) flushUnsafe() []metric { i := 0 for value := range s.data { metrics[i] = metric{ - metricType: set, - name: s.name, - tags: s.tags, - rate: 1, - svalue: value, - overrideCard: s.overrideCard, + metricType: set, + name: s.name, + tags: s.tags, + rate: 1, + svalue: value, + cardinality: s.cardinality, } i++ } @@ -154,7 +154,7 @@ type bufferedMetric struct { // it is used because we don't know better. specifiedRate float64 - overrideCard Cardinality + cardinality Cardinality } func (s *bufferedMetric) sample(v float64) { @@ -212,12 +212,12 @@ func (s *bufferedMetric) flushUnsafe() metric { } return metric{ - metricType: s.mtype, - name: s.name, - stags: s.tags, - rate: rate, - fvalues: s.data[:s.storedSamples], - overrideCard: resolveCardinality(s.overrideCard), + metricType: s.mtype, + name: s.name, + stags: s.tags, + rate: rate, + fvalues: s.data[:s.storedSamples], + cardinality: s.cardinality, } } @@ -233,7 +233,7 @@ func newHistogramMetric(name string, value float64, stringTags string, maxSample mtype: histogramAggregated, maxSamples: maxSamples, specifiedRate: rate, - overrideCard: resolveCardinality(cardinality), + cardinality: cardinality, } } @@ -249,7 +249,7 @@ func newDistributionMetric(name string, value float64, stringTags string, maxSam mtype: distributionAggregated, maxSamples: maxSamples, specifiedRate: rate, - overrideCard: resolveCardinality(cardinality), + cardinality: cardinality, } } @@ -265,7 +265,7 @@ func newTimingMetric(name string, value float64, stringTags string, maxSamples i mtype: timingAggregated, maxSamples: maxSamples, specifiedRate: rate, - overrideCard: resolveCardinality(cardinality), + cardinality: cardinality, } } diff --git a/statsd/metrics_test.go b/statsd/metrics_test.go index 373cb191..280792e9 100644 --- a/statsd/metrics_test.go +++ b/statsd/metrics_test.go @@ -15,7 +15,7 @@ func TestNewCountMetric(t *testing.T) { assert.Equal(t, c.value, int64(21)) assert.Equal(t, c.name, "test") assert.Equal(t, c.tags, []string{"tag1", "tag2"}) - assert.Equal(t, c.overrideCard, CardinalityLow) + assert.Equal(t, c.cardinality, CardinalityLow) } func TestNewCountMetricWithTimestamp(t *testing.T) { @@ -23,7 +23,7 @@ func TestNewCountMetricWithTimestamp(t *testing.T) { assert.Equal(t, c.value, int64(21)) assert.Equal(t, c.name, "test") assert.Equal(t, c.tags, []string{"tag1", "tag2"}) - assert.Equal(t, c.overrideCard, CardinalityLow) + assert.Equal(t, c.cardinality, CardinalityLow) } func TestCountMetricSample(t *testing.T) { @@ -32,7 +32,7 @@ func TestCountMetricSample(t *testing.T) { assert.Equal(t, c.value, int64(33)) assert.Equal(t, c.name, "test") assert.Equal(t, c.tags, []string{"tag1", "tag2"}) - assert.Equal(t, c.overrideCard, CardinalityLow) + assert.Equal(t, c.cardinality, CardinalityLow) } func TestFlushUnsafeCountMetricSample(t *testing.T) { @@ -42,7 +42,7 @@ func TestFlushUnsafeCountMetricSample(t *testing.T) { assert.Equal(t, m.ivalue, int64(21)) assert.Equal(t, m.name, "test") assert.Equal(t, m.tags, []string{"tag1", "tag2"}) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) c.sample(12) m = c.flushUnsafe() @@ -50,7 +50,7 @@ func TestFlushUnsafeCountMetricSample(t *testing.T) { assert.Equal(t, m.ivalue, int64(33)) assert.Equal(t, m.name, "test") assert.Equal(t, m.tags, []string{"tag1", "tag2"}) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) } func TestNewGaugeMetric(t *testing.T) { @@ -58,7 +58,7 @@ func TestNewGaugeMetric(t *testing.T) { assert.Equal(t, math.Float64frombits(g.value), float64(21)) assert.Equal(t, g.name, "test") assert.Equal(t, g.tags, []string{"tag1", "tag2"}) - assert.Equal(t, g.overrideCard, CardinalityLow) + assert.Equal(t, g.cardinality, CardinalityLow) } func TestGaugeMetricSample(t *testing.T) { @@ -67,7 +67,7 @@ func TestGaugeMetricSample(t *testing.T) { assert.Equal(t, math.Float64frombits(g.value), float64(12)) assert.Equal(t, g.name, "test") assert.Equal(t, g.tags, []string{"tag1", "tag2"}) - assert.Equal(t, g.overrideCard, CardinalityLow) + assert.Equal(t, g.cardinality, CardinalityLow) } func TestNewGaugeMetricWithTimestamp(t *testing.T) { @@ -75,7 +75,7 @@ func TestNewGaugeMetricWithTimestamp(t *testing.T) { assert.Equal(t, math.Float64frombits(g.value), float64(21)) assert.Equal(t, g.name, "test") assert.Equal(t, g.tags, []string{"tag1", "tag2"}) - assert.Equal(t, g.overrideCard, CardinalityLow) + assert.Equal(t, g.cardinality, CardinalityLow) } func TestFlushUnsafeGaugeMetricSample(t *testing.T) { @@ -85,7 +85,7 @@ func TestFlushUnsafeGaugeMetricSample(t *testing.T) { assert.Equal(t, m.fvalue, float64(21)) assert.Equal(t, m.name, "test") assert.Equal(t, m.tags, []string{"tag1", "tag2"}) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) g.sample(12) m = g.flushUnsafe() @@ -93,7 +93,7 @@ func TestFlushUnsafeGaugeMetricSample(t *testing.T) { assert.Equal(t, m.fvalue, float64(12)) assert.Equal(t, m.name, "test") assert.Equal(t, m.tags, []string{"tag1", "tag2"}) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) } func TestNewSetMetric(t *testing.T) { @@ -101,7 +101,7 @@ func TestNewSetMetric(t *testing.T) { assert.Equal(t, s.data, map[string]struct{}{"value1": struct{}{}}) assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, []string{"tag1", "tag2"}) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestSetMetricSample(t *testing.T) { @@ -110,7 +110,7 @@ func TestSetMetricSample(t *testing.T) { assert.Equal(t, s.data, map[string]struct{}{"value1": struct{}{}, "value2": struct{}{}}) assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, []string{"tag1", "tag2"}) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestFlushUnsafeSetMetricSample(t *testing.T) { @@ -123,7 +123,7 @@ func TestFlushUnsafeSetMetricSample(t *testing.T) { assert.Equal(t, m[0].svalue, "value1") assert.Equal(t, m[0].name, "test") assert.Equal(t, m[0].tags, []string{"tag1", "tag2"}) - assert.Equal(t, m[0].overrideCard, CardinalityLow) + assert.Equal(t, m[0].cardinality, CardinalityLow) s.sample("value1") s.sample("value2") @@ -138,12 +138,12 @@ func TestFlushUnsafeSetMetricSample(t *testing.T) { assert.Equal(t, m[0].svalue, "value1") assert.Equal(t, m[0].name, "test") assert.Equal(t, m[0].tags, []string{"tag1", "tag2"}) - assert.Equal(t, m[0].overrideCard, CardinalityLow) + assert.Equal(t, m[0].cardinality, CardinalityLow) assert.Equal(t, m[1].metricType, set) assert.Equal(t, m[1].svalue, "value2") assert.Equal(t, m[1].name, "test") assert.Equal(t, m[1].tags, []string{"tag1", "tag2"}) - assert.Equal(t, m[1].overrideCard, CardinalityLow) + assert.Equal(t, m[1].cardinality, CardinalityLow) } func TestNewHistogramMetric(t *testing.T) { @@ -152,7 +152,7 @@ func TestNewHistogramMetric(t *testing.T) { assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, "tag1,tag2") assert.Equal(t, s.mtype, histogramAggregated) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestHistogramMetricSample(t *testing.T) { @@ -162,7 +162,7 @@ func TestHistogramMetricSample(t *testing.T) { assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, "tag1,tag2") assert.Equal(t, s.mtype, histogramAggregated) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestFlushUnsafeHistogramMetricSample(t *testing.T) { @@ -174,7 +174,7 @@ func TestFlushUnsafeHistogramMetricSample(t *testing.T) { assert.Equal(t, m.name, "test") assert.Equal(t, m.stags, "tag1,tag2") assert.Nil(t, m.tags) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) s.sample(21) s.sample(123.45) @@ -185,7 +185,7 @@ func TestFlushUnsafeHistogramMetricSample(t *testing.T) { assert.Equal(t, m.name, "test") assert.Equal(t, m.stags, "tag1,tag2") assert.Nil(t, m.tags) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) } func TestNewDistributionMetric(t *testing.T) { @@ -194,7 +194,7 @@ func TestNewDistributionMetric(t *testing.T) { assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, "tag1,tag2") assert.Equal(t, s.mtype, distributionAggregated) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestDistributionMetricSample(t *testing.T) { @@ -204,7 +204,7 @@ func TestDistributionMetricSample(t *testing.T) { assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, "tag1,tag2") assert.Equal(t, s.mtype, distributionAggregated) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestFlushUnsafeDistributionMetricSample(t *testing.T) { @@ -216,7 +216,7 @@ func TestFlushUnsafeDistributionMetricSample(t *testing.T) { assert.Equal(t, m.name, "test") assert.Equal(t, m.stags, "tag1,tag2") assert.Nil(t, m.tags) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) s.sample(21) s.sample(123.45) @@ -227,7 +227,7 @@ func TestFlushUnsafeDistributionMetricSample(t *testing.T) { assert.Equal(t, m.name, "test") assert.Equal(t, m.stags, "tag1,tag2") assert.Nil(t, m.tags) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) } func TestNewTimingMetric(t *testing.T) { @@ -236,7 +236,7 @@ func TestNewTimingMetric(t *testing.T) { assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, "tag1,tag2") assert.Equal(t, s.mtype, timingAggregated) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestTimingMetricSample(t *testing.T) { @@ -246,7 +246,7 @@ func TestTimingMetricSample(t *testing.T) { assert.Equal(t, s.name, "test") assert.Equal(t, s.tags, "tag1,tag2") assert.Equal(t, s.mtype, timingAggregated) - assert.Equal(t, s.overrideCard, CardinalityLow) + assert.Equal(t, s.cardinality, CardinalityLow) } func TestFlushUnsafeTimingMetricSample(t *testing.T) { @@ -258,7 +258,7 @@ func TestFlushUnsafeTimingMetricSample(t *testing.T) { assert.Equal(t, m.name, "test") assert.Equal(t, m.stags, "tag1,tag2") assert.Nil(t, m.tags) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) s.sample(21) s.sample(123.45) @@ -269,5 +269,5 @@ func TestFlushUnsafeTimingMetricSample(t *testing.T) { assert.Equal(t, m.name, "test") assert.Equal(t, m.stags, "tag1,tag2") assert.Nil(t, m.tags) - assert.Equal(t, m.overrideCard, CardinalityLow) + assert.Equal(t, m.cardinality, CardinalityLow) } diff --git a/statsd/options.go b/statsd/options.go index fd72bc0a..a9f6c5f6 100644 --- a/statsd/options.go +++ b/statsd/options.go @@ -28,7 +28,6 @@ var ( defaultOriginDetection = true defaultChannelModeErrorsWhenFull = false defaultErrorHandler = func(error) {} - defaultTagCardinality = CardinalityNotSet ) // Options contains the configuration options for a client. @@ -55,7 +54,7 @@ type Options struct { containerID string channelModeErrorsWhenFull bool errorHandler ErrorHandler - tagCardinality Cardinality + tagCardinality *Cardinality } func resolveOptions(options []Option) (*Options, error) { @@ -80,7 +79,6 @@ func resolveOptions(options []Option) (*Options, error) { originDetection: defaultOriginDetection, channelModeErrorsWhenFull: defaultChannelModeErrorsWhenFull, errorHandler: defaultErrorHandler, - tagCardinality: defaultTagCardinality, } for _, option := range options { @@ -419,7 +417,10 @@ func WithContainerID(id string) Option { // WithCardinality sets the tag cardinality of the metric. func WithCardinality(card Cardinality) Option { return func(o *Options) error { - o.tagCardinality = card + if !card.isValid() { + return fmt.Errorf("invalid cardinality %d", card) + } + o.tagCardinality = &card return nil } } diff --git a/statsd/options_test.go b/statsd/options_test.go index 5f89cf7c..9253d0d4 100644 --- a/statsd/options_test.go +++ b/statsd/options_test.go @@ -27,7 +27,7 @@ func TestDefaultOptions(t *testing.T) { assert.Equal(t, options.aggregation, defaultAggregation) assert.Equal(t, options.extendedAggregation, defaultExtendedAggregation) assert.Zero(t, options.telemetryAddr) - assert.Equal(t, options.tagCardinality, defaultTagCardinality) + assert.Nil(t, options.tagCardinality) } func TestOptions(t *testing.T) { @@ -81,7 +81,7 @@ func TestOptions(t *testing.T) { assert.Equal(t, options.aggregation, true) assert.Equal(t, options.extendedAggregation, false) assert.Equal(t, options.telemetryAddr, testTelemetryAddr) - assert.Equal(t, options.tagCardinality, testTagCardinality) + assert.Equal(t, *options.tagCardinality, testTagCardinality) } func TestExtendedAggregation(t *testing.T) { @@ -119,10 +119,9 @@ func TestOptionsNamespaceWithoutDot(t *testing.T) { } func TestOptionsInvalidTagCardinality(t *testing.T) { - options, err := resolveOptions([]Option{ - WithCardinality(CardinalityInvalid), + _, err := resolveOptions([]Option{ + WithCardinality(CardinalityHigh + 1), }) - assert.NoError(t, err) - assert.Equal(t, options.tagCardinality.String(), "") + assert.EqualError(t, err, "invalid cardinality 5") } diff --git a/statsd/statsdex.go b/statsd/statsdex.go index d9e9b4ed..f051cb67 100644 --- a/statsd/statsdex.go +++ b/statsd/statsdex.go @@ -165,7 +165,7 @@ type metric struct { rate float64 timestamp int64 originDetection bool - overrideCard Cardinality + cardinality Cardinality } type noClientErr string @@ -300,6 +300,7 @@ type ClientEx struct { errorOnBlockedChannel bool errorHandler ErrorHandler originDetection bool + defaultCardinality Cardinality } // statsdTelemetry contains telemetry metrics about the client @@ -477,8 +478,13 @@ func newWithWriter(w Transport, o *Options, writerName string) (*ClientEx, error // external environment variable in case another client has enabled it and needs to access it. initExternalEnv() - // Initializes the global tag cardinality with either the value passed in by the user or the value from the DD_CARDINALITY/DATADOG_CARDINALITY environment variable. - initTagCardinality(o.tagCardinality) + if o.tagCardinality != nil { + c.defaultCardinality = *o.tagCardinality + } else if card, ok := envTagCardinality(); ok { + c.defaultCardinality = card + } else { + c.defaultCardinality = CardinalityNotSet + } initContainerID(o.containerID, fillInContainerID(o), isHostCgroupNamespace()) isUDS := writerName == writerNameUDS @@ -679,7 +685,7 @@ func (c *ClientEx) sendBlocking(m metric) error { func (c *ClientEx) sendToAggregator(mType metricType, name string, value float64, tags []string, rate float64, f bufferedMetricSampleFunc, cardinality Cardinality) error { if c.aggregatorMode == channelMode { - m := metric{metricType: mType, name: name, fvalue: value, tags: tags, rate: rate, overrideCard: cardinality} + m := metric{metricType: mType, name: name, fvalue: value, tags: tags, rate: rate, cardinality: cardinality} select { case c.aggExtended.inputMetrics <- m: default: @@ -703,13 +709,11 @@ func (c *ClientEx) Gauge(name string, value float64, tags []string, rate float64 return ErrNoClient } atomic.AddUint64(&c.telemetry.totalMetricsGauge, 1) - - cardinality := parseTagCardinality(parameters) - + cardinality := parameterCardinality(parameters, c.defaultCardinality) if c.agg != nil { return c.agg.gauge(name, value, tags, cardinality) } - return c.send(metric{metricType: gauge, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + return c.send(metric{metricType: gauge, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // GaugeWithTimestamp measures the value of a metric at a given time. @@ -728,8 +732,8 @@ func (c *ClientEx) GaugeWithTimestamp(name string, value float64, tags []string, } atomic.AddUint64(&c.telemetry.totalMetricsGauge, 1) - cardinality := parseTagCardinality(parameters) - return c.send(metric{metricType: gauge, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, timestamp: timestamp.Unix(), originDetection: c.originDetection, overrideCard: cardinality}) + cardinality := parameterCardinality(parameters, c.defaultCardinality) + return c.send(metric{metricType: gauge, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, timestamp: timestamp.Unix(), originDetection: c.originDetection, cardinality: cardinality}) } // Count tracks how many times something happened per second. @@ -738,11 +742,11 @@ func (c *ClientEx) Count(name string, value int64, tags []string, rate float64, return ErrNoClient } atomic.AddUint64(&c.telemetry.totalMetricsCount, 1) - cardinality := parseTagCardinality(parameters) + cardinality := parameterCardinality(parameters, c.defaultCardinality) if c.agg != nil { return c.agg.count(name, value, tags, cardinality) } - return c.send(metric{metricType: count, name: name, ivalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + return c.send(metric{metricType: count, name: name, ivalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // CountWithTimestamp tracks how many times something happened at the given second. @@ -761,8 +765,8 @@ func (c *ClientEx) CountWithTimestamp(name string, value int64, tags []string, r } atomic.AddUint64(&c.telemetry.totalMetricsCount, 1) - cardinality := parseTagCardinality(parameters) - return c.send(metric{metricType: count, name: name, ivalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, timestamp: timestamp.Unix(), originDetection: c.originDetection, overrideCard: cardinality}) + cardinality := parameterCardinality(parameters, c.defaultCardinality) + return c.send(metric{metricType: count, name: name, ivalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, timestamp: timestamp.Unix(), originDetection: c.originDetection, cardinality: cardinality}) } // Histogram tracks the statistical distribution of a set of values on each host. @@ -771,11 +775,11 @@ func (c *ClientEx) Histogram(name string, value float64, tags []string, rate flo return ErrNoClient } atomic.AddUint64(&c.telemetry.totalMetricsHistogram, 1) - cardinality := parseTagCardinality(parameters) + cardinality := parameterCardinality(parameters, c.defaultCardinality) if c.aggExtended != nil { return c.sendToAggregator(histogram, name, value, tags, rate, c.aggExtended.histogram, cardinality) } - return c.send(metric{metricType: histogram, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + return c.send(metric{metricType: histogram, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // Distribution tracks the statistical distribution of a set of values across your infrastructure. @@ -784,11 +788,11 @@ func (c *ClientEx) Distribution(name string, value float64, tags []string, rate return ErrNoClient } atomic.AddUint64(&c.telemetry.totalMetricsDistribution, 1) - cardinality := parseTagCardinality(parameters) + cardinality := parameterCardinality(parameters, c.defaultCardinality) if c.aggExtended != nil { return c.sendToAggregator(distribution, name, value, tags, rate, c.aggExtended.distribution, cardinality) } - return c.send(metric{metricType: distribution, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + return c.send(metric{metricType: distribution, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // Decr is just Count of -1 @@ -807,12 +811,11 @@ func (c *ClientEx) Set(name string, value string, tags []string, rate float64, p return ErrNoClient } atomic.AddUint64(&c.telemetry.totalMetricsSet, 1) - cardinality := parseTagCardinality(parameters) - + cardinality := parameterCardinality(parameters, c.defaultCardinality) if c.agg != nil { return c.agg.set(name, value, tags, cardinality) } - return c.send(metric{metricType: set, name: name, svalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + return c.send(metric{metricType: set, name: name, svalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // Timing sends timing information, it is an alias for TimeInMilliseconds @@ -827,11 +830,11 @@ func (c *ClientEx) TimeInMilliseconds(name string, value float64, tags []string, return ErrNoClient } atomic.AddUint64(&c.telemetry.totalMetricsTiming, 1) - cardinality := parseTagCardinality(parameters) + cardinality := parameterCardinality(parameters, c.defaultCardinality) if c.aggExtended != nil { return c.sendToAggregator(timing, name, value, tags, rate, c.aggExtended.timing, cardinality) } - return c.send(metric{metricType: timing, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + return c.send(metric{metricType: timing, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // Event sends the provided Event. @@ -840,8 +843,8 @@ func (c *ClientEx) Event(e *Event, parameters ...Parameter) error { return ErrNoClient } atomic.AddUint64(&c.telemetry.totalEvents, 1) - cardinality := parseTagCardinality(parameters) - return c.send(metric{metricType: event, evalue: e, rate: 1, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + cardinality := parameterCardinality(parameters, c.defaultCardinality) + return c.send(metric{metricType: event, evalue: e, rate: 1, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // SimpleEvent sends an event with the provided title and text. @@ -856,8 +859,8 @@ func (c *ClientEx) ServiceCheck(sc *ServiceCheck, parameters ...Parameter) error return ErrNoClient } atomic.AddUint64(&c.telemetry.totalServiceChecks, 1) - cardinality := parseTagCardinality(parameters) - return c.send(metric{metricType: serviceCheck, scvalue: sc, rate: 1, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, overrideCard: cardinality}) + cardinality := parameterCardinality(parameters, c.defaultCardinality) + return c.send(metric{metricType: serviceCheck, scvalue: sc, rate: 1, globalTags: c.tags, namespace: c.namespace, originDetection: c.originDetection, cardinality: cardinality}) } // SimpleServiceCheck sends an serviceCheck with the provided name and status. diff --git a/statsd/tag_cardinality.go b/statsd/tag_cardinality.go index 1289e472..f942e056 100644 --- a/statsd/tag_cardinality.go +++ b/statsd/tag_cardinality.go @@ -3,7 +3,6 @@ package statsd import ( "os" "strings" - "sync" ) type Parameter interface{} @@ -18,6 +17,10 @@ const ( CardinalityHigh ) +func (c Cardinality) isValid() bool { + return c >= CardinalityNotSet && c <= CardinalityHigh +} + func (c Cardinality) String() string { switch c { case CardinalityNone: @@ -33,67 +36,43 @@ func (c Cardinality) String() string { } // validateCardinality converts a string to Cardinality -func validateCardinality(card string) Cardinality { +func validateCardinality(card string) (Cardinality, bool) { card = strings.ToLower(card) switch card { case "none": - return CardinalityNone + return CardinalityNone, true case "low": - return CardinalityLow + return CardinalityLow, true case "orchestrator": - return CardinalityOrchestrator + return CardinalityOrchestrator, true case "high": - return CardinalityHigh + return CardinalityHigh, true default: - return CardinalityNotSet + return CardinalityNotSet, false } } -var ( - // Global setting of the tag cardinality. - tagCardinality Cardinality = CardinalityNotSet - tagCardinalityMutex sync.RWMutex -) - -// initTagCardinality initializes the tag cardinality. -func initTagCardinality(card Cardinality) { - tagCardinalityMutex.Lock() - defer tagCardinalityMutex.Unlock() - - tagCardinality = card - - // If the user has not provided a valid value, read the value from the DD_CARDINALITY environment variable. - if tagCardinality.String() == "" { - tagCardinality = validateCardinality(os.Getenv("DD_CARDINALITY")) +// envTagCardinality returns the tag cardinality value from the DD_CARDINALITY/DATADOG_CARDINALITY environment variable. +func envTagCardinality() (Cardinality, bool) { + // If the user has not provided a value, read the value from the DD_CARDINALITY environment variable. + if card, ok := validateCardinality(os.Getenv("DD_CARDINALITY")); ok { + return card, true } + // If DD_CARDINALITY is not set or valid, read the value from the DATADOG_CARDINALITY environment variable. - if tagCardinality.String() == "" { - tagCardinality = validateCardinality(os.Getenv("DATADOG_CARDINALITY")) + if card, ok := validateCardinality(os.Getenv("DATADOG_CARDINALITY")); ok { + return card, true } -} -func getTagCardinality() Cardinality { - tagCardinalityMutex.RLock() - defer tagCardinalityMutex.RUnlock() - return tagCardinality + return CardinalityNotSet, false } -func parseTagCardinality(parameters []Parameter) Cardinality { - cardinality := CardinalityNotSet +func parameterCardinality(parameters []Parameter, defaultCardinality Cardinality) Cardinality { for _, o := range parameters { c, ok := o.(Cardinality) - if ok { - cardinality = c + if ok && c.isValid() { + return c } } - return resolveCardinality(cardinality) -} - -// resolveCardinality returns the cardinality to use, prioritizing the metric-level cardinality over the global setting. -// This function validates the cardinality and falls back to the global setting if invalid. -func resolveCardinality(card Cardinality) Cardinality { - if card.String() == "" { - return getTagCardinality() - } - return card + return defaultCardinality } diff --git a/statsd/tag_cardinality_test.go b/statsd/tag_cardinality_test.go index 4e00f394..c3b0829d 100644 --- a/statsd/tag_cardinality_test.go +++ b/statsd/tag_cardinality_test.go @@ -7,92 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -const ( - CardinalityInvalid = 5 -) - -func TestValidateCardinality(t *testing.T) { - tests := []struct { - name string - input string - expected Cardinality - }{ - { - name: "valid none", - input: "none", - expected: CardinalityNone, - }, - { - name: "valid low", - input: "low", - expected: CardinalityLow, - }, - { - name: "valid orchestrator", - input: "orchestrator", - expected: CardinalityOrchestrator, - }, - { - name: "valid high", - input: "high", - expected: CardinalityHigh, - }, - { - name: "case insensitive none", - input: "NONE", - expected: CardinalityNone, - }, - { - name: "case insensitive low", - input: "LOW", - expected: CardinalityLow, - }, - { - name: "case insensitive orchestrator", - input: "ORCHESTRATOR", - expected: CardinalityOrchestrator, - }, - { - name: "case insensitive high", - input: "HIGH", - expected: CardinalityHigh, - }, - { - name: "mixed case", - input: "OrChEsTrAtOr", - expected: CardinalityOrchestrator, - }, - { - name: "empty string", - input: "", - expected: CardinalityNotSet, - }, - { - name: "invalid value", - input: "invalid", - expected: CardinalityNotSet, - }, - { - name: "partial match", - input: "orchestr", - expected: CardinalityNotSet, - }, - { - name: "whitespace", - input: " none ", - expected: CardinalityNotSet, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := validateCardinality(tt.input) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestInitTagCardinality(t *testing.T) { +func TestEnvTagCardinality(t *testing.T) { // Save original environment variables originalDDCardinality := os.Getenv("DD_CARDINALITY") originalDatadogCardinality := os.Getenv("DATADOG_CARDINALITY") @@ -112,272 +27,110 @@ func TestInitTagCardinality(t *testing.T) { tests := []struct { name string - inputCard Cardinality ddCardinality string datadogCardinality string expected Cardinality + expectedOk bool }{ - { - name: "input parameter takes precedence", - inputCard: CardinalityHigh, - ddCardinality: "low", - datadogCardinality: "orchestrator", - expected: CardinalityHigh, - }, { name: "DD_CARDINALITY used when input empty", - inputCard: CardinalityNotSet, ddCardinality: "low", datadogCardinality: "orchestrator", expected: CardinalityLow, + expectedOk: true, }, { name: "DATADOG_CARDINALITY used when DD_CARDINALITY empty", - inputCard: CardinalityNotSet, ddCardinality: "", datadogCardinality: "orchestrator", expected: CardinalityOrchestrator, + expectedOk: true, }, { name: "empty when no environment variables set", - inputCard: CardinalityNotSet, ddCardinality: "", datadogCardinality: "", expected: CardinalityNotSet, - }, - { - name: "invalid input parameter", - inputCard: 5, - ddCardinality: "low", - datadogCardinality: "orchestrator", - expected: CardinalityLow, + expectedOk: false, }, { name: "invalid DD_CARDINALITY", - inputCard: CardinalityNotSet, ddCardinality: "invalid", datadogCardinality: "orchestrator", expected: CardinalityOrchestrator, + expectedOk: true, }, { name: "invalid DATADOG_CARDINALITY", - inputCard: CardinalityNotSet, ddCardinality: "", datadogCardinality: "invalid", expected: CardinalityNotSet, + expectedOk: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - patchTagCardinality(tt.inputCard, tt.ddCardinality, tt.datadogCardinality) - - // Verify the result - result := getTagCardinality() - assert.Equal(t, tt.expected, result) - - resetTagCardinality() - }) - } -} - -func TestGetTagCardinality(t *testing.T) { - // Test that getTagCardinality returns the current value - initTagCardinality(CardinalityHigh) - result := getTagCardinality() - assert.Equal(t, CardinalityHigh, result) - - // Test that it returns empty string when not set - initTagCardinality(CardinalityNotSet) - result = getTagCardinality() - assert.Equal(t, CardinalityNotSet, result) -} - -func TestConcurrentAccess(t *testing.T) { - // Test that concurrent access to tag cardinality is safe - done := make(chan bool, 10) - - for i := 0; i < 10; i++ { - go func() { - initTagCardinality(CardinalityHigh) - _ = getTagCardinality() - done <- true - }() - } - - // Wait for all goroutines to complete - for i := 0; i < 10; i++ { - <-done - } - - // Verify final state - result := getTagCardinality() - assert.Equal(t, CardinalityHigh, result) -} - -func TestResolveCardinality(t *testing.T) { - // Save original environment variables - originalDDCardinality := os.Getenv("DD_CARDINALITY") - originalDatadogCardinality := os.Getenv("DATADOG_CARDINALITY") - defer func() { - // Restore original environment variables - if originalDDCardinality != "" { - os.Setenv("DD_CARDINALITY", originalDDCardinality) - } else { - os.Unsetenv("DD_CARDINALITY") - } - if originalDatadogCardinality != "" { - os.Setenv("DATADOG_CARDINALITY", originalDatadogCardinality) - } else { - os.Unsetenv("DATADOG_CARDINALITY") - } - }() - - tests := []struct { - name string - input Cardinality - globalSetting Cardinality - expected Cardinality - }{ - { - name: "valid cardinality returns same value", - input: CardinalityLow, - globalSetting: CardinalityHigh, - expected: CardinalityLow, - }, - { - name: "empty cardinality uses global setting", - input: CardinalityNotSet, - globalSetting: CardinalityHigh, - expected: CardinalityHigh, - }, - { - name: "empty cardinality with empty global", - input: CardinalityNotSet, - globalSetting: CardinalityNotSet, - expected: CardinalityNotSet, - }, - { - name: "invalid cardinality falls back to global", - input: CardinalityNotSet, - globalSetting: CardinalityLow, - expected: CardinalityLow, - }, - { - name: "invalid cardinality with empty global", - input: CardinalityInvalid, - globalSetting: CardinalityNotSet, - expected: CardinalityNotSet, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Set up the global cardinality setting - patchTagCardinality(tt.globalSetting, "", "") - - // Call resolveCardinality - result := resolveCardinality(tt.input) + patchTagCardinality(tt.ddCardinality, tt.datadogCardinality) // Verify the result + result, ok := envTagCardinality() assert.Equal(t, tt.expected, result) + assert.Equal(t, tt.expectedOk, ok) - // Clean up resetTagCardinality() }) } } -func TestResolveCardinalityWithEnvironmentVariables(t *testing.T) { - // Save original environment variables - originalDDCardinality := os.Getenv("DD_CARDINALITY") - originalDatadogCardinality := os.Getenv("DATADOG_CARDINALITY") - defer func() { - // Restore original environment variables - if originalDDCardinality != "" { - os.Setenv("DD_CARDINALITY", originalDDCardinality) - } else { - os.Unsetenv("DD_CARDINALITY") - } - if originalDatadogCardinality != "" { - os.Setenv("DATADOG_CARDINALITY", originalDatadogCardinality) - } else { - os.Unsetenv("DATADOG_CARDINALITY") - } - }() - - tests := []struct { - name string - input Cardinality - ddCardinality string - datadogCardinality string - expected Cardinality - }{ - { - name: "empty cardinality uses DD_CARDINALITY", - input: CardinalityNotSet, - ddCardinality: "high", - datadogCardinality: "low", - expected: CardinalityHigh, - }, - { - name: "empty cardinality uses DATADOG_CARDINALITY when DD_CARDINALITY empty", - input: CardinalityNotSet, - ddCardinality: "", - datadogCardinality: "orchestrator", - expected: CardinalityOrchestrator, - }, - { - name: "empty cardinality with no environment variables", - input: CardinalityNotSet, - ddCardinality: "", - datadogCardinality: "", - expected: CardinalityNotSet, - }, - { - name: "invalid cardinality falls back to DD_CARDINALITY", - input: CardinalityInvalid, - ddCardinality: "low", - datadogCardinality: "high", - expected: CardinalityLow, - }, - { - name: "invalid cardinality falls back to DATADOG_CARDINALITY when DD_CARDINALITY invalid", - input: CardinalityInvalid, - ddCardinality: "invalid", - datadogCardinality: "high", - expected: CardinalityHigh, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Set up the environment variables - patchTagCardinality(CardinalityNotSet, tt.ddCardinality, tt.datadogCardinality) - - // Call resolveCardinality - result := resolveCardinality(tt.input) - - // Verify the result - assert.Equal(t, tt.expected, result) - - // Clean up - resetTagCardinality() - }) - } -} -func patchTagCardinality(userInput Cardinality, DDInput string, DATADOGInput string) { +func patchTagCardinality(DDInput string, DATADOGInput string) { if DDInput != "" { os.Setenv("DD_CARDINALITY", DDInput) } if DATADOGInput != "" { os.Setenv("DATADOG_CARDINALITY", DATADOGInput) } - initTagCardinality(userInput) } func resetTagCardinality() { os.Unsetenv("DD_CARDINALITY") os.Unsetenv("DATADOG_CARDINALITY") - tagCardinality = defaultTagCardinality +} + +func TestParameterCardinality(t *testing.T) { + t.Run("nil", func(t *testing.T) { + card := parameterCardinality(nil, CardinalityHigh) + assert.Equal(t, CardinalityHigh, card) + }) + t.Run("empty", func(t *testing.T) { + card := parameterCardinality([]Parameter{}, CardinalityHigh) + assert.Equal(t, CardinalityHigh, card) + }) + t.Run("missing", func(t *testing.T) { + card := parameterCardinality([]Parameter{"foo"}, CardinalityHigh) + assert.Equal(t, CardinalityHigh, card) + }) + t.Run("invalid", func(t *testing.T) { + card := parameterCardinality([]Parameter{Cardinality(CardinalityHigh + 1)}, CardinalityHigh) + assert.Equal(t, CardinalityHigh, card) + }) + t.Run("present", func(t *testing.T) { + card := parameterCardinality([]Parameter{"foo", CardinalityLow}, CardinalityHigh) + assert.Equal(t, CardinalityLow, card) + }) + t.Run("multiple", func(t *testing.T) { + card := parameterCardinality([]Parameter{"foo", CardinalityLow, CardinalityOrchestrator}, CardinalityHigh) + assert.Equal(t, CardinalityLow, card) + }) +} + +func TestIsValid(t *testing.T) { + assert.False(t, (CardinalityNotSet - 1).isValid(), "make sure to update isValid when adding new value") + + assert.True(t, CardinalityNone.isValid()) + assert.True(t, CardinalityLow.isValid()) + assert.True(t, CardinalityOrchestrator.isValid()) + assert.True(t, CardinalityHigh.isValid()) + + assert.False(t, (CardinalityHigh + 1).isValid(), "make sure to update isValid when adding new value") } diff --git a/statsd/worker.go b/statsd/worker.go index b1bf6cf2..05628262 100644 --- a/statsd/worker.go +++ b/statsd/worker.go @@ -92,7 +92,7 @@ func (w *worker) writeAggregatedMetricUnsafe(m metric, metricSymbol []byte, prec } for { - pos, err := w.buffer.writeAggregated(metricSymbol, m.namespace, m.globalTags, m.name, m.fvalues[globalPos:], m.stags, extraSize, precision, rate, m.originDetection, m.overrideCard) + pos, err := w.buffer.writeAggregated(metricSymbol, m.namespace, m.globalTags, m.name, m.fvalues[globalPos:], m.stags, extraSize, precision, rate, m.originDetection, m.cardinality) if err == errPartialWrite { // We successfully wrote part of the histogram metrics. // We flush the current buffer and finish the histogram @@ -108,21 +108,21 @@ func (w *worker) writeAggregatedMetricUnsafe(m metric, metricSymbol []byte, prec func (w *worker) writeMetricUnsafe(m metric) error { switch m.metricType { case gauge: - return w.buffer.writeGauge(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.timestamp, m.originDetection, m.overrideCard) + return w.buffer.writeGauge(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.timestamp, m.originDetection, m.cardinality) case count: - return w.buffer.writeCount(m.namespace, m.globalTags, m.name, m.ivalue, m.tags, m.rate, m.timestamp, m.originDetection, m.overrideCard) + return w.buffer.writeCount(m.namespace, m.globalTags, m.name, m.ivalue, m.tags, m.rate, m.timestamp, m.originDetection, m.cardinality) case histogram: - return w.buffer.writeHistogram(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.originDetection, m.overrideCard) + return w.buffer.writeHistogram(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.originDetection, m.cardinality) case distribution: - return w.buffer.writeDistribution(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.originDetection, m.overrideCard) + return w.buffer.writeDistribution(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.originDetection, m.cardinality) case set: - return w.buffer.writeSet(m.namespace, m.globalTags, m.name, m.svalue, m.tags, m.rate, m.originDetection, m.overrideCard) + return w.buffer.writeSet(m.namespace, m.globalTags, m.name, m.svalue, m.tags, m.rate, m.originDetection, m.cardinality) case timing: - return w.buffer.writeTiming(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.originDetection, m.overrideCard) + return w.buffer.writeTiming(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate, m.originDetection, m.cardinality) case event: - return w.buffer.writeEvent(m.evalue, m.globalTags, m.originDetection, m.overrideCard) + return w.buffer.writeEvent(m.evalue, m.globalTags, m.originDetection, m.cardinality) case serviceCheck: - return w.buffer.writeServiceCheck(m.scvalue, m.globalTags, m.originDetection, m.overrideCard) + return w.buffer.writeServiceCheck(m.scvalue, m.globalTags, m.originDetection, m.cardinality) case histogramAggregated: return w.writeAggregatedMetricUnsafe(m, histogramSymbol, -1, m.rate) case distributionAggregated: From 268b052293e441b9650728df1dbd1b48df19871f Mon Sep 17 00:00:00 2001 From: Alexander Yastrebov Date: Thu, 8 Jan 2026 12:13:21 +0100 Subject: [PATCH 2/2] statsd: set CardinalityNotSet to zero Set CardinalityNotSet to zero to avoid gap between it and CardinalityNone which equals to one. --- statsd/tag_cardinality.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/statsd/tag_cardinality.go b/statsd/tag_cardinality.go index f942e056..23d73d53 100644 --- a/statsd/tag_cardinality.go +++ b/statsd/tag_cardinality.go @@ -10,8 +10,8 @@ type Parameter interface{} type Cardinality int const ( - CardinalityNotSet Cardinality = -1 - CardinalityNone Cardinality = iota + CardinalityNotSet Cardinality = iota + CardinalityNone CardinalityLow CardinalityOrchestrator CardinalityHigh