Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 40 additions & 52 deletions docs/dev_workflow_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,58 +20,56 @@ Its job is to expose the REST API, run the discrete-event simulation, talk to th

```
fastsim-backend/
├── Dockerfile
├── docker_fs/ # docker-compose for dev & prod
│ ├── docker-compose.dev.yml
│ └── docker-compose.prod.yml
├── scripts/ # helper bash scripts (lint, dev-startup, …)
│ ├── init-docker-dev.sh
├── example/ # examples of working simulations
│ ├── data
├── scripts/ # helper bash scripts (lint, dev-startup, …)
│ └── quality-check.sh
├── alembic/ # DB migrations (versions/ contains revision files)
│ ├── env.py
│ └── versions/
├── documentation/ # project vision & low-level docs
│ └── backend_documentation/
│ └── …
├── tests/ # unit & integration tests
├── docs/ # project vision & low-level docs
│ └── fastsim-documentation/
├── tests/ # unit & integration tests
│ ├── unit/
│ └── integration/
├── src/ # **application code lives here**
├── src/ # application code lives here
│ └── app/
│ ├── api/ # FastAPI routers & endpoint handlers
│ ├── config/ # Pydantic Settings + constants
│ ├── db/ # SQLAlchemy base, sessions, initial seed utilities
│ ├── metrics/ # helpers to compute/aggregate simulation KPIs
│ ├── resources/ # SimPy resource registry (CPU/RAM containers, etc.)
│ ├── runtime/ # simulation core
│ │ ├── rqs_state.py # RequestState & Hop
│ │ └── actors/ # SimPy “actors”: Edge, Server, Client, RqsGenerator
│ ├── samplers/ # stochastic samplers (Gaussian-Poisson, etc.)
│ ├── schemas/ # Pydantic input/output models
│ ├── main.py # FastAPI application factory / ASGI entry-point
│ └── simulation_run.py # CLI utility to run a sim outside of HTTP layer
│ ├── config/ # Pydantic Settings + constants
│ ├── metrics/ # logic to compute/aggregate simulation KPIs
│ ├── resources/ # SimPy resource registry (CPU/RAM containers, etc.)
│ ├── runtime/ # simulation core
│ │ ├── rqs_state.py # RequestState & Hop
│ │ ├── simulation_runner.py # logic to initialize the whole simulation
| └── actors/ # SimPy “actors”: Edge, Server, Client, RqsGenerator
│ ├── samplers/ # stochastic samplers (Gaussian-Poisson, etc.)
│ ├── schemas/ # Pydantic input/output models
├── poetry.lock
├── pyproject.toml
└── README.md
```
### **What each top-level directory in `src/app` does**

| Directory | Purpose |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`config/`** | Centralised configuration layer. Contains Pydantic `BaseSettings` classes for reading environment variables and constants/enums used across the simulation engine. |
| **`metrics/`** | Post-processing and analytics. Aggregates raw simulation traces into KPIs such as latency percentiles, throughput, resource utilisation, and other performance metrics. |
| **`resources/`** | Runtime resource registry for simulated hardware components (e.g., SimPy `Container`s for CPU and RAM). Decouples resource management from actor behaviour. |
| **`runtime/`** | Core simulation engine. Orchestrates SimPy execution, maintains request state, and wires together simulation components. Includes: |
| | - **`rqs_state.py`** — Defines `RequestState` and `Hop` for tracking request lifecycle. |
| | - **`simulation_runner.py`** — Entry point for initialising and running simulations. |
| | - **`actors/`** — SimPy actor classes representing system components (`RqsGenerator`, `Client`, `Server`, `Edge`) and their behaviour. |
| **`samplers/`** | Random-variable samplers for stochastic simulation. Supports Poisson, Normal, and mixed distributions for modelling inter-arrival times and service steps. |
| **`schemas/`** | Pydantic models for input/output validation and serialisation. Includes scenario definitions, topology graphs, simulation settings, and results payloads. |

#### What each top-level directory in `src/app` does
---

