Skip to content

Commit 5e5b7f1

Browse files
committed
Add support for super stream + SAC in performance tool
References #46
1 parent e6e702f commit 5e5b7f1

File tree

7 files changed

+359
-87
lines changed

7 files changed

+359
-87
lines changed

pom.xml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@
190190
<version>${jetty.version}</version>
191191
<optional>true</optional>
192192
</dependency>
193+
194+
<dependency>
195+
<groupId>com.rabbitmq</groupId>
196+
<artifactId>amqp-client</artifactId>
197+
<version>${amqp-client.version}</version>
198+
<optional>true</optional>
199+
</dependency>
193200
<!-- end of dependencies for performance tool -->
194201

195202
<dependency>
@@ -249,13 +256,6 @@
249256
<scope>test</scope>
250257
</dependency>
251258

252-
<dependency>
253-
<groupId>com.rabbitmq</groupId>
254-
<artifactId>amqp-client</artifactId>
255-
<version>${amqp-client.version}</version>
256-
<scope>test</scope>
257-
</dependency>
258-
259259
<dependency>
260260
<groupId>org.eclipse.paho</groupId>
261261
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
@@ -611,6 +611,12 @@
611611
<version>${jetty.version}</version>
612612
</dependency>
613613

614+
<dependency>
615+
<groupId>com.rabbitmq</groupId>
616+
<artifactId>amqp-client</artifactId>
617+
<version>${amqp-client.version}</version>
618+
</dependency>
619+
614620
</dependencies>
615621
<build>
616622
<finalName>${finalName}</finalName>

src/main/java/com/rabbitmq/stream/impl/SuperStreamProducer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2021 VMware, Inc. or its affiliates. All rights reserved.
1+
// Copyright (c) 2021-2022 VMware, Inc. or its affiliates. All rights reserved.
22
//
33
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
44
// Mozilla Public License 2.0 ("MPL"), and the Apache License version 2 ("ASL").

src/main/java/com/rabbitmq/stream/perf/StreamPerfTest.java

Lines changed: 130 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static java.lang.String.format;
2020

2121
import com.google.common.util.concurrent.RateLimiter;
22+
import com.rabbitmq.client.Channel;
23+
import com.rabbitmq.client.Connection;
2224
import com.rabbitmq.stream.Address;
2325
import com.rabbitmq.stream.AddressResolver;
2426
import com.rabbitmq.stream.ByteCapacity;
@@ -31,6 +33,7 @@
3133
import com.rabbitmq.stream.Environment;
3234
import com.rabbitmq.stream.EnvironmentBuilder;
3335
import com.rabbitmq.stream.EnvironmentBuilder.TlsConfiguration;
36+
import com.rabbitmq.stream.MessageBuilder;
3437
import com.rabbitmq.stream.OffsetSpecification;
3538
import com.rabbitmq.stream.Producer;
3639
import com.rabbitmq.stream.ProducerBuilder;
@@ -76,6 +79,7 @@
7679
import java.util.concurrent.TimeUnit;
7780
import java.util.concurrent.atomic.AtomicInteger;
7881
import java.util.concurrent.atomic.AtomicLong;
82+
import java.util.concurrent.atomic.AtomicReference;
7983
import java.util.function.BiFunction;
8084
import java.util.function.Function;
8185
import java.util.function.Supplier;
@@ -347,6 +351,30 @@ public class StreamPerfTest implements Callable<Integer> {
347351
converter = Utils.PositiveIntegerTypeConverter.class)
348352
private int rpcTimeout;
349353

