A C++17 discrete-event simulator that models concurrent file transfer requests served by a fixed pool of worker threads, and compares five scheduling policies on the same workload using reproducible CSV outputs and quantitative metrics.
Live interactive demo: https://tewei02.github.io/multithreaded-file-transfer-scheduler-simulator/ Equivalence check page: https://tewei02.github.io/multithreaded-file-transfer-scheduler-simulator/verify.html
The demo runs entirely in the browser (GitHub Pages) and lets a visitor drive the simulator without building anything locally:
- Choose a workload: bundled datasets or an on-the-fly generated request set (adjustable request count).
- Configure the server: worker-thread count, Round Robin quantum, per-dispatch connection overhead.
- Select any subset of the five schedulers and dispatch the same workload to all of them.
- Watch the run: a Gantt-style timeline of per-thread occupancy, a playback control with step forward/back, a per-request detail table (arrival, start, finish, waiting, turnaround, response, assigned thread) and a summary table (average waiting / turnaround / response, throughput, Jain fairness index, total completion time).
- Compare schedulers side by side on identical input, and inspect how dispatch order changes the metrics.
The demo is installable as a PWA (manifest.json + sw.js) and keeps working offline once loaded.
Scope of the demo — what it is not. The page is a JavaScript re-implementation of the simulation kernel for interactive use. It performs no real network transfers, opens no sockets and moves no bytes: every number on the page comes from the same queuing model that the C++ core evaluates. The C++ binary remains the reference implementation.
The browser port is verified against the C++ engine rather than assumed to match it:
tools/run_oracle.shrebuilds the C++ binary and re-runs 12 reference scenarios (varying request count, thread count, quantum and overhead) with all five schedulers, writing the per-request CSVs and summary CSVs intooracle/and recording their SHA-256 digests indata/reference/oracle_sha256.txt. The CSVs themselves are not committed; the digest manifest is, so the reference set stays auditable andtools/run_oracle.sh --checkreproduces all 72 digests.tools/build_oracle_bundle.pypacks those reference CSVs intodocs/oracle_bundle.js.verify.htmlreplays each scenario through the JavaScript kernel and compares every row and every summary line against the bundled reference values.
Latest verified result (12 scenarios, 2,455 request rows, 60 summary rows, 60 scheduler runs):
0 mismatches, maximum absolute difference 4.99e-5 s, within the tolerances documented in
verify.html (2.5e-5 for values printed with four decimals, 2.5e-6 for values printed with six
decimals, allowing for the rounding the CLI applies). The check can be re-run locally with
tools/check_demo.py (see §9).
The simulator reproduces the following server-side scenario:
- Clients submit file transfer requests, each with an arrival time, a file size, an estimated bandwidth and a priority.
- The server owns a fixed number of worker threads; a worker serves one request slice at a time.
- A scheduler decides which pending request a free worker picks up next, and (for Round Robin) how much service each slice gets.
- Time advances in discrete events: arrival, dispatch, and completion.
The project focuses on systems fundamentals: dispatch policy behaviour, queueing delay, throughput, fairness between clients, and reproducible benchmarking.
| Policy | Selection rule | Parameters |
|---|---|---|
fifo |
First come, first served by arrival time. | — |
sjf |
Shortest estimated remaining transfer time first (non-preemptive). | — |
priority |
Highest priority first; lower priority number wins (non-preemptive). | priority column |
rr |
Round Robin: a slice is bounded by --quantum; unfinished requests return to the queue. |
--quantum |
wfq |
WFQ-like: priority maps to a weight, requests are ordered by virtual finish tag (approximation). | priority column |
wfq computes weight = WeightForPriority(priority) and
finish_tag = max(previous_tag, virtual_time) + service / weight; the smallest finish tag is served
next. This is a simplified approximation of weighted fair queueing, not the full WFQ packet algebra.
Per request (output/result_<scheduler>.csv):
| Column | Definition |
|---|---|
arrival_time |
Submission time of the request. |
start_time |
First instant the request receives service. |
finish_time |
Instant the last byte of the request is served. |
response_time |
start_time - arrival_time. |
turnaround_time |
finish_time - arrival_time. |
waiting_time |
max(0, turnaround_time - total_processing_time). |
assigned_thread |
Worker that served the request. |
Benchmark summary (output/summary.csv):
| Column | Definition |
|---|---|
avg_waiting_time, avg_turnaround_time, avg_response_time |
Averages over completed requests. |
throughput |
requests / total_completion_time (requests per simulated second). |
fairness_index |
Jain's index over per-client throughput (file bytes served / client service span); 1.0 = perfectly even, lower = more skewed. |
total_completion_time |
Time from simulation start to the last completion (makespan). |
All durations are simulated seconds. Wall-clock time is never reported as a result: the worker threads carry the event loop and use bounded sleeps only to emulate dispatch, so runtimes and metrics are independent of machine speed.
| Layer | Choice |
|---|---|
| Core simulator | C++17, C++ standard library, std::thread / std::mutex / std::condition_variable |
| Build | GNU Make and CMake (both maintained), CTest for the test target |
| Tests | Assertion-based unit tests (tests/test_simulator.cpp) covering each scheduler and the metric collector |
| Data I/O | Hand-written CSV reader/writer (include/csv_utils.h) — no third-party CSV dependency |
| Analysis | Python 3 + matplotlib (scripts/plot_summary.py) |
| Browser demo | Hand-written vanilla JavaScript + Canvas (docs/engine.js, docs/app.js), no framework, no build step |
| Delivery | GitHub Pages (static site in docs/), PWA manifest + service worker, GitHub Actions CI |
The simulator itself has no third-party dependencies; Python is only needed for the plotting script.
Requires a C++17 compiler and either Make or CMake ≥ 3.16.
# Make
make # builds ./simulator
make test # builds and runs ./simulator_tests
# CMake
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure./simulator [options]
--input <path> Input request CSV
--scheduler <name> fifo | sjf | priority | rr | wfq
--threads <int> Number of worker threads
--quantum <float> Round Robin time quantum in simulated seconds
--overhead <float> Per-dispatch connection overhead in simulated seconds
--benchmark Run all schedulers on the same input
--output-dir <path> Output directory (default: output)
--generate-sample <path> Generate a request CSV and exit
--n <int> Number of generated requests
--seed <int> Random seed for generation
--help Show usage
Examples:
# Single scheduler
./simulator --input data/sample_requests.csv --scheduler fifo --threads 4
# Round Robin with a 1.0 s quantum and 0.05 s dispatch overhead
./simulator --input data/sample_requests.csv --scheduler rr --threads 4 --quantum 1.0 --overhead 0.05
# All five schedulers on one workload, compared in output/summary.csv
./simulator --input data/sample_requests.csv --benchmark --threads 4
# Deterministic workload generation
./simulator --generate-sample data/generated_requests.csv --n 100 --seed 42| File | Requests | Description |
|---|---|---|
data/sample_requests.csv |
15 | Compact hand-written workload used by the README examples and the default demo view. |
data/generated/n15_seed99.csv |
15 | Generated with --n 15 --seed 99. |
data/generated/n37_seed2026.csv |
37 | Generated with --n 37 --seed 2026. |
data/generated/n50_seed7.csv |
50 | Generated with --n 50 --seed 7. |
data/generated/n120_seed42.csv |
120 | Generated with --n 120 --seed 42. |
data/edge_cases.csv |
18 | Boundary workload (identical arrival times, single-thread and high-thread cases, zero overhead, extreme priorities) used by the equivalence scenarios. |
Generation is seeded and therefore deterministic: the same --n/--seed pair always yields the same
CSV. The demo loads byte-identical copies of these files from docs/data/; tools/sync_docs_data.sh
refreshes them and prints SHA-256 digests so the two trees can be compared.
data/reference/oracle_sha256.txt holds the digests of the 72 CSV files the C++ engine produces for
the equivalence scenarios in §1.1.
| Tool | Purpose |
|---|---|
tools/run_oracle.sh |
Rebuilds the simulator, recomputes the 12 reference scenarios into oracle/, and refreshes (or with --check, verifies) the digest manifest data/reference/oracle_sha256.txt. |
tools/build_oracle_bundle.py |
Packs the reference CSVs into docs/oracle_bundle.js for verify.html. |
tools/sync_docs_data.sh |
Copies the canonical workloads into docs/data/; --check verifies the two trees are byte-identical (used in CI). |
tools/check_demo.py |
Headless-Chrome check of a local or deployed demo: runs index.html to completion and executes the full verify.html comparison. Prints a pass/fail summary. |
tools/ui_smoke.html |
Browser harness that drives the demo UI (workload switching, playback controls, table rendering, canvas painting) and reports the individual steps. |
tools/make_icons.py |
Regenerates the PWA icons. |
# Recompute the reference scenarios and refresh the demo data/bundle
./tools/run_oracle.sh
./tools/sync_docs_data.sh
python3 tools/build_oracle_bundle.py
# Serve the repository and check the demo (index.html + verify.html) in headless Chrome
python3 -m http.server 8000 &
python3 tools/check_demo.py http://127.0.0.1:8000/docs/
# Drive the UI harness
# open http://127.0.0.1:8000/tools/ui_smoke.html in a browser
# Check the deployed site
python3 tools/check_demo.py https://tewei02.github.io/multithreaded-file-transfer-scheduler-simulator/tools/check_demo.py needs Google Chrome and pip install websocket-client. The browser harnesses
are development aids; the reference bundle in docs/oracle_bundle.js plus verify.html are the
committed equivalence record, and verify.html re-runs in any modern browser without extra tooling.
- Unit tests (
tests/test_simulator.cpp): per-scheduler ordering behaviour, Round Robin slicing, WFQ tag ordering, summary metric computation, CSV round-trip. Run withmake testorctest --test-dir build --output-on-failure. - CI (
.github/workflows/ci.yml): CMake and Make builds,ctest, a benchmark smoke run that validates theoutput/summary.csvheader and one row per scheduler plus the per-request row counts, a plotting smoke run, atools/run_oracle.sh --checkpass that reproduces the 72 reference digests, and atools/sync_docs_data.sh --checkpass withgit diff --exit-code -- docs/so the committed demo assets and reference bundle cannot drift from the C++ engine. - Equivalence check:
verify.html/tools/check_demo.py, as described in §1.1.
multithreaded-file-transfer-scheduler-simulator/
├── .github/workflows/ci.yml # build, tests, benchmark + plotting smoke
├── CMakeLists.txt # CMake build and CTest target
├── Makefile # make / make test
├── LICENSE # MIT
├── README.md
├── include/
│ ├── request.h # request model, thread-state model, metrics structs
│ ├── scheduler.h # IScheduler interface + five policies
│ ├── worker_manager.h # worker pool and thread assignment
│ ├── simulation_engine.h # discrete-event loop
│ ├── metrics.h # metric collector
│ └── csv_utils.h # CSV reader / writer
├── src/
│ ├── main.cpp # CLI entry point
│ ├── request.cpp
│ ├── scheduler.cpp
│ ├── worker_manager.cpp
│ ├── simulation_engine.cpp
│ ├── metrics.cpp
│ └── csv_utils.cpp
├── tests/test_simulator.cpp # unit tests
├── data/ # workloads (bundled, deterministic)
│ ├── sample_requests.csv
│ ├── edge_cases.csv
│ ├── generated/ # seeded datasets
│ └── reference/oracle_sha256.txt # digests of the C++ reference outputs
├── docs/ # GitHub Pages site (interactive demo + PWA)
│ ├── index.html
│ ├── app.js, engine.js, styles.css
│ ├── verify.html, oracle_bundle.js
│ ├── manifest.json, sw.js, icons/
│ └── data/ # copies of the bundled workloads
├── scripts/plot_summary.py # benchmark figures
├── tools/ # reproducibility, verification and maintenance scripts
└── output/ # generated CSVs and figures (git-ignored)
- Simulation only. No sockets are opened and no bytes are transferred; file sizes and bandwidths are model parameters.
- Event-driven clock. Simulated time advances only at arrivals, dispatches and completions. Worker threads execute the event loop with bounded sleeps to emulate dispatch; wall-clock timing is not part of any result.
- Service model. Non-Round-Robin policies serve a request in a single non-preemptive slice, while
Round Robin slices service by the quantum and re-queues unfinished work. Every dispatch adds
--overheadsimulated seconds of connection setup. - WFQ approximation. The virtual-finish-tag implementation captures weighted ordering but not the full WFQ scheduling algebra.
- Scope of the evaluation. The metrics are outputs of the queuing model on the bundled workloads. The project reports no measurements from a production server, no user study and no field data, and none should be inferred from the figures.
- Additional policies (EDF, DRR, MLFQ) behind the existing
ISchedulerinterface. - Export of the Gantt timeline as CSV in addition to the on-screen canvas.
- Richer workload models (bursty arrivals, heterogeneous bandwidth, multi-server stages) and stress scenarios.
- Performance-regression checks in CI across the reference scenarios.
Released under the MIT License. © 2026 Te-Wei Ko.