This is an automated email from the ASF dual-hosted git repository.
noob-se7en pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new ac8b9011c72 [Testing] Add unit tests to verify Prometheus JMX template
regexp patterns (#18414)
ac8b9011c72 is described below
commit ac8b9011c729af69152399ebdfbc83586f56ac01
Author: Akanksha kedia <[email protected]>
AuthorDate: Fri May 8 18:09:36 2026 +0530
[Testing] Add unit tests to verify Prometheus JMX template regexp patterns
(#18414)
* [Testing] Add unit tests to verify Prometheus JMX template regexp patterns
Adds PrometheusTemplateRegexpTest to validate the regexp patterns defined
in the JMX exporter YAML config files under
docker/images/pinot/etc/jmx_prometheus_javaagent/configs/.
Tests include:
- Parameterized validity check: all patterns in broker.yml, server.yml,
controller.yml, minion.yml, and pinot.yml compile as valid Java regexps
- Targeted correctness tests for key patterns
(broker/server/controller/minion),
verifying that expected JMX metric name strings match and that capture
groups produce the expected table, tableType, database, taskType, and
measurement labels
Fixes #13588
* test: load YAML patterns in targeted regexp tests instead of inlining
Replace hardcoded inline pattern strings in targeted tests with calls to
a new loadPattern(configFile, ruleIndex) helper that reads the pattern
directly from the YAML file under test. This ensures tests always validate
the live pattern — editing the YAML without updating the test will now
surface a failure.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* test: key YAML pattern lookup off rule name instead of numeric index
Replace loadPattern(file, index) with loadPatternByName(file, name),
which finds the rule whose YAML `name` field matches (e.g.
"pinot_$1_$6_$7").
Inserting or reordering a rule in the config files no longer silently
shifts indices and causes tests to match a wrong rule.
Addresses review comment from noob-se7en on apache/pinot#18414.
---------
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
---
.../prometheus/PrometheusTemplateRegexpTest.java | 302 +++++++++++++++++++++
1 file changed, 302 insertions(+)
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/metrics/prometheus/PrometheusTemplateRegexpTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/metrics/prometheus/PrometheusTemplateRegexpTest.java
new file mode 100644
index 00000000000..c7ad6eff399
--- /dev/null
+++
b/pinot-common/src/test/java/org/apache/pinot/common/metrics/prometheus/PrometheusTemplateRegexpTest.java
@@ -0,0 +1,302 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.common.metrics.prometheus;
+
+import java.io.FileReader;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.yaml.snakeyaml.Yaml;
+
+
+/**
+ * Verifies that the Prometheus JMX template regexp patterns defined in the
docker config YAML files
+ * are valid Java regexps and match expected JMX metric name strings with
correct capture groups.
+ *
+ * Config files under test:
docker/images/pinot/etc/jmx_prometheus_javaagent/configs/
+ *
+ * @see <a href="https://github.com/apache/pinot/issues/13588">Issue #13588</a>
+ */
+public class PrometheusTemplateRegexpTest {
+
+ private static final String CONFIG_BASE_PATH =
+ "../docker/images/pinot/etc/jmx_prometheus_javaagent/configs";
+
+ @DataProvider(name = "configFiles")
+ public Object[][] configFiles() {
+ return new Object[][]{
+ {"broker.yml"},
+ {"server.yml"},
+ {"controller.yml"},
+ {"minion.yml"},
+ {"pinot.yml"}
+ };
+ }
+
+ /**
+ * Verifies every pattern in each YAML config file compiles as a valid Java
regexp.
+ */
+ @Test(dataProvider = "configFiles")
+ public void testAllPatternsAreValidRegexp(String configFile)
+ throws Exception {
+ List<String> patterns = extractPatterns(CONFIG_BASE_PATH + "/" +
configFile);
+ Assert.assertFalse(patterns.isEmpty(),
+ "Expected at least one rule pattern in " + configFile);
+ for (String patternStr : patterns) {
+ try {
+ Pattern.compile(patternStr);
+ } catch (PatternSyntaxException e) {
+ Assert.fail(
+ "Invalid regexp in " + configFile + ": [" + patternStr + "] - " +
e.getDescription());
+ }
+ }
+ }
+
+ // ---- Broker patterns ----
+
+ /**
+ * broker.yml: meters/timers scoped to tableNameWithType.
+ * e.g. pinot.broker.myTable_REALTIME.queries
+ */
+ @Test
+ public void testBrokerTableWithTypeMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("broker.yml", "pinot_$1_$6_$7");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"BrokerMetrics\", "
+ + "name=\"pinot.broker.myTable_REALTIME.queries\"><>Count");
+ Assert.assertTrue(m.matches(), "Pattern should match broker table-scoped
meter");
+ Assert.assertEquals(m.group(1), "broker");
+ Assert.assertEquals(m.group(4), "myTable");
+ Assert.assertEquals(m.group(5), "REALTIME");
+ Assert.assertEquals(m.group(6), "queries");
+ Assert.assertEquals(m.group(7), "Count");
+ }
+
+ /**
+ * broker.yml: meters/timers scoped to tableNameWithType with database
prefix.
+ * e.g. pinot.broker.myDb.myTable_OFFLINE.queries
+ */
+ @Test
+ public void testBrokerTableWithTypeMeterPatternWithDatabase()
+ throws Exception {
+ String pattern = loadPatternByName("broker.yml", "pinot_$1_$6_$7");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"BrokerMetrics\", "
+ + "name=\"pinot.broker.myDb.myTable_OFFLINE.queries\"><>Count");
+ Assert.assertTrue(m.matches(), "Pattern should match broker table-scoped
meter with database prefix");
+ Assert.assertEquals(m.group(1), "broker");
+ Assert.assertEquals(m.group(3), "myDb");
+ Assert.assertEquals(m.group(4), "myTable");
+ Assert.assertEquals(m.group(5), "OFFLINE");
+ Assert.assertEquals(m.group(6), "queries");
+ Assert.assertEquals(m.group(7), "Count");
+ }
+
+ /**
+ * broker.yml: meters/timers scoped to rawTableName.
+ * e.g. pinot.broker.myTable.queries
+ */
+ @Test
+ public void testBrokerRawTableNameMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("broker.yml", "pinot_$1_$5_$6");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"BrokerMetrics\", "
+ + "name=\"pinot.broker.myTable.queries\"><>Count");
+ Assert.assertTrue(m.matches(), "Pattern should match broker raw-table-name
meter");
+ Assert.assertEquals(m.group(1), "broker");
+ Assert.assertEquals(m.group(4), "myTable");
+ Assert.assertEquals(m.group(5), "queries");
+ Assert.assertEquals(m.group(6), "Count");
+ }
+
+ /**
+ * broker.yml: global gauge/meter/timer (no table scope).
+ * e.g. pinot.broker.totalDocuments
+ */
+ @Test
+ public void testBrokerGlobalMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("broker.yml", "pinot_broker_$1_$2");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"BrokerMetrics\", "
+ + "name=\"pinot.broker.totalDocuments\"><>Value");
+ Assert.assertTrue(m.matches(), "Pattern should match global broker gauge");
+ Assert.assertEquals(m.group(1), "totalDocuments");
+ Assert.assertEquals(m.group(2), "Value");
+ }
+
+ // ---- Server patterns ----
+
+ /**
+ * server.yml: meters/timers scoped to tableNameWithType.
+ * e.g. pinot.server.myTable_OFFLINE.segmentUploadFailure
+ */
+ @Test
+ public void testServerTableWithTypeMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("server.yml", "pinot_server_$5_$6");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"ServerMetrics\", "
+ +
"name=\"pinot.server.myTable_OFFLINE.segmentUploadFailure\"><>Count");
+ Assert.assertTrue(m.matches(), "Pattern should match server table-scoped
meter");
+ Assert.assertEquals(m.group(3), "myTable");
+ Assert.assertEquals(m.group(4), "OFFLINE");
+ Assert.assertEquals(m.group(5), "segmentUploadFailure");
+ Assert.assertEquals(m.group(6), "Count");
+ }
+
+ /**
+ * server.yml: gauge scoped to tableNameWithType with partition.
+ * e.g. pinot.server.queries.myTable_REALTIME.3
+ */
+ @Test
+ public void testServerTableWithTypeAndPartitionGaugePattern()
+ throws Exception {
+ String pattern = loadPatternByName("server.yml", "pinot_server_$1_$7");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"ServerMetrics\", "
+ + "name=\"pinot.server.queries.myTable_REALTIME.3\"><>Value");
+ Assert.assertTrue(m.matches(), "Pattern should match server table-scoped
gauge with partition");
+ Assert.assertEquals(m.group(1), "queries");
+ Assert.assertEquals(m.group(4), "myTable");
+ Assert.assertEquals(m.group(5), "REALTIME");
+ Assert.assertEquals(m.group(6), "3");
+ Assert.assertEquals(m.group(7), "Value");
+ }
+
+ // ---- Controller patterns ----
+
+ /**
+ * controller.yml: minion task-type gauge.
+ * e.g. pinot.controller.numMinionTasksInProgress.SegmentGenerationAndPush
+ */
+ @Test
+ public void testControllerTaskTypeGaugePattern()
+ throws Exception {
+ String pattern = loadPatternByName("controller.yml",
"pinot_controller_$1_$3");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"ControllerMetrics\", "
+ +
"name=\"pinot.controller.numMinionTasksInProgress.SegmentGenerationAndPush\"><>Value");
+ Assert.assertTrue(m.matches(), "Pattern should match controller task-type
gauge");
+ Assert.assertEquals(m.group(1), "numMinionTasksInProgress");
+ Assert.assertEquals(m.group(2), "SegmentGenerationAndPush");
+ Assert.assertEquals(m.group(3), "Value");
+ }
+
+ /**
+ * controller.yml: meters/timers scoped to tableNameWithType.
+ * e.g. pinot.controller.myTable_OFFLINE.segmentUploadFailure
+ */
+ @Test
+ public void testControllerTableWithTypeMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("controller.yml", "pinot_$1_$6_$7");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"ControllerMetrics\", "
+ +
"name=\"pinot.controller.myTable_OFFLINE.segmentUploadFailure\"><>Count");
+ Assert.assertTrue(m.matches(), "Pattern should match controller
table-scoped meter");
+ Assert.assertEquals(m.group(1), "controller");
+ Assert.assertEquals(m.group(4), "myTable");
+ Assert.assertEquals(m.group(5), "OFFLINE");
+ Assert.assertEquals(m.group(6), "segmentUploadFailure");
+ Assert.assertEquals(m.group(7), "Count");
+ }
+
+ // ---- Minion patterns ----
+
+ /**
+ * minion.yml: meters/timers scoped to tableNameWithType and taskType.
+ * e.g.
pinot.minion.myTable_REALTIME.SegmentGenerationAndPush.segmentUploadFailure
+ */
+ @Test
+ public void testMinionTableWithTypeAndTaskTypeMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("minion.yml", "pinot_minion_$6_$7");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"MinionMetrics\", "
+ +
"name=\"pinot.minion.myTable_REALTIME.SegmentGenerationAndPush.segmentUploadFailure\"><>Count");
+ Assert.assertTrue(m.matches(), "Pattern should match minion table +
taskType scoped meter");
+ Assert.assertEquals(m.group(3), "myTable");
+ Assert.assertEquals(m.group(4), "REALTIME");
+ Assert.assertEquals(m.group(5), "SegmentGenerationAndPush");
+ Assert.assertEquals(m.group(6), "segmentUploadFailure");
+ Assert.assertEquals(m.group(7), "Count");
+ }
+
+ /**
+ * minion.yml: meters/timers accepting either rawTableName or
tableNameWithType.
+ * e.g. pinot.minion.myTable.queries
+ */
+ @Test
+ public void testMinionTableOrIdScopedMeterPattern()
+ throws Exception {
+ String pattern = loadPatternByName("minion.yml", "pinot_minion_$2_$3");
+ Matcher m = Pattern.compile(pattern).matcher(
+ "\"org.apache.pinot.common.metrics\"<type=\"MinionMetrics\", "
+ + "name=\"pinot.minion.myTable.numberOfSegmentsQueued\"><>Value");
+ Assert.assertTrue(m.matches(), "Pattern should match minion table/id
scoped meter");
+ Assert.assertEquals(m.group(1), "myTable");
+ Assert.assertEquals(m.group(2), "numberOfSegmentsQueued");
+ Assert.assertEquals(m.group(3), "Value");
+ }
+
+ /**
+ * Returns the pattern string for the rule whose {@code name} field equals
{@code ruleName}.
+ * Keying off the rule name survives YAML rule reorderings — inserting or
moving a rule in
+ * the config file will not silently shift the index and cause this test to
assert against
+ * the wrong pattern.
+ */
+ @SuppressWarnings("unchecked")
+ private String loadPatternByName(String configFile, String ruleName)
+ throws Exception {
+ Yaml yaml = new Yaml();
+ try (FileReader reader = new FileReader(CONFIG_BASE_PATH + "/" +
configFile)) {
+ Map<String, Object> config = yaml.load(reader);
+ List<Map<String, Object>> rules = (List<Map<String, Object>>)
config.get("rules");
+ return rules.stream()
+ .filter(rule -> ruleName.equals(rule.get("name")))
+ .map(rule -> (String) rule.get("pattern"))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ "No rule with name '" + ruleName + "' found in " + configFile));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<String> extractPatterns(String filePath)
+ throws Exception {
+ Yaml yaml = new Yaml();
+ try (FileReader reader = new FileReader(filePath)) {
+ Map<String, Object> config = yaml.load(reader);
+ List<Map<String, Object>> rules = (List<Map<String, Object>>)
config.get("rules");
+ return rules.stream()
+ .filter(rule -> rule.containsKey("pattern"))
+ .map(rule -> (String) rule.get("pattern"))
+ .collect(Collectors.toList());
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]