main#11
Conversation
📝 WalkthroughWalkthroughThe pull request introduces a new mock BitAxe Flask server ( Changes
Sequence Diagram(s)sequenceDiagram
participant BS as Benchmark Script
participant MS as Mock Server
participant State as Device State
BS->>MS: GET /api/system/info
MS->>State: Read current config
State-->>MS: frequency, voltage, core count
MS->>MS: Compute hashRate
MS-->>BS: Return system info + hashRate
BS->>MS: GET /api/system/asic
MS->>State: Read defaults
State-->>MS: Default voltage/frequency
MS-->>BS: Return ASIC config
BS->>MS: PATCH /api/system (new frequency/voltage)
MS->>State: Update settings
State-->>MS: Confirm update
MS-->>BS: Return updated values
BS->>MS: GET /api/system/info
MS->>State: Read updated config
State-->>MS: New frequency, recalculated hashRate
MS-->>BS: Return updated info
BS->>MS: POST /api/system/restart
MS-->>BS: Return restart status
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
.gitignore (2)
174-175: Project-specific “OLD” file ignore is a smell.Tracking
mock_bitaxe_OLD.pyin the shared.gitignoresuggests the file is being kept around locally. Prefer either deleting it (Git history retains old versions) or adding it to your personal.git/info/excluderather than committing the rule for everyone.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore around lines 174 - 175, The .gitignore currently contains a project-wide rule ignoring "mock_bitaxe_OLD.py" which is a local, developer-specific artifact; remove the "mock_bitaxe_OLD.py" entry from the shared .gitignore and either delete the file (history preserves it) or add the filename to your personal exclude file (.git/info/exclude) instead so the ignore stays local; update the repository by committing the .gitignore removal and, if you keep the file locally, add it to your .git/info/exclude on your machine.
41-41: Duplicate and misplaced ignore pattern.
*benchmark_results*.jsonis added here under the "Unit test / coverage reports" section, and again at line 172 under the more appropriate "Benchmark results" section. Drop the entry on line 41 to keep this section semantically clean.♻️ Proposed fix
# Unit test / coverage reports htmlcov/ -*benchmark_results*.json .tox/🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore at line 41, Remove the duplicate ignore pattern "*benchmark_results*.json" from the "Unit test / coverage reports" section in .gitignore so it only exists under the correct "Benchmark results" section; locate the occurrence of "*benchmark_results*.json" near the top of the file (in the unit test/coverage block) and delete that entry, keeping the existing pattern in the benchmark section intact.bitaxe_hashrate_benchmark.py (3)
98-100: Cappingmax_allowed_frequencyis safer than it looks — but document the side-effect.Setting
max_allowed_frequency = initial_frequencyensures the main loop terminates after one iteration (line 453'scurrent_frequency + frequency_increment <= max_allowed_frequencybecomes false), but it also silently overrides the operator-facing safety constant for the entire run. If anyone later prints or logsmax_allowed_frequencyfor diagnostics, they'll see a misleading value in--quickruns. Consider introducing a separatequick_iteration_limitflag or breaking after the first successful iteration based onargs.quick, instead of mutating a safety limit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bitaxe_hashrate_benchmark.py` around lines 98 - 100, The current quick-mode mutates the safety variable max_allowed_frequency by setting it to initial_frequency when args.quick is true; instead, avoid changing max_allowed_frequency and implement a separate mechanism such as a quick_iteration_limit flag or break-after-first-success logic: introduce a new boolean or counter (e.g., quick_iteration_limit or did_one_iteration) and, inside the main loop that uses current_frequency and frequency_increment, check args.quick and break after completing the first successful iteration (or decrement the counter) rather than assigning initial_frequency to max_allowed_frequency so diagnostic logs of max_allowed_frequency remain correct.
529-538: Sanitization only handles:— fine for IPv4:port, but worth noting.
safe_ip = ip_address.replace(':', '_')is enough for127.0.0.1:5000, but if a user ever passes an IPv6 literal (Bitaxe doesn't today, but the CLI accepts arbitrary text) the resulting filename will contain[and]which are valid on POSIX but cosmetically ugly. Not a blocker — flagging only because the helper-extraction comment above would centralize this rule for future hardening.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bitaxe_hashrate_benchmark.py` around lines 529 - 538, The current filename sanitization only replaces ':' via safe_ip = ip_address.replace(':', '_'), which misses characters like '[' and ']' (IPv6) and other potentially problematic chars; update the sanitization to a reusable helper (e.g., create a sanitize_filename or sanitize_ip helper used where safe_ip is set) that replaces or strips characters not allowed/desired in filenames (at minimum replace ':' '[' ']' and any path separators like '/' '\' with underscores) and use that helper when building filename (reference variables ip_address, safe_ip, filename, and the surrounding filename creation logic) so future callers can reuse centralized, hardened rules.
388-395: Duplicated filename construction acrosssave_results()and thefinallyblock.The IP sanitization + filename +
abs_pathlogic at lines 388-395 is repeated almost verbatim at lines 529-534. Factor it out into a small helper (e.g.,_results_filename()) to keep them in lock-step — otherwise a future change to the sanitization rule (e.g., also replacing/or handling IPv6 brackets) will need to be made in two places.♻️ Suggested helper
+def _results_filename(): + ip_address = bitaxe_ip.replace('http://', '') + safe_ip = ip_address.replace(':', '_') + return f"bitaxe_benchmark_results_{safe_ip}_{START_TIME}.json" + def save_results(): try: - # Extract IP from bitaxe_ip global variable and remove 'http://' - ip_address = bitaxe_ip.replace('http://', '') - # replace characters not valid in filenames on some OS (e.g., ':' on Windows) - safe_ip = ip_address.replace(':', '_') - filename = f"bitaxe_benchmark_results_{safe_ip}_{START_TIME}.json" + filename = _results_filename()Also applies to: 529-534
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bitaxe_hashrate_benchmark.py` around lines 388 - 395, Duplicate filename construction and path-printing logic appears in save_results() and the finally block; extract this into a single helper (suggestion: _results_filename or _save_results_file) that accepts ip_address, START_TIME and results (or just returns the sanitized filename and absolute path), centralizes the sanitization (e.g., safe_ip = ip_address.replace(':','_') and any future rules), performs the file write and returns the filename/abs_path (or just returns filename and let callers write if preferred), then replace the duplicated code in save_results() and the finally block with calls to that helper to generate/write the file and print the same messages; update references to save_results(), the finally block, and use the new helper name consistently.README.md (2)
93-93:flaskis installed ad-hoc and not pinned.
pip install flaskhere picks up whatever the latest version is, which can drift across contributor machines. Consider addingflaskto arequirements-dev.txt(orrequirements.txtif you want it bundled) so the mock's behavior is reproducible.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 93, Replace the ad-hoc "pip install flask" in the README with a reproducible dependency declaration: add "Flask==<pin-version>" to your requirements-dev.txt (or requirements.txt if preferred) and update the README to instruct users to run "pip install -r requirements-dev.txt"; ensure the added requirements file contains the exact pinned version string (e.g., Flask==2.2.5) so the mock behavior is consistent across machines.
221-221: Endpoint list omitsGET /api/hashrate.The mock also exposes a legacy
GET /api/hashrateendpoint (mock_bitaxe.pyline 81). Either drop it from the mock if truly unused, or list it here for completeness so readers don't wonder why it's in the file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 221, The README endpoint list omits the legacy GET /api/hashrate endpoint that the mock still exposes; either remove the legacy handler from the mock or add GET /api/hashrate to the README list. Locate the mock by searching for the string "GET /api/hashrate" in the codebase, then either delete the handler or update the README entry (keeping wording that it is a legacy endpoint if you retain it).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bitaxe_hashrate_benchmark.py`:
- Around line 70-73: The quick-mode sample budget is too small: with
benchmark_time=35 and sample_interval=5 you get 7 samples which, after the
trimming in benchmark_iteration (removing 3 highest and 3 lowest hashrates and 6
lowest temperatures), leaves only one sample or zero; update the quick-mode
behavior by either increasing the budget (e.g., set benchmark_time to 50 with
sample_interval 5 to yield 10 samples) or modify benchmark_iteration to detect
args.quick and skip/relax the aggressive trimming (or guard against empty
trimmed lists) and add a clarifying comment next to the args.quick block and the
trimming code to explain the dependency so future edits won’t silently break the
math.
In `@mock_bitaxe.py`:
- Around line 86-87: The app is currently bound to 0.0.0.0 in the __main__ block
via app.run(host="0.0.0.0", port=5000); change this to bind to localhost by
default (host="127.0.0.1") and add an optional override via an environment
variable (e.g., read os.getenv("MOCK_HOST", "127.0.0.1")) so callers can
explicitly opt into external exposure; keep the app.run(...) call but replace
its host arg with the env-driven value and update README/inline comment to note
that Docker on Linux may need extra flags to reach a host-bound loopback if you
choose to expose externally.
- Around line 59-71: The PATCH handler update_system_settings currently calls
int(...) on request values and will raise ValueError/TypeError for non-numeric
or missing inputs, causing a 500; modify update_system_settings to
validate/coerce inputs from request.get_json() before assigning to
state["coreVoltage"] and state["frequency"] — e.g., check for presence, attempt
int conversion inside a try/except (catch ValueError/TypeError) and either
ignore the bad field (leave existing state) or return a 400 JSON error response;
ensure no uncaught exceptions propagate so malformed payloads do not produce a
500.
In `@README.md`:
- Around line 215-222: Update the README example to distinguish Docker Desktop
vs native Linux usage so Linux users get a working example: keep the original
build step, then show two run variants — one using host.docker.internal:5000 for
Docker Desktop (Windows/macOS) and another using
--add-host=host.docker.internal:host-gateway for native Linux; mention that the
mock implements GET /api/system/info, GET /api/system/asic, PATCH /api/system,
POST /api/system/restart and that host.docker.internal may require the extra
flag on Linux so readers copying the original docker run command won’t see
connection failures.
- Around line 88-102: Update the "Quick Test" block in the README so it is
cross-platform like "Safe Mock Mode": keep the Windows PowerShell commands that
reference venv\Scripts\activate and mock_bitaxe.py, and add the equivalent
macOS/Linux commands using python -m venv venv and source venv/bin/activate (or
the POSIX activation) followed by pip install flask and python mock_bitaxe.py;
ensure the section heading or inline comments clarify which commands are for
Windows vs macOS/Linux so users running bitaxe_hashrate_benchmark.py --quick
against 127.0.0.1:5000 can follow the correct activation steps.
---
Nitpick comments:
In @.gitignore:
- Around line 174-175: The .gitignore currently contains a project-wide rule
ignoring "mock_bitaxe_OLD.py" which is a local, developer-specific artifact;
remove the "mock_bitaxe_OLD.py" entry from the shared .gitignore and either
delete the file (history preserves it) or add the filename to your personal
exclude file (.git/info/exclude) instead so the ignore stays local; update the
repository by committing the .gitignore removal and, if you keep the file
locally, add it to your .git/info/exclude on your machine.
- Line 41: Remove the duplicate ignore pattern "*benchmark_results*.json" from
the "Unit test / coverage reports" section in .gitignore so it only exists under
the correct "Benchmark results" section; locate the occurrence of
"*benchmark_results*.json" near the top of the file (in the unit test/coverage
block) and delete that entry, keeping the existing pattern in the benchmark
section intact.
In `@bitaxe_hashrate_benchmark.py`:
- Around line 98-100: The current quick-mode mutates the safety variable
max_allowed_frequency by setting it to initial_frequency when args.quick is
true; instead, avoid changing max_allowed_frequency and implement a separate
mechanism such as a quick_iteration_limit flag or break-after-first-success
logic: introduce a new boolean or counter (e.g., quick_iteration_limit or
did_one_iteration) and, inside the main loop that uses current_frequency and
frequency_increment, check args.quick and break after completing the first
successful iteration (or decrement the counter) rather than assigning
initial_frequency to max_allowed_frequency so diagnostic logs of
max_allowed_frequency remain correct.
- Around line 529-538: The current filename sanitization only replaces ':' via
safe_ip = ip_address.replace(':', '_'), which misses characters like '[' and ']'
(IPv6) and other potentially problematic chars; update the sanitization to a
reusable helper (e.g., create a sanitize_filename or sanitize_ip helper used
where safe_ip is set) that replaces or strips characters not allowed/desired in
filenames (at minimum replace ':' '[' ']' and any path separators like '/' '\'
with underscores) and use that helper when building filename (reference
variables ip_address, safe_ip, filename, and the surrounding filename creation
logic) so future callers can reuse centralized, hardened rules.
- Around line 388-395: Duplicate filename construction and path-printing logic
appears in save_results() and the finally block; extract this into a single
helper (suggestion: _results_filename or _save_results_file) that accepts
ip_address, START_TIME and results (or just returns the sanitized filename and
absolute path), centralizes the sanitization (e.g., safe_ip =
ip_address.replace(':','_') and any future rules), performs the file write and
returns the filename/abs_path (or just returns filename and let callers write if
preferred), then replace the duplicated code in save_results() and the finally
block with calls to that helper to generate/write the file and print the same
messages; update references to save_results(), the finally block, and use the
new helper name consistently.
In `@README.md`:
- Line 93: Replace the ad-hoc "pip install flask" in the README with a
reproducible dependency declaration: add "Flask==<pin-version>" to your
requirements-dev.txt (or requirements.txt if preferred) and update the README to
instruct users to run "pip install -r requirements-dev.txt"; ensure the added
requirements file contains the exact pinned version string (e.g., Flask==2.2.5)
so the mock behavior is consistent across machines.
- Line 221: The README endpoint list omits the legacy GET /api/hashrate endpoint
that the mock still exposes; either remove the legacy handler from the mock or
add GET /api/hashrate to the README list. Locate the mock by searching for the
string "GET /api/hashrate" in the codebase, then either delete the handler or
update the README entry (keeping wording that it is a legacy endpoint if you
retain it).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4d9833c3-71b5-414d-9bf8-98cd62fd0bcf
📒 Files selected for processing (4)
.gitignoreREADME.mdbitaxe_hashrate_benchmark.pymock_bitaxe.py
| if args.quick: | ||
| sleep_time = 2 | ||
| benchmark_time = 35 | ||
| sample_interval = 5 |
There was a problem hiding this comment.
--quick math is on the knife-edge of the validity check.
With benchmark_time=35 and sample_interval=5, total_samples = 35 // 5 = 7, which exactly meets the >= 7 guard at line 109. After the trimming logic in benchmark_iteration removes the 3 highest and 3 lowest hashrates (line 346) and the 6 lowest temperatures (line 351), the averages are computed from a single sample each — statistically meaningless and easy to break. Any future tweak to the trimming constants or this 35/5 will silently push --quick below the threshold or into a divide-by-zero on len(trimmed_temps).
Consider either bumping --quick to a slightly larger sample budget (e.g., benchmark_time=50, sample_interval=5 → 10 samples) or skipping/relaxing the trim logic when running in quick mode. At minimum, leave a comment so this trap is visible to future editors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bitaxe_hashrate_benchmark.py` around lines 70 - 73, The quick-mode sample
budget is too small: with benchmark_time=35 and sample_interval=5 you get 7
samples which, after the trimming in benchmark_iteration (removing 3 highest and
3 lowest hashrates and 6 lowest temperatures), leaves only one sample or zero;
update the quick-mode behavior by either increasing the budget (e.g., set
benchmark_time to 50 with sample_interval 5 to yield 10 samples) or modify
benchmark_iteration to detect args.quick and skip/relax the aggressive trimming
(or guard against empty trimmed lists) and add a clarifying comment next to the
args.quick block and the trimming code to explain the dependency so future edits
won’t silently break the math.
| @app.patch("/api/system") | ||
| def update_system_settings(): | ||
| body = request.get_json(silent=True) or {} | ||
| # accept and store the requested settings | ||
| if "coreVoltage" in body: | ||
| state["coreVoltage"] = int(body["coreVoltage"]) | ||
| if "frequency" in body: | ||
| state["frequency"] = int(body["frequency"]) | ||
| # return the new state | ||
| return jsonify({ | ||
| "coreVoltage": state["coreVoltage"], | ||
| "frequency": state["frequency"] | ||
| }) |
There was a problem hiding this comment.
PATCH handler will 500 on non-numeric input.
int(body["coreVoltage"]) / int(body["frequency"]) raises ValueError/TypeError for non-numeric or None payloads, which Flask turns into a 500. For a mock that should "always succeed", consider validating types and returning a 400 (or silently coercing/ignoring), so a malformed test payload doesn't masquerade as a server crash.
♻️ Suggested change
- if "coreVoltage" in body:
- state["coreVoltage"] = int(body["coreVoltage"])
- if "frequency" in body:
- state["frequency"] = int(body["frequency"])
+ try:
+ if "coreVoltage" in body:
+ state["coreVoltage"] = int(body["coreVoltage"])
+ if "frequency" in body:
+ state["frequency"] = int(body["frequency"])
+ except (TypeError, ValueError):
+ return jsonify({"error": "coreVoltage and frequency must be integers"}), 400🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mock_bitaxe.py` around lines 59 - 71, The PATCH handler
update_system_settings currently calls int(...) on request values and will raise
ValueError/TypeError for non-numeric or missing inputs, causing a 500; modify
update_system_settings to validate/coerce inputs from request.get_json() before
assigning to state["coreVoltage"] and state["frequency"] — e.g., check for
presence, attempt int conversion inside a try/except (catch
ValueError/TypeError) and either ignore the bad field (leave existing state) or
return a 400 JSON error response; ensure no uncaught exceptions propagate so
malformed payloads do not produce a 500.
| if __name__ == "__main__": | ||
| app.run(host="0.0.0.0", port=5000) |
There was a problem hiding this comment.
Bind the mock to localhost by default.
host="0.0.0.0" exposes the mock on every network interface, which is unnecessary for a local test fixture and is flagged by static analysis (Ruff S104 / CWE-668). Prefer binding to 127.0.0.1 and only expose externally when explicitly opted into (e.g., via env var) — the Docker workflow in the README already uses host.docker.internal to reach the host, which works with a loopback bind on Docker Desktop but not on Linux without extra flags, so document accordingly if you intend to keep 0.0.0.0.
🔒️ Suggested change
if __name__ == "__main__":
- app.run(host="0.0.0.0", port=5000)
+ import os
+ host = os.environ.get("MOCK_BITAXE_HOST", "127.0.0.1")
+ app.run(host=host, port=5000)🧰 Tools
🪛 ast-grep (0.42.1)
[warning] 86-86: Running flask app with host 0.0.0.0 could expose the server publicly.
Context: app.run(host="0.0.0.0", port=5000)
Note: [CWE-668]: Exposure of Resource to Wrong Sphere [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://owasp.org/Top10/A01_2021-Broken_Access_Control
(avoid_app_run_with_bad_host-python)
🪛 Ruff (0.15.11)
[error] 87-87: Possible binding to all interfaces
(S104)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mock_bitaxe.py` around lines 86 - 87, The app is currently bound to 0.0.0.0
in the __main__ block via app.run(host="0.0.0.0", port=5000); change this to
bind to localhost by default (host="127.0.0.1") and add an optional override via
an environment variable (e.g., read os.getenv("MOCK_HOST", "127.0.0.1")) so
callers can explicitly opt into external exposure; keep the app.run(...) call
but replace its host arg with the env-driven value and update README/inline
comment to note that Docker on Linux may need extra flags to reach a host-bound
loopback if you choose to expose externally.
| 1) Start the mock API (new shell): | ||
|
|
||
| ```bash | ||
| python -m venv venv | ||
| venv\Scripts\activate | ||
| pip install flask | ||
| python mock_bitaxe.py | ||
| ``` | ||
|
|
||
| 2) In another shell, run a short smoke test (≥7 samples but fast): | ||
|
|
||
| ```bash | ||
| venv\Scripts\activate | ||
| python bitaxe_hashrate_benchmark.py 127.0.0.1:5000 --quick | ||
| ``` |
There was a problem hiding this comment.
Quick Test instructions are Windows-only.
The "Safe Mock Mode" section (lines 199-211) properly shows both Windows and macOS/Linux activation, but this earlier "Quick Test" block only shows venv\Scripts\activate. Mirror the cross-platform example here so non-Windows users aren't left to figure it out.
📝 Suggested change
```bash
+# Windows (PowerShell)
python -m venv venv
venv\Scripts\activate
pip install flask
python mock_bitaxe.py
+
+# macOS/Linux
+# python -m venv venv
+# source venv/bin/activate
+# pip install flask
+# python mock_bitaxe.py
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @README.md around lines 88 - 102, Update the "Quick Test" block in the README
so it is cross-platform like "Safe Mock Mode": keep the Windows PowerShell
commands that reference venv\Scripts\activate and mock_bitaxe.py, and add the
equivalent macOS/Linux commands using python -m venv venv and source
venv/bin/activate (or the POSIX activation) followed by pip install flask and
python mock_bitaxe.py; ensure the section heading or inline comments clarify
which commands are for Windows vs macOS/Linux so users running
bitaxe_hashrate_benchmark.py --quick against 127.0.0.1:5000 can follow the
correct activation steps.
</details>
<!-- fingerprinting:phantom:poseidon:nectarine:53fc0bda-7a36-4484-b020-12f70c72c9ab -->
<!-- d98c2f50 -->
<!-- This is an auto-generated comment by CodeRabbit -->
| ```bash | ||
| docker build -t bitaxe-benchmark . | ||
| docker run --rm bitaxe-benchmark host.docker.internal:5000 | ||
| ``` | ||
|
|
||
| Notes: | ||
| - The mock implements the endpoints the script calls: `GET /api/system/info`, `GET /api/system/asic`, `PATCH /api/system`, `POST /api/system/restart`. | ||
| - Host `host.docker.internal` is commonly available on Docker Desktop to reach the host from a container. |
There was a problem hiding this comment.
host.docker.internal is broken on Linux without an extra flag.
On Docker Desktop (Windows/macOS) host.docker.internal resolves automatically, but on native Linux Docker it does not — users need --add-host=host.docker.internal:host-gateway for the example to work. The note hedges with "commonly available", but Linux contributors copy-pasting this command will get a connection failure.
📝 Suggested change
```bash
docker build -t bitaxe-benchmark .
-docker run --rm bitaxe-benchmark host.docker.internal:5000
+# Docker Desktop (Windows/macOS):
+docker run --rm bitaxe-benchmark host.docker.internal:5000
+# Linux (native Docker):
+docker run --rm --add-host=host.docker.internal:host-gateway bitaxe-benchmark host.docker.internal:5000
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @README.md around lines 215 - 222, Update the README example to distinguish
Docker Desktop vs native Linux usage so Linux users get a working example: keep
the original build step, then show two run variants — one using
host.docker.internal:5000 for Docker Desktop (Windows/macOS) and another using
--add-host=host.docker.internal:host-gateway for native Linux; mention that the
mock implements GET /api/system/info, GET /api/system/asic, PATCH /api/system,
POST /api/system/restart and that host.docker.internal may require the extra
flag on Linux so readers copying the original docker run command won’t see
connection failures.
</details>
<!-- fingerprinting:phantom:poseidon:nectarine:53fc0bda-7a36-4484-b020-12f70c72c9ab -->
<!-- 4e71b3a2 -->
<!-- This is an auto-generated comment by CodeRabbit -->
# Bitaxe Hashrate Benchmark
A Python-based benchmarking tool for optimizing Bitaxe mining performance by testing different voltage and frequency combinations while monitoring hashrate, temperature, and power efficiency.
## Features
- Automated benchmarking of different voltage/frequency combinations
- Temperature monitoring and safety cutoffs
- Power efficiency calculations (J/TH)
- Automatic saving of benchmark results
- Graceful shutdown with best settings retention
- Docker support for easy deployment
## Prerequisites
- Python 3.11 or higher
- Access to a Bitaxe miner on your network
- Docker (optional, for containerized deployment)
- Git (optional, for cloning the repository)
## Installation
### Standard Installation
1. Clone the repository:
git clone https://github.com/mrv777/Bitaxe-Hashrate-Benchmark.git cd Bitaxe-Hashrate-Benchmark2. Create and activate a virtual environment:
3. Install dependencies:
### Docker Installation
1. Build the Docker image:
docker build -t bitaxe-benchmark .## Usage
### Standard Usage
Run the benchmark tool by providing your Bitaxe's IP address:
Optional parameters:
-
-v, --voltage: Initial voltage in mV (default: 1150)-
-f, --frequency: Initial frequency in MHz (default: 500)Example:
### Docker Usage (Optional)
Run the container with your Bitaxe's IP address:
Example:
### Quick Test (Mock, Fast)
Use the built-in mock to verify end-to-end flow without hardware.
1) Start the mock API (new shell):
2) In another shell, run a short smoke test (≥7 samples but fast):
Notes:
-
--quicksets a short stabilization and sampling window for fast validation.- Results are saved to a timestamped JSON file in the repo directory.
## Configuration
The script includes several configurable parameters:
- Maximum chip temperature: 66°C
- Maximum VR temperature: 86°C
- Maximum allowed voltage: 1400mV
- Minimum allowed voltage: 1000mV
- Maximum allowed frequency: 1200MHz
- Maximum power consumption: 40W
- Minimum allowed frequency: 400MHz
- Minimum input voltage: 4800mV
- Maximum input voltage: 5500mV
- Benchmark duration: 10 minutes
- Sample interval: 15 seconds
- Sleep time before benchmark: 90 seconds
- **Minimum required samples: 7** (for valid data processing)
- Voltage increment: 20mV
- Frequency increment: 25MHz
## Output
The benchmark results are saved to
bitaxe\_benchmark\_results\_<ip\_address>.json, containing:- Complete test results for all combinations
- Top 5 performing configurations ranked by hashrate
- Top 5 most efficient configurations ranked by J/TH
- For each configuration:
- Average hashrate (with outlier removal)
- Temperature readings (excluding initial warmup period)
- VR temperature readings (when available)
- Power efficiency metrics (J/TH)
- Input voltage measurements
- Voltage/frequency combinations tested
## Safety Features
- Automatic temperature monitoring with safety cutoff (66°C chip temp)
- Voltage regulator (VR) temperature monitoring with safety cutoff (86°C)
- Input voltage monitoring with minimum threshold (4800mV) and maximum threshold (5500mV)
- Power consumption monitoring with safety cutoff (40W)
- Temperature validation (must be above 5°C)
- Graceful shutdown on interruption (Ctrl+C)
- Automatic reset to best performing settings after benchmarking
- Input validation for safe voltage and frequency ranges
- Hashrate validation to ensure stability
- Protection against invalid system data
- Outlier removal from benchmark results
## Benchmarking Process
The tool follows this process:
1. Starts with user-specified or default voltage/frequency
2. Tests each combination for 10 minutes
3. Validates hashrate is within 6% of theoretical maximum
4. Incrementally adjusts settings:
- Increases frequency if stable
- Increases voltage if unstable
- Stops at thermal or stability limits
5. Records and ranks all successful configurations
6. Automatically applies the best performing stable settings
7. Restarts system after each test for stability
8. Allows 90-second stabilization period between tests
## Data Processing
The tool implements several data processing techniques to ensure accurate results:
- Removes 3 highest and 3 lowest hashrate readings to eliminate outliers
- Excludes first 6 temperature readings during warmup period
- Validates hashrate is within 6% of theoretical maximum
- Averages power consumption across entire test period
- Monitors VR temperature when available
- Calculates efficiency in Joules per Terahash (J/TH)
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
## Disclaimer
Please use this tool responsibly. Overclocking and voltage modifications can potentially damage your hardware if not done carefully. Always ensure proper cooling and monitor your device during benchmarking.
## Safe Mock Mode
Run the benchmark against a local mock server so the script “succeeds” without touching a real Bitaxe.
1) Start the mock on your host:
2) Build and run the container, pointing at your host mock:
docker build -t bitaxe-benchmark . docker run --rm bitaxe-benchmark host.docker.internal:5000Notes:
- The mock implements the endpoints the script calls:
GET /api/system/info,GET /api/system/asic,PATCH /api/system,POST /api/system/restart.- Host
host.docker.internalis commonly available on Docker Desktop to reach the host from a container.- This keeps real hardware safe while allowing full script flow.
Summary by CodeRabbit
New Features
--quickmode for rapid smoke testing with shortened runtime.--sleep-time,--benchmark-time,--sample-interval).Documentation
Improvements