Reusable VM benchmark harness for comparing S3-compatible providers from a Linux test node.
The scripts write JSONL and logs to /dataoutput, use generated payloads in /testfiles, and use /downloads as disposable download scratch space.
Run this on a fresh apt-based Linux VM:
git clone https://github.com/fil-one/s3-speedtests
cd s3-speedtests
sudo ./scripts/setup_vm.shThe setup installs traceroute, ping, jq, curl, Python, python3-docx, python3-reportlab, Ookla speedtest, and AWS CLI. It also generates:
100 x 1 MiBfiles5 x 100 MiBfiles1 x 1 GiBfile1 x 25 GiBfile1 x 50 GiBfile
To install dependencies without generating the full payload set:
sudo ./scripts/setup_vm.sh --skip-filesTo create or repair only the /testfiles payload set after dependencies are already installed:
sudo ./scripts/setup_vm.sh --files-onlyTo regenerate files even when matching filenames already exist:
sudo ./scripts/setup_vm.sh --files-only --force-filesSetup seeds /testfiles/s3_targets.ini from config/s3_targets.example.ini if it does not already exist.
Edit /testfiles/s3_targets.ini, enable the providers you want, and add bucket names. For static-key providers, add access keys there and then write AWS CLI profiles:
./scripts/s3_write_profiles.pyFor AWS SSO or temporary AWS credentials, configure the profile directly with AWS CLI and set profile = aws and auth_mode = profile.
S3 commands default to AWS_MAX_ATTEMPTS=11, which means one initial request plus ten retries. Override globally with AWS_MAX_ATTEMPTS=... or per provider with aws_max_attempts = ... in /testfiles/s3_targets.ini.
Warning: run_all executes the full benchmark workflow after setup. It writes AWS CLI profiles from /testfiles/s3_targets.ini, checks access, uploads the standard and large file sets, downloads all uploaded objects, runs network tests and traceroutes, and then builds the default DOCX report. This can move more than 75 GiB per enabled provider in each direction and may incur cloud egress, storage, API, or bandwidth costs.
Run the complete workflow:
./scripts/run_allRun only selected providers:
./scripts/run_all --providers aws,wasabiShow AWS CLI transfer progress in the tmux console during upload and download steps:
./scripts/run_all --progressThe same behavior can be enabled with TRANSFER_PROGRESS=1:
TRANSFER_PROGRESS=1 ./scripts/run_all --providers aws,wasabiBy default, run_all uses minimal waits: PAUSE_SECONDS=5, NETWORK_RUNS=1, NETWORK_SLEEP_SECONDS=5, and NETWORK_SERVER_MODE=auto. Override them when you want more samples:
NETWORK_RUNS=3 NETWORK_SLEEP_SECONDS=30 PAUSE_SECONDS=10 ./scripts/run_allThe orchestration log is written to /dataoutput/run_all_<timestamp>.log.
The final report step prompts for the source node provider/name and location unless those values are supplied through environment variables:
SOURCE_NODE_PROVIDER="AWS EC2" \
SOURCE_NODE_LOCATION="eu-west-3 | Paris, France" \
SOURCE_NODE_NETWORK="1 Gbit connection" \
./scripts/run_all --providers aws,wasabiCheck bucket access:
./scripts/s3_access_check.pyCheck one provider by target section, provider name, or AWS profile:
./scripts/s3_access_check.py --providers f1
./scripts/s3_access_check.py --providers aws-us-west-2Print each AWS CLI command plus stdout/stderr for debugging:
./scripts/s3_access_check.py --providers f1 --verbosePass AWS CLI --debug for deep troubleshooting. This is very noisy and can include request metadata, so redirect it to a log when needed:
./scripts/s3_access_check.py --providers f1 --debug-aws 2>&1 | tee /dataoutput/f1_access_debug.logNetwork baseline:
RUNS=1 ./scripts/network_speedtest_ookla.shBy default, the network baseline uses Ookla automatic server selection, so it should pick a nearby/best server for the current VM location. This avoids the old Spain/Paris fixed-server behavior when the VM is in another region such as New York.
Network server modes:
NETWORK_SERVER_MODE=auto RUNS=1 ./scripts/network_speedtest_ookla.sh
NETWORK_SERVER_MODE=fixed RUNS=1 ./scripts/network_speedtest_ookla.sh
NETWORK_SERVER_MODE=geo RUNS=1 ./scripts/network_speedtest_ookla.sh
NETWORK_SERVER_MODE=all RUNS=1 ./scripts/network_speedtest_ookla.shauto uses Ookla's selected server and does not call the Speedtest server-list API. fixed uses the legacy Barcelona, Madrid, and Paris server IDs. geo searches the Speedtest server-list API for the configured city coordinates and may be rate-limited by Ookla. all combines auto, fixed, and geo.
Provider traceroutes:
./scripts/s3_provider_traceroutes.shTraceroutes are generated only for enabled endpoints in /testfiles/s3_targets.ini. Use --targets to point at a different target config.
Upload standard and large file sets together:
./scripts/s3_upload_speedtest.sh --file-set fullShow upload progress and per-object elapsed time / throughput in the console:
./scripts/s3_upload_speedtest.sh --file-set full --progressRun upload file sets separately when you want independent standard and large result files:
./scripts/s3_upload_speedtest.sh --file-set standard
./scripts/s3_upload_speedtest.sh --file-set largeDownload all uploaded files:
./scripts/s3_download_speedtest.sh --file-set fullShow download progress and per-object elapsed time / throughput in the console:
./scripts/s3_download_speedtest.sh --file-set full --progressWith --progress, the AWS CLI progress display streams to the terminal. The scripts still write JSONL metrics and print a final DONE ... elapsed=... throughput_mbps=... line for each object. When progress is enabled through run_all, the orchestration log also captures the console stream because run_all uses tee.
Watch newest logs:
tail -f "$(ls -t /dataoutput/*.log | head -n 1)"Preview deletion of test objects from all enabled providers. This is a dry run by default:
./scripts/s3_cleanup_test_files.pyActually delete each provider's configured test prefix from all enabled providers:
./scripts/s3_cleanup_test_files.py --executeClean only selected providers:
./scripts/s3_cleanup_test_files.py --providers aws,wasabi --executeDelete a specific prefix instead of each provider's configured prefix:
./scripts/s3_cleanup_test_files.py --providers f1 --prefix test/20260715T185306Z --executeDelete all objects in selected buckets:
./scripts/s3_cleanup_test_files.py --providers f1 --entire-bucket --executeCleanup audit records are written to /dataoutput/s3_cleanup_test_files.jsonl.
Build a summary report from JSONL output in /dataoutput:
./scripts/build_summary_report.pyDOCX is the default format. Generated reports are written under /dataoutput/reports.
Choose a report format explicitly:
./scripts/build_summary_report.py --format docx
./scripts/build_summary_report.py --format pdf
./scripts/build_summary_report.py --format bothThe report builder loads the latest available benchmark artifacts:
/dataoutput/network_speedtest_ookla_summary.jsonl/dataoutput/s3_upload_speedtest_summary.jsonlor the latests3_upload_speedtest_summary_*.jsonl/dataoutput/s3_download_speedtest_summary.jsonlor the latests3_download_speedtest_summary_*.jsonl/dataoutput/s3_provider_traceroutes.jsonlor the latests3_provider_traceroutes_*.jsonl
Upload and download ranking cells include median throughput, average throughput, total elapsed time, and median elapsed time when available. The traceroute section includes only enabled target endpoints from the target config, plus the full CLI traceroute command output with hop lines.
Provider names and regions in the upload/download result tables are read from /testfiles/s3_targets.ini, so changing a bucket target region there changes the report label on the next report build. Optional display_name and location fields in each target section override the report label, for example display_name = AWS and location = Paris, France. Use --targets to point at a different target config.
When run interactively, the report builder prompts for the source node provider/name and source node location. It auto-detects hostname, vCPU count, and RAM from the VM.
Prompted run:
./scripts/build_summary_report.pyExpected prompts:
Source node provider/name:
Source node location:
For non-interactive runs:
./scripts/build_summary_report.py \
--format docx \
--source-provider "AWS EC2" \
--source-location "eu-west-3 | Paris, France" \
--node-network "1 Gbit connection" \
--no-promptYou can also use environment variables:
SOURCE_NODE_PROVIDER="AWS EC2" \
SOURCE_NODE_LOCATION="eu-west-3 | Paris, France" \
SOURCE_NODE_NETWORK="1 Gbit connection" \
./scripts/build_summary_report.py --format pdf --no-promptUse explicit input and output directories when rebuilding a report from archived results:
./scripts/build_summary_report.py \
--format both \
--targets /testfiles/s3_targets.ini \
--data-dir /dataoutput \
--output-dir /dataoutput/reports