diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/JmxPerformanceCounterLoader.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/JmxPerformanceCounterLoader.java new file mode 100644 index 00000000000..446214648e5 --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/JmxPerformanceCounterLoader.java @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.agent.internal.init; + +import static com.microsoft.applicationinsights.agent.internal.diagnostics.MsgId.CUSTOM_JMX_METRIC_ERROR; + +import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.Strings; +import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; +import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxAttributeData; +import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxDataFetcher; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +public class JmxPerformanceCounterLoader { + + private static final Logger logger = LoggerFactory.getLogger(JmxPerformanceCounterLoader.class); + + private static final String METRIC_NAME_REGEXP = "[a-zA-Z0-9_.-/]+"; + private static final String INVALID_CHARACTER_REGEXP = "[^a-zA-Z0-9_.-/]"; + + private JmxPerformanceCounterLoader() {} + + /** + * The method will load the Jmx performance counters requested by the user to the system: 1. Build + * a map where the key is the Jmx object name and the value is a list of requested attributes. 2. + * Go through all the requested Jmx counters: a. If the object name is not in the map, add it with + * an empty list Else get the list b. Add the attribute to the list. 3. Go through the map For + * every entry (object name and attributes) to build a meter per attribute & for each meter + * register a callback to report the metric value. + */ + public static void loadCustomJmxPerfCounters(List jmxXmlElements) { + HashMap> data = new HashMap<>(); + + // Build a map of object name to its requested attributes + for (Configuration.JmxMetric jmxElement : jmxXmlElements) { + Collection collection = + data.computeIfAbsent(jmxElement.objectName, k -> new ArrayList<>()); + + if (Strings.isNullOrEmpty(jmxElement.objectName)) { + try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { + logger.error("JMX object name is empty, will be ignored"); + } + continue; + } + + if (Strings.isNullOrEmpty(jmxElement.attribute)) { + try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { + logger.error("JMX attribute is empty for '{}'", jmxElement.objectName); + } + continue; + } + + if (Strings.isNullOrEmpty(jmxElement.name)) { + try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { + logger.error("JMX name is empty for '{}', will be ignored", jmxElement.objectName); + } + continue; + } + + collection.add(new JmxAttributeData(jmxElement.name, jmxElement.attribute)); + } + + createMeterPerAttribute(data); + } + + // Create a meter for each attribute & declare the callback that reports the metric in the meter. + public static void createMeterPerAttribute( + Map> objectAndAttributesMap) { + for (Map.Entry> entry : + objectAndAttributesMap.entrySet()) { + String objectName = entry.getKey(); + + for (JmxAttributeData jmxAttributeData : entry.getValue()) { + + String otelMetricName; + if (jmxAttributeData.metricName.matches(METRIC_NAME_REGEXP)) { + otelMetricName = jmxAttributeData.metricName; + } else { + otelMetricName = jmxAttributeData.metricName.replaceAll(INVALID_CHARACTER_REGEXP, "_"); + } + + GlobalOpenTelemetry.getMeter("com.microsoft.applicationinsights.jmx") + .gaugeBuilder(otelMetricName) + .buildWithCallback( + observableDoubleMeasurement -> { + calculateAndRecordValueForAttribute( + observableDoubleMeasurement, objectName, jmxAttributeData); + }); + } + } + } + + private static void calculateAndRecordValueForAttribute( + ObservableDoubleMeasurement observableDoubleMeasurement, + String objectName, + JmxAttributeData jmxAttributeData) { + try { + List result = + JmxDataFetcher.fetch( + objectName, jmxAttributeData.attribute); // should return the [val, ...] here + + logger.trace( + "Size of the JmxDataFetcher.fetch result: {}, for objectName:{} and metricName:{}", + result.size(), + objectName, + jmxAttributeData.metricName); + + boolean ok = true; + double value = 0.0; + for (Object obj : result) { + try { + if (obj instanceof Boolean) { + value = ((Boolean) obj).booleanValue() ? 1 : 0; + } else { + value += Double.parseDouble(String.valueOf(obj)); + } + } catch (RuntimeException e) { + ok = false; + break; + } + } + if (ok) { + logger.trace( + "value {} for objectName:{} and metricName{}", + value, + objectName, + jmxAttributeData.metricName); + observableDoubleMeasurement.record( + value, + Attributes.of( + AttributeKey.stringKey("applicationinsights.internal.metric_name"), + jmxAttributeData.metricName)); + } + } catch (Exception e) { + try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { + logger.error( + "Failed to calculate the metric value for objectName {} and metric name {}", + objectName, + jmxAttributeData.metricName); + logger.error("Exception: {}", e.toString()); + } + } + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/PerformanceCounterInitializer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/PerformanceCounterInitializer.java index ff488d94aad..9afd6a19cff 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/PerformanceCounterInitializer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/PerformanceCounterInitializer.java @@ -3,43 +3,28 @@ package com.microsoft.applicationinsights.agent.internal.init; -import static com.microsoft.applicationinsights.agent.internal.diagnostics.MsgId.CUSTOM_JMX_METRIC_ERROR; - import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.PropertyHelper; -import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.Strings; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.perfcounter.DeadLockDetectorPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.FreeMemoryPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.GcPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxAttributeData; -import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxDataFetcher; import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxMetricPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.JvmHeapMemoryUsedPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.OshiPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.PerformanceCounterContainer; import com.microsoft.applicationinsights.agent.internal.perfcounter.ProcessCpuPerformanceCounter; import com.microsoft.applicationinsights.agent.internal.perfcounter.ProcessMemoryPerformanceCounter; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.MDC; public class PerformanceCounterInitializer { private static final Logger logger = LoggerFactory.getLogger(PerformanceCounterInitializer.class); - private static final String METRIC_NAME_REGEXP = "[a-zA-Z0-9_.-/]+"; - private static final String INVALID_CHARACTER_REGEXP = "[^a-zA-Z0-9_.-/]"; public static void initialize(Configuration configuration) { @@ -47,7 +32,7 @@ public static void initialize(Configuration configuration) { configuration.metricIntervalSeconds); if (logger.isDebugEnabled()) { - PerformanceCounterContainer.INSTANCE.setLogAvailableJmxMetrics(); + PerformanceCounterContainer.INSTANCE.setLogAvailableJmxMetrics(configuration.jmxMetrics); } // We don't want these two to be flowing to the OTLP endpoint @@ -65,7 +50,7 @@ public static void initialize(Configuration configuration) { "LoadedClassCount", configuration.jmxMetrics); - loadCustomJmxPerfCounters(configuration.jmxMetrics); + JmxPerformanceCounterLoader.loadCustomJmxPerfCounters(configuration.jmxMetrics); PerformanceCounterContainer.INSTANCE.register( new ProcessCpuPerformanceCounter( @@ -114,127 +99,5 @@ private static boolean isMetricInConfig( return false; } - /** - * The method will load the Jmx performance counters requested by the user to the system: 1. Build - * a map where the key is the Jmx object name and the value is a list of requested attributes. 2. - * Go through all the requested Jmx counters: a. If the object name is not in the map, add it with - * an empty list Else get the list b. Add the attribute to the list. 3. Go through the map For - * every entry (object name and attributes) to build a meter per attribute & for each meter - * register a callback to report the metric value. - */ - private static void loadCustomJmxPerfCounters(List jmxXmlElements) { - HashMap> data = new HashMap<>(); - - // Build a map of object name to its requested attributes - for (Configuration.JmxMetric jmxElement : jmxXmlElements) { - Collection collection = - data.computeIfAbsent(jmxElement.objectName, k -> new ArrayList<>()); - - if (Strings.isNullOrEmpty(jmxElement.objectName)) { - try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { - logger.error("JMX object name is empty, will be ignored"); - } - continue; - } - - if (Strings.isNullOrEmpty(jmxElement.attribute)) { - try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { - logger.error("JMX attribute is empty for '{}'", jmxElement.objectName); - } - continue; - } - - if (Strings.isNullOrEmpty(jmxElement.name)) { - try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { - logger.error("JMX name is empty for '{}', will be ignored", jmxElement.objectName); - } - continue; - } - - collection.add(new JmxAttributeData(jmxElement.name, jmxElement.attribute)); - } - - createMeterPerAttribute(data); - } - - // Create a meter for each attribute & declare the callback that reports the metric in the meter. - private static void createMeterPerAttribute( - Map> objectAndAttributesMap) { - for (Map.Entry> entry : - objectAndAttributesMap.entrySet()) { - String objectName = entry.getKey(); - - for (JmxAttributeData jmxAttributeData : entry.getValue()) { - - String otelMetricName; - if (jmxAttributeData.metricName.matches(METRIC_NAME_REGEXP)) { - otelMetricName = jmxAttributeData.metricName; - } else { - otelMetricName = jmxAttributeData.metricName.replaceAll(INVALID_CHARACTER_REGEXP, "_"); - } - - GlobalOpenTelemetry.getMeter("com.microsoft.applicationinsights.jmx") - .gaugeBuilder(otelMetricName) - .buildWithCallback( - observableDoubleMeasurement -> { - calculateAndRecordValueForAttribute( - observableDoubleMeasurement, objectName, jmxAttributeData); - }); - } - } - } - - private static void calculateAndRecordValueForAttribute( - ObservableDoubleMeasurement observableDoubleMeasurement, - String objectName, - JmxAttributeData jmxAttributeData) { - try { - List result = - JmxDataFetcher.fetch( - objectName, jmxAttributeData.attribute); // should return the [val, ...] here - - logger.trace( - "Size of the JmxDataFetcher.fetch result: {}, for objectName:{} and metricName:{}", - result.size(), - objectName, - jmxAttributeData.metricName); - - boolean ok = true; - double value = 0.0; - for (Object obj : result) { - try { - if (obj instanceof Boolean) { - value = ((Boolean) obj).booleanValue() ? 1 : 0; - } else { - value += Double.parseDouble(String.valueOf(obj)); - } - } catch (RuntimeException e) { - ok = false; - break; - } - } - if (ok) { - logger.trace( - "value {} for objectName:{} and metricName{}", - value, - objectName, - jmxAttributeData.metricName); - observableDoubleMeasurement.record( - value, - Attributes.of( - AttributeKey.stringKey("applicationinsights.internal.metric_name"), - jmxAttributeData.metricName)); - } - } catch (Exception e) { - try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) { - logger.error( - "Failed to calculate the metric value for objectName {} and metric name {}", - objectName, - jmxAttributeData.metricName); - logger.error("Exception: {}", e.toString()); - } - } - } - private PerformanceCounterInitializer() {} } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/AvailableJmxMetricLogger.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/JmxMetricRefresher.java similarity index 77% rename from agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/AvailableJmxMetricLogger.java rename to agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/JmxMetricRefresher.java index ee672ed4f4f..48fe50f1763 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/AvailableJmxMetricLogger.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/JmxMetricRefresher.java @@ -6,13 +6,17 @@ import static java.util.Collections.singleton; import static java.util.Collections.singletonList; +import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; +import com.microsoft.applicationinsights.agent.internal.init.JmxPerformanceCounterLoader; import io.opentelemetry.instrumentation.api.internal.GuardedBy; import java.lang.management.ManagementFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -28,32 +32,71 @@ import org.slf4j.LoggerFactory; // TODO (trask) add tests -class AvailableJmxMetricLogger { +class JmxMetricRefresher { - private static final Logger logger = LoggerFactory.getLogger(AvailableJmxMetricLogger.class); + private static final Logger logger = LoggerFactory.getLogger(JmxMetricRefresher.class); private static final String NEWLINE = System.getProperty("line.separator"); + private final List jmxMetricsConfig; + @GuardedBy("lock") private Map> priorAttributeMap = new HashMap<>(); private final Object lock = new Object(); - void logAvailableJmxMetrics() { + public JmxMetricRefresher(List jmxMetricsConfig) { + this.jmxMetricsConfig = jmxMetricsConfig; + } + + void refresh() { synchronized (lock) { Map> attributeMap = getAttributeMap(); - logDifference(priorAttributeMap, attributeMap); + Map> newlyAvailableJmxMetrics = + logDifferenceAndReturnNewlyAvailable(priorAttributeMap, attributeMap); + if (!newlyAvailableJmxMetrics.isEmpty()) { + List configsWithNewlyAvailableMetrics = + newlyAvailableJmxMetrics.entrySet().stream() + .flatMap(newly -> findConfigurations(newly).stream()) + .collect(Collectors.toList()); + JmxPerformanceCounterLoader.loadCustomJmxPerfCounters(configsWithNewlyAvailableMetrics); + } priorAttributeMap = attributeMap; } } - private static void logDifference( + List findConfigurations(Map.Entry> newly) { + List newlyAvailableJmxMetrics = new ArrayList<>(); + for (Configuration.JmxMetric jmxMetric : jmxMetricsConfig) { + if (jmxMetric.objectName.equals(newly.getKey())) { + Optional potentialJmxMetric = + findPotentialConfig(newly, jmxMetric); + if (potentialJmxMetric.isPresent()) { + newlyAvailableJmxMetrics.add(potentialJmxMetric.get()); + } + } + } + return newlyAvailableJmxMetrics; + } + + private static Optional findPotentialConfig( + Map.Entry> newly, Configuration.JmxMetric jmxMetric) { + Set attributes = newly.getValue(); + for (String attribute : attributes) { + if (jmxMetric.attribute.equals(attribute)) { + return Optional.of(jmxMetric); + } + } + return Optional.empty(); + } + + private static Map> logDifferenceAndReturnNewlyAvailable( Map> priorAvailableJmxAttributes, Map> currentAvailableJmxAttributes) { if (priorAvailableJmxAttributes.isEmpty()) { // first time logger.info("available jmx metrics:{}{}", NEWLINE, toString(currentAvailableJmxAttributes)); - return; + return Collections.emptyMap(); } Map> newlyAvailable = difference(currentAvailableJmxAttributes, priorAvailableJmxAttributes); @@ -64,11 +107,13 @@ private static void logDifference( Map> noLongerAvailable = difference(priorAvailableJmxAttributes, currentAvailableJmxAttributes); if (!noLongerAvailable.isEmpty()) { + // TODO Don't recreate the JMX metric if it is no longer available logger.info( "no longer available jmx metrics since last output:{}{}", NEWLINE, toString(noLongerAvailable)); } + return newlyAvailable; } private static String toString(Map> jmxAttributes) { diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/PerformanceCounterContainer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/PerformanceCounterContainer.java index 0670d82a4ac..4960bf361c2 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/PerformanceCounterContainer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/PerformanceCounterContainer.java @@ -4,6 +4,7 @@ package com.microsoft.applicationinsights.agent.internal.perfcounter; import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.ThreadPoolUtils; +import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient; import java.util.List; import java.util.Locale; @@ -45,7 +46,7 @@ public enum PerformanceCounterContainer { private final List performanceCounters = new CopyOnWriteArrayList<>(); - @Nullable private volatile AvailableJmxMetricLogger availableJmxMetricLogger; + @Nullable private volatile JmxMetricRefresher jmxMetricRefresher; private volatile boolean initialized = false; @@ -90,8 +91,8 @@ public void setCollectionFrequencyInSec(long collectionFrequencyInSec) { this.collectionFrequencyInMillis = collectionFrequencyInSec * 1000; } - public void setLogAvailableJmxMetrics() { - availableJmxMetricLogger = new AvailableJmxMetricLogger(); + public void setLogAvailableJmxMetrics(List jmxMetrics) { + jmxMetricRefresher = new JmxMetricRefresher(jmxMetrics); } /** @@ -119,8 +120,8 @@ private void scheduleWork() { new Runnable() { @Override public void run() { - if (availableJmxMetricLogger != null) { - availableJmxMetricLogger.logAvailableJmxMetrics(); + if (jmxMetricRefresher != null) { + jmxMetricRefresher.refresh(); } TelemetryClient telemetryClient = TelemetryClient.getActive(); diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/perfcounter/AvailableJmxMetricLoggerTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/perfcounter/AvailableJmxMetricLoggerTest.java deleted file mode 100644 index 99f4dbe16b5..00000000000 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/perfcounter/AvailableJmxMetricLoggerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.applicationinsights.agent.internal.perfcounter; - -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import org.junit.jupiter.api.Test; - -public class AvailableJmxMetricLoggerTest { - - @Test - public void testDifference() { - // given - Map> map1 = new HashMap<>(); - map1.put("one", singleton("1")); - map1.put("two", new HashSet<>(asList("2", "22"))); - map1.put("three", new HashSet<>(asList("3", "33", "333"))); - - Map> map2 = new HashMap<>(); - map2.put("one", singleton("1")); - map2.put("two", singleton("22")); - - // when - Map> difference = AvailableJmxMetricLogger.difference(map1, map2); - - // then - assertThat(difference).containsOnlyKeys("two", "three"); - assertThat(difference.get("two")).containsExactly("2"); - assertThat(difference.get("three")).containsExactlyInAnyOrder("3", "33", "333"); - } - - @Test - public void test() { - AvailableJmxMetricLogger availableJmxMetricLogger = new AvailableJmxMetricLogger(); - - availableJmxMetricLogger.logAvailableJmxMetrics(); - availableJmxMetricLogger.logAvailableJmxMetrics(); - } -} diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/perfcounter/JmxMetricRefresherTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/perfcounter/JmxMetricRefresherTest.java new file mode 100644 index 00000000000..dde2eac8c22 --- /dev/null +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/perfcounter/JmxMetricRefresherTest.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.agent.internal.perfcounter; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; + +import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class JmxMetricRefresherTest { + + @Test + void testDifference() { + // given + Map> map1 = new HashMap<>(); + map1.put("one", singleton("1")); + map1.put("two", new HashSet<>(asList("2", "22"))); + map1.put("three", new HashSet<>(asList("3", "33", "333"))); + + Map> map2 = new HashMap<>(); + map2.put("one", singleton("1")); + map2.put("two", singleton("22")); + + // when + Map> difference = JmxMetricRefresher.difference(map1, map2); + + // then + assertThat(difference).containsOnlyKeys("two", "three"); + assertThat(difference.get("two")).containsExactly("2"); + assertThat(difference.get("three")).containsExactlyInAnyOrder("3", "33", "333"); + } + + @Test + void test() { + JmxMetricRefresher jmxMetricRefresher = new JmxMetricRefresher(Collections.emptyList()); + + jmxMetricRefresher.refresh(); + jmxMetricRefresher.refresh(); + } + + @Test + void shouldFindConfigurationFromAvailableJmxMetric() { + // given + List jmxMetricsConfig = new ArrayList<>(); + Configuration.JmxMetric metric1 = new Configuration.JmxMetric(); + metric1.objectName = "objectName1"; + metric1.attribute = "attribute1"; + jmxMetricsConfig.add(metric1); + + Configuration.JmxMetric metric1bis = new Configuration.JmxMetric(); + metric1bis.objectName = "objectName1"; + metric1bis.attribute = "attribute1bis"; + jmxMetricsConfig.add(metric1bis); + + Configuration.JmxMetric metric2 = new Configuration.JmxMetric(); + metric2.objectName = "objectName2"; + metric2.attribute = "attribute2"; + jmxMetricsConfig.add(metric2); + + JmxMetricRefresher jmxMetricRefresher = new JmxMetricRefresher(jmxMetricsConfig); + Map.Entry> newly = + new AbstractMap.SimpleEntry<>( + "objectName1", new HashSet<>(Collections.singletonList("attribute1"))); + + // when + List result = jmxMetricRefresher.findConfigurations(newly); + + // then + assertThat(result).hasSize(1); + Configuration.JmxMetric jmxConfigurationFound = result.get(0); + assertThat(jmxConfigurationFound.objectName).isEqualTo("objectName1"); + assertThat(jmxConfigurationFound.attribute).isEqualTo("attribute1"); + } +}