Skip to content

Commit d44a2ad

Browse files
authored
[awsemfexporter] Restructure Metric Translator Logic (#1353)
* Restructure buildCWMetric logic (#1) * Restructure code to remove duplicated logic * Update format * Improve function and variable names * Extract logic for dimension creation and add test * Implement minor fixes * Remove changes to go.sum * Implement tests for getCWMetrics * Implement tests for buildCWMetric * Format metric_translator_test.go * Run with gofmt -s * Disregard ordering of dimensions in test case * Perform dimension equality checking as a helper function
1 parent c01f532 commit d44a2ad

File tree

2 files changed

+857
-159
lines changed

2 files changed

+857
-159
lines changed

exporter/awsemfexporter/metric_translator.go

+109-158
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,50 @@ type CWMetricStats struct {
7676
Sum float64
7777
}
7878

79+
// Wrapper interface for:
80+
// - pdata.IntDataPointSlice
81+
// - pdata.DoubleDataPointSlice
82+
// - pdata.IntHistogramDataPointSlice
83+
// - pdata.DoubleHistogramDataPointSlice
84+
type DataPoints interface {
85+
Len() int
86+
At(int) DataPoint
87+
}
88+
89+
// Wrapper interface for:
90+
// - pdata.IntDataPoint
91+
// - pdata.DoubleDataPoint
92+
// - pdata.IntHistogramDataPoint
93+
// - pdata.DoubleHistogramDataPoint
94+
type DataPoint interface {
95+
IsNil() bool
96+
LabelsMap() pdata.StringMap
97+
}
98+
99+
// Define wrapper interfaces such that At(i) returns a `DataPoint`
100+
type IntDataPointSlice struct {
101+
pdata.IntDataPointSlice
102+
}
103+
type DoubleDataPointSlice struct {
104+
pdata.DoubleDataPointSlice
105+
}
106+
type DoubleHistogramDataPointSlice struct {
107+
pdata.DoubleHistogramDataPointSlice
108+
}
109+
110+
func (dps IntDataPointSlice) At(i int) DataPoint {
111+
return dps.IntDataPointSlice.At(i)
112+
}
113+
func (dps DoubleDataPointSlice) At(i int) DataPoint {
114+
return dps.DoubleDataPointSlice.At(i)
115+
}
116+
func (dps DoubleHistogramDataPointSlice) At(i int) DataPoint {
117+
return dps.DoubleHistogramDataPointSlice.At(i)
118+
}
119+
79120
// TranslateOtToCWMetric converts OT metrics to CloudWatch Metric format
80121
func TranslateOtToCWMetric(rm *pdata.ResourceMetrics, dimensionRollupOption string, namespace string) ([]*CWMetrics, int) {
81-
var cwMetricLists []*CWMetrics
122+
var cwMetricList []*CWMetrics
82123
totalDroppedMetrics := 0
83124
var instrumentationLibName string
84125

@@ -117,11 +158,11 @@ func TranslateOtToCWMetric(rm *pdata.ResourceMetrics, dimensionRollupOption stri
117158
totalDroppedMetrics++
118159
continue
119160
}
120-
cwMetricList := getMeasurements(&metric, namespace, instrumentationLibName, dimensionRollupOption)
121-
cwMetricLists = append(cwMetricLists, cwMetricList...)
161+
cwMetrics := getCWMetrics(&metric, namespace, instrumentationLibName, dimensionRollupOption)
162+
cwMetricList = append(cwMetricList, cwMetrics...)
122163
}
123164
}
124-
return cwMetricLists, totalDroppedMetrics
165+
return cwMetricList, totalDroppedMetrics
125166
}
126167

