Scenario file

A workload file describes one path: an exchange, a queue, publishers, consumers. A scenario file describes a whole topology — several exchanges, several queues with their own bindings and their own consumer counts, several producers — and runs all of it at once.

This is the file the studio exports, and the same -f reads it:

java -jar acemq-workload.jar -f acemq-workload-orders-2026-09-06.json

Nothing has to say which kind of file it is. A scenario names exchanges, queues and producers; a workload names a topology with publishers and consumers. That difference is not cosmetic, and it is enough to decide.

The shape

JSON or YAML — the same fields either way.

name: orders-peak
description: what the fulfilment leg costs when audit is behind
broker: amqp://guest:${BROKER_PASSWORD}@localhost:5672
warmup: 10s
runFor: 2m
declare: true

exchanges:
  - { name: orders, type: topic }

queues:
  - name: orders.shipping
    type: quorum
    bindings: [ { exchange: orders, routingKey: "order.*" } ]
    consumers: { concurrency: 8, prefetch: 200, handlerTime: 1ms }
    expect:
      p99Below: 50ms
      noBacklog: true

  - name: orders.audit
    type: stream
    bindings: [ { exchange: orders, routingKey: "#" } ]
    consumers: { concurrency: 1, prefetch: 100 }

producers:
  - name: checkout
    exchange: orders
    routingKeys: [ order.placed, order.cancelled ]
    rate: 20000
    messageSize: 1024
    expect:
      withinPercentOfOffered: 5
      noFailures: true
Field
name required
description what question this run answers
broker the AMQP URL; --broker on the command line wins
warmup, runFor 500ms, 10s, 2m. Default 5s and 30s
declare whether to declare the topology first. Turn it off against a real environment
exchanges[] name, type, durable, enabled, arguments
queues[] name, type, bindings[], consumers, deadLetterExchange, arguments, expect
producers[] name, exchange, routingKeys[], rate, threads, messageSize, confirms, maxInFlight, maxMessages, expect

There is no management setting to give here, and a workload file's one has no equivalent. Queue depth during a scenario is read over AMQP, because a run with ten queues would otherwise make ten HTTP requests a second to the broker it is measuring. A management key written by the studio is carried through the file for the studio's own use and does nothing to a run.

Queue types are classic, classic-mirrored, quorum and stream. Several routing keys on a producer are used in turn, which is what makes a topic exchange behave like one: a producer on a single key measures one binding however many the exchange has.

enabled: false on anything keeps its settings and leaves it out of the run — what you were about to put back is still there, prefetch and all.

Objectives

expect is what a node must prove. Without it a scenario can only describe what happened, and a pipeline cannot act on a description.

Per node, not for the whole run. The interesting property is usually asymmetric: above, the audit stream may lag as much as it likes while the fulfilment queue must not. One overall p99 would average away exactly the distinction worth keeping.

On a queue:

p50Below, p99Below, p999Below end-to-end latency, from when a message was due
consumeRateAtLeast messages a second this queue's consumers must handle
noBacklog it must not be deeper at the end than at the start

On a producer:

achievedRateAtLeast messages a second it must actually offer
withinPercentOfOffered how far the achieved rate may fall short of the configured one, as a percentage
noFailures every publish must succeed

Everything is optional, and what is not stated is not checked — an expectation nobody wrote is not one the run failed. Anything that is stated produces a FAILED finding naming the node and both numbers:

[FAILED] expected-p99:orders.shipping
    observed:  orders.shipping p99 was 91.4ms, and was asked for under 50ms
    means:     the queue answered, and answered more slowly than this run required

and the process exits 1. The exit codes are the same as a workload's, because a pipeline should not have to care which kind of file it was given.

Two cases worth knowing:

Secrets

${VAR} and ${VAR:-default} come from the environment, so a password never has to live in a file that gets committed. An unset variable with no default is an error rather than an empty password.

The studio does not resolve these when it opens a file. Doing so would read the studio's own environment on behalf of whoever uploaded the file and hand the value back, so the placeholder stays a placeholder and is resolved by the command line, where the person running it owns the environment.

Checking one without running it

java -jar acemq-workload.jar -f scenario.json --dry-run

Prints the topology, the objectives and the problems — a binding pointing at an exchange nothing declares, a producer aimed at nothing — with the password redacted, and touches no broker. Left to the broker, those arrive mid-run as a channel closure that reads like a broker problem.

Reports

--report <dir> writes html, md and json, the same as a workload, with a table of queues and a table of producers rather than one row. Each node carries what it was asked for alongside what it did: "p99 was 41ms" is a number, and "p99 was 41ms against the 50ms this required" is an answer.