Cloud Benchmarker is a specialized tool that benchmarks the performance of cloud instances, particularly beneficial for low-cost VPS hosting services that often oversell their resources. With such services, you might find the performance to be volatile and sometimes below what's advertised. By leveraging the well-regarded sysbench for benchmarking tests, Cloud Benchmarker provides a reliable way to monitor various performance metrics such as CPU speed, memory speed, and disk I/O.
- Automated Benchmarking: Executes
sysbenchtests orchestrated through an Ansible playbook. - Dynamic Performance Monitoring: Ideal for monitoring low-cost, oversold VPS services.
- API Endpoints: RESTful API built on FastAPI for raw and processed data retrieval.
- Data Visualization: Built-in chart generation for quick performance insights.
- Scheduled Runs: Automatically benchmarks cloud instances at specified intervals.
sudo apt-get update
sudo apt-get install ansible python3-pip uvicorn -y
git clone https://github.com/Dicklesworthstone/cloud_benchmarker
cd cloud_benchmarker
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
cp .env.example .env # then edit the copy with your settings
source venv/bin/activateNow simply edit the Ansible inventory file (my_ansible_inventory_file.ini) to include your machine IP addresses and SSH key information (edit or replace my-secret-ssh-key.pem) and you can start the system with:
uvicorn web_app.app.main:app --host 0.0.0.0 --port 9999
Note that the first time you run it, it will immediately create the required directories and start the benchmarking playbook. Thereafter, it will do another benchmark every 6 hours (you can set the schedule to any interval by editing the value in the .env file — see .env.example for all available knobs).
Two prerequisites for the target machines: the playbook installs packages with become: true, so the Ansible user needs passwordless sudo (or configure ansible_become_pass), and the example inventory sets StrictHostKeyChecking=accept-new so first contact with a new host succeeds instead of failing SSH host-key verification.
Run the server with a single uvicorn worker (the default). Each worker process spawns its own benchmark scheduler, so multiple workers would run duplicate benchmarking cycles against your instances.
pip install -r requirements-dev.txt
python -m pytest tests/ -vruff check .
mypy web_app/app script_to_generate_overall_benchmark_scores_from_subscores.py
bandit -r web_app -q --skip B404,B603,B607Interactive API documentation is served at /docs; the root URL now hosts the charts dashboard.
- SQLAlchemy: A Python ORM for database interactions.
- FastAPI: A modern web framework for building APIs.
- Uvicorn: ASGI server for serving FastAPI applications.
- Pandas: Data manipulation and analysis library.
- Pydantic: Data validation using Python type annotations.
- Ansible: Automation tool for configuration and task management.
- Schedule: In-process task scheduler for periodic jobs.
- Python-Decouple: Library for separating configuration from code.
- Plotly: High-level plotting library for interactive visualizations (the
plotly.expressmodule ships inside the mainplotlypackage).
- Configure Ansible Inventory: Update your Ansible inventory file with the cloud instances you want to benchmark.
- Run Benchmarking: Use the Ansible playbook
benchmark-playbook.ymlto initiate benchmarking. - Access Dashboard: Navigate to
http://localhost:9999to view charts and metrics. - API Endpoints: Use the REST API to fetch raw or processed benchmarking data.
-
GET
/data/raw/: Fetches raw benchmark subscores. Filters available for time periods like "last_7_days", "last_30_days", and "last_year". Unsupported values are rejected with a 422 validation error. -
GET
/data/overall/: Retrieves overall normalized benchmark scores, filtered by the same time periods as the raw data. -
GET
/benchmark_charts/: Generates and retrieves benchmark charts based on the latest data. Returns a friendly "no data yet" page until the first successful benchmark run has been ingested. -
GET
/benchmark_historical_csv/: Downloads a CSV file containing historical raw and overall benchmark data. Rows are merged by closest timestamp per host, so every row always pairs a machine's raw metrics with that machine's own overall score.
The scheduler is set up to run the Ansible playbook at intervals defined by PLAYBOOK_RUN_INTERVAL_IN_MINUTES. Before each run it checks whether the output JSON files are stale, using a threshold of half the configured interval (at the default 360 minutes this is the historical 3 hours; shorter intervals take effect immediately rather than being overridden by a fixed constant). This ensures that you don't run unnecessary benchmarks if the data is relatively fresh. After running the playbook, the scheduler ingests the new data into the database.
The Ansible playbook orchestrates the entire benchmarking process. Here's a breakdown of its operation:
- Install required packages: Installs
sysbench,gawk, andgrepon the target machines. - Initialize empty dictionary for results: Sets up an empty dictionary to store the benchmark results.
- CPU Test: Executes a CPU benchmark test with
sysbenchusing 4 threads. The events per second are stored. - Memory Test: Measures the memory speed with a block size of 1K and a total size of 100G. The MiB transferred are stored.
- FileIO Test: Runs a random read-write disk I/O test. Reads per second are stored.
- Mutex Test: Conducts a mutex test with 10,000 locks and 128 mutexes. Average latency is stored.
- Threads Test: Executes a threads test using 4 threads. Average latency is stored.
- Save benchmark results: Saves the collected metrics into a JSON file.
- Fetch benchmark results: Fetches the JSON file back to the control node.
- Read most recent JSON files: Reads the JSON files fetched to the control node.
- Assemble combined JSON file: Combines these JSON files into a single, comprehensive JSON file.
After the playbook runs, a Python script calculates the overall performance scores based on the metrics gathered.
- Each metric is normalized to a scale of 0 to 100, where 100 is always the best observed value across hosts. For lower-is-better metrics (
mutex_test__avg_latency,threads_test__avg_latency), the host with the lowest latency receives 100. If every host reports the same value for a metric (including the single-host case), that metric contributes a neutral 50 instead of a misleading perfect score. - For each host, an overall score is calculated as the weighted sum of the normalized metrics.
The script allows for custom weighting, where you can specify the importance of each metric. For example, you might give CPU speed twice as much weight as disk I/O.
custom_weights = {
"cpu_speed_test__events_per_second": 2.0,
"fileio_test__reads_per_second": 1.0,
"memory_speed_test__MiB_transferred": 2.0,
"mutex_test__avg_latency": 0.5,
"threads_test__avg_latency": 0.5
}The final scores are saved into a JSON file, sorted in descending order based on the overall performance score.
The provided Python script is designed to generate interactive charts visualizing benchmark data using the Plotly library for charting with dynamic client-side interactivity. The charts are served through a FastAPI endpoint, which can be accessed through the browser or through the API.
- Query Raw Benchmark Data: The script queries the database for the raw benchmark subscores, sorts them by datetime, and limits the number of data points based on
MAX_DATA_POINTS_FOR_CHART. - Data to Pandas DataFrame: The fetched data is converted into a Pandas DataFrame for easy manipulation. Datetimes are also converted to Pandas datetime objects for accurate plotting.
- Initialize Figure: An empty Plotly figure (
go.Figure) is created. - Adding Traces: For each unique IP address and each metric ('cpu_speed_test__events_per_second', 'fileio_test__reads_per_second', etc.), a trace is added to the figure. This allows the chart to show lines for each combination of IP address and metric.
- Visibility Toggling: Initially, only the lines for the 'cpu_speed_test__events_per_second' metric are set to be visible.
- By Metric: Dropdown buttons are created to allow the user to toggle the visibility of lines based on metrics.
- By IP Address: Additional dropdown buttons are created to toggle the visibility of lines based on IP addresses.
- Query and Prepare Data: Similar to the raw benchmark data, the overall scores are queried, sorted, and converted into a Pandas DataFrame.
- Create Figure: A Plotly Express line chart is created, which plots the overall normalized score over time, categorized by hostname.
All knobs live in a .env file in the repository root (see .env.example for a template — copy it to .env and adjust):
| Variable | Default | Purpose |
|---|---|---|
SQLALCHEMY_ENGINE_CONNECTION_STRING |
sqlite:///cloud_benchmarker.sqlite |
SQLAlchemy database URL. A relative SQLite path is anchored to the repository root, so the same database file is used no matter which directory you launch from. |
PLAYBOOK_RUN_INTERVAL_IN_MINUTES |
360 |
How often the benchmarking playbook runs. |
ANSIBLE_INVENTORY_FILE_PATH |
my_ansible_inventory_file.ini |
Ansible inventory path; relative paths are resolved against the repository root. |
MAX_DATA_POINTS_FOR_CHART |
1000 |
Maximum number of recent rows plotted per table in the charts dashboard. |
To change the benchmark interval:
- Open the
.envfile in a text editor. - Locate the line
PLAYBOOK_RUN_INTERVAL_IN_MINUTES=360. - Change
360to the desired number of minutes you'd like to set as the new interval.
The PLAYBOOK_RUN_INTERVAL_IN_MINUTES is currently set to 360 minutes, or 6 hours. This is a relatively long interval, which is generally good for minimizing the performance impact on your cloud instances. However, if you're looking for more frequent updates, you may consider reducing this number. Here are some trade-offs:
-
Performance Impact: Reducing this interval will result in more frequent benchmarking, which can affect the performance of your cloud instances. Make sure to understand the resource requirements of your other applications running on these instances before changing this value.
-
Data Freshness: A shorter interval will yield more up-to-date data but at the cost of higher resource utilization. If you need real-time monitoring, you might consider lowering this value cautiously.
This project is under the MIT License (with OpenAI/Anthropic Rider).


