Skip to content

Commit e6c1ff3

Browse files
committed
Aggregate dimensions and perform dedup
1 parent b662599 commit e6c1ff3

File tree

4 files changed

+204
-250
lines changed

4 files changed

+204
-250
lines changed

exporter/awsemfexporter/metric_declaration.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func dedupDimensionSet(dimensions []string) (deduped []string, hasDuplicate bool
6060
}
6161

6262
// Init initializes the MetricDeclaration struct. Performs validation and compiles
63-
// regex strings.
63+
// regex strings. Dimensions are deduped and sorted.
6464
func (m *MetricDeclaration) Init(logger *zap.Logger) (err error) {
6565
// Return error if no metric name selectors are defined
6666
if len(m.MetricNameSelectors) == 0 {
@@ -116,7 +116,7 @@ func (m *MetricDeclaration) Matches(metric *pdata.Metric) bool {
116116
}
117117

118118
// ExtractDimensions extracts dimensions within the MetricDeclaration that only
119-
// contains labels found in `labels`.
119+
// contains labels found in `labels`. Returned order of dimensions are preserved.
120120
func (m *MetricDeclaration) ExtractDimensions(labels map[string]string) (dimensions [][]string) {
121121
for _, dimensionSet := range m.Dimensions {
122122
if len(dimensionSet) == 0 {
@@ -136,16 +136,36 @@ func (m *MetricDeclaration) ExtractDimensions(labels map[string]string) (dimensi
136136
return
137137
}
138138

139-
// processMetricDeclarations processes a list of MetricDeclarations and returns a
140-
// list of dimensions that matches the given `metric`.
141-
func processMetricDeclarations(metricDeclarations []*MetricDeclaration, metric *pdata.Metric, labels map[string]string) (dimensionsList [][][]string) {
139+
// processMetricDeclarations processes a list of MetricDeclarations and creates a
140+
// list of dimension sets that matches the given `metric`. This list is then aggregated
141+
// together with the rolled-up dimensions. Returned dimension sets
142+
// are deduped and the dimensions in each dimension set are sorted.
143+
// Prerequisite:
144+
// 1. metricDeclarations' dimensions are sorted.
145+
func processMetricDeclarations(metricDeclarations []*MetricDeclaration, metric *pdata.Metric,
146+
labels map[string]string, rolledUpDimensions [][]string) (dimensions [][]string) {
147+
seen := make(map[string]bool)
148+
addDimSet := func(dimSet []string) {
149+
key := strings.Join(dimSet, ",")
150+
// Only add dimension set if not a duplicate
151+
if _, ok := seen[key]; !ok {
152+
dimensions = append(dimensions, dimSet)
153+
seen[key] = true
154+
}
155+
}
156+
// Extract and append dimensions from metric declarations
142157
for _, m := range metricDeclarations {
143158
if m.Matches(metric) {
144-
dimensions := m.ExtractDimensions(labels)
145-
if len(dimensions) > 0 {
146-
dimensionsList = append(dimensionsList, dimensions)
159+
extractedDims := m.ExtractDimensions(labels)
160+
for _, dimSet := range extractedDims {
161+
addDimSet(dimSet)
147162
}
148163
}
149164
}
165+
// Add on rolled-up dimensions
166+
for _, dimSet := range rolledUpDimensions {
167+
sort.Strings(dimSet)
168+
addDimSet(dimSet)
169+
}
150170
return
151171
}

exporter/awsemfexporter/metric_declaration_test.go

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ func TestExtractDimensions(t *testing.T) {
229229
err := m.Init(logger)
230230
assert.Nil(t, err)
231231
dimensions := m.ExtractDimensions(tc.labels)
232-
assertDimsEqual(t, tc.extractedDimensions, dimensions)
232+
assert.Equal(t, tc.extractedDimensions, dimensions)
233233
})
234234
}
235235
}
@@ -238,14 +238,14 @@ func TestProcessMetricDeclarations(t *testing.T) {
238238
metricDeclarations := []*MetricDeclaration{
239239
{
240240
Dimensions: [][]string{{"dim1", "dim2"}},
241-
MetricNameSelectors: []string{"a", "b"},
241+
MetricNameSelectors: []string{"a", "b", "c"},
242242
},
243243
{
244244
Dimensions: [][]string{{"dim1"}},
245245
MetricNameSelectors: []string{"aa", "b"},
246246
},
247247
{
248-
Dimensions: [][]string{{"dim1", "dim2"}, {"dim1"}},
248+
Dimensions: [][]string{{"dim2", "dim1"}, {"dim1"}},
249249
MetricNameSelectors: []string{"a"},
250250
},
251251
}
@@ -255,43 +255,87 @@ func TestProcessMetricDeclarations(t *testing.T) {
255255
assert.Nil(t, err)
256256
}
257257
testCases := []struct {
258-
testName string
259-
metricName string
260-
labels map[string]string
261-
dimensionsList [][][]string
258+
testName string
259+
metricName string
260+
labels map[string]string
261+
rollUpDims [][]string
262+
expectedDims [][]string
262263
}{
263264
{
264-
"Matching multiple dimensions 1",
265-
"a",
265+
"Matching single declaration",
266+
"c",
266267
map[string]string{
267268
"dim1": "foo",
268269
"dim2": "bar",
269270
},
270-
[][][]string{
271-
{{"dim1", "dim2"}},
272-
{{"dim1", "dim2"}, {"dim1"}},
271+
nil,
272+
[][]string{
273+
{"dim1", "dim2"},
274+
},
275+
},
276+
{
277+
"Match single dimension set",
278+
"a",
279+
map[string]string{
280+
"dim1": "foo",
281+
},
282+
nil,
283+
[][]string{
284+
{"dim1"},
273285
},
274286
},
275287
{
276-
"Matching multiple dimensions 2",
288+
"Match single dimension set w/ rolled-up dims",
289+
"a",
290+
map[string]string{
291+
"dim1": "foo",
292+
"dim3": "car",
293+
},
294+
[][]string{{"dim1"}, {"dim3"}},
295+
[][]string{
296+
{"dim1"},
297+
{"dim3"},
298+
},
299+
},
300+
{
301+
"Matching multiple declarations",
277302
"b",
278303
map[string]string{
279304
"dim1": "foo",
280305
"dim2": "bar",
281306
},
282-
[][][]string{
283-
{{"dim1", "dim2"}},
284-
{{"dim1"}},
307+
nil,
308+
[][]string{
309+
{"dim1", "dim2"},
310+
{"dim1"},
285311
},
286312
},
287313
{
288-
"Match single dimension set",
314+
"Matching multiple declarations w/ duplicate",
289315
"a",
290316
map[string]string{
291317
"dim1": "foo",
318+
"dim2": "bar",
292319
},
293-
[][][]string{
294-
{{"dim1"}},
320+
nil,
321+
[][]string{
322+
{"dim1", "dim2"},
323+
{"dim1"},
324+
},
325+
},
326+
{
327+
"Matching multiple declarations w/ duplicate + rolled-up dims",
328+
"a",
329+
map[string]string{
330+
"dim1": "foo",
331+
"dim2": "bar",
332+
"dim3": "car",
333+
},
334+
[][]string{{"dim2", "dim1"}, {"dim3"}},
335+
[][]string{
336+
{"dim1", "dim2"},
337+
{"dim1"},
338+
{"dim3"},
295339
},
296340
},
297341
{
@@ -301,6 +345,16 @@ func TestProcessMetricDeclarations(t *testing.T) {
301345
"dim2": "bar",
302346
},
303347
nil,
348+
nil,
349+
},
350+
{
351+
"No matching dimension set w/ rolled-up dims",
352+
"a",
353+
map[string]string{
354+
"dim2": "bar",
355+
},
356+
[][]string{{"dim2"}},
357+
[][]string{{"dim2"}},
304358
},
305359
{
306360
"No matching metric name",
@@ -309,6 +363,16 @@ func TestProcessMetricDeclarations(t *testing.T) {
309363
"dim1": "foo",
310364
},
311365
nil,
366+
nil,
367+
},
368+
{
369+
"No matching metric name w/ rolled-up dims",
370+
"c",
371+
map[string]string{
372+
"dim1": "foo",
373+
},
374+
[][]string{{"dim1"}},
375+
[][]string{{"dim1"}},
312376
},
313377
}
314378

@@ -317,11 +381,8 @@ func TestProcessMetricDeclarations(t *testing.T) {
317381
metric.InitEmpty()
318382
metric.SetName(tc.metricName)
319383
t.Run(tc.testName, func(t *testing.T) {
320-
dimensionsList := processMetricDeclarations(metricDeclarations, &metric, tc.labels)
321-
assert.Equal(t, len(tc.dimensionsList), len(dimensionsList))
322-
for i, dimensions := range dimensionsList {
323-
assertDimsEqual(t, tc.dimensionsList[i], dimensions)
324-
}
384+
dimensions := processMetricDeclarations(metricDeclarations, &metric, tc.labels, tc.rollUpDims)
385+
assert.Equal(t, tc.expectedDims, dimensions)
325386
})
326387
}
327388
}

exporter/awsemfexporter/metric_translator.go

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"sort"
23-
"strings"
2423
"time"
2524

2625
"go.opentelemetry.io/collector/consumer/pdata"
@@ -274,40 +273,52 @@ func buildCWMetric(dp DataPoint, pmd *pdata.Metric, namespace string, metricSlic
274273
idx++
275274
})
276275

276+
// Apply single/zero dimension rollup to labels
277+
rollupDimensionArray := dimensionRollup(dimensionRollupOption, labelsSlice, instrumentationLibName)
278+
277279
// Add OTel instrumentation lib name as an additional dimension if it is defined
278280
if instrumentationLibName != noInstrumentationLibraryName {
279281
labels[OTellibDimensionKey] = instrumentationLibName
280282
fields[OTellibDimensionKey] = instrumentationLibName
281283
}
282284

283285
// Create list of dimension sets
284-
var dimensionsArray [][][]string
286+
var dimensions [][]string
285287
if len(metricDeclarations) > 0 {
286-
// Filter metric declarations and map each metric declaration to a list
287-
// of dimension sets
288-
dimensionsArray = processMetricDeclarations(metricDeclarations, pmd, labels)
289-
} else if instrumentationLibName != noInstrumentationLibraryName {
290-
// If no metric declarations defined and OTel instrumentation lib name is defined,
291-
// create a single dimension set containing the list of labels +
292-
// instrumentationLibName dimension key
293-
dimensionsArray = [][][]string{{append(labelsSlice, OTellibDimensionKey)}}
288+
// If metric declarations are defined, extract dimension sets from them
289+
dimensions = processMetricDeclarations(metricDeclarations, pmd, labels, rollupDimensionArray)
294290
} else {
295-
// If no metric declarations or OTel instrumentation lib name defined,
296-
// create a single dimension set containing only label names
297-
dimensionsArray = [][][]string{{labelsSlice}}
298-
}
291+
// If no metric declarations defined, create a single dimension set containing
292+
// the list of labels
293+
dims := labelsSlice
294+
if instrumentationLibName != noInstrumentationLibraryName {
295+
// If OTel instrumentation lib name is defined, add instrumentation lib
296+
// name as a dimension
297+
dims = append(dims, OTellibDimensionKey)
298+
}
299299

300-
// Apply single/zero dimension rollup to labels
301-
rollupDimensionArray := dimensionRollup(dimensionRollupOption, labelsSlice, instrumentationLibName)
300+
if len(rollupDimensionArray) > 0 {
301+
// Perform de-duplication check for edge case with a single label and single roll-up
302+
// is activated
303+
if len(labelsSlice) > 1 || (dimensionRollupOption != SingleDimensionRollupOnly &&
304+
dimensionRollupOption != ZeroAndSingleDimensionRollup) {
305+
dimensions = [][]string{dims}
306+
}
307+
dimensions = append(dimensions, rollupDimensionArray...)
308+
} else {
309+
dimensions = [][]string{dims}
310+
}
311+
}
302312

303313
// Build list of CW Measurements
304-
cwMeasurements := make([]CwMeasurement, len(dimensionsArray))
305-
for i, dimensions := range dimensionsArray {
306-
dimensions = dedupDimensions(dimensions, rollupDimensionArray)
307-
cwMeasurements[i] = CwMeasurement{
308-
Namespace: namespace,
309-
Dimensions: dimensions,
310-
Metrics: metricSlice,
314+
var cwMeasurements []CwMeasurement
315+
if len(dimensions) > 0 {
316+
cwMeasurements = []CwMeasurement{
317+
{
318+
Namespace: namespace,
319+
Dimensions: dimensions,
320+
Metrics: metricSlice,
321+
},
311322
}
312323
}
313324

@@ -409,6 +420,7 @@ func calculateRate(fields map[string]interface{}, val interface{}, timestamp int
409420
return metricRate
410421
}
411422

423+
// dimensionRollup creates rolled-up dimensions from the metric's label set.
412424
func dimensionRollup(dimensionRollupOption string, originalDimensionSlice []string, instrumentationLibName string) [][]string {
413425
var rollupDimensionArray [][]string
414426
dimensionZero := []string{}
@@ -431,32 +443,6 @@ func dimensionRollup(dimensionRollupOption string, originalDimensionSlice []stri
431443
return rollupDimensionArray
432444
}
433445

434-
// dedupDimensions appends rolled-up dimensions to existing dimensions and removes duplicate dimension sets.
435-
func dedupDimensions(dimensions, rolledUpDims [][]string) [][]string {
436-
deduped := make([][]string, len(dimensions)+len(rolledUpDims))
437-
seen := make(map[string]bool, len(deduped))
438-
idx := 0
439-
440-
addDeduped := func(dimSet []string) {
441-
sort.Strings(dimSet)
442-
key := strings.Join(dimSet, ",")
443-
if _, ok := seen[key]; ok {
444-
return
445-
}
446-
seen[key] = true
447-
deduped[idx] = dimSet
448-
idx++
449-
}
450-
451-
for _, dimSet := range dimensions {
452-
addDeduped(dimSet)
453-
}
454-
for _, dimSet := range rolledUpDims {
455-
addDeduped(dimSet)
456-
}
457-
return deduped[:idx]
458-
}
459-
460446
func needsCalculateRate(pmd *pdata.Metric) bool {
461447
switch pmd.DataType() {
462448
case pdata.MetricDataTypeIntSum:

0 commit comments

Comments
 (0)