/* * Copyright 2014 - 2017 Real Logic Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package io.aeron.archiver.workloads; import io.aeron.*; import io.aeron.archiver.*; import io.aeron.archiver.client.*; import io.aeron.driver.*; import io.aeron.logbuffer.*; import io.aeron.protocol.DataHeaderFlyweight; import org.agrona.*; import org.agrona.concurrent.UnsafeBuffer; import org.junit.*; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import java.io.*; import java.util.Random; import java.util.concurrent.CountDownLatch; import static io.aeron.archiver.TestUtil.*; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @Ignore public class ArchiveReplayLoadTest { private static final int TIMEOUT = 5000; private static final double MEGABYTE = 1024.0d * 1024.0d; static final String REPLY_URI = "aeron:ipc?endpoint=127.0.0.1:54327"; static final int REPLY_STREAM_ID = 100; private static final String REPLAY_URI = "aeron:ipc?endpoint=127.0.0.1:54326"; private static final String PUBLISH_URI = "aeron:ipc?endpoint=127.0.0.1:54325"; private static final int PUBLISH_STREAM_ID = 1; private static final int MAX_FRAGMENT_SIZE = 1024; public static final int MESSAGE_COUNT = 100000; private final MediaDriver.Context driverCtx = new MediaDriver.Context(); private final Archiver.Context archiverCtx = new Archiver.Context(); private Aeron publishingClient; private Archiver archiver; private MediaDriver driver; private UnsafeBuffer buffer = new UnsafeBuffer(new byte[4096]); private File archiveDir; private int recordingId; private String source; private long remaining; private int fragmentCount; private int[] fragmentLength; private long totalDataLength; private long totalRecordingLength; private long recorded; private volatile int lastTermId = -1; private Throwable trackerError; private Random rnd = new Random(); private long seed; @Rule public TestWatcher testWatcher = new TestWatcher() { protected void failed(final Throwable t, final Description description) { System.err.println( "ArchiveAndReplaySystemTest failed with random seed: " + ArchiveReplayLoadTest.this.seed); } }; private Subscription reply; private long correlationId; private long joiningPosition; @Before public void setUp() throws Exception { seed = System.nanoTime(); rnd.setSeed(seed); driverCtx .termBufferSparseFile(true) .threadingMode(ThreadingMode.DEDICATED) .errorHandler(LangUtil::rethrowUnchecked) .dirsDeleteOnStart(true); driver = MediaDriver.launch(driverCtx); archiveDir = TestUtil.makeTempDir(); archiverCtx.archiveDir(archiveDir); archiver = Archiver.launch(archiverCtx); println("Archiver started, dir: " + archiverCtx.archiveDir().getAbsolutePath()); publishingClient = Aeron.connect(); } @After public void closeEverything() throws Exception { CloseHelper.close(publishingClient); CloseHelper.close(archiver); CloseHelper.close(driver); if (null != archiveDir) { IoUtil.delete(archiveDir, false); } driverCtx.deleteAeronDirectory(); } @Test(timeout = 180000) public void replay() throws IOException, InterruptedException { try (Publication archiverServiceRequest = publishingClient.addPublication( archiverCtx.controlRequestChannel(), archiverCtx.controlRequestStreamId()); Subscription archiverNotifications = publishingClient.addSubscription( archiverCtx.recordingEventsChannel(), archiverCtx.recordingEventsStreamId())) { final ArchiveClient client = new ArchiveClient(archiverServiceRequest, archiverNotifications); awaitPublicationIsConnected(archiverServiceRequest); awaitSubscriptionIsConnected(archiverNotifications); println("Archive service connected"); reply = publishingClient.addSubscription(REPLY_URI, REPLY_STREAM_ID); client.connect(REPLY_URI, REPLY_STREAM_ID); awaitSubscriptionIsConnected(reply); println("Client connected"); final long startRecordingCorrelationId = this.correlationId++; waitFor(() -> client.startRecording(PUBLISH_URI, PUBLISH_STREAM_ID, startRecordingCorrelationId)); println("Recording requested"); waitForOk(client, reply, startRecordingCorrelationId); final Publication publication = publishingClient.addPublication(PUBLISH_URI, PUBLISH_STREAM_ID); awaitPublicationIsConnected(publication); final int messageCount = prepAndSendMessages(client, publication); assertNull(trackerError); println("All data arrived"); println("Request stop recording"); final long requestStopCorrelationId = this.correlationId++; waitFor(() -> client.stopRecording(recordingId, requestStopCorrelationId)); waitForOk(client, reply, requestStopCorrelationId); final long limit = System.currentTimeMillis() + 120000; int i = 0; while (System.currentTimeMillis() < limit) { final long start = System.currentTimeMillis(); validateReplay(client, publication, messageCount); final long delta = System.currentTimeMillis() - start; final double mbps = (totalDataLength * 1000.0) / (MEGABYTE * delta); System.out.printf("Replay[%d] speed %fMBps %n", ++i, mbps); } } } private int prepAndSendMessages( final ArchiveClient client, final Publication publication) throws InterruptedException { final int messageCount = MESSAGE_COUNT; fragmentLength = new int[messageCount]; for (int i = 0; i < messageCount; i++) { final int messageLength = 64 + rnd.nextInt(MAX_FRAGMENT_SIZE - 64) - DataHeaderFlyweight.HEADER_LENGTH; fragmentLength[i] = messageLength + DataHeaderFlyweight.HEADER_LENGTH; totalDataLength += fragmentLength[i]; } final CountDownLatch waitForData = new CountDownLatch(1); System.out.printf("Sending %d messages, total length=%d %n", messageCount, totalDataLength); trackRecordingProgress(client, publication.termBufferLength(), waitForData); publishDataToRecorded(publication, messageCount); waitForData.await(); return messageCount; } private void publishDataToRecorded(final Publication publication, final int messageCount) { final int positionBitsToShift = Integer.numberOfTrailingZeros(publication.termBufferLength()); joiningPosition = publication.position(); final int initialTermOffset = LogBufferDescriptor.computeTermOffsetFromPosition( joiningPosition, positionBitsToShift); // clear out the buffer we write for (int i = 0; i < 1024; i++) { buffer.putByte(i, (byte)'z'); } buffer.putStringAscii(32, "TEST"); for (int i = 0; i < messageCount; i++) { final int dataLength = fragmentLength[i] - DataHeaderFlyweight.HEADER_LENGTH; buffer.putInt(0, i); printf("Sending: index=%d length=%d %n", i, dataLength); offer(publication, buffer, dataLength); } final int lastTermOffset = LogBufferDescriptor.computeTermOffsetFromPosition( publication.position(), positionBitsToShift); final int termIdFromPosition = LogBufferDescriptor.computeTermIdFromPosition( publication.position(), positionBitsToShift, publication.initialTermId()); totalRecordingLength = (termIdFromPosition - publication.initialTermId()) * publication.termBufferLength() + (lastTermOffset - initialTermOffset); assertThat(publication.position() - joiningPosition, is(totalRecordingLength)); lastTermId = termIdFromPosition; } private void validateReplay( final ArchiveClient client, final Publication publication, final int messageCount) { final int replayStreamId = (int)correlationId; try (Subscription replay = publishingClient.addSubscription(REPLAY_URI, replayStreamId)) { // request replay final long correlationId = this.correlationId++; TestUtil.waitFor(() -> client.replay( recordingId, joiningPosition, totalRecordingLength, REPLAY_URI, replayStreamId, correlationId )); awaitSubscriptionIsConnected(replay); fragmentCount = 0; remaining = totalDataLength; while (fragmentCount < messageCount && remaining > 0 && !replay.isClosed() && !replay.hasNoImages()) { replay.poll(this::validateFragment, 128); } assertThat(fragmentCount, is(messageCount)); assertThat(remaining, is(0L)); } } private void validateFragment( final DirectBuffer buffer, final int offset, final int length, @SuppressWarnings("unused") final Header header) { assertThat(length, is(fragmentLength[fragmentCount] - DataHeaderFlyweight.HEADER_LENGTH)); assertThat(buffer.getInt(offset), is(fragmentCount)); assertThat(buffer.getByte(offset + 4), is((byte)'z')); remaining -= fragmentLength[fragmentCount]; fragmentCount++; } private void trackRecordingProgress( final ArchiveClient client, final int termBufferLength, final CountDownLatch waitForData) { final Thread t = new Thread( () -> { try { recorded = 0; long start = System.currentTimeMillis(); long startBytes = remaining; // each message is fragmentLength[fragmentCount] while (lastTermId == -1 || recorded < totalRecordingLength) { TestUtil.waitFor(() -> (client.pollEvents(new RecordingEventsListener() { public void onProgress( final long recordingId0, final long joiningPosition, final long currentPosition) { assertThat(recordingId0, is(recordingId)); recorded = currentPosition - joiningPosition; printf("a=%d total=%d %n", recorded, totalRecordingLength); } public void onStart( final long recordingId, final String source, final int sessionId, final String channel, final int streamId) { } public void onStop(final long recordingId0) { } }, 1)) != 0); final long end = System.currentTimeMillis(); final long deltaTime = end - start; if (deltaTime > TIMEOUT) { start = end; final long deltaBytes = remaining - startBytes; startBytes = remaining; final double mbps = ((deltaBytes * 1000.0) / deltaTime) / MEGABYTE; printf("Archive reported speed: %f MB/s %n", mbps); } } final long end = System.currentTimeMillis(); final long deltaTime = end - start; final long deltaBytes = remaining - startBytes; final double mbps = ((deltaBytes * 1000.0) / deltaTime) / MEGABYTE; printf("Archive reported speed: %f MB/s %n", mbps); } catch (final Throwable throwable) { trackerError = throwable; } waitForData.countDown(); }); t.setDaemon(true); t.start(); } }