metric_declaration_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package awsemfexporter
  4. import (
  5. "testing"
  6. "github.com/stretchr/testify/assert"
  7. "go.opentelemetry.io/collector/pdata/pmetric"
  8. "go.uber.org/zap"
  9. "go.uber.org/zap/zapcore"
  10. "go.uber.org/zap/zaptest/observer"
  11. )
  12. func TestLabelMatcherInit(t *testing.T) {
  13. lm := &LabelMatcher{
  14. LabelNames: []string{"label1", "label2"},
  15. Regex: ".+",
  16. }
  17. err := lm.init()
  18. assert.Nil(t, err)
  19. assert.Equal(t, ";", lm.Separator)
  20. assert.Equal(t, ".+", lm.Regex)
  21. assert.NotNil(t, lm.compiledRegex)
  22. lm.Separator = ""
  23. err = lm.init()
  24. assert.Nil(t, err)
  25. assert.Equal(t, ";", lm.Separator)
  26. lm.Separator = ","
  27. err = lm.init()
  28. assert.Nil(t, err)
  29. assert.Equal(t, ",", lm.Separator)
  30. lm.Regex = "a*"
  31. err = lm.init()
  32. assert.Nil(t, err)
  33. assert.Equal(t, "a*", lm.Regex)
  34. assert.NotNil(t, lm.compiledRegex)
  35. // Test error
  36. lm.Regex = ""
  37. err = lm.init()
  38. assert.NotNil(t, err)
  39. assert.EqualError(t, err, "regex not specified for label matcher")
  40. lm.LabelNames = []string{}
  41. lm.Regex = ".+"
  42. err = lm.init()
  43. assert.NotNil(t, err)
  44. assert.EqualError(t, err, "label matcher must have at least one label name specified")
  45. }
  46. func TestGetConcatenatedLabels(t *testing.T) {
  47. labels := map[string]string{
  48. "label1": "a",
  49. "label2": "b",
  50. "label3": "c",
  51. }
  52. testCases := []struct {
  53. testName string
  54. labelNames []string
  55. separator string
  56. expected string
  57. }{
  58. {
  59. "Single label w/ default separator",
  60. []string{"label1"},
  61. "",
  62. "a",
  63. },
  64. {
  65. "Multiple labels w/ default separator",
  66. []string{"label1", "label3", "label2"},
  67. "",
  68. "a;c;b",
  69. },
  70. {
  71. "Single label w/ custom separator",
  72. []string{"label1"},
  73. ",",
  74. "a",
  75. },
  76. {
  77. "Multiple labels w/ custom separator",
  78. []string{"label1", "label3", "label2"},
  79. ",",
  80. "a,c,b",
  81. },
  82. {
  83. "Multiple labels w/ missing label",
  84. []string{"label1", "label4", "label2"},
  85. "",
  86. "a;;b",
  87. },
  88. }
  89. for _, tc := range testCases {
  90. lm := &LabelMatcher{
  91. LabelNames: tc.labelNames,
  92. Separator: tc.separator,
  93. Regex: ".+",
  94. }
  95. err := lm.init()
  96. assert.Nil(t, err)
  97. t.Run(tc.testName, func(t *testing.T) {
  98. concatenatedLabels := lm.getConcatenatedLabels(labels)
  99. assert.Equal(t, tc.expected, concatenatedLabels)
  100. })
  101. }
  102. }
  103. func TestLabelMatcherMatches(t *testing.T) {
  104. testCases := []struct {
  105. testName string
  106. labels map[string]string
  107. labelMatcher *LabelMatcher
  108. expected bool
  109. }{
  110. {
  111. "Single label",
  112. map[string]string{
  113. "label1": "foo",
  114. },
  115. &LabelMatcher{
  116. LabelNames: []string{"label1"},
  117. Regex: "^fo+$",
  118. },
  119. true,
  120. },
  121. {
  122. "Single label, no match",
  123. map[string]string{
  124. "label1": "foo",
  125. },
  126. &LabelMatcher{
  127. LabelNames: []string{"label1"},
  128. Regex: "^f+$",
  129. },
  130. false,
  131. },
  132. {
  133. "Single label w/ missing label name",
  134. map[string]string{
  135. "label1": "foo",
  136. },
  137. &LabelMatcher{
  138. LabelNames: []string{"label2"},
  139. Regex: ".+",
  140. },
  141. false,
  142. },
  143. {
  144. "Multiple labels",
  145. map[string]string{
  146. "label1": "foo",
  147. "label2": "bar",
  148. "label3": "car",
  149. },
  150. &LabelMatcher{
  151. LabelNames: []string{"label1", "label3", "label2"},
  152. Regex: "fo+;car;b.*$",
  153. },
  154. true,
  155. },
  156. {
  157. "Multiple labels w/ custom separator",
  158. map[string]string{
  159. "label1": "foo",
  160. "label2": "bar",
  161. "label3": "car",
  162. },
  163. &LabelMatcher{
  164. LabelNames: []string{"label1", "label3", "label2"},
  165. Separator: ",",
  166. Regex: "fo+,car,b.*$",
  167. },
  168. true,
  169. },
  170. {
  171. "Multiple labels, no match",
  172. map[string]string{
  173. "label1": "foo",
  174. "label2": "bar",
  175. "label3": "car",
  176. },
  177. &LabelMatcher{
  178. LabelNames: []string{"label1", "label3", "label2"},
  179. Separator: ",",
  180. Regex: "fo+;car;b.*$",
  181. },
  182. false,
  183. },
  184. {
  185. "Multiple labels w/ missing label name",
  186. map[string]string{
  187. "label1": "foo",
  188. "label2": "bar",
  189. "label3": "car",
  190. },
  191. &LabelMatcher{
  192. LabelNames: []string{"label1", "label4", "label2"},
  193. Regex: "fo+;;b.*$",
  194. },
  195. true,
  196. },
  197. }
  198. for _, tc := range testCases {
  199. err := tc.labelMatcher.init()
  200. assert.Nil(t, err)
  201. t.Run(tc.testName, func(t *testing.T) {
  202. matches := tc.labelMatcher.Matches(tc.labels)
  203. assert.Equal(t, tc.expected, matches)
  204. })
  205. }
  206. }
  207. func TestMetricDeclarationInit(t *testing.T) {
  208. logger := zap.NewNop()
  209. t.Run("no dimensions", func(t *testing.T) {
  210. m := &MetricDeclaration{
  211. MetricNameSelectors: []string{"a", "b", "aa"},
  212. }
  213. err := m.init(logger)
  214. assert.Nil(t, err)
  215. assert.Equal(t, 3, len(m.metricRegexList))
  216. })
  217. t.Run("with dimensions", func(t *testing.T) {
  218. m := &MetricDeclaration{
  219. Dimensions: [][]string{
  220. {"foo"},
  221. {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
  222. },
  223. MetricNameSelectors: []string{"a.*", "b$", "aa+"},
  224. }
  225. err := m.init(logger)
  226. assert.Nil(t, err)
  227. assert.Equal(t, 3, len(m.metricRegexList))
  228. assert.Equal(t, 2, len(m.Dimensions))
  229. })
  230. // Test removal of dimension sets with more than 10 elements
  231. t.Run("dimension set with more than 10 elements", func(t *testing.T) {
  232. m := &MetricDeclaration{
  233. Dimensions: [][]string{
  234. {"foo"},
  235. {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"},
  236. },
  237. MetricNameSelectors: []string{"a.*", "b$", "aa+"},
  238. }
  239. obs, logs := observer.New(zap.WarnLevel)
  240. obsLogger := zap.New(obs)
  241. err := m.init(obsLogger)
  242. assert.Nil(t, err)
  243. assert.Equal(t, 3, len(m.metricRegexList))
  244. assert.Equal(t, 1, len(m.Dimensions))
  245. // Check logged warning message
  246. expectedLogs := []observer.LoggedEntry{{
  247. Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "Dropped dimension set: > 10 dimensions specified."},
  248. Context: []zapcore.Field{zap.String("dimensions", "a,b,c,d,e,f,g,h,i,j,k")},
  249. }}
  250. assert.Equal(t, 1, logs.Len())
  251. assert.Equal(t, expectedLogs, logs.AllUntimed())
  252. })
  253. // Test removal of duplicate dimensions within a dimension set, and removal of
  254. // duplicate dimension sets
  255. t.Run("remove duplicate dimensions and dimension sets", func(t *testing.T) {
  256. m := &MetricDeclaration{
  257. Dimensions: [][]string{
  258. {"a", "c", "b", "c"},
  259. {"c", "b", "a"},
  260. },
  261. MetricNameSelectors: []string{"a.*", "b$", "aa+"},
  262. }
  263. obs, logs := observer.New(zap.DebugLevel)
  264. obsLogger := zap.New(obs)
  265. err := m.init(obsLogger)
  266. assert.Nil(t, err)
  267. assert.Equal(t, 1, len(m.Dimensions))
  268. assert.Equal(t, []string{"a", "b", "c"}, m.Dimensions[0])
  269. // Check logged warning message
  270. expectedLogs := []observer.LoggedEntry{
  271. {
  272. Entry: zapcore.Entry{Level: zap.DebugLevel, Message: "Removed duplicates from dimension set."},
  273. Context: []zapcore.Field{zap.String("dimensions", "a,c,b,c")},
  274. },
  275. {
  276. Entry: zapcore.Entry{Level: zap.DebugLevel, Message: "Dropped dimension set: duplicated dimension set."},
  277. Context: []zapcore.Field{zap.String("dimensions", "c,b,a")},
  278. },
  279. }
  280. assert.Equal(t, 2, logs.Len())
  281. assert.Equal(t, expectedLogs, logs.AllUntimed())
  282. })
  283. // Test invalid metric declaration
  284. t.Run("invalid metric declaration", func(t *testing.T) {
  285. m := &MetricDeclaration{}
  286. err := m.init(logger)
  287. assert.NotNil(t, err)
  288. assert.EqualError(t, err, "invalid metric declaration: no metric name selectors defined")
  289. })
  290. // Test initialization of label matchers
  291. t.Run("initialization of label matchers", func(t *testing.T) {
  292. m := &MetricDeclaration{
  293. MetricNameSelectors: []string{"foo"},
  294. LabelMatchers: []*LabelMatcher{
  295. {
  296. LabelNames: []string{"label1", "label2"},
  297. Regex: ".+",
  298. },
  299. {
  300. LabelNames: []string{"label1", "label3"},
  301. Separator: ",",
  302. Regex: "a*",
  303. },
  304. },
  305. }
  306. err := m.init(logger)
  307. assert.Nil(t, err)
  308. assert.Equal(t, 2, len(m.LabelMatchers))
  309. assert.Equal(t, ";", m.LabelMatchers[0].Separator)
  310. assert.Equal(t, ".+", m.LabelMatchers[0].Regex)
  311. assert.NotNil(t, m.LabelMatchers[0].compiledRegex)
  312. assert.Equal(t, ",", m.LabelMatchers[1].Separator)
  313. assert.Equal(t, "a*", m.LabelMatchers[1].Regex)
  314. assert.NotNil(t, m.LabelMatchers[1].compiledRegex)
  315. })
  316. // Test error from label matcher initialization
  317. t.Run("label matcher init error", func(t *testing.T) {
  318. m := &MetricDeclaration{
  319. MetricNameSelectors: []string{"foo"},
  320. LabelMatchers: []*LabelMatcher{
  321. {
  322. LabelNames: []string{"label1", "label2"},
  323. Regex: ".+",
  324. },
  325. {
  326. LabelNames: []string{},
  327. Regex: ".+",
  328. },
  329. },
  330. }
  331. err := m.init(logger)
  332. assert.NotNil(t, err)
  333. assert.EqualError(t, err, "label matcher must have at least one label name specified")
  334. m = &MetricDeclaration{
  335. MetricNameSelectors: []string{"foo"},
  336. LabelMatchers: []*LabelMatcher{
  337. {
  338. LabelNames: []string{"label1", "label2"},
  339. },
  340. },
  341. }
  342. err = m.init(logger)
  343. assert.NotNil(t, err)
  344. assert.EqualError(t, err, "regex not specified for label matcher")
  345. })
  346. }
  347. func TestMetricDeclarationMatchesName(t *testing.T) {
  348. m := &MetricDeclaration{
  349. MetricNameSelectors: []string{"^a+$", "^b.*$", "^ac+a$"},
  350. }
  351. logger := zap.NewNop()
  352. err := m.init(logger)
  353. assert.Nil(t, err)
  354. assert.True(t, m.MatchesName("a"))
  355. assert.True(t, m.MatchesName("aa"))
  356. assert.True(t, m.MatchesName("aaaa"))
  357. assert.False(t, m.MatchesName("aaab"))
  358. assert.True(t, m.MatchesName("b"))
  359. assert.True(t, m.MatchesName("ba"))
  360. assert.False(t, m.MatchesName("c"))
  361. assert.True(t, m.MatchesName("aca"))
  362. assert.True(t, m.MatchesName("accca"))
  363. }
  364. func TestMetricDeclarationMatchesLabels(t *testing.T) {
  365. labels := map[string]string{
  366. "label1": "foo",
  367. "label2": "bar",
  368. "label3": "car",
  369. }
  370. testCases := []struct {
  371. testName string
  372. labelMatchers []*LabelMatcher
  373. expected bool
  374. }{
  375. {
  376. "Single label",
  377. []*LabelMatcher{
  378. {
  379. LabelNames: []string{"label1"},
  380. Regex: "^fo+$",
  381. },
  382. },
  383. true,
  384. },
  385. {
  386. "Multiple matchers w/ single label",
  387. []*LabelMatcher{
  388. {
  389. LabelNames: []string{"label1"},
  390. Regex: "food",
  391. },
  392. {
  393. LabelNames: []string{"label3"},
  394. Regex: "^c.*$",
  395. },
  396. },
  397. true,
  398. },
  399. {
  400. "Multiple matchers w/ single label, no match",
  401. []*LabelMatcher{
  402. {
  403. LabelNames: []string{"label1"},
  404. Regex: "food",
  405. },
  406. {
  407. LabelNames: []string{"label3"},
  408. Regex: "cat",
  409. },
  410. },
  411. false,
  412. },
  413. {
  414. "Multiple labels",
  415. []*LabelMatcher{
  416. {
  417. LabelNames: []string{"label1", "label3", "label2"},
  418. Regex: "fo+;car;b.*$",
  419. },
  420. },
  421. true,
  422. },
  423. {
  424. "Multiple labels w/ custom separator",
  425. []*LabelMatcher{
  426. {
  427. LabelNames: []string{"label1", "label3", "label2"},
  428. Separator: ",",
  429. Regex: "fo+,car,b.*$",
  430. },
  431. },
  432. true,
  433. },
  434. {
  435. "Multiple matchers w/ multiple labels",
  436. []*LabelMatcher{
  437. {
  438. LabelNames: []string{"label1", "label3", "label2"},
  439. Separator: ",",
  440. Regex: "fo+,car,b.*$",
  441. },
  442. {
  443. LabelNames: []string{"label3"},
  444. Regex: "fat",
  445. },
  446. },
  447. true,
  448. },
  449. {
  450. "Multiple matchers w/ multiple labels, no match",
  451. []*LabelMatcher{
  452. {
  453. LabelNames: []string{"label1", "label3", "label2"},
  454. Separator: ",",
  455. Regex: "fo+,cat,b.*$",
  456. },
  457. {
  458. LabelNames: []string{"label3"},
  459. Regex: "fat",
  460. },
  461. },
  462. false,
  463. },
  464. }
  465. logger := zap.NewNop()
  466. metric := pmetric.NewMetric()
  467. metric.SetName("a")
  468. for _, tc := range testCases {
  469. m := MetricDeclaration{
  470. MetricNameSelectors: []string{"^a+$"},
  471. LabelMatchers: tc.labelMatchers,
  472. }
  473. t.Run(tc.testName, func(t *testing.T) {
  474. err := m.init(logger)
  475. assert.Nil(t, err)
  476. matches := m.MatchesLabels(labels)
  477. assert.Equal(t, tc.expected, matches)
  478. })
  479. }
  480. }
  481. func TestExtractDimensions(t *testing.T) {
  482. testCases := []struct {
  483. testName string
  484. dimensions [][]string
  485. labels map[string]string
  486. extractedDimensions [][]string
  487. }{
  488. {
  489. "matches single dimension set exactly",
  490. [][]string{{"a", "b"}},
  491. map[string]string{
  492. "a": "foo",
  493. "b": "bar",
  494. },
  495. [][]string{{"a", "b"}},
  496. },
  497. {
  498. "matches subset of single dimension set",
  499. [][]string{{"a"}},
  500. map[string]string{
  501. "a": "foo",
  502. "b": "bar",
  503. },
  504. [][]string{{"a"}},
  505. },
  506. {
  507. "does not match single dimension set",
  508. [][]string{{"a", "b"}},
  509. map[string]string{
  510. "b": "bar",
  511. },
  512. nil,
  513. },
  514. {
  515. "matches multiple dimension sets",
  516. [][]string{{"a", "b"}, {"a"}},
  517. map[string]string{
  518. "a": "foo",
  519. "b": "bar",
  520. },
  521. [][]string{{"a", "b"}, {"a"}},
  522. },
  523. {
  524. "matches one of multiple dimension sets",
  525. [][]string{{"a", "b"}, {"a"}},
  526. map[string]string{
  527. "a": "foo",
  528. },
  529. [][]string{{"a"}},
  530. },
  531. {
  532. "no dimensions",
  533. [][]string{},
  534. map[string]string{
  535. "a": "foo",
  536. },
  537. nil,
  538. },
  539. {
  540. "empty dimension set",
  541. [][]string{{}},
  542. map[string]string{
  543. "a": "foo",
  544. },
  545. [][]string{{}},
  546. },
  547. }
  548. logger := zap.NewNop()
  549. for _, tc := range testCases {
  550. m := MetricDeclaration{
  551. Dimensions: tc.dimensions,
  552. MetricNameSelectors: []string{"foo"},
  553. }
  554. t.Run(tc.testName, func(t *testing.T) {
  555. err := m.init(logger)
  556. assert.Nil(t, err)
  557. dimensions := m.ExtractDimensions(tc.labels)
  558. assert.Equal(t, tc.extractedDimensions, dimensions)
  559. })
  560. }
  561. }