Skip to content

cpu-miner: make the CPU backend a first-class citizen for API/tooling#56

Open
Schnitzel wants to merge 1 commit into
256foundation:mainfrom
Schnitzel:cpu-miner-observable-telemetry
Open

cpu-miner: make the CPU backend a first-class citizen for API/tooling#56
Schnitzel wants to merge 1 commit into
256foundation:mainfrom
Schnitzel:cpu-miner-observable-telemetry

Conversation

@Schnitzel
Copy link
Copy Markdown
Member

Summary

Three small, related changes that take the CPU mining backend from "runs and hashes" to "shows up correctly in the public API," so it's actually usable for exercising mujina end-to-end without ASIC hardware — developing against the REST API, testing UIs, prototyping integrations like exporters or message-bus bridges, etc.

  • board/cpu.rs — keep the board's telemetry sender alive and publish per-thread telemetry from it.
    Previously the watch sender was bound to _telemetry_tx and dropped immediately on construction. BoardRegistry::boards() evicts any registration whose senders have all gone away, so the CPU board silently disappeared from /api/v0/boards (and from MinerTelemetry.boards) as soon as the registry was first queried. The board now snapshots each thread's status handle before handing the threads to the scheduler, then runs a small task that owns the sender and refreshes BoardTelemetry.threads from those handles every two seconds. Aborting the task on shutdown drops the sender, letting the registry clean up the board normally.

  • cpu_miner/thread.rs — expose status_handle() so the board layer can read each thread's live HashThreadStatus (which the hasher already populates every five seconds with measured hashrate, share counts, etc.) without taking ownership of the thread itself. Ownership still goes to the scheduler.

  • scheduler.rs — in measured_hashrate() and operational_hashrate(), fall back to entry.thread.status().hashrate when the share-based estimator has no settled value. The CPU backend measures hashrate directly from cycle counts, but at typical CPU rates against any reasonable source target it would essentially never produce shares fast enough for the estimator to settle, so the API field stayed at zero forever. ASIC backends that don't self-report keep falling through to the existing capabilities().hashrate_estimate constant exactly as before — this is purely an additional fallback, ordered after the truthful estimator and before the static guess.

Why

The CPU backend exists in the tree for exactly the kind of work where you don't want to plug in (or own) real hashing hardware: developing against the API, testing the daemon end-to-end, and building tools that consume mujina's output. Today, even though the threads do hash, an API consumer sees `{"hashrate": 0, "boards": []}` indefinitely — which is indistinguishable from "nothing is running." That makes the CPU backend a poor base for any of those use cases. These changes close the gap so the API reflects what the backend is actually doing.

Net effect

  • On a CPU-only host: `/api/v0/miner` reports a non-zero aggregate `hashrate` within ~5s of startup, `boards[0]` shows up with per-thread `hashrate` and `is_active` populated.
  • On ASIC hardware: behaviour is unchanged for boards whose threads don't write `HashThreadStatus.hashrate`. The new fallback in `measured_hashrate()` / `operational_hashrate()` only kicks in when both the share-based estimator is unsettled and the thread self-reports — neither condition holds for current ASIC backends.

Test plan

  • `cargo test -p mujina-miner --lib` — existing scheduler and registry tests still pass.
  • Cross-built per Make non-glibc Linux cross-compilation work #55 and deployed to an Antminer S19 control board (aarch64 musl):
    • Start: `MUJINA_CPUMINER_THREADS=2 MUJINA_CPUMINER_DUTY=50 MUJINA_USB_DISABLE=1 MUJINA_API_LISTEN=0.0.0.0:7785 ./mujina-minerd`
    • `curl /api/v0/miner` after ~12s:
      ```json
      {"uptime_secs":10,"hashrate":174652,"shares_submitted":0,"paused":false,
      "boards":[{"name":"cpu-2x50%","model":"CPU Miner","serial":"cpu-2x50%",
      "fans":[],"temperatures":[],"powers":[],
      "threads":[
      {"name":"CPU Core 0","hashrate":89974,"is_active":true},
      {"name":"CPU Core 1","hashrate":89964,"is_active":true}
      ]}],
      "sources":[{"name":"dummy","difficulty":2328}]}
      ```
    • `curl /api/v0/boards` — previously `[]`, now lists the CPU board with the same per-thread detail.

Notes

