package org.axonframework.integrationtests.eventstore.benchmark; import org.axonframework.common.AxonThreadFactory; import org.axonframework.common.transaction.NoTransactionManager; import org.axonframework.eventhandling.EventListener; import org.axonframework.eventhandling.*; import org.axonframework.eventhandling.tokenstore.inmemory.InMemoryTokenStore; import org.axonframework.eventsourcing.eventstore.AbstractEventStorageEngine; import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore; import org.axonframework.eventsourcing.eventstore.EventStorageEngine; import org.axonframework.messaging.unitofwork.DefaultUnitOfWork; import org.axonframework.messaging.unitofwork.UnitOfWork; import org.axonframework.serialization.Serializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StopWatch; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import static org.axonframework.eventsourcing.eventstore.EventStoreTestUtils.createEvent; import static org.axonframework.serialization.MessageSerializer.serializeMetaData; import static org.axonframework.serialization.MessageSerializer.serializePayload; /** * @author Rene de Waele */ public abstract class AbstractEventStoreBenchmark { private static final int DEFAULT_THREAD_COUNT = 10, DEFAULT_BATCH_SIZE = 5, DEFAULT_BATCH_COUNT = 5000; private static final DecimalFormat decimalFormat = new DecimalFormat("0.00"); private final Logger logger = LoggerFactory.getLogger(getClass()); private final EmbeddedEventStore eventStore; private final EventProcessor eventProcessor; private final EventStorageEngine storageEngine; private final int threadCount, batchSize, batchCount; private final ExecutorService executorService; private final CountDownLatch remainingEvents; private final Set<String> readEvents = new HashSet<>(); protected AbstractEventStoreBenchmark(EventStorageEngine storageEngine) { this(storageEngine, DEFAULT_THREAD_COUNT, DEFAULT_BATCH_SIZE, DEFAULT_BATCH_COUNT); } protected AbstractEventStoreBenchmark(EventStorageEngine storageEngine, int threadCount, int batchSize, int batchCount) { this.eventStore = new EmbeddedEventStore(this.storageEngine = storageEngine); this.threadCount = threadCount; this.batchSize = batchSize; this.batchCount = batchCount; this.remainingEvents = new CountDownLatch(getTotalEventCount()); this.eventProcessor = new TrackingEventProcessor("benchmark", new SimpleEventHandlerInvoker( (EventListener) (e) -> { if (readEvents.add(e.getIdentifier())) { remainingEvents.countDown(); } else { throw new IllegalStateException("Double event!"); } }), eventStore, new InMemoryTokenStore(), NoTransactionManager.INSTANCE, batchSize); this.executorService = Executors.newFixedThreadPool(threadCount, new AxonThreadFactory("storageJobs")); } public void start() { logger.info("Preparing for benchmark", getTotalEventCount()); List<Callable<Object>> storageJobs = createStorageJobs(threadCount, batchSize, batchCount); Collections.shuffle(storageJobs); logger.info("Created {} event storage jobs", storageJobs.size()); prepareForBenchmark(); logger.info("Started benchmark. Storing {} events", getTotalEventCount()); StopWatch stopWatch = new StopWatch(); stopWatch.start("Storing events"); try { executorService.invokeAll(storageJobs); } catch (InterruptedException e) { throw new IllegalStateException("Benchmark was interrupted", e); } stopWatch.stop(); logger.info("Stored {} events in {} seconds. That's about {} events/sec", getTotalEventCount(), decimalFormat.format(stopWatch.getTotalTimeSeconds()), (int) (getTotalEventCount() / stopWatch.getTotalTimeSeconds())); stopWatch.start("Waiting for event processor to catch up"); try { remainingEvents.await(1, TimeUnit.MINUTES); } catch (InterruptedException e) { throw new IllegalStateException("Benchmark was interrupted", e); } stopWatch.stop(); logger.info("Read {} events in {} seconds. That's about {} events/sec.", getTotalEventCount(), decimalFormat.format(stopWatch.getTotalTimeSeconds()), (int) (getTotalEventCount() / stopWatch.getTotalTimeSeconds())); logger.info("Cleaning up"); cleanUpAfterBenchmark(); } protected void prepareForBenchmark() { eventProcessor.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new IllegalStateException("Benchmark was interrupted", e); } } protected void cleanUpAfterBenchmark() { executorService.shutdown(); eventProcessor.shutDown(); eventStore.shutDown(); } protected List<Callable<Object>> createStorageJobs(int threadCount, int batchSize, int batchCount) { return IntStream.range(0, threadCount) .mapToObj(i -> createStorageJobs(String.valueOf(i), batchSize, batchCount)).flatMap(Collection::stream) .collect(Collectors.toList()); } protected List<Callable<Object>> createStorageJobs(String aggregateId, int batchSize, int batchCount) { return IntStream.range(0, batchCount).mapToObj(i -> (Callable<Object>) () -> { EventMessage<?>[] events = createEvents(aggregateId, i * batchSize, batchSize); executeStorageJob(events); return i; }).collect(Collectors.toList()); } protected EventMessage<?>[] createEvents(String aggregateId, int startSequenceNumber, int count) { Stream<EventMessage<?>> stream = IntStream.range(startSequenceNumber, startSequenceNumber + count) .mapToObj(sequenceNumber -> createEvent(aggregateId, sequenceNumber)).map(event -> { serializer().ifPresent(serializer -> { serializePayload(event, serializer, byte[].class); serializeMetaData(event, serializer, byte[].class); }); return event; }); return stream.toArray(EventMessage[]::new); } protected void executeStorageJob(EventMessage<?>... events) { UnitOfWork<?> unitOfWork = new DefaultUnitOfWork<>(null); unitOfWork.execute(() -> storeEvents(events)); } protected void storeEvents(EventMessage<?>... events) { eventStore.publish(events); } protected Optional<Serializer> serializer() { return storageEngine instanceof AbstractEventStorageEngine ? Optional.of(((AbstractEventStorageEngine) storageEngine).getSerializer()) : Optional.empty(); } public int getTotalEventCount() { return threadCount * batchSize * batchCount; } protected EventStorageEngine getStorageEngine() { return storageEngine; } }