From 713802a2119677e7b9c1ccbf4189774a7bd8902b Mon Sep 17 00:00:00 2001 From: Theodore Robert Campbell Jr Date: Wed, 15 Apr 2026 19:26:37 -0400 Subject: [PATCH 1/2] Updated frontend title --- frontend/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 072a57e..4ab9b05 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - frontend + Bitaxe Hashrate Benchmark
From 17871e12b69c4fc739f3f8370d306cbc42a001c4 Mon Sep 17 00:00:00 2001 From: Theodore Robert Campbell Jr Date: Thu, 16 Apr 2026 11:56:02 -0400 Subject: [PATCH 2/2] Add live error percentage tracking with UI display and documentation Backend now includes error_percent in SampleProgress, and the frontend displays it in real time during benchmarking. Types and models updated for full-stack support. Documentation updated in README to cover live error percentage, configurability, and test failure thresholds, clarifying new UI and backend behavior. --- README.md | 24 ++++++++------- frontend/src/components/ConfigEditor.tsx | 24 +++++++++++++++ frontend/src/components/ProgressDisplay.tsx | 12 ++++++++ frontend/src/components/ResultsTable.tsx | 6 ++++ frontend/src/types.ts | 3 ++ src/benchmark/core.py | 33 +++++++++++++++++++++ src/config.py | 1 + src/models.py | 2 ++ 8 files changed, 95 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 96bf338..75581e3 100644 --- a/README.md +++ b/README.md @@ -109,8 +109,9 @@ docker run --rm bitaxe-benchmark 192.168.2.26 --set-values -v 1150 -f 780 ## **Configuration** -The script includes several configurable parameters. These can be adjusted in the bitaxe_hashrate_benchmark.py file: +The script includes several configurable parameters. These can be adjusted in the UI or in the bitaxe_hashrate_benchmark.py file: +* Max Acceptable Error Percentage: 1%. * Maximum chip temperature: 66°C * Maximum VR temperature: 86°C * Maximum allowed voltage: 1400mV @@ -136,15 +137,16 @@ The benchmark results are saved to `bitaxe_benchmark_results__ + {/* Analysis */} +
+

Analysis

+
+
+ + handleChange('analysis', 'max_error_percent', e.target.value)} + disabled={disabled} + className="mt-1 w-full bg-gray-700 border border-gray-600 rounded px-2 py-1 text-white text-sm disabled:opacity-50" + /> + Default: 1% +
+
+
+ {error && (
{error}
)} diff --git a/frontend/src/components/ProgressDisplay.tsx b/frontend/src/components/ProgressDisplay.tsx index d5711b9..dde5f78 100644 --- a/frontend/src/components/ProgressDisplay.tsx +++ b/frontend/src/components/ProgressDisplay.tsx @@ -19,6 +19,11 @@ export function ProgressDisplay({ progress, voltageStep }: ProgressDisplayProps) const { sample, core_voltage, frequency, sample_number, total_samples, progress_percent, running_stddev } = progress; + // Try to get error percent from sample if present (for live display) + // If not present, display "--" + // If you have a better source for live error %, replace here + const errorPercent = (progress as any).error_percent; + return (

Live Progress

