From b7a0000e3a438e592605057976be21041610d1ce Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Mon, 26 Jun 2023 20:15:43 +0200 Subject: [PATCH] Add support for JMX TabularData that uses a CompositeData key (#814) * [feature] Add support for JMX TabularData that uses a CompositeData key Signed-off-by: Adam Retter * [test] Add Integration Test for support for JMX TabularData that uses a CompositeData key Signed-off-by: Adam Retter --------- Signed-off-by: Adam Retter Signed-off-by: Doug Hoard --- .../java/io/prometheus/jmx/JmxScraper.java | 16 ++- .../java/io/prometheus/jmx/ExistDbMXBean.java | 124 ++++++++++++++++++ .../io/prometheus/jmx/JmxCollectorTest.java | 8 ++ .../jmx/test/CompositeKeyDataTest.java | 80 +++++++++++ .../JavaAgent/application.sh | 6 + .../JavaAgent/exporter.yaml | 2 + .../Standalone/application.sh | 13 ++ .../Standalone/exporter.sh | 5 + .../Standalone/exporter.yaml | 3 + .../java/io/prometheus/jmx/ExistDbMXBean.java | 124 ++++++++++++++++++ .../prometheus/jmx/JmxExampleApplication.java | 2 + 11 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 collector/src/test/java/io/prometheus/jmx/ExistDbMXBean.java create mode 100644 integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/CompositeKeyDataTest.java create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/application.sh create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/exporter.yaml create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/application.sh create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.sh create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.yaml create mode 100644 integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/ExistDbMXBean.java diff --git a/collector/src/main/java/io/prometheus/jmx/JmxScraper.java b/collector/src/main/java/io/prometheus/jmx/JmxScraper.java index 90930245..e8d57736 100644 --- a/collector/src/main/java/io/prometheus/jmx/JmxScraper.java +++ b/collector/src/main/java/io/prometheus/jmx/JmxScraper.java @@ -274,12 +274,24 @@ private void processBeanValue( for (String idx : rowKeys) { Object obj = composite.get(idx); if (obj != null) { + // Nested tabulardata will repeat the 'key' label, so // append a suffix to distinguish each. while (l2s.containsKey(idx)) { - idx = idx + "_"; + idx = idx + "_"; + } + + if (obj instanceof CompositeData) { + // TabularData key is a composite key + CompositeData compositeKey = (CompositeData) obj; + CompositeType ct = compositeKey.getCompositeType(); + for (final String compositeKeyIdx : ct.keySet()) { + l2s.put(idx + "_" + compositeKeyIdx, compositeKey.get(compositeKeyIdx).toString()); + } + } else { + // TabularData key is an Open type key + l2s.put(idx, obj.toString()); } - l2s.put(idx, obj.toString()); } } for(String valueIdx : valueKeys) { diff --git a/collector/src/test/java/io/prometheus/jmx/ExistDbMXBean.java b/collector/src/test/java/io/prometheus/jmx/ExistDbMXBean.java new file mode 100644 index 00000000..40dd9bcb --- /dev/null +++ b/collector/src/test/java/io/prometheus/jmx/ExistDbMXBean.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022-2023 The Prometheus jmx_exporter Authors + * + * Licensed 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 io.prometheus.jmx; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import java.util.Map; +import java.util.TreeMap; + +public interface ExistDbMXBean { + Map getRunningQueries(); + + class QueryKey implements Comparable { + private final int id; + private final String path; + + public QueryKey(final int id, final String path) { + this.id = id; + this.path = path; + } + + public int getId() { + return id; + } + + public String getPath() { + return path; + } + + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + QueryKey queryKey = (QueryKey) other; + if (id != queryKey.id) { + return false; + } + return path.equals(queryKey.path); + } + + public int hashCode() { + int result = id; + result = 31 * result + path.hashCode(); + return result; + } + + public int compareTo(final QueryKey other) { + if (other == null) { + return 1; + } + + return path.compareTo(other.path); + } + } + + class RunningQuery { + private final int id; + private final String path; + + private final long startedAtTime; + + public RunningQuery(final int id, final String path, final long startedAtTime) { + this.id = id; + this.path = path; + this.startedAtTime = startedAtTime; + } + + public int getId() { + return id; + } + + public String getPath() { + return path; + } + + public long getStartedAtTime() { + return startedAtTime; + } + + public long getElapsedTime() { + return System.currentTimeMillis() - startedAtTime; + } + } +} + +class ExistDb implements ExistDbMXBean { + + public static void registerBean(MBeanServer mbs) + throws javax.management.JMException { + ObjectName mxbeanName = new ObjectName( + "org.exist.management.exist:type=ProcessReport"); + ExistDb mxbean = new ExistDb(); + mbs.registerMBean(mxbean, mxbeanName); + } + + public Map getRunningQueries() { + final Map queries = new TreeMap(); + + final RunningQuery runningQuery1 = new RunningQuery(1, "/db/query1.xq", System.currentTimeMillis()); + final RunningQuery runningQuery2 = new RunningQuery(2, "/db/query2.xq", System.currentTimeMillis()); + + queries.put(new QueryKey(runningQuery1.getId(), runningQuery1.getPath()), runningQuery1); + queries.put(new QueryKey(runningQuery2.getId(), runningQuery2.getPath()), runningQuery2); + + return queries; + } +} diff --git a/collector/src/test/java/io/prometheus/jmx/JmxCollectorTest.java b/collector/src/test/java/io/prometheus/jmx/JmxCollectorTest.java index bd5c667b..6158984b 100644 --- a/collector/src/test/java/io/prometheus/jmx/JmxCollectorTest.java +++ b/collector/src/test/java/io/prometheus/jmx/JmxCollectorTest.java @@ -36,6 +36,7 @@ public static void OneTimeSetUp() throws Exception { CassandraMetrics.registerBean(mbs); Hadoop.registerBean(mbs); HadoopDataNode.registerBean(mbs); + ExistDb.registerBean(mbs); BeanWithEnum.registerBean(mbs); TomcatServlet.registerBean(mbs); @@ -168,6 +169,13 @@ public void nestedTabularDataTest() throws Exception { assertEquals(338, registry.getSampleValue("Hadoop_DataNodeInfo_DatanodeNetworkCounts", new String[]{"service", "key", "key_"}, new String[]{"DataNode", "1.2.3.4", "networkErrors"}), .001); } + @Test + public void tabularDataCompositeKeyTest() throws Exception { + JmxCollector jc = new JmxCollector("---").register(registry); + assertEquals(1, registry.getSampleValue("org_exist_management_exist_ProcessReport_RunningQueries_id", new String[]{"key_id", "key_path"}, new String[]{"1", "/db/query1.xq"}), .001); + assertEquals(2, registry.getSampleValue("org_exist_management_exist_ProcessReport_RunningQueries_id", new String[]{"key_id", "key_path"}, new String[]{"2", "/db/query2.xq"}), .001); + } + @Test public void testWhitelist() throws Exception { JmxCollector jc = new JmxCollector("\n---\nwhitelistObjectNames:\n- java.lang:*\n- java.lang:*\n- org.apache.cassandra.concurrent:*".replace('`','"')).register(registry); diff --git a/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/CompositeKeyDataTest.java b/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/CompositeKeyDataTest.java new file mode 100644 index 00000000..e2cffb4d --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/CompositeKeyDataTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Prometheus jmx_exporter Authors + * + * Licensed 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 io.prometheus.jmx.test; + +import io.prometheus.jmx.test.support.ContentConsumer; +import io.prometheus.jmx.test.support.HealthyRequest; +import io.prometheus.jmx.test.support.HealthyResponse; +import io.prometheus.jmx.test.support.MetricsRequest; +import io.prometheus.jmx.test.support.MetricsResponse; +import io.prometheus.jmx.test.support.OpenMetricsRequest; +import io.prometheus.jmx.test.support.OpenMetricsResponse; +import io.prometheus.jmx.test.support.PrometheusMetricsRequest; +import io.prometheus.jmx.test.support.PrometheusMetricsResponse; +import org.antublue.test.engine.api.TestEngine; + +import java.util.Collection; + +import static io.prometheus.jmx.test.support.MetricsAssertions.assertThatMetricIn; +import static io.prometheus.jmx.test.support.RequestResponseAssertions.assertThatResponseForRequest; + +public class CompositeKeyDataTest extends BaseTest implements ContentConsumer { + + @TestEngine.Test + public void testHealthy() { + assertThatResponseForRequest(new HealthyRequest(testState.httpClient())) + .isSuperset(HealthyResponse.RESULT_200); + } + + @TestEngine.Test + public void testMetrics() { + assertThatResponseForRequest(new MetricsRequest(testState.httpClient())) + .isSuperset(MetricsResponse.RESULT_200) + .dispatch(this); + } + + @TestEngine.Test + public void testMetricsOpenMetricsFormat() { + assertThatResponseForRequest(new OpenMetricsRequest(testState.httpClient())) + .isSuperset(OpenMetricsResponse.RESULT_200) + .dispatch(this); + } + + @TestEngine.Test + public void testMetricsPrometheusFormat() { + assertThatResponseForRequest(new PrometheusMetricsRequest(testState.httpClient())) + .isSuperset(PrometheusMetricsResponse.RESULT_200) + .dispatch(this); + } + + @Override + public void accept(String content) { + Collection metrics = MetricsParser.parse(content); + + assertThatMetricIn(metrics) + .withName("org_exist_management_exist_ProcessReport_RunningQueries_id") + .withLabel("key_id", "1") + .withLabel("key_path", "/db/query1.xq") + .exists(); + + assertThatMetricIn(metrics) + .withName("org_exist_management_exist_ProcessReport_RunningQueries_id") + .withLabel("key_id", "2") + .withLabel("key_path", "/db/query2.xq") + .exists(); + } +} diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/application.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/application.sh new file mode 100644 index 00000000..9e5717d6 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/application.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -javaagent:jmx_prometheus_javaagent.jar=8888:exporter.yaml \ + -jar jmx_example_application.jar \ No newline at end of file diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/exporter.yaml new file mode 100644 index 00000000..cb19fee9 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/JavaAgent/exporter.yaml @@ -0,0 +1,2 @@ +rules: + - pattern: ".*" \ No newline at end of file diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/application.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/application.sh new file mode 100644 index 00000000..80c2b344 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/application.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -Dcom.sun.management.jmxremote=true \ + -Dcom.sun.management.jmxremote.authenticate=false \ + -Dcom.sun.management.jmxremote.local.only=false \ + -Dcom.sun.management.jmxremote.port=9999 \ + -Dcom.sun.management.jmxremote.registry.ssl=false \ + -Dcom.sun.management.jmxremote.rmi.port=9999 \ + -Dcom.sun.management.jmxremote.ssl.need.client.auth=false \ + -Dcom.sun.management.jmxremote.ssl=false \ + -jar jmx_example_application.jar \ No newline at end of file diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.sh new file mode 100644 index 00000000..a04f3b63 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -jar jmx_prometheus_httpserver.jar 8888 exporter.yaml \ No newline at end of file diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.yaml new file mode 100644 index 00000000..67cbff69 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/CompositeKeyDataTest/Standalone/exporter.yaml @@ -0,0 +1,3 @@ +hostPort: application:9999 +rules: + - pattern: ".*" \ No newline at end of file diff --git a/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/ExistDbMXBean.java b/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/ExistDbMXBean.java new file mode 100644 index 00000000..40dd9bcb --- /dev/null +++ b/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/ExistDbMXBean.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022-2023 The Prometheus jmx_exporter Authors + * + * Licensed 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 io.prometheus.jmx; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import java.util.Map; +import java.util.TreeMap; + +public interface ExistDbMXBean { + Map getRunningQueries(); + + class QueryKey implements Comparable { + private final int id; + private final String path; + + public QueryKey(final int id, final String path) { + this.id = id; + this.path = path; + } + + public int getId() { + return id; + } + + public String getPath() { + return path; + } + + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + QueryKey queryKey = (QueryKey) other; + if (id != queryKey.id) { + return false; + } + return path.equals(queryKey.path); + } + + public int hashCode() { + int result = id; + result = 31 * result + path.hashCode(); + return result; + } + + public int compareTo(final QueryKey other) { + if (other == null) { + return 1; + } + + return path.compareTo(other.path); + } + } + + class RunningQuery { + private final int id; + private final String path; + + private final long startedAtTime; + + public RunningQuery(final int id, final String path, final long startedAtTime) { + this.id = id; + this.path = path; + this.startedAtTime = startedAtTime; + } + + public int getId() { + return id; + } + + public String getPath() { + return path; + } + + public long getStartedAtTime() { + return startedAtTime; + } + + public long getElapsedTime() { + return System.currentTimeMillis() - startedAtTime; + } + } +} + +class ExistDb implements ExistDbMXBean { + + public static void registerBean(MBeanServer mbs) + throws javax.management.JMException { + ObjectName mxbeanName = new ObjectName( + "org.exist.management.exist:type=ProcessReport"); + ExistDb mxbean = new ExistDb(); + mbs.registerMBean(mxbean, mxbeanName); + } + + public Map getRunningQueries() { + final Map queries = new TreeMap(); + + final RunningQuery runningQuery1 = new RunningQuery(1, "/db/query1.xq", System.currentTimeMillis()); + final RunningQuery runningQuery2 = new RunningQuery(2, "/db/query2.xq", System.currentTimeMillis()); + + queries.put(new QueryKey(runningQuery1.getId(), runningQuery1.getPath()), runningQuery1); + queries.put(new QueryKey(runningQuery2.getId(), runningQuery2.getPath()), runningQuery2); + + return queries; + } +} diff --git a/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/JmxExampleApplication.java b/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/JmxExampleApplication.java index adc0306e..aedc7608 100644 --- a/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/JmxExampleApplication.java +++ b/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/JmxExampleApplication.java @@ -37,6 +37,8 @@ public static void main(String[] args) throws Exception { ObjectName autoIncrementingMBan = new ObjectName("io.prometheus.jmx:type=autoIncrementing"); server.registerMBean(new AutoIncrementing(), autoIncrementingMBan); + ExistDb.registerBean(server); + System.out.println( String.format("%s | %s | INFO | %s | %s", SIMPLE_DATE_FORMAT.format(new Date()),