66package io .opentelemetry .instrumentation .jmx .engine ;
77
88import static io .opentelemetry .sdk .testing .assertj .OpenTelemetryAssertions .assertThat ;
9- import static org .assertj .core .groups .Tuple .tuple ;
109import static org .junit .jupiter .params .ParameterizedInvocationConstants .ARGUMENTS_PLACEHOLDER ;
1110
1211import io .opentelemetry .api .common .AttributeKey ;
1312import io .opentelemetry .api .common .Attributes ;
14- import io .opentelemetry .sdk .OpenTelemetrySdk ;
15- import io .opentelemetry .sdk .metrics .SdkMeterProvider ;
16- import io .opentelemetry .sdk .metrics .data .LongPointData ;
17- import io .opentelemetry .sdk .metrics .data .MetricData ;
18- import io .opentelemetry .sdk .metrics .data .PointData ;
19- import io .opentelemetry .sdk .testing .exporter .InMemoryMetricReader ;
13+ import io .opentelemetry .instrumentation .testing .junit .InstrumentationExtension ;
14+ import io .opentelemetry .instrumentation .testing .junit .LibraryInstrumentationExtension ;
15+ import io .opentelemetry .sdk .testing .assertj .LongPointAssert ;
16+ import io .opentelemetry .sdk .testing .assertj .MetricAssert ;
2017import java .util .ArrayList ;
2118import java .util .Arrays ;
22- import java .util .Collection ;
2319import java .util .Collections ;
2420import java .util .List ;
21+ import java .util .Locale ;
22+ import java .util .concurrent .atomic .AtomicInteger ;
23+ import java .util .function .Consumer ;
2524import javax .annotation .Nullable ;
2625import javax .management .InstanceNotFoundException ;
2726import javax .management .MBeanRegistrationException ;
3231import org .junit .jupiter .api .AfterAll ;
3332import org .junit .jupiter .api .AfterEach ;
3433import org .junit .jupiter .api .BeforeAll ;
35- import org .junit .jupiter .api .BeforeEach ;
34+ import org .junit .jupiter .api .extension . RegisterExtension ;
3635import org .junit .jupiter .params .ParameterizedTest ;
3736import org .junit .jupiter .params .provider .MethodSource ;
3837
@@ -58,6 +57,9 @@ public int getValue() {
5857 }
5958 }
6059
60+ @ RegisterExtension
61+ static final InstrumentationExtension testing = LibraryInstrumentationExtension .create ();
62+
6163 private static final String DOMAIN = "otel.jmx.test" ;
6264 private static MBeanServer theServer ;
6365
@@ -71,15 +73,6 @@ static void tearDown() {
7173 MBeanServerFactory .releaseMBeanServer (theServer );
7274 }
7375
74- @ BeforeEach
75- void before () {
76- reader = InMemoryMetricReader .createDelta ();
77- sdk =
78- OpenTelemetrySdk .builder ()
79- .setMeterProvider (SdkMeterProvider .builder ().registerMetricReader (reader ).build ())
80- .build ();
81- }
82-
8376 @ AfterEach
8477 void after () throws Exception {
8578 ObjectName objectName = new ObjectName (DOMAIN + ":type=" + Hello .class .getSimpleName () + ",*" );
@@ -93,15 +86,8 @@ void after() throws Exception {
9386 throw new RuntimeException (e );
9487 }
9588 });
96-
97- if (sdk != null ) {
98- sdk .getSdkMeterProvider ().close ();
99- }
10089 }
10190
102- private InMemoryMetricReader reader ;
103- private OpenTelemetrySdk sdk ;
104-
10591 private static ObjectName getObjectName (@ Nullable String a , @ Nullable String b )
10692 throws MalformedObjectNameException {
10793 StringBuilder parts = new StringBuilder ();
@@ -126,8 +112,10 @@ void singleInstance(MetricInfo.Type metricType) throws Exception {
126112 ObjectName bean = getObjectName (null , null );
127113 theServer .registerMBean (new Hello (42 ), bean );
128114
129- Collection <MetricData > data = testMetric (bean .toString (), Collections .emptyList (), metricType );
130- checkSingleValue (data , 42 , metricType );
115+ String metricName = generateMetricName (metricType );
116+ startTestMetric (metricName , bean .toString (), Collections .emptyList (), metricType );
117+ waitAndAssertMetric (
118+ metricName , metricType , point -> point .hasValue (42 ).hasAttributes (Attributes .empty ()));
131119 }
132120
133121 @ ParameterizedTest (name = ARGUMENTS_PLACEHOLDER )
@@ -137,14 +125,17 @@ void aggregateOneParam(MetricInfo.Type metricType) throws Exception {
137125 theServer .registerMBean (new Hello (37 ), getObjectName ("value2" , null ));
138126
139127 String bean = getObjectName ("*" , null ).toString ();
140- Collection <MetricData > data = testMetric (bean , Collections .emptyList (), metricType );
141- int expected = 79 ;
142- if (metricType == MetricInfo .Type .GAUGE ) {
143- // last-value aggregation produces unpredictable result unless a single mbean instance is used
144- // test here is only used as a way to document behavior and should not be considered a feature
145- expected = 37 ;
146- }
147- checkSingleValue (data , expected , metricType );
128+ String metricName = generateMetricName (metricType );
129+ startTestMetric (metricName , bean , Collections .emptyList (), metricType );
130+
131+ // last-value aggregation produces an unpredictable result unless a single mbean instance is
132+ // used
133+ // test here is only used as a way to document behavior and should not be considered a feature
134+ long expected = metricType == MetricInfo .Type .GAUGE ? 37 : 79 ;
135+ waitAndAssertMetric (
136+ metricName ,
137+ metricType ,
138+ point -> point .hasValue (expected ).hasAttributes (Attributes .empty ()));
148139 }
149140
150141 @ ParameterizedTest (name = ARGUMENTS_PLACEHOLDER )
@@ -156,15 +147,17 @@ void aggregateMultipleParams(MetricInfo.Type metricType) throws Exception {
156147 theServer .registerMBean (new Hello (4 ), getObjectName ("4" , "y" ));
157148
158149 String bean = getObjectName ("*" , "*" ).toString ();
159- Collection <MetricData > data = testMetric (bean , Collections .emptyList (), metricType );
160-
161- int expected = 10 ;
162- if (metricType == MetricInfo .Type .GAUGE ) {
163- // last-value aggregation produces unpredictable result unless a single mbean instance is used
164- // test here is only used as a way to document behavior and should not be considered a feature
165- expected = 1 ;
166- }
167- checkSingleValue (data , expected , metricType );
150+ String metricName = generateMetricName (metricType );
151+ startTestMetric (metricName , bean , Collections .emptyList (), metricType );
152+
153+ // last-value aggregation produces an unpredictable result unless a single mbean instance is
154+ // used
155+ // test here is only used as a way to document behavior and should not be considered a feature
156+ long expected = metricType == MetricInfo .Type .GAUGE ? 1 : 10 ;
157+ waitAndAssertMetric (
158+ metricName ,
159+ metricType ,
160+ point -> point .hasValue (expected ).hasAttributes (Attributes .empty ()));
168161 }
169162
170163 @ ParameterizedTest (name = ARGUMENTS_PLACEHOLDER )
@@ -181,44 +174,66 @@ void partialAggregateMultipleParams(MetricInfo.Type metricType) throws Exception
181174 Collections .singletonList (
182175 new MetricAttribute (
183176 "test.metric.param" , MetricAttributeExtractor .fromObjectNameParameter ("b" )));
184- Collection <MetricData > data = testMetric (bean , attributes , metricType );
185-
186- assertThat (data )
187- .isNotEmpty ()
188- .satisfiesExactly (
189- metric -> {
190- assertThat (metric .getName ()).isEqualTo ("test.metric" );
191- AttributeKey <String > metricAttribute = AttributeKey .stringKey ("test.metric.param" );
192-
193- if (metricType == MetricInfo .Type .GAUGE ) {
194- // last-value aggregation produces unpredictable result unless a single mbean
195- // instance is used
196- // test here is only used as a way to document behavior and should not be considered
197- // a feature
198- assertThat (metric .getLongGaugeData ().getPoints ())
199- .extracting (LongPointData ::getValue , PointData ::getAttributes )
200- .containsExactlyInAnyOrder (
201- tuple (4L , Attributes .of (metricAttribute , "y" )),
202- tuple (1L , Attributes .of (metricAttribute , "x" )));
203- } else {
204- // sum aggregation
205- assertThat (metric .getLongSumData ().getPoints ())
206- .extracting (LongPointData ::getValue , PointData ::getAttributes )
207- .containsExactlyInAnyOrder (
208- tuple (6L , Attributes .of (metricAttribute , "y" )),
209- tuple (4L , Attributes .of (metricAttribute , "x" )));
210- }
211- });
177+ String metricName = generateMetricName (metricType );
178+ startTestMetric (metricName , bean , attributes , metricType );
179+
180+ AttributeKey <String > metricAttribute = AttributeKey .stringKey ("test.metric.param" );
181+ if (metricType == MetricInfo .Type .GAUGE ) {
182+ waitAndAssertMetric (
183+ metricName ,
184+ metricType ,
185+ point -> point .hasValue (1 ).hasAttribute (metricAttribute , "x" ),
186+ point -> point .hasValue (4 ).hasAttribute (metricAttribute , "y" ));
187+ } else {
188+ waitAndAssertMetric (
189+ metricName ,
190+ metricType ,
191+ point -> point .hasValue (4 ).hasAttribute (metricAttribute , "x" ),
192+ point -> point .hasValue (6 ).hasAttribute (metricAttribute , "y" ));
193+ }
194+ }
195+
196+ private static final AtomicInteger metricCounter = new AtomicInteger (0 );
197+
198+ private static String generateMetricName (MetricInfo .Type metricType ) {
199+ // generate a sequential metric name that prevents naming conflicts and unexpected behaviors
200+ return "test.metric"
201+ + metricCounter .incrementAndGet ()
202+ + "."
203+ + metricType .name ().toLowerCase (Locale .ROOT );
212204 }
213205
214- private Collection <MetricData > testMetric (
215- String mbean , List <MetricAttribute > attributes , MetricInfo .Type metricType )
206+ @ SafeVarargs
207+ @ SuppressWarnings ("varargs" )
208+ private static void waitAndAssertMetric (
209+ String metricName , MetricInfo .Type metricType , Consumer <LongPointAssert >... pointAsserts ) {
210+
211+ testing .waitAndAssertMetrics (
212+ "io.opentelemetry.jmx" ,
213+ metricName ,
214+ metrics ->
215+ metrics .anySatisfy (
216+ metricData -> {
217+ MetricAssert metricAssert =
218+ assertThat (metricData ).hasDescription ("description" ).hasUnit ("1" );
219+ if (metricType == MetricInfo .Type .GAUGE ) {
220+ metricAssert .hasLongGaugeSatisfying (
221+ gauge -> gauge .hasPointsSatisfying (pointAsserts ));
222+ } else {
223+ metricAssert .hasLongSumSatisfying (sum -> sum .hasPointsSatisfying (pointAsserts ));
224+ }
225+ }));
226+ }
227+
228+ private static void startTestMetric (
229+ String metricName , String mbean , List <MetricAttribute > attributes , MetricInfo .Type metricType )
216230 throws MalformedObjectNameException {
217- JmxMetricInsight metricInsight = JmxMetricInsight .createService (sdk , 0 );
231+ JmxMetricInsight metricInsight =
232+ JmxMetricInsight .createService (testing .getOpenTelemetrySdk (), 0 );
218233 MetricConfiguration metricConfiguration = new MetricConfiguration ();
219234 List <MetricExtractor > extractors = new ArrayList <>();
220235
221- MetricInfo metricInfo = new MetricInfo ("test.metric" , "description" , null , "1" , metricType );
236+ MetricInfo metricInfo = new MetricInfo (metricName , "description" , null , "1" , metricType );
222237 BeanAttributeExtractor beanExtractor = BeanAttributeExtractor .fromName ("Value" );
223238 MetricExtractor extractor = new MetricExtractor (beanExtractor , metricInfo , attributes );
224239 extractors .add (extractor );
@@ -227,44 +242,5 @@ private Collection<MetricData> testMetric(
227242
228243 metricConfiguration .addMetricDef (metricDef );
229244 metricInsight .startLocal (metricConfiguration );
230-
231- return waitMetricsReceived ();
232- }
233-
234- private static void checkSingleValue (
235- Collection <MetricData > data , long expectedValue , MetricInfo .Type metricType ) {
236- assertThat (data )
237- .isNotEmpty ()
238- .satisfiesExactlyInAnyOrder (
239- metric -> {
240- assertThat (metric .getName ()).isEqualTo ("test.metric" );
241-
242- Collection <LongPointData > points ;
243- if (metricType == MetricInfo .Type .GAUGE ) {
244- points = metric .getLongGaugeData ().getPoints ();
245- } else {
246- points = metric .getLongSumData ().getPoints ();
247- }
248- assertThat (points )
249- .extracting (LongPointData ::getValue , PointData ::getAttributes )
250- .containsExactlyInAnyOrder (tuple (expectedValue , Attributes .empty ()));
251- });
252- }
253-
254- private Collection <MetricData > waitMetricsReceived () {
255- int retries = 100 ;
256- Collection <MetricData > data ;
257- do {
258- try {
259- Thread .sleep (100 );
260- } catch (InterruptedException e ) {
261- throw new RuntimeException (e );
262- }
263- data = reader .collectAllMetrics ();
264- } while (data .isEmpty () && retries -- > 0 );
265- if (data .isEmpty ()) {
266- throw new RuntimeException ("timed out waiting for metrics received" );
267- }
268- return data ;
269245 }
270246}
0 commit comments