1818import com .google .common .util .concurrent .Uninterruptibles ;
1919import io .opentelemetry .api .common .AttributeKey ;
2020import io .opentelemetry .api .common .Attributes ;
21+ import io .opentelemetry .api .incubator .metrics .BoundDoubleCounter ;
22+ import io .opentelemetry .api .incubator .metrics .BoundDoubleGauge ;
23+ import io .opentelemetry .api .incubator .metrics .BoundDoubleHistogram ;
24+ import io .opentelemetry .api .incubator .metrics .BoundDoubleUpDownCounter ;
25+ import io .opentelemetry .api .incubator .metrics .BoundLongCounter ;
26+ import io .opentelemetry .api .incubator .metrics .BoundLongGauge ;
27+ import io .opentelemetry .api .incubator .metrics .BoundLongHistogram ;
28+ import io .opentelemetry .api .incubator .metrics .BoundLongUpDownCounter ;
29+ import io .opentelemetry .api .incubator .metrics .ExtendedDoubleCounter ;
30+ import io .opentelemetry .api .incubator .metrics .ExtendedDoubleGauge ;
31+ import io .opentelemetry .api .incubator .metrics .ExtendedDoubleHistogram ;
32+ import io .opentelemetry .api .incubator .metrics .ExtendedDoubleUpDownCounter ;
33+ import io .opentelemetry .api .incubator .metrics .ExtendedLongCounter ;
34+ import io .opentelemetry .api .incubator .metrics .ExtendedLongGauge ;
35+ import io .opentelemetry .api .incubator .metrics .ExtendedLongHistogram ;
36+ import io .opentelemetry .api .incubator .metrics .ExtendedLongUpDownCounter ;
2137import io .opentelemetry .api .metrics .Meter ;
2238import io .opentelemetry .internal .testing .CleanupExtension ;
2339import io .opentelemetry .sdk .common .export .MemoryMode ;
4460import java .util .ArrayList ;
4561import java .util .Arrays ;
4662import java .util .Collections ;
63+ import java .util .HashMap ;
4764import java .util .List ;
65+ import java .util .Map ;
4866import java .util .concurrent .CountDownLatch ;
4967import java .util .concurrent .atomic .AtomicLong ;
5068import java .util .stream .LongStream ;
5674
5775/**
5876 * {@link #stressTest(AggregationTemporality, InstrumentType, Aggregation, MemoryMode,
59- * InstrumentValueType)} performs a stress test to confirm simultaneous record and collections do
60- * not have concurrency issues like lost writes, partial writes, duplicate writes, etc. All
61- * combinations of the following dimensions are tested: aggregation temporality, instrument type
62- * (synchronous), memory mode, instrument value type.
77+ * InstrumentValueType, boolean)} performs a stress test to confirm simultaneous record and
78+ * collections do not have concurrency issues like lost writes, partial writes, duplicate writes,
79+ * etc. All combinations of the following dimensions are tested: aggregation temporality, instrument
80+ * type (synchronous), memory mode, instrument value type, and whether recording goes through bound
81+ * instruments ({@code Extended*#bind(Attributes)}) or unbound instruments.
82+ *
83+ * <p>Lives in {@code testIncubating} because the bound dimension exercises {@code
84+ * opentelemetry-api-incubator}, which the base {@code test} source set is not allowed to depend on.
6385 */
6486class SynchronousInstrumentStressTest {
6587
@@ -88,10 +110,16 @@ void stressTest(
88110 InstrumentType instrumentType ,
89111 Aggregation aggregation ,
90112 MemoryMode memoryMode ,
91- InstrumentValueType instrumentValueType ) {
113+ InstrumentValueType instrumentValueType ,
114+ boolean bound ) {
92115 for (int repetition = 0 ; repetition < STRESS_TEST_REPETITIONS ; repetition ++) {
93116 stressTestOnce (
94- aggregationTemporality , instrumentType , aggregation , memoryMode , instrumentValueType );
117+ aggregationTemporality ,
118+ instrumentType ,
119+ aggregation ,
120+ memoryMode ,
121+ instrumentValueType ,
122+ bound );
95123 }
96124 }
97125
@@ -101,7 +129,8 @@ private void stressTestOnce(
101129 InstrumentType instrumentType ,
102130 Aggregation aggregation ,
103131 MemoryMode memoryMode ,
104- InstrumentValueType instrumentValueType ) {
132+ InstrumentValueType instrumentValueType ,
133+ boolean bound ) {
105134 // Initialize metric SDK
106135 DefaultAggregationSelector aggregationSelector =
107136 DefaultAggregationSelector .getDefault ().with (instrumentType , aggregation );
@@ -117,7 +146,8 @@ private void stressTestOnce(
117146 Meter meter = meterProvider .get ("test" );
118147 List <Attributes > attributes = Arrays .asList (ATTR_1 , ATTR_2 , ATTR_3 , ATTR_4 );
119148 Collections .shuffle (attributes );
120- Instrument instrument = getInstrument (meter , instrumentType , instrumentValueType );
149+ Instrument instrument =
150+ getInstrument (meter , instrumentType , instrumentValueType , bound , attributes );
121151
122152 // Define list of measurements to record
123153 // Later, we'll assert that the data collected matches these measurements, with no lost writes,
@@ -298,20 +328,24 @@ private static Stream<Arguments> stressTestArgs() {
298328 InstrumentTypeAndAggregation .values ()) {
299329 for (MemoryMode memoryMode : MemoryMode .values ()) {
300330 for (InstrumentValueType instrumentValueType : InstrumentValueType .values ()) {
301- argumentsList .add (
302- Arguments .argumentSet (
303- aggregationTemporality
304- + " "
305- + instrumentTypeAndAggregation .instrumentType
306- + " "
307- + memoryMode
308- + " "
309- + instrumentValueType ,
310- aggregationTemporality ,
311- instrumentTypeAndAggregation .instrumentType ,
312- instrumentTypeAndAggregation .aggregation ,
313- memoryMode ,
314- instrumentValueType ));
331+ for (boolean bound : new boolean [] {false , true }) {
332+ argumentsList .add (
333+ Arguments .argumentSet (
334+ aggregationTemporality
335+ + " "
336+ + instrumentTypeAndAggregation .instrumentType
337+ + " "
338+ + memoryMode
339+ + " "
340+ + instrumentValueType
341+ + (bound ? " bound" : " unbound" ),
342+ aggregationTemporality ,
343+ instrumentTypeAndAggregation .instrumentType ,
344+ instrumentTypeAndAggregation .aggregation ,
345+ memoryMode ,
346+ instrumentValueType ,
347+ bound ));
348+ }
315349 }
316350 }
317351 }
@@ -320,7 +354,19 @@ private static Stream<Arguments> stressTestArgs() {
320354 }
321355
322356 private static Instrument getInstrument (
323- Meter meter , InstrumentType instrumentType , InstrumentValueType instrumentValueType ) {
357+ Meter meter ,
358+ InstrumentType instrumentType ,
359+ InstrumentValueType instrumentValueType ,
360+ boolean bound ,
361+ List <Attributes > attributesList ) {
362+ if (bound ) {
363+ // Bind one bound instrument per attribute set up front, then dispatch records by attributes.
364+ // The bound record path is the one under test; the per-record map lookup here is just test
365+ // plumbing to reuse the unbound recording loop.
366+ Map <Attributes , BoundInstrument > boundInstruments =
367+ bindInstruments (meter , instrumentType , instrumentValueType , attributesList );
368+ return (value , attributes ) -> boundInstruments .get (attributes ).record (value );
369+ }
324370 switch (instrumentType ) {
325371 case COUNTER :
326372 return instrumentValueType == InstrumentValueType .DOUBLE
@@ -354,10 +400,112 @@ private static Instrument getInstrument(
354400 throw new IllegalArgumentException ();
355401 }
356402
403+ /**
404+ * Builds the instrument for the given type / value type, then binds one {@link BoundInstrument}
405+ * per attribute set in {@code attributesList}.
406+ */
407+ private static Map <Attributes , BoundInstrument > bindInstruments (
408+ Meter meter ,
409+ InstrumentType instrumentType ,
410+ InstrumentValueType instrumentValueType ,
411+ List <Attributes > attributesList ) {
412+ Map <Attributes , BoundInstrument > result = new HashMap <>();
413+ boolean isDouble = instrumentValueType == InstrumentValueType .DOUBLE ;
414+ switch (instrumentType ) {
415+ case COUNTER :
416+ if (isDouble ) {
417+ ExtendedDoubleCounter instrument =
418+ (ExtendedDoubleCounter ) meter .counterBuilder (INSTRUMENT_NAME ).ofDoubles ().build ();
419+ for (Attributes attributes : attributesList ) {
420+ BoundDoubleCounter bound = instrument .bind (attributes );
421+ result .put (attributes , bound ::add );
422+ }
423+ } else {
424+ ExtendedLongCounter instrument =
425+ (ExtendedLongCounter ) meter .counterBuilder (INSTRUMENT_NAME ).build ();
426+ for (Attributes attributes : attributesList ) {
427+ BoundLongCounter bound = instrument .bind (attributes );
428+ result .put (attributes , bound ::add );
429+ }
430+ }
431+ return result ;
432+ case UP_DOWN_COUNTER :
433+ if (isDouble ) {
434+ ExtendedDoubleUpDownCounter instrument =
435+ (ExtendedDoubleUpDownCounter )
436+ meter .upDownCounterBuilder (INSTRUMENT_NAME ).ofDoubles ().build ();
437+ for (Attributes attributes : attributesList ) {
438+ BoundDoubleUpDownCounter bound = instrument .bind (attributes );
439+ result .put (attributes , bound ::add );
440+ }
441+ } else {
442+ ExtendedLongUpDownCounter instrument =
443+ (ExtendedLongUpDownCounter ) meter .upDownCounterBuilder (INSTRUMENT_NAME ).build ();
444+ for (Attributes attributes : attributesList ) {
445+ BoundLongUpDownCounter bound = instrument .bind (attributes );
446+ result .put (attributes , bound ::add );
447+ }
448+ }
449+ return result ;
450+ case HISTOGRAM :
451+ if (isDouble ) {
452+ ExtendedDoubleHistogram instrument =
453+ (ExtendedDoubleHistogram )
454+ meter
455+ .histogramBuilder (INSTRUMENT_NAME )
456+ .setExplicitBucketBoundariesAdvice (BUCKET_BOUNDARIES )
457+ .build ();
458+ for (Attributes attributes : attributesList ) {
459+ BoundDoubleHistogram bound = instrument .bind (attributes );
460+ result .put (attributes , bound ::record );
461+ }
462+ } else {
463+ ExtendedLongHistogram instrument =
464+ (ExtendedLongHistogram )
465+ meter
466+ .histogramBuilder (INSTRUMENT_NAME )
467+ .setExplicitBucketBoundariesAdvice (BUCKET_BOUNDARIES )
468+ .ofLongs ()
469+ .build ();
470+ for (Attributes attributes : attributesList ) {
471+ BoundLongHistogram bound = instrument .bind (attributes );
472+ result .put (attributes , bound ::record );
473+ }
474+ }
475+ return result ;
476+ case GAUGE :
477+ if (isDouble ) {
478+ ExtendedDoubleGauge instrument =
479+ (ExtendedDoubleGauge ) meter .gaugeBuilder (INSTRUMENT_NAME ).build ();
480+ for (Attributes attributes : attributesList ) {
481+ BoundDoubleGauge bound = instrument .bind (attributes );
482+ result .put (attributes , bound ::set );
483+ }
484+ } else {
485+ ExtendedLongGauge instrument =
486+ (ExtendedLongGauge ) meter .gaugeBuilder (INSTRUMENT_NAME ).ofLongs ().build ();
487+ for (Attributes attributes : attributesList ) {
488+ BoundLongGauge bound = instrument .bind (attributes );
489+ result .put (attributes , bound ::set );
490+ }
491+ }
492+ return result ;
493+ case OBSERVABLE_COUNTER :
494+ case OBSERVABLE_UP_DOWN_COUNTER :
495+ case OBSERVABLE_GAUGE :
496+ }
497+ throw new IllegalArgumentException ();
498+ }
499+
357500 private interface Instrument {
358501 void record (long value , Attributes attributes );
359502 }
360503
504+ @ FunctionalInterface
505+ private interface BoundInstrument {
506+ void record (long value );
507+ }
508+
361509 private static MetricData copy (MetricData m ) {
362510 switch (m .getType ()) {
363511 case LONG_GAUGE :
0 commit comments