Three small, related changes that take the CPU mining backend from
"runs and hashes" to "shows up correctly in the public API," so it's
actually usable for exercising mujina end-to-end without ASIC hardware
(developing against the REST API, testing UIs, prototyping integrations
like exporters or message-bus bridges, etc.).

* board/cpu.rs: keep the board's telemetry sender alive, and publish
  per-thread telemetry from it.
  Previously the watch sender was bound to `_telemetry_tx` and dropped
  immediately on construction. `BoardRegistry::boards()` evicts any
  registration whose senders have all gone away, so the CPU board
  silently disappeared from `/api/v0/boards` (and from
  `MinerTelemetry.boards`) as soon as it was first queried. The board
  now snapshots each thread's status handle before handing the threads
  to the scheduler, then runs a small task that owns the sender and
  refreshes `BoardTelemetry.threads` from those handles every two
  seconds. Aborting the task on shutdown drops the sender, letting the
  registry clean up the board normally.

* cpu_miner/thread.rs: expose `status_handle()` so the board layer can
  read each thread's live `HashThreadStatus` (which the hasher already
  populates every five seconds with measured hashrate, share counts,
  etc.) without taking ownership of the thread itself. Ownership still
  goes to the scheduler.

* scheduler.rs: in `measured_hashrate()` and `operational_hashrate()`,
  fall back to `entry.thread.status().hashrate` when the share-based
  estimator has no settled value. The CPU backend measures hashrate
  directly from cycle counts, but at typical CPU rates against any
  reasonable source target it would essentially never produce shares
  fast enough for the estimator to settle, so the API field stayed at
  zero forever. ASIC backends that don't self-report keep falling
  through to the existing `capabilities().hashrate_estimate` constant
  exactly as before -- this is purely an additional fallback, ordered
  after the truthful estimator and before the static guess.

Net effect on a CPU-only host: `/api/v0/miner` reports an aggregate
hashrate within ~5s of startup, `boards[0]` shows up with per-thread
`hashrate` and `is_active` populated. ASIC behaviour is unchanged for
boards whose threads don't write `HashThreadStatus.hashrate`.

Test plan
- `cargo test -p mujina-miner --lib` -- existing scheduler and
  registry tests still pass.
- `MUJINA_CPUMINER_THREADS=2 MUJINA_CPUMINER_DUTY=50 MUJINA_USB_DISABLE=1 \
   ./mujina-minerd` on an Antminer S19 control board (aarch64 musl,
   built per 256foundation#55), then:
  - `curl /api/v0/miner` returns non-zero `hashrate` after the first
    5-second hasher cycle, lists the CPU board with both threads,
    each showing `hashrate ~ 90000` and `is_active: true`.
  - `curl /api/v0/boards` shows the CPU board (previously empty
    array).
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make the CPU mining backend show up correctly in Mujina’s public API so CPU-only setups can be used for end-to-end API/UI/tooling workflows without ASIC hardware.

Changes:

  • Keeps the CPU board registered in API board telemetry by retaining its watch sender in a background publisher task.
  • Exposes a live CPU thread status handle so board-level telemetry can read per-thread state.
  • Adds scheduler fallback logic to use self-reported thread hashrate when share-based estimation has not settled.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
mujina-miner/src/scheduler.rs Adds self-reported hashrate fallback for aggregate and operational scheduler telemetry.
mujina-miner/src/cpu_miner/thread.rs Exposes a cloneable live status handle from CPU hash threads.
mujina-miner/src/board/cpu.rs Publishes CPU board/thread telemetry and keeps the board registration alive.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 97 to +101
Ok(BackplaneConnector {
info,
threads,
telemetry_rx,
shutdown: None,
shutdown: Some(Box::pin(async move { publisher.abort() })),
/// Lets the board layer publish per-thread telemetry without taking
/// ownership of the thread itself (which gets handed to the
/// scheduler).
pub fn status_handle(&self) -> Arc<RwLock<HashThreadStatus>> {
Comment on lines +237 to +241
let reported = entry.thread.status().hashrate;
if reported.is_zero() {
entry.thread.capabilities().hashrate_estimate
} else {
reported
Comment on lines +213 to +217
.map(|entry| {
let estimated = entry.hashrate.hashrate();
if estimated.is_zero() {
entry.thread.status().hashrate
} else {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants