This is a tool to assist exercising and benchmarking of the NATS Java client in your own environment.
Administration and server related benchmarking of NATS should prefer the NATS CLI tooling, especially in production environments.
You can use the maven exec plugin...
> mvn clean compile exec:java -Dexec.mainClass="io.nats.jsmulti.JsMulti" -Dexec.args="-a PubSync -s nats://localhost:4222 -u sub -m 10_000"
> mvn clean compile exec:java -Dexec.mainClass="io.nats.jsmulti.examples.Producer" -Dexec.args="[arguments]"
> mvn clean compile exec:java -Dexec.mainClass="io.nats.jsmulti.examples.Consumer" -Dexec.args="[arguments]"
> mvn clean compile exec:java -Dexec.mainClass="io.nats.jsmulti.examples.Rtt" -Dexec.args="[arguments]"
You can increase memory for maven via environment variable, i.e.
set MAVEN_OPTS=-Xmx6g
There are tasks available for js
> gradle clean jsMulti --args="-a PubSync -s nats://localhost:4222 -u sub -m 10_000"
> gradle clean producer --args="[arguments]"
> gradle clean consumer --args="[arguments]"
> gradle clean rtt --args="[arguments]"
You can increase memory for the gradle task by changing the jvmArgs
value for the correct task in build.gradle.
Making some assumptions that you have a copy of the Java Nats Client (jnats) in your maven repo, since you needed it to build this project, you can simply run java with the correct classpath, which includes the location of classes folder as compiled and the location of the jar file.
At a minimum, you will need to provide <my-code-path>
and <my-repo-path>
> java -cp <my-code-path>/java-nats-examples/js-multi-tool/target/classes;<my-repo-path>/io/nats/jnats/2.16.14/jnats-2.16.14.jar io.nats.jsmulti.JsMulti -a PubSync -s nats://localhost:4222 -u sub -m 10_000
You could also use a gradle task to build an uber jar. This jar will contain all necessary classes and the jnats library.
gradle uberJar
At that point you can run directly the programs you can run with the gradle tasks
> java -cp <my-code-path>/java-nats-examples/js-multi-tool/build/libs/js-multi-tool-1.5.3-uber.jar io.nats.jsmulti.JsMulti [arguments]
> java -cp <my-code-path>/java-nats-examples/js-multi-tool/build/libs/js-multi-tool-1.5.3-uber.jar io.nats.jsmulti.examples.Producer [arguments]
> java -cp <my-code-path>/java-nats-examples/js-multi-tool/build/libs/js-multi-tool-1.5.3-uber.jar io.nats.jsmulti.examples.Consumer [arguments]
> java -cp <my-code-path>/java-nats-examples/js-multi-tool/build/libs/js-multi-tool-1.5.3-uber.jar io.nats.jsmulti.examples.Rtt [arguments]
The JsMulti program has a main
method so it's easy enough to run from any ide.
You can also call it from another program using the public static void run(Arguments a)
method.
We have provided an Arguments
class to help build configurations.
You could also use your IDE and build a runtime configuration that passes command line arguments.
The examples below show both command line and builder examples.
Number arguments are all integers. When providing numbers on the command line, you can use underscore _
, comma ,
or period .
to make it more readable. So these are all valid for 1 million 1000000
, 1,000,000
, 1.000.000
or 1_000_000
-a
The action to execute. Always required. One of the following (case ignored)
RTT
Round Trip TestPubSync
publish synchronouslyPubAsync
publish asynchronouslyPubCore
publish synchronously using the core apiSubCore
core subscribe read messages (synchronously)SubPush
push subscribe read messages (synchronously)SubQueue
push subscribe read messages with queue (synchronously)SubPull
pull subscribe fetch messages (different durable if threaded)SubPullQueue
pull subscribe fetch messages (all with same durable)SubPullRead
pull subscribe read messages (different durable if threaded)SubPullReadQueue
pull subscribe read messages (all with same durable)
The builder was created to give another way to build configurations if you typically run from an IDE.
You could use the builder and modify the JsMulti
class or just create your own classes that make it easier to run from the command line.
The actions all have smart builder creation methods...
Context ctx = Arguments.rtt() ... .build();
Context ctx = Arguments.pubSync("subject-name") ... .build();
Context ctx = Arguments.pubAsync("subject-name") ... .build();
Context ctx = Arguments.pubCore("subject-name") ... .build();
Context ctx = Arguments.subCore("subject-name") ... .build();
Context ctx = Arguments.subPush("subject-name") ... .build();
Context ctx = Arguments.subQueue("subject-name") ... .build();
Context ctx = Arguments.subPull("subject-name") ... .build();
Context ctx = Arguments.subPullRead("subject-name") ... .build();
Context ctx = Arguments.subPullReadQueue("subject-name") ... .build();
You could build your own custom class to run from the command line:
public class MyMulti {
public static void main(String[] args) throws Exception {
Context ctx = Arguments.pubSync("subject-name") ... .build();
JsMulti.run(new Arguments(args));
}
}
Here are some variations of JsMulti.run(...)
that are available.
public static List<Stats> run(String[] args) throws Exception
public static List<Stats> run(String[] args, boolean printArgs, boolean reportWhenDone) throws Exception
public static List<Stats> run(Arguments args) throws Exception
public static List<Stats> run(Arguments args, boolean printArgs, boolean reportWhenDone) throws Exception
-s
The url of the server. if not provided, will default to nats://localhost:4222
Command Line
... JsMulti ... -s nats://myhost:4444 ...
Builder
Context ctx = Arguments ... .server("nats://myhost:4444") ...
-of
Options factory class name. Takes precedence over -s
. Must have a default constructor so it can be instantiated by reflection.
By providing your own options factory you are able to connect to a server however you need to.
For instance, you can provide your own authentication or could override JsMulti's default No-Op error listener.
Make sure this value is a fully qualified class name with package, i.e. com.myco.MyOptionsFactory
public class MyOptionsFactory implements OptionsFactory {
@Override
public Options getOptions() throws Exception {
return ... do work here
}
}
-rf
report frequency (number) how often to print progress, defaults to 1000 messages. <= 0 for no reporting.
Reporting time is excluded from timings.
Command Line
... JsMulti ... -rf 5000 ...
... JsMulti ... -rf -1 ...
Builder
Context ctx = Arguments ... .reportFrequency(5000) ...
Context ctx = Arguments ... .noReporting() ...
Publishing and subscribing have some options in common.
-u
subject (string), required for publishing or subscribing
-m
message count (number) for publishing or subscribing, defaults to 100,000
-d
threads (number) for publishing or subscribing, defaults to 1
-n
connection strategy (shared|individual) when threading, whether to share
shared
When running with more than 1 thread, only connect to the server once and share the connection among all threads. This is the default.individual
When running with more than 1 thread, each thread will make its own connection to the server.
-j
jitter (number) between publishes or subscribe message retrieval of random
number from 0 to j-1, in milliseconds, defaults to 0 (no jitter), maximum 10_000
time spent in jitter is excluded from timings
... JsMulti ... -u subject-name -m 1_000_000 -d 4 -n shared -j 1500
... JsMulti ... -u subject-name -d 4 -j 1500
... JsMulti ... -u subject-name -m 2_000_000 -d 4 -n individual -j 1500
-ps
payload size (number) for publishing, defaults to 128, maximum 1048576"
-rs
round size (number) for pubAsync, default to 100, maximum 1000.
Publishing asynchronously uses the "sawtooth" pattern. This means we publish a round of messages, collecting all the futures that receive the PublishAck
until we reach the round size. At that point we process all the futures we have collected, then we start over until we have published all the messages.
-lf
latency flag tells the publish to add information so the subscribe half of the run can calculate latency
... JsMulti ... -ps 512 -rs 50
In real applications there are other algorithms one might use. For instance, you could optionally set up a separate thread to process the Publish Ack. This would require some concurrent mechanism like the
java.util.concurrent.LinkedBlockingQueue
Publishing would run on one thread and place the futures in the queue. The second thread would be pulling from the queue.
-kp
ack policy (explicit|none|all) for subscriptions. Ack Policy must be explicit
on pull subscriptions.
explicit
Explicit Ack Policy. Acknowledge each message received. This is the default.none
None Ack Policy. Configures the consumer to not have to ack messages.all
All Ack Policy. Configures the consumer to ack with one message for all the previous messages.
-kf
ack frequency (number), applies to Ack Policy all, ack after kf messages, defaults to 1, maximum 256.
For Ack Policy explicit
, all messages will be acked after kf number of messages are received.
For Ack Policy all
, the last message will be acked once kf number of messages are received.
Does not apply to Ack Policy none
-bs
batch size (number) for subPull*, defaults to 10, maximum 256
... JsMulti ... -kp explicit -kf 100 -bs 50
... JsMulti ... -kp explicit -kf 100 -bs 50
... JsMulti ... -kp all -kf 100
... JsMulti ... -kp none
- publish to
subject-name
- 1 thread (default)
- 1 million messages (default)
- payload size of 128 bytes (default)
- shared connection (default)
Command Line
... JsMulti -a PubSync -u subject-name
Builder
Context ctx = Arguments.pubSync("subject-name").build();
- publish to
subject-name
- 5 million messages
- 5 threads
- payload size of 256 bytes
- jitter of 1 second (1000 milliseconds)
Command Line
... JsMulti -a PubSync -u subject-name -d 5 -m 5_000_000 -p 256 -j 1000
Builder
Context ctx = Arguments.pubSync("subject-name")
.messageCount(5_000_000)
.threads(5)
.jitter(1000)
.payloadSize(256)
.build();
- publish to
subject-name
- 2 million messages
- 2 threads
- payload size of 512 bytes
- shared/individual connections to the server
Command Line
... JsMulti -a PubAsync -u subject-name -m 2_000_000 -d 2 -p 512 -n shared
... JsMulti -a PubAsync -u subject-name -m 2_000_000 -d 2 -p 512 -n individual
Builder
Context ctx = Arguments.pubSync("subject-name")
.messageCount(2_000_000)
.threads(2)
.payloadSize(512)
.sharedConnection()
.build();
Context ctx = Arguments.pubSync("subject-name")
.messageCount(2_000_000)
.threads(2)
.payloadSize(512)
.individualConnection()
.build();
- publish to
subject-name
- 2 million messages
- 2 threads
- payload size of 512 bytes
- individual connections to the server
Command Line
... JsMulti -a PubCore -u subject-name -m 2_000_000 -d 2 -p 512 -n individual
Builder
Context ctx = Arguments.pubCore("subject-name")
.messageCount(2_000_000)
.threads(2)
.payloadSize(512)
.individual()
.build();
- from 'subject-name'
- 1 thread (default)
- 1 million messages (default)
- explicit ack (default)
Command Line
... JsMulti -a SubPush -u subject-name
Builder
Context ctx = Arguments.subPush("subject-name").build();
IMPORTANT subscribing (push or pull) with a queue requires multiple threads
- from 'subject-name'
- 2 threads
- 1 million messages (default)
- ack none
Command Line
... JsMulti -a SubQueue -u subject-name -d 2 -kp none
Builder
Context ctx = Arguments.subQueue("subject-name")
.threads(2)
.ackNone()
.build();
... JsMulti -u sub -a SubPullQueue -d 3 -n individual -rf 10000 -j 0 -m 100000
In order to test latency you must start a consumer run first, then start the publish with the latency flag. This means that 2 instances run at the same time.
Start the consumer first...
... JsMulti -u sub -a SubPullQueue -d 3 -n individual -rf 10000 -j 0 -m 100000
The start the producer...
... JsMulti -s nats://localhost:4222 -u sub -a PubSync -lf -d 3 -n individual -rf 10000 -j 0 -m 100000
Look at the java programs src/main/java/io/nats/jsmulti/examples
Producer.java
demonstrates how to build a simple main to produce messages (publish).
Consumer.java
demonstrates how to build a simple main to consume messages (subscribe).
These are provided as customization examples. They also have instructions on how to run them.
Actions Arguments
-----------------
-a action (string), required, one of
RTT - round trip timing
pubSync - publish synchronously
pubAsync - publish asynchronously
pubCore - core publish (synchronously) to subject
subPush - push subscribe read messages (synchronously)
subQueue - push subscribe read messages with queue (synchronously).
Requires 2 or more threads
subPull - pull subscribe fetch messages
subPullQueue - pull subscribe fetch messages, queue (using common durable)
subPullRead - pull subscribe read messages
subPullReadQueue - pull subscribe read messages, queue (using common durable)
Requires 2 or more threads
Server Arguments
----------------
-s server url (string), optional, defaults to nats://localhost:4222
-cf credentials file (string), optional
-ctms connection timeout millis, optional, defaults to 5000
-rwms reconnect wait millis, optional, defaults to 1000
-of options factory class name. Class with no op constructor that implements OptionsFactory
If supplied, used instead of -s, -cf, -ctms and -rwms.
-rf report frequency (number) how often to print progress, defaults to 10% of message count.
<= 0 for no reporting. Reporting time is excluded from timings
Latency Arguments
-----------------
-lf latency flag. Needed when publishing to test latency. See examples.
-lcsv latency-csv-file-spec
General Arguments
-----------------
-u subject (string), required for publishing or subscribing
-m message count (number) required > 1 for publishing or subscribing, defaults to 100_000
-d threads (number) for publishing or subscribing, defaults to 1
-n connection strategy (shared|individual) when threading, whether to share
the connection, defaults to shared
-j jitter (number) between publishes or subscribe message retrieval of random
number from 0 to j-1, in milliseconds, defaults to 0 (no jitter), maximum 10_000
time spent in jitter is excluded from timings
-ps payload size (number) for publishing, defaults to 128, maximum 1048576
-rs round size (number) for pubAsync, default to 100, maximum 1000
-kp ack policy (explicit|none|all) for subscriptions, defaults to explicit
-kf ack all frequency (number), applies to ack policy all, ack after kf messages
defaults to 1, maximum 100
-bs batch size (number) for subPull*, defaults to 10, maximum 200
-rqwms request wait millis, time to wait for a JetStream request to complete, default is 1000 milliseconds
-rtoms read timeout wait millis, time to wait for a individual synchronous message read (next or fetch), default is 1000 milliseconds
-rmxwms rmxwms read max wait millis, when reading messages in a loop, stop if there are no messages in this time, default is 10 seconds (10000ms)
Notes
-----
All text constants are case insensitive, i.e.
action, connection strategy, ack policy, pull type
Input numbers can be formatted for easier viewing. For instance, ten thousand
can be any of these: 10000 10,000 10.000 10_000
and can end with factors k, m, g meaning x 1000, x 1_000_000, x 1_000_000_000
or ki, mi, gi meaning x 1024, x 1024 * 1024, x 1024 * 1024 * 1024
Use tls:// or opentls:// in the server url to require tls, via the Default SSLContext