This page documents the workload models exported from:
from asyncflow.workload import RqsGenerator, RVConfigUse these to describe how traffic is generated (active users, per-user RPM, and the re-sampling window). The workload is independent from your topology and settings and plugs into the builder or payload directly.
Stability: This is part of the public API. Fields and enum values are semver-stable (new options may be added in minor releases; breaking changes only in a major).
from asyncflow.workload import RqsGenerator, RVConfig
rqs = RqsGenerator(
id="rqs-1",
avg_active_users=RVConfig(mean=100, distribution="poisson"), # or "normal"
avg_request_per_minute_per_user=RVConfig(mean=20, distribution="poisson"),
user_sampling_window=60, # seconds, re-sample active users every 60s
)
# … then compose with the builder
from asyncflow.builder.asyncflow_builder import AsyncFlow
payload = (AsyncFlow()
.add_generator(rqs)
# .add_client(...).add_servers(...).add_edges(...).add_simulation_settings(...)
.build_payload())class RqsGenerator(BaseModel):
id: str
type: SystemNodes = SystemNodes.GENERATOR # fixed
avg_active_users: RVConfig # Poisson or Normal
avg_request_per_minute_per_user: RVConfig # Poisson (required)
user_sampling_window: int = 60 # seconds, bounds [1, 120]| Field | Type | Allowed / Bounds | Description |
|---|---|---|---|
id |
str |
— | Identifier used by edges (e.g., source="rqs-1"). |
type |
enum (fixed) | generator |
Constant; not user-set. |
avg_active_users |
RVConfig |
Distribution: poisson or normal |
Random variable for active concurrent users. If normal, variance is auto-filled (see RVConfig). |
avg_request_per_minute_per_user |
RVConfig |
Distribution: must be poisson |
Per-user rate (RPM). Enforced to Poisson by validator. |
user_sampling_window |
int (seconds) |
1 ≤ value ≤ 120 |
How often to re-sample avg_active_users. Larger windows → slower drift; smaller windows → more volatility. |
Units: RPM = requests per minute; times are in seconds.
class RVConfig(BaseModel):
mean: float
distribution: Distribution = "poisson"
variance: float | None = Nonemeanis required and coerced tofloat. (Generic numeric check; positivity is contextual. For example, edge latency enforcesmean > 0, while workloads acceptmean ≥ 0and rely on samplers to truncate at 0 when needed.)distributiondefaults to"poisson".- Variance auto-fill: if
distributionis"normal"or"log_normal"andvarianceis omitted, it is set tovariance = mean.
"poisson","normal","log_normal","exponential","uniform"(For workload:avg_active_users→ Poisson/Normal;avg_request_per_minute_per_user→ Poisson only.)
AsyncFlow provides joint samplers for the two main cases:
- Poisson–Poisson (
avg_active_users ~ Poisson,rpm ~ Poisson)
- Every
user_sampling_windowseconds, draw users:U ~ Poisson(mean_users). - Aggregate rate:
Λ = U * (rpm_per_user / 60)(requests/second). - Within the window, inter-arrival gaps are exponential:
Δt ~ Exponential(Λ)(via inverse CDF). - If
U == 0, no arrivals until the next window.
- Gaussian–Poisson (
avg_active_users ~ Normal,rpm ~ Poisson)
- Draw users with truncation at 0 (negative draws become 0):
U ~ max(N(mean, variance), 0). - Then same steps as above:
Λ = U * (rpm_per_user / 60),Δt ~ Exponential(Λ).
Implications of user_sampling_window:
- Smaller windows → more frequent changes in
U(bursty arrivals). - Larger windows → steadier rate within each window, fewer rate shifts.
rqs = RqsGenerator(
id="steady",
avg_active_users=RVConfig(mean=80, distribution="poisson"),
avg_request_per_minute_per_user=RVConfig(mean=15, distribution="poisson"),
user_sampling_window=60,
)rqs = RqsGenerator(
id="bursty",
avg_active_users=RVConfig(mean=50, distribution="normal", variance=200), # bigger var → burstier
avg_request_per_minute_per_user=RVConfig(mean=18, distribution="poisson"),
user_sampling_window=15, # faster re-sampling → faster drift
)rqs = RqsGenerator(
id="smoke",
avg_active_users=RVConfig(mean=1, distribution="poisson"),
avg_request_per_minute_per_user=RVConfig(mean=2, distribution="poisson"),
user_sampling_window=30,
)If you configure via YAML/JSON, the equivalent block is:
rqs_input:
id: rqs-1
avg_active_users:
mean: 100
distribution: poisson # or normal
# variance: 100 # optional; auto=mean if normal/log_normal
avg_request_per_minute_per_user:
mean: 20
distribution: poisson # must be poisson
user_sampling_window: 60 # [1..120] secondsavg_request_per_minute_per_user.distribution != poisson→ValueError("At the moment the variable avg request must be Poisson")avg_active_users.distributionnot in{poisson, normal}→ValueError("At the moment the variable active user must be Poisson or Gaussian")- Non-numeric
meanin anyRVConfig→ValueError("mean must be a number (int or float)") user_sampling_windowoutside[1, 120]→ Pydantic range validation error with clear bounds in the message.
Note: Positivity for means is enforced contextually. For workload, negative draws are handled by the samplers (e.g., truncated Normal). For edge latency, positivity is enforced at the edge model level.
- Using Normal without variance: If you set
distribution="normal"and omitvariance, it auto-fills tovariance=mean. Set it explicitly if you want heavier or lighter variance than the default. - Confusing units: RPM is per minute, not per second. The engine converts internally.
- Over-reactive windows: Very small
user_sampling_window(e.g.,1–5s) makes the rate jumpy; this is fine for “bursty” scenarios but can be noisy. - Zero arrivals: If a window samples
U=0, you’ll get no arrivals until the next window; this is expected.
- The workload does not depend on the sampling cadence of time-series metrics (
SimulationSettings.sample_period_s). Sampling controls observability, not arrivals. - Baseline sampled metrics are mandatory in the current release (ready-queue length, I/O sleep, RAM, edge concurrency). Future metrics can be opt-in.
With RqsGenerator + RVConfig you can describe steady, bursty, or sparse loads with a few lines—then reuse the same topology and settings to compare how architecture choices behave under different traffic profiles.