Skip to main content

Simulating traffic from the WebApp

The management WebApp can drive synthetic traffic through the real platform. A site Owner switches on simulate mode in Settings → Simulation, and a Simulate page appears under Manage. From there the simulator:

  • publishes schema-valid generated events from every endpoint that produces event types, at a configurable rate per event type;
  • hosts the handlers of the consuming endpoints the Owner has explicitly handed to it, each with a failure mode (healthy, random failures, transient, slow, poison, no handler);
  • can be started, paused and stopped, with a speed multiplier, scenario presets, counters and a live feed of the last 100 deliveries.

Everything runs inside the WebApp process, so it works the same on a deployed dev site as in the local Aspire stack. The messages are real: they go through Service Bus and the Resolver, and they show up in Messages, Flow, Insights and the audit log like any other traffic.

Where it may run​

Simulation is gated on the Environment setting, which the Bicep deploy writes per environment (appsettings.json defaults it to dev). The gate does not use ASPNETCORE_ENVIRONMENT: App Service runs the deployed dev site as Production.

Environment valueResult
missing or blankblocked (EnvironmentMissing); the gate fails closed
prod, production, prd, live (any case)blocked (Production), always
listed in NimBus:Simulation:AllowedEnvironmentsallowed
anything elseblocked (NotAllowed)

The production names are hard-coded and no setting can allow them. Putting one in AllowedEnvironments fails options validation at startup, so the WebApp does not start. Outside the gate every simulation endpoint except GET /api/admin/simulation returns 403, and the GET reports allowed: false with the reason.

All simulation endpoints require the site Owner role and share the admin rate limit (60 requests a minute per user by default, see rate-limiting.md). The Simulate page polls every 2 seconds, so keep one Simulate tab open per user. Every change is audited (UpdateSimulationSettings, ControlSimulation, UpdateSimulationConfig), including denied attempts.

Who handles the messages​

A simulated handler reads the endpoint's real subscription. If a real subscriber process for the same endpoint is running, they compete for the same messages. So ownership is explicit:

  • Every consuming endpoint is External by default. The simulator publishes to it but never hosts its handler; its own process handles the messages.
  • An endpoint becomes Simulated only when an Owner ticks "Simulator owns this endpoint" in Settings → Simulation (with a confirmation), or when it is listed in NimBus:Simulation:OwnedEndpoints. Unknown or non-consuming endpoints in that list fail startup validation.
  • Ownership changes apply at the next Start. They are refused (409) while a run is active.
  • Heartbeat data is advisory only. When an owned endpoint answered a heartbeat recently while the simulator was not hosting it, the Simulation settings and the Simulate page warn that a live instance may be competing. Heartbeats never change ownership.

Failure modes​

ModeWhat the handler doesWhat the platform does
Healthycompletes after 20–80 msCompleted
Randomthrows with probability rate %retried, then Failed and the session blocks
Transientthrows while the retry count is below failAttempts, then completesretries, then Completed
Slowcompletes after latencyMinMs–latencyMaxMsexercises lock renewal and throughput
Poisonthrows a permanent exceptiondead-lettered without retrying
No handlerreports no handler for the event typeUnsupported

A mode can be limited to some of the endpoint's event types and to sessions matching a glob (* and ? only), and can revert to Healthy after a number of minutes. The exception message is configurable, so Insights groups simulated failures under stable text. Simulated handlers use a fixed retry policy: 3 retries, 5 seconds apart.

Marking simulated traffic​

Every simulated message has a session id and a correlation id starting with the session prefix (sim- by default). Each publisher rotates its event types through a pool of SessionPoolSize session keys (sim-StorefrontEndpoint-007), so a failure blocks later messages on the same session, as it would in production. Filter Messages by session to find them.

The counters and feed are what the simulator itself saw: published, handled, handler errors, poisoned, publish errors and abandoned sends. The Resolver's status in Messages is the authoritative outcome.

Configuration​

Deploy-time options, bound from NimBus:Simulation and validated at startup:

KeyRangeDefault
EnabledByDefaultboolfalse
AllowedEnvironmentslist, no production namesdev, development
OwnedEndpointsconsuming endpoint idsnone
AutoStopMinutes1–24060
MaxRateCeilingPerMinute1–60001200
RateCeilingPerMinute1–MaxRateCeilingPerMinute600
SessionPrefix1–16 characterssim-
SessionPoolSize1–100040
DrainSeconds0–12030
PauseDeadlineSeconds1–12010
StopDeadlineSeconds1–12015

AllowedEnvironments binds on top of the defaults, so configuring it adds environments rather than replacing dev and development.

Runtime limits, enforced on every PUT (a 400 lists every violation):

SettingRange
speed0.5, 1, 2, 5, 10 or 20
rate per event type1–rate ceiling, per minute, before speed
Random rate1–100 %
Transient fail attempts1–3, so the last attempt can succeed
latency bounds0–10,000 ms, minimum ≤ maximum
exception message1–512 characters
session patternup to 64 characters of letters, digits, -_.: and */?
revert after1–240 minutes, or never

The rate ceiling applies to the whole simulation, not per publisher. All publisher loops share one token bucket (a one-second burst at most). When the summed rate × speed exceeds the ceiling, throughput is capped, the loops share it fairly, and the page shows "capped at N/min".

Pause, stop and shutdown​

  • Pause stops the publishers and the simulated handlers within PauseDeadlineSeconds. Messages already published stay in their subscriptions until Resume.
  • Stop stops the publishers, lets the simulated handlers drain for up to DrainSeconds, then stops them within StopDeadlineSeconds.
  • WebApp shutdown does one bounded stop without the drain, capped by the host's shutdown timeout.
  • Auto-stop stops a run AutoStopMinutes after it started. Disabling simulate mode stops a running simulation.

Each publisher owns its Service Bus sender. When a send ignores cancellation past the deadline, the loop is abandoned, its sender is disposed (which aborts the send), the "abandoned sends" counter goes up and a warning is logged; the transition still completes. The next Start builds fresh publishers, senders and handlers.

Stop does not delete anything. To clear a leftover backlog, use Purge in Topology → Subscriptions.

State is per instance​

Settings, ownership, config and counters are held in memory by each WebApp instance. A restart starts again from configuration and stops any run. The dev site runs a single instance; on a scaled-out plan each instance would run its own simulation.

Local Aspire stack​

Start the AppHost with NIMBUS_SIMULATION=true (or --NIMBUS_SIMULATION true). The AppHost then leaves out the sample publisher, subscriber and warehouse-subscriber projects and configures the WebApp with simulate mode on and BillingEndpoint and WarehouseEndpoint owned:

$env:NIMBUS_SIMULATION = "true"
$env:NIMBUS_SB_EMULATOR = "true" # optional: the local Service Bus emulator
dotnet run --project src/NimBus.AppHost

Without the switch the samples run as before, no endpoint is owned, and the Simulate page lists Billing and Warehouse as External: simulated publishes are handled by the sample processes.