| Directory | Purpose |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`api/`** | Defines the public HTTP surface. Each module holds a router with path operations and dependency wiring. |
| **`config/`** | Centralised configuration: `settings.py` (Pydantic `BaseSettings`) reads env vars; `constants.py` stores enums and global literals. |
| **`db/`** | Persistence layer. Contains the SQLAlchemy base class, the session factory, and a thin wrapper that seeds or resets the database (Alembic migration scripts live at project root). |
| **`metrics/`** | Post-processing helpers that turn raw simulation traces into aggregated KPIs (latency percentiles, cost per request, utilisation curves, …). |
| **`resources/`** | A tiny run-time registry mapping every simulated server to its SimPy `Container`s (CPU, RAM). Keeps resource management separate from actor logic. |
| **`runtime/`** | The heart of the simulator. `rqs_state.py` holds the mutable `RequestState`; sub-package **`actors/`** contains each SimPy process class (Generator, Edge, Server, Client). |
| **`samplers/`** | Probability-distribution utilities that generate inter-arrival and service-time samples—used by the actors during simulation. |
| **`schemas/`** | All Pydantic models for validation and (de)serialisation: request DTOs, topology definitions, simulation settings, outputs. |
| **`main.py`** | Creates and returns the FastAPI app; imported by Uvicorn/Gunicorn. |
| **`simulation_run.py`** | Convenience script to launch a simulation offline (e.g. inside tests or CLI). |
### **Other Top-Level Directories**

Everything under `src/` is import-safe thanks to Poetry’s `packages = [{ include = "app" }]` entry in `pyproject.toml`.
| Directory | Purpose |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`example/`** | Ready-to-run simulation scenarios and example configurations. Includes `data/` with YAML definitions and scripts to demonstrate engine usage. |
| **`scripts/`** | Utility shell scripts for development workflow, linting, formatting, and local startup (`quality-check.sh`, etc.). |
| **`docs/`** | Project documentation. Contains both high-level vision documents and low-level technical references (`fastsim-documentation/`). |
| **`tests/`** | Automated test suite, split into **unit** and **integration** tests to verify correctness of both individual components and end-to-end scenarios. |

---

## 3. Branching Strategy: Git Flow

Expand Down Expand Up @@ -182,21 +180,11 @@ We will start to describe the CI part related to push and PR in the develop bran

* **Full Suite (push to `develop`)**
*Runs in a few minutes; includes real services and Docker.*

* All steps from the Quick Suite
* PostgreSQL service container started via `services:`
* Alembic migrations applied to the test database

* Full test suite, including `@pytest.mark.integration` tests
* Multi-stage Docker build of the backend image
* Smoke test: container started with Uvicorn → `curl /health`



### 4.1.3 Key Implementation Details

* **Service containers** – PostgreSQL 17 is spun up in CI with a health-check to ensure migrations run against a live instance.
* **Test markers** – integration tests are isolated with `@pytest.mark.integration`, enabling selective execution.
* **Caching** – Poetry’s download cache is restored to cut installation time; Docker layer cache is reused between builds.
* **Smoke test logic** – after the image is built, CI launches it in detached mode, polls the `/health` endpoint, prints logs, and stops the container. The job fails if the endpoint is unreachable.
* **Secrets management** – database credentials and registry tokens are stored in GitHub Secrets and injected as environment variables only at runtime.


150 changes: 129 additions & 21 deletions docs/fastsim-docs/requests_generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,151 @@ This document describes the design of the **requests generator**, which models a

Following the FastSim philosophy, we accept a small set of input parameters to drive a “what-if” analysis in a pre-production environment. These inputs let you explore reliability and cost implications under different traffic scenarios.

**Inputs**
## **Inputs**

1. **Average concurrent users** – expected number of users (or sessions) simultaneously hitting the endpoint.
2. **Average requests per minute per user** – average number of requests each user issues per minute.
3. **Simulation time** – total duration of the simulation, in seconds.
1. **Average Concurrent Users (`avg_active_users`)**
Expected number of simultaneous active users (or sessions) interacting with the system.

**Output**
A continuous sequence of timestamps (seconds) marking individual request arrivals.
* Modeled as a random variable (`RVConfig`).
* Allowed distributions: **Poisson** or **Normal**.

2. **Average Requests per Minute per User (`avg_request_per_minute_per_user`)**
Average request rate per user, expressed in requests per minute.

* Modeled as a random variable (`RVConfig`).
* **Must** use the **Poisson** distribution.

3. **User Sampling Window (`user_sampling_window`)**
Time interval (in seconds) over which active users are resampled.

* Constrained between `MIN_USER_SAMPLING_WINDOW` and `MAX_USER_SAMPLING_WINDOW`.
* Defaults to `USER_SAMPLING_WINDOW`.

