Class Scenario

java.lang.Object
org.acemq.workloads.scenario.Scenario

public final class Scenario extends Object
A whole topology under load, rather than one queue.

Workload measures a single path: one exchange, one queue, one rate. That is the right shape for "how fast is a quorum queue", and the wrong shape for the question people actually arrive with — what happens to this topology, the one we run, when Monday morning hits it. A fan-out where one consumer is slow, a dead-letter path that only matters under load, two services sharing an exchange: none of those are a single path, and measuring them one queue at a time measures something else.

A scenario is that topology plus who is pushing and who is pulling:


 Scenario scenario = Scenario.named("monday-morning")
         .exchange("orders", "topic")
         .queue("orders.shipping", q -> q.quorum().boundTo("orders", "order.*"))
         .queue("orders.audit", q -> q.classic().boundTo("orders", "#"))
         .producer("checkout", p -> p.to("orders", "order.placed").rate(20_000).messageSize(512))
         .consumers("orders.shipping", c -> c.concurrency(8).prefetch(200).handlerTime(1ms))
         .consumers("orders.audit", c -> c.concurrency(2).prefetch(50))
         .warmup(Duration.ofSeconds(10))
         .runFor(Duration.ofMinutes(2));
 

Every node can be switched off without being deleted. A designer needs that more than it needs anything else: the interesting question is usually "what happens if this consumer stops", and answering it by deleting the consumer loses the thing you were about to put back.

  • Method Details

    • named

      public static Scenario named(String name)
      Parameters:
      name - what this scenario is called, and what its report is filed under
      Returns:
      a new scenario
    • describedAs

      public Scenario describedAs(String text)
      Parameters:
      text - what this scenario is for, in a sentence
      Returns:
      this scenario
    • exchange

      public Scenario exchange(String name, String type)
      Parameters:
      name - the exchange
      type - direct, topic, fanout or headers
      Returns:
      this scenario
    • queue

      public Scenario queue(String name, Consumer<QueueNode> spec)
      Parameters:
      name - the queue
      spec - its type, arguments and bindings
      Returns:
      this scenario
    • queue

      public Scenario queue(String name)
      Parameters:
      name - the queue, with defaults
      Returns:
      this scenario
    • producer

      public Scenario producer(String name, Consumer<ProducerNode> spec)
      Parameters:
      name - what to call this producer, so its numbers can be told apart
      spec - where it publishes and how hard
      Returns:
      this scenario
    • consumers

      public Scenario consumers(String queueName, Consumer<ConsumerGroupNode> spec)
      Attaches consumers to a queue that is already in the scenario.
      Parameters:
      queueName - the queue
      spec - how many, what prefetch, how slow the handler is
      Returns:
      this scenario
      Throws:
      IllegalArgumentException - if no queue of that name has been added
    • warmup

      public Scenario warmup(Duration warmup)
      Parameters:
      warmup - how long to run before the counters are reset
      Returns:
      this scenario
    • runFor

      public Scenario runFor(Duration duration)
      Parameters:
      duration - how long to measure for
      Returns:
      this scenario
    • useExisting

      public Scenario useExisting()
      Runs against the topology as it already exists.

      What to use against a real environment: declaring would either be refused for mismatched arguments or, worse, quietly create something subtly different from what production runs and measure that instead.

      Returns:
      this scenario
    • name

      public String name()
    • description

      public String description()
    • exchanges

      public List<ExchangeNode> exchanges()
    • queues

      public List<QueueNode> queues()
    • producers

      public List<ProducerNode> producers()
    • activeExchanges

      public List<ExchangeNode> activeExchanges()
      Returns:
      the exchanges that are switched on
    • activeQueues

      public List<QueueNode> activeQueues()
      Returns:
      the queues that are switched on
    • activeProducers

      public List<ProducerNode> activeProducers()
      Returns:
      the producers that are switched on
    • findQueue

      public Optional<QueueNode> findQueue(String name)
      Parameters:
      name - a queue name
      Returns:
      that queue, if it is in this scenario
    • warmup

      public Duration warmup()
    • duration

      public Duration duration()
    • shouldDeclare

      public boolean shouldDeclare()
    • problems

      public List<String> problems()
      What is wrong with this scenario, before a broker sees any of it.

      Every one of these is a mistake a designer makes constantly — a binding to a queue that was renamed, a producer aimed at an exchange somebody switched off — and every one of them fails at the broker in a way that reads like a broker problem.

      Returns:
      the problems, empty when there are none
    • warnings

      public List<String> warnings()
      Warnings about a scenario that will run but may not measure what was intended.

      Kept apart from problems() on purpose. A queue nobody consumes is a legitimate thing to measure — it is how you find out what a backlog costs — so it is worth saying and not worth refusing.

      Returns:
      the warnings, empty when there are none
    • toString

      public String toString()
      Overrides:
      toString in class Object