127168
func TranslateCWMetricToEMF(cwMetricLists []*CWMetrics) []*LogEvent {
@@ -150,217 +191,127 @@ func TranslateCWMetricToEMF(cwMetricLists []*CWMetrics) []*LogEvent {
150191
return ples
151192
}
152193

153-
func getMeasurements(metric *pdata.Metric, namespace string, instrumentationLibName string, dimensionRollupOption string) []*CWMetrics {
194+
// Translates OTLP Metric to list of CW Metrics
195+
func getCWMetrics(metric *pdata.Metric, namespace string, instrumentationLibName string, dimensionRollupOption string) []*CWMetrics {
154196
var result []*CWMetrics
197+
var dps DataPoints
155198

156199
// metric measure data from OT
157200
metricMeasure := make(map[string]string)
158-
// metric measure slice could include multiple metric measures
159-
metricSlice := []map[string]string{}
160201
metricMeasure["Name"] = metric.Name()
161202
metricMeasure["Unit"] = metric.Unit()
162-
metricSlice = append(metricSlice, metricMeasure)
203+
// metric measure slice could include multiple metric measures
204+
metricSlice := []map[string]string{metricMeasure}
163205

206+
// Retrieve data points
164207
switch metric.DataType() {
165208
case pdata.MetricDataTypeIntGauge:
166-
dps := metric.IntGauge().DataPoints()
167-
if dps.Len() == 0 {
168-
return result
169-
}
170-
for m := 0; m < dps.Len(); m++ {
171-
dp := dps.At(m)
172-
if dp.IsNil() {
173-
continue
174-
}
175-
cwMetric := buildCWMetricFromDP(dp, metric, namespace, metricSlice, instrumentationLibName, dimensionRollupOption)
176-
if cwMetric != nil {
177-
result = append(result, cwMetric)
178-
}
179-
}
209+
dps = IntDataPointSlice{metric.IntGauge().DataPoints()}
180210
case pdata.MetricDataTypeDoubleGauge:
181-
dps := metric.DoubleGauge().DataPoints()
182-
if dps.Len() == 0 {
183-
return result
184-
}
185-
for m := 0; m < dps.Len(); m++ {
186-
dp := dps.At(m)
187-
if dp.IsNil() {
188-
continue
189-
}
190-
cwMetric := buildCWMetricFromDP(dp, metric, namespace, metricSlice, instrumentationLibName, dimensionRollupOption)
191-
if cwMetric != nil {
192-
result = append(result, cwMetric)
193-
}
194-
}
211+
dps = DoubleDataPointSlice{metric.DoubleGauge().DataPoints()}
195212
case pdata.MetricDataTypeIntSum:
196-
dps := metric.IntSum().DataPoints()
197-
if dps.Len() == 0 {
198-
return result
199-
}
200-
for m := 0; m < dps.Len(); m++ {
201-
dp := dps.At(m)
202-
if dp.IsNil() {
203-
continue
204-
}
205-
cwMetric := buildCWMetricFromDP(dp, metric, namespace, metricSlice, instrumentationLibName, dimensionRollupOption)
206-
if cwMetric != nil {
207-
result = append(result, cwMetric)
208-
}
209-
}
213+
dps = IntDataPointSlice{metric.IntSum().DataPoints()}
210214
case pdata.MetricDataTypeDoubleSum:
211-
dps := metric.DoubleSum().DataPoints()
212-
if dps.Len() == 0 {
213-
return result
214-
}
215-
for m := 0; m < dps.Len(); m++ {
216-
dp := dps.At(m)
217-
if dp.IsNil() {
218-
continue
219-
}
220-
cwMetric := buildCWMetricFromDP(dp, metric, namespace, metricSlice, instrumentationLibName, dimensionRollupOption)
221-
if cwMetric != nil {
222-
result = append(result, cwMetric)
223-
}
224-
}
215+
dps = DoubleDataPointSlice{metric.DoubleSum().DataPoints()}
225216
case pdata.MetricDataTypeDoubleHistogram:
226-
dps := metric.DoubleHistogram().DataPoints()
227-
if dps.Len() == 0 {
228-
return result
217+
dps = DoubleHistogramDataPointSlice{metric.DoubleHistogram().DataPoints()}
218+
}
219+
220+
if dps.Len() == 0 {
221+
return result
222+
}
223+
for m := 0; m < dps.Len(); m++ {
224+
dp := dps.At(m)
225+
if dp.IsNil() {
226+
continue
229227
}
230-
for m := 0; m < dps.Len(); m++ {
231-
dp := dps.At(m)
232-
if dp.IsNil() {
233-
continue
234-
}
235-
cwMetric := buildCWMetricFromHistogram(dp, metric, namespace, metricSlice, instrumentationLibName, dimensionRollupOption)
236-
if cwMetric != nil {
237-
result = append(result, cwMetric)
238-
}
228+
cwMetric := buildCWMetric(dp, metric, namespace, metricSlice, instrumentationLibName, dimensionRollupOption)
229+
if cwMetric != nil {
230+
result = append(result, cwMetric)
239231
}
240232
}
241233
return result
242234
}
243235

244-
func buildCWMetricFromDP(dp interface{}, pmd *pdata.Metric, namespace string, metricSlice []map[string]string, instrumentationLibName string, dimensionRollupOption string) *CWMetrics {
245-
// fields contains metric and dimensions key/value pairs
246-
fieldsPairs := make(map[string]interface{})
247-
var dimensionArray [][]string
248-
// Dimensions Slice
249-
var dimensionSlice []string
250-
var dimensionKV pdata.StringMap
251-
switch metric := dp.(type) {
252-
case pdata.IntDataPoint:
253-
dimensionKV = metric.LabelsMap()
254-
case pdata.DoubleDataPoint:
255-
dimensionKV = metric.LabelsMap()
256-
}
257-
258-
dimensionKV.ForEach(func(k string, v pdata.StringValue) {
259-
fieldsPairs[k] = v.Value()
260-
dimensionSlice = append(dimensionSlice, k)
261-
})
262-
// add OTel instrumentation lib name as an additional dimension if it is defined
263-
if instrumentationLibName != noInstrumentationLibraryName {
264-
fieldsPairs[OTellibDimensionKey] = instrumentationLibName
265-
dimensionArray = append(dimensionArray, append(dimensionSlice, OTellibDimensionKey))
266-
} else {
267-
dimensionArray = append(dimensionArray, dimensionSlice)
236+
// Build CWMetric from DataPoint
237+
func buildCWMetric(dp DataPoint, pmd *pdata.Metric, namespace string, metricSlice []map[string]string, instrumentationLibName string, dimensionRollupOption string) *CWMetrics {
238+
dimensions, fields := createDimensions(dp, instrumentationLibName, dimensionRollupOption)
239+
cwMeasurement := &CwMeasurement{
240+
Namespace: namespace,
241+
Dimensions: dimensions,
242+
Metrics: metricSlice,
268243
}
269-
244+
metricList := []CwMeasurement{*cwMeasurement}
270245
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
246+
247+
// Extract metric
271248
var metricVal interface{}
272249
switch metric := dp.(type) {
273250
case pdata.IntDataPoint:
274-
// Put a fake but identical metric value here in order to add metric name into fieldsPairs
251+
// Put a fake but identical metric value here in order to add metric name into fields
275252
// since calculateRate() needs metric name as one of metric identifiers
276-
fieldsPairs[pmd.Name()] = int64(FakeMetricValue)
253+
fields[pmd.Name()] = int64(FakeMetricValue)
277254
metricVal = metric.Value()
278255
if needsCalculateRate(pmd) {
279-
metricVal = calculateRate(fieldsPairs, metric.Value(), timestamp)
256+
metricVal = calculateRate(fields, metric.Value(), timestamp)
280257
}
281258
case pdata.DoubleDataPoint:
282-
fieldsPairs[pmd.Name()] = float64(FakeMetricValue)
259+
fields[pmd.Name()] = float64(FakeMetricValue)
283260
metricVal = metric.Value()
284261
if needsCalculateRate(pmd) {
285-
metricVal = calculateRate(fieldsPairs, metric.Value(), timestamp)
262+
metricVal = calculateRate(fields, metric.Value(), timestamp)
263+
}
264+
case pdata.DoubleHistogramDataPoint:
265+
bucketBounds := metric.ExplicitBounds()
266+
metricVal = &CWMetricStats{
267+
Min: bucketBounds[0],
268+
Max: bucketBounds[len(bucketBounds)-1],
269+
Count: metric.Count(),
270+
Sum: metric.Sum(),
286271
}
287272
}
288273
if metricVal == nil {
289274
return nil
290275
}
291-
fieldsPairs[pmd.Name()] = metricVal
276+
fields[pmd.Name()] = metricVal
292277

293-
// EMF dimension attr takes list of list on dimensions. Including single/zero dimension rollup
294-
rollupDimensionArray := dimensionRollup(dimensionRollupOption, dimensionSlice, instrumentationLibName)
295-
if len(rollupDimensionArray) > 0 {
296-
dimensionArray = append(dimensionArray, rollupDimensionArray...)
297-
}
298-
299-
cwMeasurement := &CwMeasurement{
300-
Namespace: namespace,
301-
Dimensions: dimensionArray,
302-
Metrics: metricSlice,
303-
}
304-
metricList := make([]CwMeasurement, 1)
305-
metricList[0] = *cwMeasurement
306278
cwMetric := &CWMetrics{
307279
Measurements: metricList,
308280
Timestamp: timestamp,
309-
Fields: fieldsPairs,
281+
Fields: fields,
310282
}
311283
return cwMetric
312284
}
313285

314-
func buildCWMetricFromHistogram(metric pdata.DoubleHistogramDataPoint, pmd *pdata.Metric, namespace string, metricSlice []map[string]string, instrumentationLibName string, dimensionRollupOption string) *CWMetrics {
286+
// Create dimensions from DataPoint labels, where dimensions is a 2D array of dimension names,
287+
// and initialize fields with dimension key/value pairs
288+
func createDimensions(dp DataPoint, instrumentationLibName string, dimensionRollupOption string) (dimensions [][]string, fields map[string]interface{}) {
315289
// fields contains metric and dimensions key/value pairs
316-
fieldsPairs := make(map[string]interface{})
317-
var dimensionArray [][]string
318-
// Dimensions Slice
319-
var dimensionSlice []string
320-
dimensionKV := metric.LabelsMap()
290+
fields = make(map[string]interface{})
291+
dimensionKV := dp.LabelsMap()
321292

293+
dimensionSlice := make([]string, dimensionKV.Len(), dimensionKV.Len()+1)
294+
idx := 0
322295
dimensionKV.ForEach(func(k string, v pdata.StringValue) {
323-
fieldsPairs[k] = v.Value()
324-
dimensionSlice = append(dimensionSlice, k)
296+
fields[k] = v.Value()
297+
dimensionSlice[idx] = k
298+
idx++
325299
})
326-
// add OTel instrumentation lib name as an additional dimension if it is defined
300+
// Add OTel instrumentation lib name as an additional dimension if it is defined
327301
if instrumentationLibName != noInstrumentationLibraryName {
328-
fieldsPairs[OTellibDimensionKey] = instrumentationLibName
329-
dimensionArray = append(dimensionArray, append(dimensionSlice, OTellibDimensionKey))
302+
fields[OTellibDimensionKey] = instrumentationLibName
303+
dimensions = append(dimensions, append(dimensionSlice, OTellibDimensionKey))
330304
} else {
331-
dimensionArray = append(dimensionArray, dimensionSlice)
332-
}
333-
334-
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
335-
336-
bucketBounds := metric.ExplicitBounds()
337-
metricStats := &CWMetricStats{
338-
Min: bucketBounds[0],
339-
Max: bucketBounds[len(bucketBounds)-1],
340-
Count: metric.Count(),
341-
Sum: metric.Sum(),
305+
dimensions = append(dimensions, dimensionSlice)
342306
}
343-
fieldsPairs[pmd.Name()] = metricStats
344307

345308
// EMF dimension attr takes list of list on dimensions. Including single/zero dimension rollup
346309
rollupDimensionArray := dimensionRollup(dimensionRollupOption, dimensionSlice, instrumentationLibName)
347310
if len(rollupDimensionArray) > 0 {
348-
dimensionArray = append(dimensionArray, rollupDimensionArray...)
311+
dimensions = append(dimensions, rollupDimensionArray...)
349312
}
350313

351-
cwMeasurement := &CwMeasurement{
352-
Namespace: namespace,
353-
Dimensions: dimensionArray,
354-
Metrics: metricSlice,
355-
}
356-
metricList := make([]CwMeasurement, 1)
357-
metricList[0] = *cwMeasurement
358-
cwMetric := &CWMetrics{
359-
Measurements: metricList,
360-
Timestamp: timestamp,
361-
Fields: fieldsPairs,
362-
}
363-
return cwMetric
314+
return
364315
}
365316

366317
// rate is calculated by valDelta / timeDelta

0 commit comments

Comments
 (0)