@@ -119,6 +124,13 @@ export function ProgressDisplay({ progress, voltageStep }: ProgressDisplayProps) color="text-gray-300" /> )} + {/* Error % in lower right */} +
); diff --git a/frontend/src/components/ResultsTable.tsx b/frontend/src/components/ResultsTable.tsx index 3ed7e6f..ab81bf6 100644 --- a/frontend/src/components/ResultsTable.tsx +++ b/frontend/src/components/ResultsTable.tsx @@ -122,6 +122,7 @@ export function ResultsTable({ Temp Power Efficiency + Error % Status @@ -185,6 +186,11 @@ export function ResultsTable({ J/TH + + {typeof result.error_percent === 'number' && !isNaN(result.error_percent) + ? result.error_percent.toFixed(2) + : '--'} + {result.error_reason ? ( diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d5dae9f..fe511a0 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -45,6 +45,7 @@ export interface SampleProgress { frequency: number; sample: SampleData; running_stddev: number; + error_percent?: number | null; timestamp: string; } @@ -60,6 +61,7 @@ export interface IterationResult { efficiency_jth: number; hashrate_within_tolerance: boolean; error_reason: string | null; + error_percent?: number | null; } export interface IterationComplete { @@ -145,6 +147,7 @@ export interface AnalysisConfig { trim_outliers: number; warmup_samples: number; min_samples: number; + max_error_percent?: number; // Maximum acceptable error percentage (default 1%) } export interface BenchmarkConfig { diff --git a/src/benchmark/core.py b/src/benchmark/core.py index 911b048..9b6bd28 100644 --- a/src/benchmark/core.py +++ b/src/benchmark/core.py @@ -282,6 +282,28 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="SYSTEM_INFO_FAILURE", + error_percent=None, + ) + + # --- Error Percentage Check --- + error_percentage = info.get("errorPercentage", 100.0) # Default to 100% if not provided + max_error = getattr(self.config.analysis, "max_error_percent", 1.0) + if error_percentage is not None and error_percentage > max_error: + self._log( + "error", + f"Error percentage {error_percentage:.2f}% exceeds threshold ({max_error:.2f}%)" + ) + return IterationResult( + core_voltage=voltage, + frequency=frequency, + average_hashrate=0, + hashrate_stddev=0, + average_temperature=info.get("temp", 0), + average_power=info.get("power", 0), + efficiency_jth=float("inf"), + hashrate_within_tolerance=False, + error_reason="ERROR_PERCENTAGE_EXCEEDED", + error_percent=error_percentage, ) temp = info.get("temp") @@ -303,6 +325,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="TEMPERATURE_DATA_FAILURE" if temp is None else "TEMPERATURE_BELOW_5", + error_percent=error_percentage, ) if temp >= self.config.safety.max_temp: @@ -316,6 +339,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="CHIP_TEMP_EXCEEDED", + error_percent=error_percentage, ) if vr_temp is not None and vr_temp >= self.config.safety.max_vr_temp: @@ -330,6 +354,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="VR_TEMP_EXCEEDED", + error_percent=error_percentage, ) if input_voltage < self.config.safety.min_input_voltage: @@ -343,6 +368,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="INPUT_VOLTAGE_BELOW_MIN", + error_percent=error_percentage, ) if input_voltage > self.config.safety.max_input_voltage: @@ -356,6 +382,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="INPUT_VOLTAGE_ABOVE_MAX", + error_percent=error_percentage, ) if hash_rate is None or power is None: @@ -369,6 +396,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="HASHRATE_POWER_DATA_FAILURE", + error_percent=error_percentage, ) if power > self.config.safety.max_power: @@ -382,6 +410,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="POWER_CONSUMPTION_EXCEEDED", + error_percent=error_percentage, ) # Record sample @@ -413,6 +442,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) fan_speed=fan_speed, ), running_stddev=self._running_stddev(sample_num + 1, s1, s2), + error_percent=error_percentage, ) ) @@ -432,6 +462,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="NO_DATA_COLLECTED", + error_percent=error_percentage, ) # Trim outliers @@ -469,6 +500,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=float("inf"), hashrate_within_tolerance=False, error_reason="ZERO_HASHRATE", + error_percent=error_percentage, ) hashrate_within_tolerance = average_hashrate >= expected_hashrate * self.config.analysis.hashrate_tolerance @@ -485,6 +517,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int) efficiency_jth=efficiency_jth, hashrate_within_tolerance=hashrate_within_tolerance, error_reason=None, + error_percent=error_percentage, ) async def _check_pause(self) -> bool: diff --git a/src/config.py b/src/config.py index b0168fc..8d95e73 100644 --- a/src/config.py +++ b/src/config.py @@ -43,6 +43,7 @@ class AnalysisConfig(BaseModel): trim_outliers: int = Field(default=3, description="Number of outliers to trim from each end") warmup_samples: int = Field(default=6, description="Number of warmup samples to exclude from temp averaging") min_samples: int = Field(default=7, description="Minimum samples required for valid benchmark") + max_error_percent: float = Field(default=1.0, description="Maximum acceptable error percentage before marking iteration as bad") class BenchmarkConfig(BaseModel): diff --git a/src/models.py b/src/models.py index e868b39..0ecc85b 100644 --- a/src/models.py +++ b/src/models.py @@ -60,6 +60,7 @@ class SampleProgress(BaseModel): frequency: int = Field(description="Current frequency in MHz") sample: SampleData = Field(description="Sample measurement data") running_stddev: float = Field(description="Running standard deviation of hashrate") + error_percent: float = Field(default=None, description="Current error percentage reported by device (if available)") timestamp: datetime = Field(default_factory=datetime.now) @@ -77,6 +78,7 @@ class IterationResult(BaseModel): efficiency_jth: float = Field(description="Efficiency in J/TH") hashrate_within_tolerance: bool = Field(description="Whether hashrate met expected threshold") error_reason: Optional[str] = Field(default=None, description="Error if iteration failed") + error_percent: Optional[float] = Field(default=None, description="Error percentage reported by device (if available)") class IterationComplete(BaseModel):