---

## Model Assumptions
## **Model Assumptions**

* *Concurrent users* and *requests per minute per user* are **random variables**.
* *Simulation time* is **deterministic**.
* **Random variables**:

We model:
* *Concurrent users* and *requests per minute per user* are independent random variables.
* Each is configured via the `RVConfig` model, which specifies:

* **Requests per minute per user** as Poisson($\lambda_r$).
* **Concurrent users** as either Poisson($\lambda_u$) or truncated Normal.
* **The variables are independent**
* **mean** (mandatory, must be numeric and positive),
* **distribution** (default: Poisson),
* **variance** (optional; defaults to `mean` for Normal and Log-Normal distributions).

```python
from pydantic import BaseModel
from typing import Literal
* **Supported joint sampling cases**:

* Poisson (users) × Poisson (requests)
* Normal (users) × Poisson (requests)

Other combinations are currently unsupported.

* **Variance handling**:

* If the distribution is **Normal** or **Log-Normal** and `variance` is not provided, it is automatically set to the `mean`.

---

## **Validation Rules**

* `avg_request_per_minute_per_user`:

* **Must** be Poisson-distributed.
* Validation enforces this constraint.

* `avg_active_users`:

* Must be either Poisson or Normal.
* Validation enforces this constraint.

* `mean` in `RVConfig`:

* Must be a positive number (int or float).
* Automatically coerced to `float`.

```python
class RVConfig(BaseModel):
"""Configure a random-variable parameter."""
"""class to configure random variables"""

mean: float
distribution: Literal["poisson", "normal", "gaussian"] = "poisson"
variance: float | None = None # required only for normal/gaussian
distribution: Distribution = Distribution.POISSON
variance: float | None = None

@field_validator("mean", mode="before")
def ensure_mean_is_numeric_and_positive(
cls, # noqa: N805
v: float,
) -> float:
"""Ensure `mean` is numeric, then coerce to float."""
err_msg = "mean must be a number (int or float)"
if not isinstance(v, (float, int)):
raise ValueError(err_msg) # noqa: TRY004

return float(v)

@model_validator(mode="after") # type: ignore[arg-type]
def default_variance(cls, model: "RVConfig") -> "RVConfig": # noqa: N805
"""Set variance = mean when distribution require and variance is missing."""
needs_variance: set[Distribution] = {
Distribution.NORMAL,
Distribution.LOG_NORMAL,
}

if model.variance is None and model.distribution in needs_variance:
model.variance = model.mean
return model


class RqsGeneratorInput(BaseModel):
"""Define simulation inputs."""
"""Define the expected variables for the simulation"""

id: str
type: SystemNodes = SystemNodes.GENERATOR
avg_active_users: RVConfig
avg_request_per_minute_per_user: RVConfig
total_simulation_time: int | None = None

user_sampling_window: int = Field(
default=TimeDefaults.USER_SAMPLING_WINDOW,
ge=TimeDefaults.MIN_USER_SAMPLING_WINDOW,
le=TimeDefaults.MAX_USER_SAMPLING_WINDOW,
description=(
"Sampling window in seconds "
f"({TimeDefaults.MIN_USER_SAMPLING_WINDOW}-"
f"{TimeDefaults.MAX_USER_SAMPLING_WINDOW})."
),
)

@field_validator("avg_request_per_minute_per_user", mode="after")
def ensure_avg_request_is_poisson(
cls, # noqa: N805
v: RVConfig,
) -> RVConfig:
"""
Force the distribution for the rqs generator to be poisson
at the moment we have a joint sampler just for the poisson-poisson
and gaussian-poisson case
"""
if v.distribution != Distribution.POISSON:
msg = "At the moment the variable avg request must be Poisson"
raise ValueError(msg)
return v

@field_validator("avg_active_users", mode="after")
def ensure_avg_user_is_poisson_or_gaussian(
cls, # noqa: N805
v: RVConfig,
) -> RVConfig:
"""
Force the distribution for the rqs generator to be poisson
at the moment we have a joint sampler just for the poisson-poisson
and gaussian-poisson case
"""
if v.distribution not in {Distribution.POISSON, Distribution.NORMAL}:
msg = "At the moment the variable active user must be Poisson or Gaussian"
raise ValueError(msg)
return v

```

---
Expand Down
Loading