From ea40fdf6e56d6dbd23b686669bcd36e72315bdbc Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Sun, 7 Sep 2025 21:34:44 +0800 Subject: [PATCH] 03: engine comparability --- README.md | 12 +- crates/engine-cpu/src/lib.rs | 35 +- crates/miner-cli/src/main.rs | 8 +- crates/miner-service/src/lib.rs | 15 +- .../grafana/engines-comparison-dashboard.json | 405 ++++++++++++++++++ docs/grafana/miner-dashboard.json | 302 +++++++++++++ .../03-prompt-engine-comparability.md | 113 +++++ .../03-response-engine-comparability.md | 89 ++++ 8 files changed, 955 insertions(+), 24 deletions(-) create mode 100644 docs/grafana/engines-comparison-dashboard.json create mode 100644 docs/grafana/miner-dashboard.json create mode 100644 docs/implementation/03-prompt-engine-comparability.md create mode 100644 docs/implementation/03-response-engine-comparability.md diff --git a/README.md b/README.md index 7ab2610..a79be70 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # External Miner Service for Quantus Network -Note: This repository is now a Cargo workspace. Build and run the CLI with: +Note: This repository is now a Cargo workspace. Build and run the CLI with (the --num-cores flag remains available as an alias for --cores): - cargo build -p miner-cli --release -- cargo run -p miner-cli -- --port 9833 [--metrics-port 9900] [--num-cores N] +- cargo run -p miner-cli -- --port 9833 [--metrics-port 9900] [--cores N] This crate provides an external mining service that can be used with a Quantus Network node. It exposes an HTTP API for managing mining jobs. @@ -25,7 +25,7 @@ The service can be configured using command-line arguments or environment variab | Argument | Environment Variable | Description | Default | |-------------------|----------------------|--------------------------------------------|---------------| | `--port ` | `MINER_PORT` | The port for the HTTP server to listen on. | `9833` | -| `--num-cores ` | `MINER_CORES` | The number of CPU cores to use for mining. | All available | +| `--cores ` (alias: `--num-cores`) | `MINER_CORES` | The number of CPU cores to use for mining. | All available | Example: @@ -34,7 +34,7 @@ Example: ../target/release/quantus-miner # Run on a custom port with 4 cores -../target/release/quantus-miner --port 8000 --num-cores 4 +../target/release/quantus-miner --port 8000 --cores 4 # Equivalent using environment variables export MINER_PORT=8000 @@ -51,10 +51,10 @@ After building the service, you can run it directly from the command line: RUST_LOG=info ../target/release/quantus-miner # Run with a specific port and 2 cores -RUST_LOG=info ../target/release/quantus-miner --port 12345 --num-cores 2 +RUST_LOG=info ../target/release/quantus-miner --port 12345 --cores 2 # Run in debug mode -RUST_LOG=info,miner=debug ../target/release/quantus-miner --num-cores 4 +RUST_LOG=info,miner=debug ../target/release/quantus-miner --cores 4 ``` diff --git a/crates/engine-cpu/src/lib.rs b/crates/engine-cpu/src/lib.rs index 3a81934..cafd30b 100644 --- a/crates/engine-cpu/src/lib.rs +++ b/crates/engine-cpu/src/lib.rs @@ -43,10 +43,19 @@ pub struct Candidate { /// `Running` is included for potential future async/streaming engines. #[derive(Clone, Debug)] pub enum EngineStatus { - Running { hash_count: u64 }, - Found(Candidate), - Exhausted { hash_count: u64 }, - Cancelled { hash_count: u64 }, + Running { + hash_count: u64, + }, + Found { + candidate: Candidate, + hash_count: u64, + }, + Exhausted { + hash_count: u64, + }, + Cancelled { + hash_count: u64, + }, } /// Abstract mining engine interface. @@ -120,7 +129,10 @@ impl MinerEngine for BaselineCpuEngine { work, distance, }; - return EngineStatus::Found(candidate); + return EngineStatus::Found { + candidate, + hash_count, + }; } // Advance or finish @@ -182,11 +194,14 @@ impl MinerEngine for FastCpuEngine { if is_valid_distance(ctx, distance) { let work = current.to_big_endian(); - return EngineStatus::Found(Candidate { - nonce: current, - work, - distance, - }); + return EngineStatus::Found { + candidate: Candidate { + nonce: current, + work, + distance, + }, + hash_count, + }; } if current == range.end { diff --git a/crates/miner-cli/src/main.rs b/crates/miner-cli/src/main.rs index dbec087..9f416a1 100644 --- a/crates/miner-cli/src/main.rs +++ b/crates/miner-cli/src/main.rs @@ -11,7 +11,7 @@ struct Args { port: u16, /// Number of CPU cores to use for mining (defaults to all logical CPUs) - #[arg(long, env = "MINER_CORES")] + #[arg(long = "cores", alias = "num-cores", env = "MINER_CORES")] num_cores: Option, /// Optional Prometheus metrics exporter port; if omitted, metrics are disabled @@ -48,7 +48,11 @@ impl From for EngineSelection { async fn main() { let args = Args::parse(); - // Initialize logger early to capture startup messages + // Initialize logger early to capture startup messages. + // If RUST_LOG is not set, default to info level for our app. + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info"); + } env_logger::init(); // Log effective configuration diff --git a/crates/miner-service/src/lib.rs b/crates/miner-service/src/lib.rs index ecbdca9..cf00500 100644 --- a/crates/miner-service/src/lib.rs +++ b/crates/miner-service/src/lib.rs @@ -479,17 +479,20 @@ fn mine_range_with_engine( }; match status { - engine_cpu::EngineStatus::Found(EngineCandidate { - nonce, - work, - distance, - }) => { + engine_cpu::EngineStatus::Found { + candidate: EngineCandidate { + nonce, + work, + distance, + }, + hash_count, + } => { final_result.result = Some(MiningJobResult { nonce, work, distance, }); - final_result.hash_count = 0; // engine may not report per-iteration counts + final_result.hash_count = hash_count; } engine_cpu::EngineStatus::Exhausted { hash_count } => { final_result.hash_count = hash_count; diff --git a/docs/grafana/engines-comparison-dashboard.json b/docs/grafana/engines-comparison-dashboard.json new file mode 100644 index 0000000..3603649 --- /dev/null +++ b/docs/grafana/engines-comparison-dashboard.json @@ -0,0 +1,405 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "type": "stat", + "title": "Engine Hash Rate Ratio (left/right)", + "description": "Ratio of total per-job hash rate between two selected engines", + "gridPos": { "h": 5, "w": 8, "x": 0, "y": 0 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum(miner_job_hash_rate{engine=~\"$engine_left\"}) / sum(miner_job_hash_rate{engine=~\"$engine_right\"})" + } + ], + "options": { + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "orientation": "auto", + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "textMode": "value" + }, + "fieldConfig": { + "defaults": { "unit": "none", "decimals": 2 }, + "overrides": [] + } + }, + { + "type": "stat", + "title": "Solutions Found (24h) by Engine (left/right)", + "description": "Completed jobs increase in the last 24h for left and right engines", + "gridPos": { "h": 5, "w": 8, "x": 8, "y": 0 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "Left", + "expr": "sum(increase(miner_jobs_by_engine_total{engine=~\"$engine_left\", status=\"completed\"}[24h]))" + }, + { + "refId": "Right", + "expr": "sum(increase(miner_jobs_by_engine_total{engine=~\"$engine_right\", status=\"completed\"}[24h]))" + } + ], + "options": { + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "orientation": "horizontal", + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "textMode": "auto" + }, + "fieldConfig": { + "defaults": { "unit": "none", "decimals": 0 }, + "overrides": [] + } + }, + { + "type": "stat", + "title": "Global Hash Rate (All Engines)", + "description": "Sum of reported job hash rates across all engines", + "gridPos": { "h": 5, "w": 8, "x": 16, "y": 0 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum(miner_job_hash_rate)" + } + ], + "options": { + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "orientation": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "textMode": "value" + }, + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + } + }, + { + "type": "timeseries", + "title": "Hash Rate by Engine", + "description": "sum(miner_job_hash_rate) grouped by engine", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 5 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (engine) (miner_job_hash_rate{engine=~\"$engine\"})", + "legendFormat": "{{engine}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "bargauge", + "title": "Jobs by Engine and Status (Total)", + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (engine, status) (miner_jobs_by_engine_total{engine=~\"$engine\"})" + } + ], + "fieldConfig": { + "defaults": { "unit": "none", "decimals": 0 }, + "overrides": [] + }, + "options": { + "displayMode": "gradient", + "orientation": "horizontal", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showUnfilled": true + } + }, + { + "type": "timeseries", + "title": "Per-Engine Total Hashes Increase (over time)", + "description": "increase(miner_job_hashes_total[$__rate_interval]) grouped by engine", + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (engine) (increase(miner_job_hashes_total{engine=~\"$engine\"}[$__rate_interval]))", + "legendFormat": "{{engine}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "timeseries", + "title": "Average Per-Thread Hash Rate by Engine", + "description": "avg(miner_thread_hash_rate) grouped by engine (EMA-smoothed)", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 21 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "avg by (engine) (miner_thread_hash_rate{engine=~\"$engine\"})", + "legendFormat": "{{engine}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "table", + "title": "Top 10 Jobs by Hash Rate (filter by engine)", + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 29 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "topk(10, miner_job_hash_rate{engine=~\"$engine\"})" + } + ], + "fieldConfig": { + "defaults": { + "custom": { "align": "auto" }, + "unit": "ops", + "decimals": 2 + }, + "overrides": [] + }, + "options": { + "showHeader": true, + "footer": { "show": false }, + "frameIndex": 0 + } + }, + { + "type": "table", + "title": "Per-Engine Total Hashes (5m increase)", + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 29 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (engine) (increase(miner_job_hashes_total{engine=~\"$engine\"}[5m]))" + } + ], + "fieldConfig": { + "defaults": { + "custom": { "align": "auto" }, + "unit": "ops", + "decimals": 0 + }, + "overrides": [] + }, + "options": { + "showHeader": true, + "footer": { "show": false }, + "frameIndex": 0 + } + }, + { + "type": "timeseries", + "title": "Per-Job Hash Rate by Engine (focus comparisons)", + "description": "Use filters above to narrow by engine(s) and job(s)", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 37 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "miner_job_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\"}", + "legendFormat": "{{engine}} | {{job_id}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "table", + "title": "Per-Thread Hash Rate (filter by engine and job)", + "description": "EMA-smoothed per-thread rates with labels for engine/job/thread", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 45 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "miner_thread_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\", thread_id=~\"$thread_id\"}" + } + ], + "fieldConfig": { + "defaults": { + "custom": { "align": "auto" }, + "unit": "ops", + "decimals": 2 + }, + "overrides": [] + }, + "options": { + "showHeader": true, + "footer": { "show": false }, + "frameIndex": 0 + } + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "dark", + "tags": ["quantus", "miner", "prometheus", "engines", "comparison"], + "templating": { + "list": [ + { + "type": "datasource", + "name": "DS_PROMETHEUS", + "hide": 0, + "query": "prometheus", + "current": {}, + "options": [] + }, + { + "type": "query", + "name": "engine", + "label": "Engine(s)", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_job_hash_rate, engine)", + "includeAll": true, + "multi": true, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_job_hash_rate, engine)", + "regex": "", + "current": { + "selected": false, + "text": ["cpu-baseline", "cpu-fast"], + "value": ["cpu-baseline", "cpu-fast"] + } + }, + { + "type": "query", + "name": "engine_left", + "label": "Left Engine", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_job_hash_rate, engine)", + "includeAll": false, + "multi": false, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_job_hash_rate, engine)", + "regex": "", + "current": { + "selected": true, + "text": "cpu-fast", + "value": "cpu-fast" + } + }, + { + "type": "query", + "name": "engine_right", + "label": "Right Engine", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_job_hash_rate, engine)", + "includeAll": false, + "multi": false, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_job_hash_rate, engine)", + "regex": "", + "current": { + "selected": true, + "text": "cpu-baseline", + "value": "cpu-baseline" + } + }, + { + "type": "query", + "name": "job_id", + "label": "Job ID", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_job_hash_rate{engine=~\"$engine\"}, job_id)", + "includeAll": true, + "multi": true, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_job_hash_rate{engine=~\"$engine\"}, job_id)", + "regex": "", + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + } + }, + { + "type": "query", + "name": "thread_id", + "label": "Thread ID", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_thread_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\"}, thread_id)", + "includeAll": true, + "multi": true, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_thread_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\"}, thread_id)", + "regex": "", + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + } + } + ] + }, + "time": { "from": "now-6h", "to": "now" }, + "timepicker": {}, + "timezone": "", + "title": "Quantus Miner - Engines Comparison", + "version": 1, + "uid": null +} diff --git a/docs/grafana/miner-dashboard.json b/docs/grafana/miner-dashboard.json new file mode 100644 index 0000000..41d4354 --- /dev/null +++ b/docs/grafana/miner-dashboard.json @@ -0,0 +1,302 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "type": "stat", + "title": "Global Hash Rate (nonces/sec)", + "description": "Sum of reported miner hash rate across all jobs and engines", + "gridPos": { "h": 5, "w": 8, "x": 0, "y": 0 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum(miner_hash_rate)" + } + ], + "options": { + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "orientation": "auto", + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "textMode": "value" + }, + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + } + }, + { + "type": "timeseries", + "title": "Global Hash Rate (nonces/sec) over time", + "gridPos": { "h": 8, "w": 16, "x": 8, "y": 0 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum(miner_hash_rate)", + "legendFormat": "global" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom", "calcs": [] }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "bargauge", + "title": "Jobs by Status (total)", + "gridPos": { "h": 8, "w": 8, "x": 0, "y": 5 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (status) (miner_jobs_total)" + } + ], + "fieldConfig": { + "defaults": { "unit": "none", "decimals": 0 }, + "overrides": [] + }, + "options": { + "displayMode": "gradient", + "orientation": "horizontal", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showUnfilled": true + } + }, + { + "type": "bargauge", + "title": "Jobs by Engine and Status (total)", + "gridPos": { "h": 8, "w": 8, "x": 0, "y": 13 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (engine, status) (miner_jobs_by_engine_total)" + } + ], + "fieldConfig": { + "defaults": { "unit": "none", "decimals": 0 }, + "overrides": [] + }, + "options": { + "displayMode": "gradient", + "orientation": "horizontal", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showUnfilled": true + } + }, + { + "type": "timeseries", + "title": "Job Hash Rate by Job (filter by engine)", + "gridPos": { "h": 8, "w": 16, "x": 8, "y": 8 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (job_id) (miner_job_hash_rate{engine=~\"$engine\"})", + "legendFormat": "{{job_id}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "timeseries", + "title": "Thread Hash Rate (filter by engine and job_id)", + "description": "EMA-smoothed per-thread rates. Use variables above to filter.", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 21 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "miner_thread_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\", thread_id=~\"$thread_id\"}", + "legendFormat": "{{engine}} | job={{job_id}} | th={{thread_id}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + }, + { + "type": "stat", + "title": "Total Hashes (5m increase)", + "gridPos": { "h": 5, "w": 8, "x": 0, "y": 29 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "increase(miner_hashes_total[5m])" + } + ], + "options": { + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "orientation": "auto", + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "textMode": "value" + }, + "fieldConfig": { + "defaults": { "unit": "ops" }, + "overrides": [] + } + }, + { + "type": "table", + "title": "Per-Job Hashes (5m increase, filter by engine)", + "gridPos": { "h": 8, "w": 16, "x": 8, "y": 29 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "sum by (engine, job_id) (increase(miner_job_hashes_total{engine=~\"$engine\"}[5m]))" + } + ], + "fieldConfig": { + "defaults": { "custom": { "align": "auto" } }, + "overrides": [] + }, + "options": { + "showHeader": true, + "footer": { "show": false }, + "frameIndex": 0 + } + }, + { + "type": "timeseries", + "title": "Job Status Gauges (running/completed/failed/cancelled)", + "description": "Job status gauges as time series (1 for current status, else 0). Filter by engine/job.", + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 37 }, + "datasource": "$DS_PROMETHEUS", + "targets": [ + { + "refId": "A", + "expr": "miner_job_status{engine=~\"$engine\", job_id=~\"$job_id\"}", + "legendFormat": "{{job_id}}:{{status}}" + } + ], + "fieldConfig": { + "defaults": { "unit": "none", "min": 0, "max": 1 }, + "overrides": [] + }, + "options": { + "legend": { "displayMode": "list", "placement": "bottom" }, + "tooltip": { "mode": "multi", "sort": "none" } + } + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "dark", + "tags": ["quantus", "miner", "prometheus"], + "templating": { + "list": [ + { + "type": "datasource", + "name": "DS_PROMETHEUS", + "hide": 0, + "query": "prometheus", + "current": {}, + "options": [] + }, + { + "type": "query", + "name": "engine", + "label": "Engine", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_job_hash_rate, engine)", + "includeAll": true, + "multi": true, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_job_hash_rate, engine)", + "regex": "", + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + } + }, + { + "type": "query", + "name": "job_id", + "label": "Job ID", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_job_hash_rate{engine=~\"$engine\"}, job_id)", + "includeAll": true, + "multi": true, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_job_hash_rate{engine=~\"$engine\"}, job_id)", + "regex": "", + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + } + }, + { + "type": "query", + "name": "thread_id", + "label": "Thread ID", + "hide": 0, + "datasource": "$DS_PROMETHEUS", + "definition": "label_values(miner_thread_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\"}, thread_id)", + "includeAll": true, + "multi": true, + "refresh": 2, + "sort": 1, + "query": "label_values(miner_thread_hash_rate{engine=~\"$engine\", job_id=~\"$job_id\"}, thread_id)", + "regex": "", + "current": { + "selected": false, + "text": ["All"], + "value": ["$__all"] + } + } + ] + }, + "time": { "from": "now-6h", "to": "now" }, + "timepicker": {}, + "timezone": "", + "title": "Quantus Miner - Overview", + "version": 1, + "uid": null +} diff --git a/docs/implementation/03-prompt-engine-comparability.md b/docs/implementation/03-prompt-engine-comparability.md new file mode 100644 index 0000000..ec577a8 --- /dev/null +++ b/docs/implementation/03-prompt-engine-comparability.md @@ -0,0 +1,113 @@ +# Prompt Summary (03): Accurate Metrics for Engine Comparisons, Logging Defaults, CLI Flag Rename, and Dashboards + +## Context + +To compare the performance of different mining engines (e.g., `cpu-baseline` vs `cpu-fast`) in Grafana using Prometheus metrics, we need precise per-job and per-thread hash counts. Currently, when a worker thread finds a solution, the engine reports only the `Candidate` without the associated `hash_count`. This causes undercounting in totals/rates, especially for the winning thread. + +Additionally, we want a better out-of-the-box operator experience and evaluation tooling: +- Default the logging level to `info` when `RUST_LOG` is not set. +- Rename the CLI flag `--num-cores` to `--cores` but keep the internal field `num_cores`, and preserve `--num-cores` as an alias for backward compatibility. +- Provide two Grafana dashboards: + - A “Miner Overview” dashboard for operators, focusing on the fastest implementation. + - An “Engines Comparison” dashboard for performance evaluations across implementations. + +We want to extend the engine’s result reporting so that the final “Found” event carries the `hash_count` accumulated by that thread. The miner-service will consume this information to update metrics correctly (global, per-job, per-thread), enabling accurate comparison between engines. + +We also want sane defaults for logging, a consistent CLI, and packaged dashboards to support evaluation and production usage. + +## Objectives + +- Extend the engine result type to carry per-thread `hash_count` when a solution is found. +- Update both CPU engines (`cpu-baseline` and `cpu-fast`) to return the `hash_count` on `Found`. +- Update the miner-service to: + - Include `hash_count` from the `Found` status in totals and rate calculations. + - Maintain engine-aware, per-job, and per-thread metrics parity. +- Preserve the node-facing HTTP API and service behavior. +- Keep engine selection and metrics toggle behavior unchanged. +- Default RUST_LOG to `info` if not set, so operators get useful logs without extra env configuration. +- Rename CLI flag to `--cores` (keep `--num-cores` as an alias) while keeping the code field as `num_cores`. +- Add two Grafana dashboards under `docs/grafana/`: + - `miner-dashboard.json` for operators running the fastest engine. + - `engines-comparison-dashboard.json` for evaluating implementations. + +## Requirements + +- Engine API: + - Change `EngineStatus::Found(Candidate)` to carry `{ candidate: Candidate, hash_count: u64 }`. + - Maintain the existing `Exhausted { hash_count }` and `Cancelled { hash_count }` patterns for consistency. +- Engines: + - `BaselineCpuEngine` and `FastCpuEngine` must return accurate `hash_count` on `Found`. +- Service: + - In `update_from_results`, when a `ThreadResult` is built from an engine’s `Found`, carry the returned `hash_count` and accumulate it in: + - Global totals: `miner_hashes_total` + - Per-job totals: `miner_job_hashes_total{engine,job_id}` + - Per-thread totals: `miner_thread_hashes_total{engine,job_id,thread_id}` + - Ensure per-thread delta-rate and EMA smoothing logic still runs using the `hash_count` returned by `Found`. +- Metrics: + - No new metrics required; existing labeled counters/gauges should be used. + - Accuracy improves because the winning thread’s final work is now captured. +- Logging: + - If `RUST_LOG` is unset, default to `info` at startup. +- CLI: + - Rename `--num-cores` to `--cores`, keeping `--num-cores` as an alias and `num_cores` as the field name. +- Dashboards: + - Add `docs/grafana/miner-dashboard.json` (operator view). + - Add `docs/grafana/engines-comparison-dashboard.json` (evaluation view). + +## Constraints and Compatibility + +- The public HTTP API (endpoints/payloads) must remain unchanged. +- The engine abstraction is internal to the repo, so the type change is acceptable and should be updated wherever consumed (service only). +- Maintain compatibility between `cpu-baseline` and `cpu-fast` engines for apples-to-apples comparison. +- Logging default must not override an explicitly set `RUST_LOG`. +- CLI change must preserve backward compatibility (`--num-cores` alias). +- Dashboards must be optional artifacts (no runtime impact). + +## Acceptance Criteria + +- `EngineStatus::Found` carries `{ candidate, hash_count }`. +- Both engines report the correct `hash_count` when returning `Found`. +- Miner-service uses this `hash_count` to update: + - `miner_hashes_total` + - `miner_job_hashes_total{engine,job_id}` + - `miner_thread_hashes_total{engine,job_id,thread_id}` + - Per-thread delta-rate → `miner_thread_hash_rate{engine,job_id,thread_id}` (EMA-smoothed). +- Build success in release mode. +- No changes to node-facing HTTP behavior. +- Grafana can now accurately compare `cpu-baseline` vs `cpu-fast` with improved fidelity for the winning thread. +- When `RUST_LOG` is unset, logs default to `info`. +- `--cores` is available (with `--num-cores` as an alias) and maps to `num_cores`. +- Two dashboards exist under `docs/grafana/` for operators and engine evaluation. + +## Deliverables + +- Engine API update: + - Enum change for `EngineStatus::Found` to include `hash_count`. +- Engine implementations: + - Baseline and fast CPU engines updated to return `hash_count` on `Found`. +- Service updates: + - `mine_range_with_engine` and `update_from_results` modified to forward and accumulate the `hash_count` from `Found`. + - Metrics update hooks remain correct and comprehensive (global, per-job, per-thread, per-thread EMA rate). +- CLI/logging updates: + - Default `RUST_LOG` to `info` when unset. + - Expose `--cores` flag (keep `--num-cores` alias) while retaining `num_cores` as the code field. +- Dashboards: + - `docs/grafana/miner-dashboard.json` (operator-focused). + - `docs/grafana/engines-comparison-dashboard.json` (engine evaluation). +- Documentation: + - Capture this prompt and a separate response summary as iteration “03” in `docs/implementation/`. + +## Out of Scope + +- Additional metrics beyond those already implemented. +- Changes to the HTTP API or external protocol. +- Performance optimizations (e.g., Montgomery arithmetic) not directly related to the `hash_count` reporting tweak. +- Further CLI renames or logging customization (beyond defaults). +- Grafana provisioning/export automation. + +## Notes + +- This tweak corrects the main remaining accuracy gap for the winning thread in metrics. +- With this change, Grafana dashboards can make fair and accurate comparisons between different engines using the `engine` label, across global/job/thread-level telemetry. +- Default logging reduces operational friction; CLI alias retains backward compatibility during the transition. +- Packaged dashboards (overview and engines comparison) provide out-of-the-box visibility for operators and evaluators. diff --git a/docs/implementation/03-response-engine-comparability.md b/docs/implementation/03-response-engine-comparability.md new file mode 100644 index 0000000..1c1ad29 --- /dev/null +++ b/docs/implementation/03-response-engine-comparability.md @@ -0,0 +1,89 @@ +# Response Summary (03): Engine `Found` hash_count Included for Accurate Metrics, Logging/CLI Updates, and Dashboards + +This document summarizes the implementation that augments the mining engine’s result reporting so that the final “Found” event carries the per-thread `hash_count`. This enables accurate global, per-job, and per-thread metrics (including rates), improving Grafana comparisons between engines (e.g., `cpu-baseline` vs `cpu-fast`). + +## Overview + +- Extended the engine result type so `Found` includes `{ candidate, hash_count }`. +- Updated both CPU engines to return the final `hash_count` on `Found`. +- Modified the service to forward and accumulate this count: + - Global totals (`miner_hashes_total`) + - Per-job totals (`miner_job_hashes_total{engine,job_id}`) + - Per-thread totals (`miner_thread_hashes_total{engine,job_id,thread_id}`) + - Per-thread hash-rate gauge via delta-rate with EMA smoothing +- No changes to the node-facing HTTP API. + +## Changes Implemented + +### 1) Engine API + +- File: `crates/engine-cpu/src/lib.rs` +- Updated `EngineStatus`: + - Before: `Found(Candidate)` + - Now: `Found { candidate: Candidate, hash_count: u64 }` +- Other variants (`Exhausted`, `Cancelled`, `Running`) retain their `hash_count` shape for consistency. + +### 2) Engines + +- Files: `crates/engine-cpu/src/lib.rs` +- `BaselineCpuEngine`: + - On discovering a solution, returns `EngineStatus::Found { candidate, hash_count }` with the accumulated count for the current range. +- `FastCpuEngine`: + - Same behavior as baseline but using the incremental path (init once with `init_worker_y0`, then `step_mul` per nonce). + +### 3) Service + +- File: `crates/miner-service/src/lib.rs` +- `mine_range_with_engine`: + - Updated to extract `hash_count` from `EngineStatus::Found` and include it in the `ThreadResult`. +- `update_from_results`: + - Accumulates `hash_count` into: + - `total_hash_count` + - Global/engine-aware per-job/per-thread counters + - Per-thread delta-rate calculation with EMA smoothing now uses the `hash_count` from `Found` as well. +- All metric updates are engine-aware and job-/thread-labeled where appropriate. + +### 4) Metrics + +- No new metrics introduced; existing labeled series are now fully accurate for winning threads: + - Global: + - `miner_jobs_total{status}` + - `miner_hashes_total` + - `miner_hash_rate` + - Engine-/job-aware: + - `miner_job_hashes_total{engine,job_id}` + - `miner_job_hash_rate{engine,job_id}` + - `miner_job_status{engine,job_id,status}` (IntGauge) + - `miner_jobs_by_engine_total{engine,status}` + - Engine-/job-/thread-aware: + - `miner_thread_hashes_total{engine,job_id,thread_id}` + - `miner_thread_hash_rate{engine,job_id,thread_id}` (EMA-smoothed) + +## Compatibility + +- The public HTTP API (endpoints and payloads) remains unchanged. +- Engine abstraction change is internal to the workspace; service updated accordingly. +- Works with both `cpu-baseline` and `cpu-fast`, enabling apples-to-apples comparisons using the `engine` label in Grafana. + +## Build and Run + +- Build (release): + - `cargo build -p miner-cli --release` +- Run (baseline, no metrics): + - `cargo run -p miner-cli -- --port 9833 --engine cpu-baseline` +- Run (fast engine, with metrics): + - `cargo run -p miner-cli -- --port 9833 --engine cpu-fast --metrics-port 9900` + - Scrape: `http://localhost:9900/metrics` + +## Verification + +- Compile-time: Verified in release mode after updating the service to consume the new `Found` shape. +- Runtime: The winning thread’s final work is now counted in totals and informs the per-thread rate EMA, reducing undercounting and improving rate fidelity. + +## Notes and Next Steps + +- With winning-thread `hash_count` included, Grafana dashboards can fairly compare engines across global/job/thread metrics. +- Next logical optimization steps: + - Add correctness tests that compare baseline vs fast engine outcomes on small ranges (golden tests). + - Add benchmark harness to quantify nonces/sec improvements. + - Implement Montgomery multiplication in `pow-core` and switch `cpu-fast` to it for further speedups. \ No newline at end of file