354+
@CommandLine.Option(
355+
names = {"--super-streams", "-sst"},
356+
description = "use super streams",
357+
defaultValue = "false")
358+
private boolean superStreams;
359+
360+
@CommandLine.Option(
361+
names = {"--super-streams-partitions", "-ssp"},
362+
description = "number of partitions for the super streams",
363+
defaultValue = "3",
364+
converter = Utils.PositiveIntegerTypeConverter.class)
365+
private int superStreamsPartitions;
366+
367+
@CommandLine.Option(
368+
names = {"--single-active-consumer", "-sac"},
369+
description = "use single active consumer",
370+
defaultValue = "false")
371+
private boolean singleActiveConsumer;
372+
373+
@CommandLine.Option(
374+
names = {"--amqp-uri", "-au"},
375+
description = "AMQP URI to use to create super stream topology")
376+
private String amqpUri;
377+
350378
private MetricsCollector metricsCollector;
351379
private PerformanceMetrics performanceMetrics;
352380
private List<Monitoring> monitorings;
@@ -601,33 +629,32 @@ public Integer call() throws Exception {
601629

602630
streams = Utils.streams(this.streamCount, this.streams);
603631

604-
for (String stream : streams) {
605-
StreamCreator streamCreator =
606-
environment.streamCreator().stream(stream)
607-
.maxLengthBytes(this.maxLengthBytes)
608-
.maxSegmentSizeBytes(this.maxSegmentSize)
609-
.leaderLocator(this.leaderLocator);
610-
611-
if (this.maxAge != null) {
612-
streamCreator.maxAge(this.maxAge);
632+
AtomicReference<Channel> amqpChannel = new AtomicReference<>();
633+
Connection amqpConnection;
634+
if (this.superStreams) {
635+
amqpConnection = Utils.amqpConnection(this.amqpUri, uris, tls, this.sniServerNames);
636+
if (this.deleteStreams) {
637+
// we keep it open for deletion, so adding a close step
638+
shutdownService.wrap(
639+
closeStep("Closing AMQP connection for super streams", () -> amqpConnection.close()));
613640
}
641+
amqpChannel.set(amqpConnection.createChannel());
642+
} else {
643+
amqpConnection = null;
644+
}
614645

615-
try {
616-
streamCreator.create();
617-
} catch (StreamException e) {
618-
if (e.getCode() == Constants.RESPONSE_CODE_PRECONDITION_FAILED) {
619-
String message =
620-
String.format(
621-
"Warning: stream '%s' already exists, but with different properties than "
622-
+ "max-length-bytes=%s, stream-max-segment-size-bytes=%s, queue-leader-locator=%s",
623-
stream, this.maxLengthBytes, this.maxSegmentSize, this.leaderLocator);
624-
if (this.maxAge != null) {
625-
message += String.format(", max-age=%s", this.maxAge);
626-
}
627-
this.out.println(message);
628-
} else {
629-
throw e;
646+
for (String stream : streams) {
647+
if (this.superStreams) {
648+
List<String> partitions =
649+
Utils.superStreamPartitions(stream, this.superStreamsPartitions);
650+
for (String partition : partitions) {
651+
createStream(environment, partition);
630652
}
653+
654+
Utils.declareSuperStreamExchangeAndBindings(amqpChannel.get(), stream, partitions);
655+
656+
} else {
657+
createStream(environment, stream);
631658
}
632659
}
633660

@@ -637,22 +664,32 @@ public Integer call() throws Exception {
637664
"Deleting stream(s)",
638665
() -> {
639666
for (String stream : streams) {
640-
LOGGER.debug("Deleting {}", stream);
641-
try {
642-
environment.deleteStream(stream);
643-
LOGGER.debug("Deleted {}", stream);
644-
} catch (Exception e) {
645-
LOGGER.warn("Could not delete stream {}: {}", stream, e.getMessage());
667+
if (this.superStreams) {
668+
List<String> partitions =
669+
Utils.superStreamPartitions(stream, this.superStreamsPartitions);
670+
for (String partition : partitions) {
671+
environment.deleteStream(partition);
672+
}
673+
Utils.deleteSuperStreamExchange(amqpChannel.get(), stream);
674+
675+
} else {
676+
LOGGER.debug("Deleting {}", stream);
677+
try {
678+
environment.deleteStream(stream);
679+
LOGGER.debug("Deleted {}", stream);
680+
} catch (Exception e) {
681+
LOGGER.warn("Could not delete stream {}: {}", stream, e.getMessage());
682+
}
646683
}
647684
}
648685
}));
686+
} else {
687+
if (this.superStreams) {
688+
// we don't want to delete the super streams at the end, so we close the AMQP connection
689+
amqpConnection.close();
690+
}
649691
}
650692

651-
// FIXME handle metadata update for consumers and publishers
652-
// they should at least issue a warning that their stream has been deleted and that they're
653-
// now
654-
// useless
655-
656693
List<Producer> producers = Collections.synchronizedList(new ArrayList<>(this.producers));
657694
List<Runnable> producerRunnables =
658695
IntStream.range(0, this.producers)
@@ -675,6 +712,16 @@ public Integer call() throws Exception {
675712
producerBuilder.name(producerName).confirmTimeout(Duration.ZERO);
676713
}
677714

715+
java.util.function.Consumer<MessageBuilder> messageBuilderConsumer;
716+
if (this.superStreams) {
717+
producerBuilder.routing(msg -> msg.getProperties().getMessageIdAsString());
718+
AtomicLong messageIdSequence = new AtomicLong(0);
719+
messageBuilderConsumer =
720+
mg -> mg.properties().messageId(messageIdSequence.getAndIncrement());
721+
} else {
722+
messageBuilderConsumer = mg -> {};
723+
}
724+
678725
Producer producer =
679726
producerBuilder
680727
.subEntrySize(this.subEntrySize)
@@ -707,9 +754,10 @@ public Integer call() throws Exception {
707754
long creationTime = System.currentTimeMillis();
708755
byte[] payload = new byte[msgSize];
709756
Utils.writeLong(payload, creationTime);
757+
MessageBuilder messageBuilder = producer.messageBuilder();
758+
messageBuilderConsumer.accept(messageBuilder);
710759
producer.send(
711-
producer.messageBuilder().addData(payload).build(),
712-
confirmationHandler);
760+
messageBuilder.addData(payload).build(), confirmationHandler);
713761
}
714762
} catch (Exception e) {
715763
if (e instanceof InterruptedException
@@ -734,7 +782,21 @@ public Integer call() throws Exception {
734782
AtomicLong messageCount = new AtomicLong(0);
735783
String stream = stream(streams, i);
736784
ConsumerBuilder consumerBuilder = environment.consumerBuilder();
737-
consumerBuilder = consumerBuilder.stream(stream).offset(this.offset);
785+
consumerBuilder = consumerBuilder.offset(this.offset);
786+
787+
if (this.superStreams) {
788+
consumerBuilder.superStream(stream);
789+
} else {
790+
consumerBuilder.stream(stream);
791+
}
792+
793+
if (this.singleActiveConsumer) {
794+
consumerBuilder.singleActiveConsumer();
795+
// single active consumer requires a name
796+
if (this.storeEvery == 0) {
797+
this.storeEvery = 10_000;
798+
}
799+
}
738800

739801
if (this.storeEvery > 0) {
740802
String consumerName = this.consumerNameStrategy.apply(stream, i + 1);
@@ -831,6 +893,36 @@ public Integer call() throws Exception {
831893
return 0;
832894
}
833895

896+
private void createStream(Environment environment, String stream) {
897+
StreamCreator streamCreator =
898+
environment.streamCreator().stream(stream)
899+
.maxLengthBytes(this.maxLengthBytes)
900+
.maxSegmentSizeBytes(this.maxSegmentSize)
901+
.leaderLocator(this.leaderLocator);
902+
903+
if (this.maxAge != null) {
904+
streamCreator.maxAge(this.maxAge);
905+
}
906+
907+
try {
908+
streamCreator.create();
909+
} catch (StreamException e) {
910+
if (e.getCode() == Constants.RESPONSE_CODE_PRECONDITION_FAILED) {
911+
String message =
912+
String.format(
913+
"Warning: stream '%s' already exists, but with different properties than "
914+
+ "max-length-bytes=%s, stream-max-segment-size-bytes=%s, queue-leader-locator=%s",
915+
stream, this.maxLengthBytes, this.maxSegmentSize, this.leaderLocator);
916+
if (this.maxAge != null) {
917+
message += String.format(", max-age=%s", this.maxAge);
918+
}
919+
this.out.println(message);
920+
} else {
921+
throw e;
922+
}
923+
}
924+
}
925+
834926
private void overridePropertiesWithEnvironmentVariables() throws Exception {
835927
Function<String, String> optionToEnvMappings =
836928
OPTION_TO_ENVIRONMENT_VARIABLE

0 commit comments

Comments
 (0)