grouped_metric_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package awsemfexporter
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. "go.opentelemetry.io/collector/pdata/pmetric"
  12. "go.uber.org/zap"
  13. "go.uber.org/zap/zapcore"
  14. "go.uber.org/zap/zaptest/observer"
  15. )
  16. var (
  17. logGroup = "logGroup"
  18. logStreamName = "logStream"
  19. testCfg = createDefaultConfig().(*Config)
  20. )
  21. func TestAddToGroupedMetric(t *testing.T) {
  22. namespace := "namespace"
  23. instrumentationLibName := "cloudwatch-otel"
  24. timestamp := time.Now().UnixNano() / int64(time.Millisecond)
  25. logger := zap.NewNop()
  26. testCases := []struct {
  27. name string
  28. metric pmetric.Metrics
  29. expectedMetricType pmetric.MetricType
  30. expectedLabels map[string]string
  31. expectedMetricInfo map[string]*metricInfo
  32. }{
  33. {
  34. name: "Double gauge",
  35. metric: generateTestGaugeMetric("foo", doubleValueType),
  36. expectedMetricType: pmetric.MetricTypeGauge,
  37. expectedLabels: map[string]string{oTellibDimensionKey: instrumentationLibName, "label1": "value1"},
  38. expectedMetricInfo: map[string]*metricInfo{
  39. "foo": {
  40. value: 0.1,
  41. unit: "Count",
  42. },
  43. },
  44. },
  45. {
  46. name: "Int sum",
  47. metric: generateTestSumMetric("foo", intValueType),
  48. expectedMetricType: pmetric.MetricTypeSum,
  49. expectedLabels: map[string]string{oTellibDimensionKey: instrumentationLibName, "label1": "value1"},
  50. expectedMetricInfo: map[string]*metricInfo{
  51. "foo": {
  52. value: float64(1),
  53. unit: "Count",
  54. },
  55. },
  56. },
  57. {
  58. name: "Histogram",
  59. metric: generateTestHistogramMetric("foo"),
  60. expectedMetricType: pmetric.MetricTypeHistogram,
  61. expectedLabels: map[string]string{oTellibDimensionKey: instrumentationLibName, "label1": "value1"},
  62. expectedMetricInfo: map[string]*metricInfo{
  63. "foo": {
  64. value: &cWMetricStats{
  65. Count: 18,
  66. Sum: 35.0,
  67. },
  68. unit: "Seconds",
  69. },
  70. },
  71. },
  72. {
  73. name: "Summary",
  74. metric: generateTestSummaryMetric("foo"),
  75. expectedMetricType: pmetric.MetricTypeSummary,
  76. expectedLabels: map[string]string{oTellibDimensionKey: instrumentationLibName, "label1": "value1"},
  77. expectedMetricInfo: map[string]*metricInfo{
  78. "foo": {
  79. value: &cWMetricStats{
  80. Min: 1,
  81. Max: 5,
  82. Count: 5,
  83. Sum: 15,
  84. },
  85. unit: "Seconds",
  86. },
  87. },
  88. },
  89. }
  90. for _, tc := range testCases {
  91. t.Run(tc.name, func(t *testing.T) {
  92. emfCalcs := setupEmfCalculators()
  93. defer require.NoError(t, shutdownEmfCalculators(emfCalcs))
  94. groupedMetrics := make(map[any]*groupedMetric)
  95. rms := tc.metric.ResourceMetrics()
  96. ilms := rms.At(0).ScopeMetrics()
  97. metrics := ilms.At(0).Metrics()
  98. assert.Equal(t, 1, rms.Len())
  99. assert.Equal(t, 1, ilms.Len())
  100. for i := 0; i < metrics.Len(); i++ {
  101. err := addToGroupedMetric(metrics.At(i), groupedMetrics,
  102. generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, metrics.At(i).Type()),
  103. true, zap.NewNop(),
  104. nil,
  105. testCfg,
  106. emfCalcs)
  107. assert.Nil(t, err)
  108. }
  109. assert.Equal(t, 1, len(groupedMetrics))
  110. for _, v := range groupedMetrics {
  111. assert.Equal(t, len(tc.expectedMetricInfo), len(v.metrics))
  112. assert.Equal(t, tc.expectedMetricInfo, v.metrics)
  113. assert.Equal(t, 2, len(v.labels))
  114. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, tc.expectedMetricType), v.metadata)
  115. assert.Equal(t, tc.expectedLabels, v.labels)
  116. }
  117. })
  118. }
  119. t.Run("Add multiple different metrics", func(t *testing.T) {
  120. emfCalcs := setupEmfCalculators()
  121. defer require.NoError(t, shutdownEmfCalculators(emfCalcs))
  122. groupedMetrics := make(map[any]*groupedMetric)
  123. generateMetrics := []pmetric.Metrics{
  124. generateTestGaugeMetric("int-gauge", intValueType),
  125. generateTestGaugeMetric("double-gauge", doubleValueType),
  126. generateTestHistogramMetric("histogram"),
  127. generateTestSumMetric("int-sum", intValueType),
  128. generateTestSumMetric("double-sum", doubleValueType),
  129. generateTestSummaryMetric("summary"),
  130. }
  131. finalOtelMetrics := generateOtelTestMetrics(generateMetrics...)
  132. rms := finalOtelMetrics.ResourceMetrics()
  133. ilms := rms.At(0).ScopeMetrics()
  134. metrics := ilms.At(0).Metrics()
  135. assert.Equal(t, 9, metrics.Len())
  136. for i := 0; i < metrics.Len(); i++ {
  137. err := addToGroupedMetric(metrics.At(i),
  138. groupedMetrics,
  139. generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, metrics.At(i).Type()),
  140. true,
  141. logger,
  142. nil,
  143. testCfg,
  144. emfCalcs)
  145. assert.Nil(t, err)
  146. }
  147. assert.Equal(t, 4, len(groupedMetrics))
  148. for _, group := range groupedMetrics {
  149. for metricName, metricInfo := range group.metrics {
  150. switch metricName {
  151. case "int-gauge", "double-gauge":
  152. assert.Len(t, group.metrics, 2)
  153. assert.Equal(t, "Count", metricInfo.unit)
  154. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeGauge), group.metadata)
  155. case "int-sum", "double-sum":
  156. assert.Len(t, group.metrics, 2)
  157. assert.Equal(t, "Count", metricInfo.unit)
  158. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeSum), group.metadata)
  159. case "histogram":
  160. assert.Len(t, group.metrics, 1)
  161. assert.Equal(t, "Seconds", metricInfo.unit)
  162. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeHistogram), group.metadata)
  163. case "summary":
  164. assert.Len(t, group.metrics, 1)
  165. assert.Equal(t, "Seconds", metricInfo.unit)
  166. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeSummary), group.metadata)
  167. default:
  168. assert.Fail(t, fmt.Sprintf("Unhandled metric %s not expected", metricName))
  169. }
  170. expectedLabels := map[string]string{
  171. oTellibDimensionKey: "cloudwatch-otel",
  172. "label1": "value1",
  173. }
  174. assert.Equal(t, expectedLabels, group.labels)
  175. }
  176. }
  177. })
  178. t.Run("Add multiple different metrics with NaN types", func(t *testing.T) {
  179. emfCalcs := setupEmfCalculators()
  180. defer require.NoError(t, shutdownEmfCalculators(emfCalcs))
  181. groupedMetrics := make(map[any]*groupedMetric)
  182. generateMetrics := []pmetric.Metrics{
  183. generateTestGaugeMetric("int-gauge", intValueType),
  184. generateTestGaugeMetric("double-gauge", doubleValueType),
  185. generateTestHistogramMetric("histogram"),
  186. generateTestSumMetric("int-sum", intValueType),
  187. generateTestSumMetric("double-sum", doubleValueType),
  188. generateTestSummaryMetric("summary"),
  189. // We do not expect these to be added to the grouped metric. Metrics with NaN values should be dropped.
  190. generateTestGaugeMetricNaN("double-gauge-nan"),
  191. generateTestExponentialHistogramMetricWithNaNs("expo-with-nan"),
  192. generateTestHistogramMetricWithNaNs("histo-with-nan"),
  193. generateTestSummaryMetricWithNaN("sum-with-nan"),
  194. }
  195. finalOtelMetrics := generateOtelTestMetrics(generateMetrics...)
  196. rms := finalOtelMetrics.ResourceMetrics()
  197. ilms := rms.At(0).ScopeMetrics()
  198. metrics := ilms.At(0).Metrics()
  199. require.Equal(t, 14, metrics.Len(), "mock metric creation failed")
  200. for i := 0; i < metrics.Len(); i++ {
  201. err := addToGroupedMetric(metrics.At(i),
  202. groupedMetrics,
  203. generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, metrics.At(i).Type()),
  204. true,
  205. logger,
  206. nil,
  207. testCfg,
  208. emfCalcs)
  209. assert.Nil(t, err)
  210. }
  211. assert.Equal(t, 4, len(groupedMetrics))
  212. for _, group := range groupedMetrics {
  213. for metricName, metricInfo := range group.metrics {
  214. switch metricName {
  215. case "int-gauge", "double-gauge":
  216. assert.Len(t, group.metrics, 2)
  217. assert.Equal(t, "Count", metricInfo.unit)
  218. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeGauge), group.metadata)
  219. case "int-sum", "double-sum":
  220. assert.Len(t, group.metrics, 2)
  221. assert.Equal(t, "Count", metricInfo.unit)
  222. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeSum), group.metadata)
  223. case "histogram":
  224. assert.Len(t, group.metrics, 1)
  225. assert.Equal(t, "Seconds", metricInfo.unit)
  226. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeHistogram), group.metadata)
  227. case "summary":
  228. assert.Len(t, group.metrics, 1)
  229. assert.Equal(t, "Seconds", metricInfo.unit)
  230. assert.Equal(t, generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeSummary), group.metadata)
  231. default:
  232. assert.Fail(t, fmt.Sprintf("Unhandled metric %s not expected", metricName))
  233. }
  234. expectedLabels := map[string]string{
  235. oTellibDimensionKey: "cloudwatch-otel",
  236. "label1": "value1",
  237. }
  238. assert.Equal(t, expectedLabels, group.labels)
  239. }
  240. }
  241. })
  242. t.Run("Add same metric but different log group", func(t *testing.T) {
  243. emfCalcs := setupEmfCalculators()
  244. defer require.NoError(t, shutdownEmfCalculators(emfCalcs))
  245. groupedMetrics := make(map[any]*groupedMetric)
  246. otelMetrics := generateTestGaugeMetric("int-gauge", "int")
  247. ilms := otelMetrics.ResourceMetrics().At(0).ScopeMetrics()
  248. metric := ilms.At(0).Metrics().At(0)
  249. metricMetadata1 := generateTestMetricMetadata(namespace, timestamp, "log-group-1", logStreamName, instrumentationLibName, metric.Type())
  250. err := addToGroupedMetric(metric,
  251. groupedMetrics,
  252. metricMetadata1,
  253. true, logger,
  254. nil,
  255. testCfg,
  256. emfCalcs)
  257. assert.Nil(t, err)
  258. metricMetadata2 := generateTestMetricMetadata(namespace,
  259. timestamp,
  260. "log-group-2",
  261. logStreamName,
  262. instrumentationLibName,
  263. metric.Type(),
  264. )
  265. err = addToGroupedMetric(metric, groupedMetrics, metricMetadata2, true, logger, nil, testCfg, emfCalcs)
  266. assert.Nil(t, err)
  267. assert.Len(t, groupedMetrics, 2)
  268. seenLogGroup1 := false
  269. seenLogGroup2 := false
  270. for _, group := range groupedMetrics {
  271. assert.Len(t, group.metrics, 1)
  272. expectedMetrics := map[string]*metricInfo{
  273. "int-gauge": {
  274. value: float64(1),
  275. unit: "Count",
  276. },
  277. }
  278. assert.Equal(t, expectedMetrics, group.metrics)
  279. expectedLabels := map[string]string{
  280. oTellibDimensionKey: "cloudwatch-otel",
  281. "label1": "value1",
  282. }
  283. assert.Equal(t, expectedLabels, group.labels)
  284. if group.metadata.logGroup == "log-group-2" {
  285. seenLogGroup2 = true
  286. } else if group.metadata.logGroup == "log-group-1" {
  287. seenLogGroup1 = true
  288. }
  289. }
  290. assert.True(t, seenLogGroup1)
  291. assert.True(t, seenLogGroup2)
  292. })
  293. t.Run("Duplicate metric names", func(t *testing.T) {
  294. emfCalcs := setupEmfCalculators()
  295. defer require.NoError(t, shutdownEmfCalculators(emfCalcs))
  296. groupedMetrics := make(map[any]*groupedMetric)
  297. generateMetrics := []pmetric.Metrics{
  298. generateTestGaugeMetric("foo", "int"),
  299. generateTestGaugeMetric("foo", "double"),
  300. }
  301. finalOtelMetrics := generateOtelTestMetrics(generateMetrics...)
  302. rms := finalOtelMetrics.ResourceMetrics()
  303. ilms := rms.At(0).ScopeMetrics()
  304. metrics := ilms.At(0).Metrics()
  305. assert.Equal(t, 2, metrics.Len())
  306. obs, logs := observer.New(zap.WarnLevel)
  307. obsLogger := zap.New(obs)
  308. for i := 0; i < metrics.Len(); i++ {
  309. err := addToGroupedMetric(metrics.At(i),
  310. groupedMetrics,
  311. generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, metrics.At(i).Type()),
  312. true, obsLogger,
  313. nil,
  314. testCfg,
  315. emfCalcs,
  316. )
  317. assert.Nil(t, err)
  318. }
  319. assert.Equal(t, 1, len(groupedMetrics))
  320. labels := map[string]string{
  321. oTellibDimensionKey: instrumentationLibName,
  322. "label1": "value1",
  323. }
  324. // Test output warning logs
  325. expectedLogs := []observer.LoggedEntry{
  326. {
  327. Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "Duplicate metric found"},
  328. Context: []zapcore.Field{
  329. zap.String("Name", "foo"),
  330. zap.Any("Labels", labels),
  331. },
  332. },
  333. }
  334. assert.Equal(t, 1, logs.Len())
  335. assert.Equal(t, expectedLogs, logs.AllUntimed())
  336. })
  337. t.Run("Unhandled metric type", func(t *testing.T) {
  338. emfCalcs := setupEmfCalculators()
  339. defer require.NoError(t, shutdownEmfCalculators(emfCalcs))
  340. groupedMetrics := make(map[any]*groupedMetric)
  341. md := pmetric.NewMetrics()
  342. rms := md.ResourceMetrics()
  343. metric := rms.AppendEmpty().ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
  344. metric.SetName("foo")
  345. metric.SetUnit("Count")
  346. obs, logs := observer.New(zap.WarnLevel)
  347. obsLogger := zap.New(obs)
  348. err := addToGroupedMetric(metric,
  349. groupedMetrics,
  350. generateTestMetricMetadata(namespace, timestamp, logGroup, logStreamName, instrumentationLibName, pmetric.MetricTypeEmpty),
  351. true,
  352. obsLogger,
  353. nil,
  354. testCfg,
  355. emfCalcs,
  356. )
  357. assert.Nil(t, err)
  358. assert.Equal(t, 0, len(groupedMetrics))
  359. // Test output warning logs
  360. expectedLogs := []observer.LoggedEntry{
  361. {
  362. Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "Unhandled metric data type."},
  363. Context: []zapcore.Field{
  364. zap.String("DataType", "Empty"),
  365. zap.String("Name", "foo"),
  366. zap.String("Unit", "Count"),
  367. },
  368. },
  369. }
  370. assert.Equal(t, 1, logs.Len())
  371. assert.Equal(t, expectedLogs, logs.AllUntimed())
  372. })
  373. }
  374. func TestAddKubernetesWrapper(t *testing.T) {
  375. t.Run("Test basic creation", func(t *testing.T) {
  376. dockerObj := struct {
  377. ContainerID string `json:"container_id"`
  378. }{
  379. ContainerID: "Container mccontainter the third",
  380. }
  381. expectedCreatedObj := struct {
  382. ContainerName string `json:"container_name"`
  383. Docker any `json:"docker"`
  384. Host string `json:"host"`
  385. PodID string `json:"pod_id"`
  386. }{
  387. ContainerName: "container mccontainer",
  388. Docker: dockerObj,
  389. Host: "hosty de la host",
  390. PodID: "Le id de Pod",
  391. }
  392. inputs := make(map[string]string)
  393. inputs["container_id"] = "Container mccontainter the third"
  394. inputs["container"] = "container mccontainer"
  395. inputs["NodeName"] = "hosty de la host"
  396. inputs["PodId"] = "Le id de Pod"
  397. jsonBytes, _ := json.Marshal(expectedCreatedObj)
  398. addKubernetesWrapper(inputs)
  399. assert.Equal(t, string(jsonBytes), inputs["kubernetes"], "The created and expected objects should be the same")
  400. })
  401. }
  402. func BenchmarkAddToGroupedMetric(b *testing.B) {
  403. emfCalcs := setupEmfCalculators()
  404. defer require.NoError(b, shutdownEmfCalculators(emfCalcs))
  405. generateMetrics := []pmetric.Metrics{
  406. generateTestGaugeMetric("int-gauge", intValueType),
  407. generateTestGaugeMetric("int-gauge", doubleValueType),
  408. generateTestHistogramMetric("histogram"),
  409. generateTestSumMetric("int-sum", intValueType),
  410. generateTestSumMetric("double-sum", doubleValueType),
  411. generateTestSummaryMetric("summary"),
  412. }
  413. finalOtelMetrics := generateOtelTestMetrics(generateMetrics...)
  414. rms := finalOtelMetrics.ResourceMetrics()
  415. metrics := rms.At(0).ScopeMetrics().At(0).Metrics()
  416. numMetrics := metrics.Len()
  417. logger := zap.NewNop()
  418. b.ResetTimer()
  419. for n := 0; n < b.N; n++ {
  420. groupedMetrics := make(map[any]*groupedMetric)
  421. for i := 0; i < numMetrics; i++ {
  422. metadata := generateTestMetricMetadata("namespace", int64(1596151098037), "log-group", "log-stream", "cloudwatch-otel", metrics.At(i).Type())
  423. err := addToGroupedMetric(metrics.At(i), groupedMetrics, metadata, true, logger, nil, testCfg, emfCalcs)
  424. assert.Nil(b, err)
  425. }
  426. }
  427. }
  428. func TestTranslateUnit(t *testing.T) {
  429. metric := pmetric.NewMetric()
  430. metric.SetName("writeIfNotExist")
  431. translator := &metricTranslator{
  432. metricDescriptor: map[string]MetricDescriptor{
  433. "writeIfNotExist": {
  434. MetricName: "writeIfNotExist",
  435. Unit: "Count",
  436. Overwrite: false,
  437. },
  438. "forceOverwrite": {
  439. MetricName: "forceOverwrite",
  440. Unit: "Count",
  441. Overwrite: true,
  442. },
  443. },
  444. }
  445. translateUnitCases := map[string]string{
  446. "Count": "Count",
  447. "ms": "Milliseconds",
  448. "s": "Seconds",
  449. "us": "Microseconds",
  450. "By": "Bytes",
  451. "Bi": "Bits",
  452. }
  453. for input, output := range translateUnitCases {
  454. t.Run(input, func(tt *testing.T) {
  455. metric.SetUnit(input)
  456. v := translateUnit(metric, translator.metricDescriptor)
  457. assert.Equal(t, output, v)
  458. })
  459. }
  460. metric.SetName("forceOverwrite")
  461. v := translateUnit(metric, translator.metricDescriptor)
  462. assert.Equal(t, "Count", v)
  463. }
  464. func generateTestMetricMetadata(namespace string, timestamp int64, logGroup, logStreamName, instrumentationScopeName string, metricType pmetric.MetricType) cWMetricMetadata {
  465. return cWMetricMetadata{
  466. receiver: prometheusReceiver,
  467. groupedMetricMetadata: groupedMetricMetadata{
  468. namespace: namespace,
  469. timestampMs: timestamp,
  470. logGroup: logGroup,
  471. logStream: logStreamName,
  472. metricDataType: metricType,
  473. },
  474. instrumentationScopeName: instrumentationScopeName,
  475. }
  476. }