-
Notifications
You must be signed in to change notification settings - Fork 1k
document & test jmx metric implicit aggregation #14111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
37cf457
update documentation
SylvainJuge 2279e00
refactor a bit test
SylvainJuge 9b35aa2
add testcase to reproduce issue
SylvainJuge 21dda9d
test for single metric attribute aggregation
SylvainJuge d582535
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge 66a98f4
spotless
SylvainJuge 60714cb
improve doc a bit
SylvainJuge c35e1e0
checkstyle
SylvainJuge 5ea01db
test with all metric types
SylvainJuge 89abd67
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge c62574f
Merge branch 'main' into jmx-metrics-advice
SylvainJuge edb6423
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge 39da926
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge 5d86086
post-review changes
SylvainJuge edcc15e
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge 29b2b09
use testing extension for metrics assertions
SylvainJuge 5bc3ead
Update instrumentation/jmx-metrics/library/src/test/java/io/opentelem…
SylvainJuge fed5ddd
./gradlew spotlessApply
otelbot[bot] c9152c9
post-review changes
SylvainJuge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
246 changes: 246 additions & 0 deletions
246
...rary/src/test/java/io/opentelemetry/instrumentation/jmx/engine/MetricAggregationTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.jmx.engine; | ||
|
|
||
| import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; | ||
| import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; | ||
|
|
||
| import io.opentelemetry.api.common.AttributeKey; | ||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; | ||
| import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; | ||
| import io.opentelemetry.sdk.testing.assertj.LongPointAssert; | ||
| import io.opentelemetry.sdk.testing.assertj.MetricAssert; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.function.Consumer; | ||
| import javax.annotation.Nullable; | ||
| import javax.management.InstanceNotFoundException; | ||
| import javax.management.MBeanRegistrationException; | ||
| import javax.management.MBeanServer; | ||
| import javax.management.MBeanServerFactory; | ||
| import javax.management.MalformedObjectNameException; | ||
| import javax.management.ObjectName; | ||
| import org.junit.jupiter.api.AfterAll; | ||
| import org.junit.jupiter.api.AfterEach; | ||
| import org.junit.jupiter.api.BeforeAll; | ||
| import org.junit.jupiter.api.extension.RegisterExtension; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.MethodSource; | ||
|
|
||
| public class MetricAggregationTest { | ||
|
|
||
| @SuppressWarnings({"unused", "checkstyle:AbbreviationAsWordInName"}) | ||
| public interface HelloMBean { | ||
|
|
||
| int getValue(); | ||
| } | ||
|
|
||
| public static class Hello implements HelloMBean { | ||
|
|
||
| private final int value; | ||
|
|
||
| public Hello(int value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| @Override | ||
| public int getValue() { | ||
| return value; | ||
| } | ||
| } | ||
|
|
||
| @RegisterExtension | ||
| static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); | ||
|
|
||
| private static final String DOMAIN = "otel.jmx.test"; | ||
| private static MBeanServer theServer; | ||
|
|
||
| @BeforeAll | ||
| static void setUp() { | ||
| theServer = MBeanServerFactory.createMBeanServer(DOMAIN); | ||
| } | ||
|
|
||
| @AfterAll | ||
| static void tearDown() { | ||
| MBeanServerFactory.releaseMBeanServer(theServer); | ||
| } | ||
|
|
||
| @AfterEach | ||
| void after() throws Exception { | ||
| ObjectName objectName = new ObjectName(DOMAIN + ":type=" + Hello.class.getSimpleName() + ",*"); | ||
| theServer | ||
| .queryMBeans(objectName, null) | ||
| .forEach( | ||
| instance -> { | ||
| try { | ||
| theServer.unregisterMBean(instance.getObjectName()); | ||
| } catch (InstanceNotFoundException | MBeanRegistrationException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private static ObjectName getObjectName(@Nullable String a, @Nullable String b) | ||
| throws MalformedObjectNameException { | ||
| StringBuilder parts = new StringBuilder(); | ||
| parts.append("otel.jmx.test:type=").append(Hello.class.getSimpleName()); | ||
| if (a != null) { | ||
| parts.append(",a=").append(a); | ||
| } | ||
| if (b != null) { | ||
| parts.append(",b=").append(b); | ||
| } | ||
| return new ObjectName(parts.toString()); | ||
| } | ||
|
|
||
| static List<MetricInfo.Type> metricTypes() { | ||
| return Arrays.asList( | ||
| MetricInfo.Type.COUNTER, MetricInfo.Type.UPDOWNCOUNTER, MetricInfo.Type.GAUGE); | ||
| } | ||
|
|
||
| @ParameterizedTest(name = ARGUMENTS_PLACEHOLDER) | ||
| @MethodSource("metricTypes") | ||
| void singleInstance(MetricInfo.Type metricType) throws Exception { | ||
| ObjectName bean = getObjectName(null, null); | ||
| theServer.registerMBean(new Hello(42), bean); | ||
|
|
||
| String metricName = generateMetricName(metricType); | ||
| startTestMetric(metricName, bean.toString(), Collections.emptyList(), metricType); | ||
| waitAndAssertMetric( | ||
| metricName, metricType, point -> point.hasValue(42).hasAttributes(Attributes.empty())); | ||
| } | ||
|
|
||
| @ParameterizedTest(name = ARGUMENTS_PLACEHOLDER) | ||
| @MethodSource("metricTypes") | ||
| void aggregateOneParam(MetricInfo.Type metricType) throws Exception { | ||
| theServer.registerMBean(new Hello(42), getObjectName("value1", null)); | ||
| theServer.registerMBean(new Hello(37), getObjectName("value2", null)); | ||
|
|
||
| String bean = getObjectName("*", null).toString(); | ||
| String metricName = generateMetricName(metricType); | ||
| startTestMetric(metricName, bean, Collections.emptyList(), metricType); | ||
|
|
||
| // last-value aggregation produces an unpredictable result unless a single mbean instance is | ||
| // used | ||
| // test here is only used as a way to document behavior and should not be considered a feature | ||
| long expected = metricType == MetricInfo.Type.GAUGE ? 37 : 79; | ||
| waitAndAssertMetric( | ||
| metricName, | ||
| metricType, | ||
| point -> point.hasValue(expected).hasAttributes(Attributes.empty())); | ||
| } | ||
|
|
||
| @ParameterizedTest(name = ARGUMENTS_PLACEHOLDER) | ||
| @MethodSource("metricTypes") | ||
| void aggregateMultipleParams(MetricInfo.Type metricType) throws Exception { | ||
| theServer.registerMBean(new Hello(1), getObjectName("1", "x")); | ||
| theServer.registerMBean(new Hello(2), getObjectName("2", "y")); | ||
| theServer.registerMBean(new Hello(3), getObjectName("3", "x")); | ||
| theServer.registerMBean(new Hello(4), getObjectName("4", "y")); | ||
|
|
||
| String bean = getObjectName("*", "*").toString(); | ||
| String metricName = generateMetricName(metricType); | ||
| startTestMetric(metricName, bean, Collections.emptyList(), metricType); | ||
|
|
||
| // last-value aggregation produces an unpredictable result unless a single mbean instance is | ||
| // used | ||
| // test here is only used as a way to document behavior and should not be considered a feature | ||
| long expected = metricType == MetricInfo.Type.GAUGE ? 1 : 10; | ||
| waitAndAssertMetric( | ||
| metricName, | ||
| metricType, | ||
| point -> point.hasValue(expected).hasAttributes(Attributes.empty())); | ||
| } | ||
|
|
||
| @ParameterizedTest(name = ARGUMENTS_PLACEHOLDER) | ||
| @MethodSource("metricTypes") | ||
| void partialAggregateMultipleParams(MetricInfo.Type metricType) throws Exception { | ||
| theServer.registerMBean(new Hello(1), getObjectName("1", "x")); | ||
| theServer.registerMBean(new Hello(2), getObjectName("2", "y")); | ||
| theServer.registerMBean(new Hello(3), getObjectName("3", "x")); | ||
| theServer.registerMBean(new Hello(4), getObjectName("4", "y")); | ||
|
|
||
| String bean = getObjectName("*", "*").toString(); | ||
|
|
||
| List<MetricAttribute> attributes = | ||
| Collections.singletonList( | ||
| new MetricAttribute( | ||
| "test.metric.param", MetricAttributeExtractor.fromObjectNameParameter("b"))); | ||
| String metricName = generateMetricName(metricType); | ||
| startTestMetric(metricName, bean, attributes, metricType); | ||
|
|
||
| AttributeKey<String> metricAttribute = AttributeKey.stringKey("test.metric.param"); | ||
| if (metricType == MetricInfo.Type.GAUGE) { | ||
| waitAndAssertMetric( | ||
| metricName, | ||
| metricType, | ||
| point -> point.hasValue(1).hasAttribute(metricAttribute, "x"), | ||
| point -> point.hasValue(4).hasAttribute(metricAttribute, "y")); | ||
| } else { | ||
| waitAndAssertMetric( | ||
| metricName, | ||
| metricType, | ||
| point -> point.hasValue(4).hasAttribute(metricAttribute, "x"), | ||
| point -> point.hasValue(6).hasAttribute(metricAttribute, "y")); | ||
| } | ||
| } | ||
|
|
||
| private static final AtomicInteger metricCounter = new AtomicInteger(0); | ||
|
|
||
| private static String generateMetricName(MetricInfo.Type metricType) { | ||
| // generate a sequential metric name that prevents naming conflicts and unexpected behaviors | ||
| return "test.metric" | ||
| + metricCounter.incrementAndGet() | ||
| + "." | ||
| + metricType.name().toLowerCase(Locale.ROOT); | ||
| } | ||
|
|
||
| @SafeVarargs | ||
| @SuppressWarnings("varargs") | ||
| private static void waitAndAssertMetric( | ||
| String metricName, MetricInfo.Type metricType, Consumer<LongPointAssert>... pointAsserts) { | ||
|
|
||
| testing.waitAndAssertMetrics( | ||
| "io.opentelemetry.jmx", | ||
| metricName, | ||
| metrics -> | ||
| metrics.anySatisfy( | ||
| metricData -> { | ||
| MetricAssert metricAssert = | ||
| assertThat(metricData).hasDescription("description").hasUnit("1"); | ||
| if (metricType == MetricInfo.Type.GAUGE) { | ||
| metricAssert.hasLongGaugeSatisfying( | ||
| gauge -> gauge.hasPointsSatisfying(pointAsserts)); | ||
| } else { | ||
| metricAssert.hasLongSumSatisfying(sum -> sum.hasPointsSatisfying(pointAsserts)); | ||
| } | ||
| })); | ||
| } | ||
|
|
||
| private static void startTestMetric( | ||
| String metricName, String mbean, List<MetricAttribute> attributes, MetricInfo.Type metricType) | ||
| throws MalformedObjectNameException { | ||
| JmxMetricInsight metricInsight = | ||
| JmxMetricInsight.createService(testing.getOpenTelemetrySdk(), 0); | ||
SylvainJuge marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| MetricConfiguration metricConfiguration = new MetricConfiguration(); | ||
| List<MetricExtractor> extractors = new ArrayList<>(); | ||
|
|
||
| MetricInfo metricInfo = new MetricInfo(metricName, "description", null, "1", metricType); | ||
| BeanAttributeExtractor beanExtractor = BeanAttributeExtractor.fromName("Value"); | ||
| MetricExtractor extractor = new MetricExtractor(beanExtractor, metricInfo, attributes); | ||
| extractors.add(extractor); | ||
| MetricDef metricDef = | ||
| new MetricDef(BeanGroup.forBeans(Collections.singletonList(mbean)), extractors); | ||
|
|
||
| metricConfiguration.addMetricDef(metricDef); | ||
| metricInsight.startLocal(metricConfiguration); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.