|
12 | 12 | import static org.mockito.Mockito.times;
|
13 | 13 | import static org.mockito.Mockito.verify;
|
14 | 14 |
|
| 15 | +import com.google.common.util.concurrent.AtomicDouble; |
| 16 | +import com.google.common.util.concurrent.Uninterruptibles; |
15 | 17 | import io.github.netmikey.logunit.api.LogCapturer;
|
16 | 18 | import io.opentelemetry.api.common.AttributeKey;
|
17 | 19 | import io.opentelemetry.api.common.Attributes;
|
|
21 | 23 | import io.opentelemetry.sdk.metrics.Aggregation;
|
22 | 24 | import io.opentelemetry.sdk.metrics.InstrumentType;
|
23 | 25 | import io.opentelemetry.sdk.metrics.InstrumentValueType;
|
| 26 | +import io.opentelemetry.sdk.metrics.data.ExemplarData; |
24 | 27 | import io.opentelemetry.sdk.metrics.data.LongExemplarData;
|
25 | 28 | import io.opentelemetry.sdk.metrics.data.LongPointData;
|
26 | 29 | import io.opentelemetry.sdk.metrics.data.MetricData;
|
| 30 | +import io.opentelemetry.sdk.metrics.data.PointData; |
27 | 31 | import io.opentelemetry.sdk.metrics.internal.aggregator.Aggregator;
|
28 | 32 | import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
|
29 | 33 | import io.opentelemetry.sdk.metrics.internal.aggregator.EmptyMetricData;
|
|
37 | 41 | import io.opentelemetry.sdk.resources.Resource;
|
38 | 42 | import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
|
39 | 43 | import io.opentelemetry.sdk.testing.time.TestClock;
|
| 44 | +import java.time.Duration; |
| 45 | +import java.util.ArrayList; |
| 46 | +import java.util.List; |
| 47 | +import java.util.concurrent.CountDownLatch; |
| 48 | +import java.util.function.BiConsumer; |
| 49 | +import java.util.stream.Stream; |
40 | 50 | import org.junit.jupiter.api.Test;
|
41 | 51 | import org.junit.jupiter.api.extension.RegisterExtension;
|
| 52 | +import org.junit.jupiter.params.ParameterizedTest; |
| 53 | +import org.junit.jupiter.params.provider.Arguments; |
| 54 | +import org.junit.jupiter.params.provider.MethodSource; |
42 | 55 | import org.slf4j.event.Level;
|
43 | 56 |
|
44 | 57 | @SuppressLogger(DefaultSynchronousMetricStorage.class)
|
@@ -370,4 +383,79 @@ void recordAndCollect_DeltaAtLimit() {
|
370 | 383 | assertThat(storage.getAggregatorHandlePool()).hasSize(CARDINALITY_LIMIT);
|
371 | 384 | logs.assertContains("Instrument name has exceeded the maximum allowed cardinality");
|
372 | 385 | }
|
| 386 | + |
| 387 | + @ParameterizedTest |
| 388 | + @MethodSource("concurrentStressTestArguments") |
| 389 | + void recordAndCollect_concurrentStressTest( |
| 390 | + DefaultSynchronousMetricStorage<?, ?> storage, BiConsumer<Double, AtomicDouble> collect) { |
| 391 | + // Define record threads. Each records a value of 1.0, 2000 times |
| 392 | + List<Thread> threads = new ArrayList<>(); |
| 393 | + CountDownLatch latch = new CountDownLatch(4); |
| 394 | + for (int i = 0; i < 4; i++) { |
| 395 | + Thread thread = |
| 396 | + new Thread( |
| 397 | + () -> { |
| 398 | + for (int j = 0; j < 2000; j++) { |
| 399 | + storage.recordDouble(1.0, Attributes.empty(), Context.current()); |
| 400 | + Uninterruptibles.sleepUninterruptibly(Duration.ofMillis(1)); |
| 401 | + } |
| 402 | + latch.countDown(); |
| 403 | + }); |
| 404 | + threads.add(thread); |
| 405 | + } |
| 406 | + |
| 407 | + // Define collect thread. Collect thread collects and aggregates the |
| 408 | + AtomicDouble cumulativeSum = new AtomicDouble(); |
| 409 | + Thread collectThread = |
| 410 | + new Thread( |
| 411 | + () -> { |
| 412 | + do { |
| 413 | + Uninterruptibles.sleepUninterruptibly(Duration.ofMillis(1)); |
| 414 | + MetricData metricData = |
| 415 | + storage.collect(Resource.empty(), InstrumentationScopeInfo.empty(), 0, 1); |
| 416 | + if (metricData.isEmpty()) { |
| 417 | + continue; |
| 418 | + } |
| 419 | + metricData.getDoubleSumData().getPoints().stream() |
| 420 | + .findFirst() |
| 421 | + .ifPresent(pointData -> collect.accept(pointData.getValue(), cumulativeSum)); |
| 422 | + } while (latch.getCount() != 0); |
| 423 | + }); |
| 424 | + |
| 425 | + // Start all the threads |
| 426 | + collectThread.start(); |
| 427 | + threads.forEach(Thread::start); |
| 428 | + |
| 429 | + // Wait for the collect thread to end, which collects until the record threads are done |
| 430 | + Uninterruptibles.joinUninterruptibly(collectThread); |
| 431 | + |
| 432 | + assertThat(cumulativeSum.get()).isEqualTo(8000.0); |
| 433 | + } |
| 434 | + |
| 435 | + private static Stream<Arguments> concurrentStressTestArguments() { |
| 436 | + Aggregator<PointData, ExemplarData> aggregator = |
| 437 | + ((AggregatorFactory) Aggregation.sum()) |
| 438 | + .createAggregator(DESCRIPTOR, ExemplarFilter.alwaysOff()); |
| 439 | + return Stream.of( |
| 440 | + Arguments.of( |
| 441 | + // Delta |
| 442 | + new DefaultSynchronousMetricStorage<>( |
| 443 | + RegisteredReader.create(InMemoryMetricReader.createDelta(), ViewRegistry.create()), |
| 444 | + METRIC_DESCRIPTOR, |
| 445 | + aggregator, |
| 446 | + AttributesProcessor.noop(), |
| 447 | + CARDINALITY_LIMIT), |
| 448 | + (BiConsumer<Double, AtomicDouble>) |
| 449 | + (value, cumulativeCount) -> cumulativeCount.addAndGet(value)), |
| 450 | + Arguments.of( |
| 451 | + // Cumulative |
| 452 | + new DefaultSynchronousMetricStorage<>( |
| 453 | + RegisteredReader.create(InMemoryMetricReader.create(), ViewRegistry.create()), |
| 454 | + METRIC_DESCRIPTOR, |
| 455 | + aggregator, |
| 456 | + AttributesProcessor.noop(), |
| 457 | + CARDINALITY_LIMIT), |
| 458 | + (BiConsumer<Double, AtomicDouble>) |
| 459 | + (value, cumulativeCount) -> cumulativeCount.set(value))); |
| 460 | + } |
373 | 461 | }
|
0 commit comments