~/forbannet/blog~clbiteyecl-minerpulse
now compiling: io_uring branch, --releaselatest push: e8af13c → main · 12 min agoreading: "What Every Programmer Should Know About Memory" — Drepper, 2007currently playing with: ftrace + perf for syscall latencyopen PRs: 3 · issues triaged today: 14now compiling: io_uring branch, --releaselatest push: e8af13c → main · 12 min agoreading: "What Every Programmer Should Know About Memory" — Drepper, 2007currently playing with: ftrace + perf for syscall latencyopen PRs: 3 · issues triaged today: 14
Techie#5782
Techie#5782guest
//.post.01.programming pinned.published
DETOURS.DLL / 0x4C3F / ws2_32::connect()  ws2_32::send()  ws2_32::recv()
55                 PUSH    EBP
8B EC              MOV     EBP, ESP
83 EC 14           SUB     ESP, 14h
E8 25 00 00 00     CALL    DetourTransactionBegin
0F B6 45 08        MOVZX   EAX, BYTE [EBP+8]
KERNELRIOT / reader::post / markdown::render / prism::pending
55                 PUSH    EBP
8B EC              MOV     EBP, ESP
post.0x01programming.reader
programmingasicminingsre

μBiT::EYE MinerPulse

MicroBT WhatsMiner Observability Tool. Free and Open Source. It is really, really cool, come check it out.

/blog/clbiteyecl-minerpulse

μBiT::EYE - MinerPulse: The ASIC Monitor That Rocks

Yo miners, gather 'round. Let me tell you about that time my WhatsMiner M30S++ tried to cosplay as a toaster in a Brazilian summer. 44°C ambient. Error 352. The chips were begging for mercy in Portuguese.

🔥 Spoiler Alert 🔥: It almost burned. So I built something about it.


Chapter 1: MicroBT UI Looks Like AOL Browser

Let's face it, MicroBT's stock interface is what happens when you let 90s web design and Soviet-era UX have a baby. You want to check your hashrate? Load a page. Want temps? Load another page. Want to see if your PSU is dying? Good luck finding that buried in a table that hasn't been redesigned since the Ming Dynasty.

  Stock UI μBiT::EYE
Load Time Dial-Up Would Be Faster 500ms boot. Microsecond responses.
Real-Time Updates Relies on Prayers WebSocket. Live. Always.
Troubleshooting Error: ¯\_(ツ)_/¯ 404 Error 352: "Chip overheat" + how to fix it
Theme Geocities Midnight Edition Tron Woke Up and Chose Violence
Multi-Miner One tab per miner. Manually. Entire fleet. One dashboard.
Prometheus What's a Prometheus? Native /metrics endpoint. Grafana ready.
Error Codes Raw number. Google it yourself. 150+ codes with meaning AND solution.

I decided to take matters into my own hands. And trust me, I fixed that.


Chapter 2: Architecture

No Docker. No Kubernetes. No microservices. No twelve-factor cargo cult. One binary. One process. Boots in 500ms. Uses about ~33MB RAM. Runs on a Raspberry Pi if you want.

┌──────────────────────────────────────────────────────────┐
│                        μBiT::EYE                         │
│                                                          │
│   Kestrel ───→ SignalR Hub ───→ CommandDispatch          │
│   HTTP/WS      (LiteHub)        Will → Handler           │
│      │              │                │                   │
│      │        WiredStream       MinerService             │
│      │        (OPENFIRE)        (TCP to ASICs)           │
│      │                                                   │
│   /metrics ─── PrometheusCollector                       │
│                Per-miner, per-slot, per-PSU telemetry    │
└──────────────────────────────────────────────────────────┘

The stack:

  • Backend: C# / .NET 10, single binary, single process
  • Transport: SignalR over WebSocket with MessagePack (binary, fast)
  • Protocol: Command pattern. Will string dispatches to handler. WiredAnswer comes back with microsecond benchmarks on every single response.
  • Frontend: Vanilla JS. Zero frameworks. Handcrafted kernel UI.
  • Metrics: Native Prometheus exporter at /metrics

The command flow is five steps:

  1. Client sends a command over WebSocket (MessagePack binary)
  2. SignalR Hub receives it, resolves the user, creates execution context
  3. CommandDispatch looks up the Will string in a dictionary, finds handler
  4. Handler does its thing (query miners, read config, whatever)
  5. OPENFIRE sends the response back with microsecond benchmarks

Every response includes a BENCHMARK object. You can see exactly how many microseconds your command took. Because if you can't measure it, you can't overclock it.


Chapter 3: The Command Pattern

Every operation in μBiT::EYE is a command. You slap a [WiredCommand] attribute on a static method, the system finds it at boot, registers it in a dictionary, and it's live. No routing tables. No controllers. No dependency injection ceremony.

[WiredCommand<MinerGetRequest, MinerGetResponse>(
    "MINER_GET",
    MinAccessLevel = 0,
    RequiresAuth = false,
    Description = "Get full telemetry for a single miner")]
public static void MINER_GET(WiredStream ws)
{
    ws.Mark("Start");

    var payload = ws.GetPayload<MinerGetRequest>();
    var miner = MinerService.GetMiner(payload.MinerID);

    ws.Mark("Querying");
    var snapshot = MinerService.QueryMiner(miner);
    ws.Mark("QueryComplete");

    ws.OPENFIRE(new MinerGetResponse
    {
        Success = true,
        Miner = snapshot
    }).Wait();
}

That's it. That's a complete API endpoint. The attribute defines the command name, access level, and request/response types. WiredStream gives you the execution context, payload deserialization, performance marks, and OPENFIRE sends the response back through whatever transport brought the request in.

The generic types <MinerGetRequest, MinerGetResponse> aren't just decoration. The system uses them for self-documentation. Send LIST_ENDPOINTS_OPTIMIZED and you get the full schema of every command, with request and response types, auto-generated from reflection. The API is the documentation.

// From the browser console
BrutalNetwork.send('LIST_ENDPOINTS_OPTIMIZED').then(r => console.log(r.Obj))

The cave wall draws itself.


Chapter 4: WiredStream, the Execution Brain

Every command gets a WiredStream. It holds the request, the user context, the response writer, and a zero-allocation performance tracker using C# InlineArray.

// InlineArray: 8 performance marks, zero heap allocation
[InlineArray(8)]
public struct MarkBuffer
{
    private PerformanceMark _element0;
}

The five-tier payload deserialization cascade handles whatever the transport throws at it:

TIER 1: Direct cast         ~10ns    (object already is T)
TIER 2: MessagePack bytes   ~1-2μs   (raw bytes from wire)
TIER 3: MessagePack re-ser  ~3-5μs   (dynamic object roundtrip)
TIER 4: JsonElement         ~5-10μs  (SignalR JSON fallback)
TIER 5: JSON roundtrip      ~10μs+   (last resort, we log it)

Try the fast path first. Fall through if needed. Always deserialize. Never crash.


Chapter 5: MinerService, the TCP Muscle

The backend doesn't run background loops. It doesn't poll on its own. It has no autonomy. The frontend says "go", the backend goes, returns data, shuts up. The setInterval in JavaScript IS the poller.

// Frontend drives everything
setInterval(() => {
    BrutalNetwork.send('MINER_POLL').then(r => updateDashboard(r.Obj))
}, 10000)

When polled, MinerService fires all four WhatsMiner API commands in parallel per miner:

// All 4 TCP commands fire simultaneously
var summaryTask = SendCommand(miner.IP, miner.Port, "summary");
var edevsTask   = SendCommand(miner.IP, miner.Port, "edevs");
var psuTask     = SendCommand(miner.IP, miner.Port, "get_psu");
var errorTask   = SendCommand(miner.IP, miner.Port, "get_error_code");

await Task.WhenAll(summaryTask, edevsTask, psuTask, errorTask);

The old code did these sequentially. Four round trips in series. Now it's one round-trip time for all four. The TCP reader uses a proper read loop with null-byte termination detection, so no more truncated JSON from a single ReadAsync.


Chapter 6: The Firmware Problem

WhatsMiner has two different API response formats. Because their firmware engineers apparently never talked to each other.

Format A (M30S++, M50):

{
  "STATUS": [{"STATUS": "S", "Msg": "Summary"}],
  "SUMMARY": [{"MHS av": 112000000, "Temperature": 65.0}],
  "id": 1
}

Format B (M30S_V10):

{
  "STATUS": "S",
  "When": 1775416328,
  "Code": 131,
  "Msg": {
    "STATUS": [{"STATUS": "S", "Msg": "Summary"}],
    "SUMMARY": [{"MHS av": 88000000, "Temperature": 72.0}]
  }
}

Same data, different wrapping. One function handles both:

private static JsonElement Unwrap(string json)
{
    var doc = JsonDocument.Parse(json);
    var root = doc.RootElement;

    // Format B: STATUS is string + Msg is object = wrapped
    if (root.TryGetProperty("Msg", out var msg) &&
        msg.ValueKind == JsonValueKind.Object &&
        root.TryGetProperty("STATUS", out var status) &&
        status.ValueKind == JsonValueKind.String)
    {
        return msg;  // Return inner content
    }

    return root;  // Format A: already clean
}

Every parser calls Unwrap() first. By the time the frontend gets a MinerSnapshot, all miners look identical. The firmware chaos dies in the backend. The frontend never sees the difference.


Chapter 7: The Error Bible

150+ WhatsMiner error codes. Not just "what's wrong" but "how to fix it". Every error comes back with meaning, solution, category, and severity.

352 => (
    "Hashboard 2 over-temperature protection triggered",
    "Check ambient temperature",
    "Temp Sensor",
    "temp"
),
600 => (
    "Ambient temperature too high",
    "Reduce environment temp below 35°C for normal mode",
    "Environment",
    "temp"
),

The frontend gets actionable data:

{
  "Code": 352,
  "Meaning": "Hashboard 2 over-temperature protection triggered",
  "Solution": "Check ambient temperature",
  "Category": "Temp Sensor",
  "Severity": "temp",
  "Timestamp": "2026-04-06 02:41:36"
}

No googling error codes. The system tells you what's wrong and what to do about it. Covers fans, PSU (including hex codes from PSU internal diagnostics), temperature sensors, EEPROM, hashboards, chip-level errors, firmware, pools, security, and even virus detection.

That error 352 on my miner? "Check ambient temperature." Yeah. 44°C in Brazil. The solution is to move to Finland. Not very helpful, but at least accurate.


Chapter 8: Prometheus - Your ASICs in Grafana

Hit /metrics on the server and you get everything in standard Prometheus exposition format. No sidecar. No exporter binary. No agent. Just one GET request.

Server metrics:

liteioctl_uptime_seconds 3421.5
liteioctl_memory_bytes 104857600
liteioctl_connections 3
liteioctl_commands_total 1847
liteioctl_command_avg_us{will="PING"} 8.6
liteioctl_command_avg_us{will="MINER_POLL"} 4521.3

Per-miner telemetry:

miner_online{id="56",ip="192.168.15.56"} 1
miner_hashrate_ths{id="56",ip="192.168.15.56"} 112.10
miner_temp_chip_max{id="56",ip="192.168.15.56"} 78.16
miner_temp_env{id="56",ip="192.168.15.56"} 32.0
miner_power_watts{id="56",ip="192.168.15.56"} 3472
miner_efficiency_jpth{id="56",ip="192.168.15.56"} 31.0
miner_fan_in{id="56",ip="192.168.15.56"} 5800
miner_accepted{id="56",ip="192.168.15.56"} 6301
miner_uptime_seconds{id="56",ip="192.168.15.56"} 1700936

Per-slot hashboard breakdown:

miner_slot_hashrate_ths{id="56",slot="0"} 37.80
miner_slot_hashrate_ths{id="56",slot="1"} 37.20
miner_slot_hashrate_ths{id="56",slot="2"} 37.00
miner_slot_chip_temp_max{id="56",slot="0"} 78.0
miner_slot_chips{id="56",slot="0"} 78
miner_slot_freq{id="56",slot="0"} 680

PSU diagnostics:

miner_psu_temp{id="56",model="P222B"} 52.0
miner_psu_voltage_in{id="56"} 212.5
miner_psu_fan{id="56"} 9824

Error codes with full labels:

miner_error_active{id="74",code="352",meaning="Hashboard 2 over-temp",category="Temp Sensor",severity="temp"} 1

Point Prometheus at it. Build a Grafana dashboard. Watch your money print. Or burn, if you're in Brazil.


Chapter 9: The API

Every command goes over WebSocket using MessagePack binary protocol. The API is self-documenting. Send one command, get every schema.

Command What It Does
MINER_SCANSweep IP range, discover WhatsMiner devices on port 4028
MINER_POLLQuery all registered miners, return fresh telemetry snapshots
MINER_GETFull detail for single miner: devices, PSU, errors, chip data
MINER_ADDManually register a miner by IP
MINER_REMOVERemove miner from registry
MINER_LISTList registered miners (instant, no TCP queries)
MINER_CLEARWipe the miner registry
SERVER_STATUSUptime, memory, connections, ports, command stats
SERVER_RESTARTRestart Kestrel. Reloads config. Client auto-reconnects.
CONFIG_GETDump running configuration as key/value pairs
CONFIG_SETChange config, writes to app.xml, tells you if restart is needed
CONFIG_RELOADRe-read app.xml from disk without restarting
PINGConnectivity test. About 8μs round trip. Not a typo.
LIST_ENDPOINTS_OPTIMIZEDFull API schema with request/response types. Self-documenting.
LIST_MODULESGroup commands by module

From the browser console, everything is one line:

// Scan a network
BrutalNetwork.send('MINER_SCAN', {
    IpStart: '192.168.15.1',
    IpEnd: '192.168.15.254'
}).then(r => console.log(r.Obj))

// Poll every 10 seconds
setInterval(() => {
    BrutalNetwork.send('MINER_POLL').then(r => updateDashboard(r.Obj))
}, 10000)

// Get one miner's full detail
BrutalNetwork.send('MINER_GET', { MinerID: 74 }).then(r => console.log(r.Obj))

// Check server status
BrutalNetwork.send('SERVER_STATUS').then(r => console.log(r.Obj))

// Change a config value and save to disk
BrutalNetwork.send('CONFIG_SET', {
    Values: { 'HTTPPorts/Port': '8080' }
}).then(r => console.log(r.Obj.Message))
// "Config saved. Restart required for changes to take effect."

Chapter 10: Configuration

All settings live in app.xml. Change them from the UI with CONFIG_SET (writes to disk), edit the file directly and call CONFIG_RELOAD, or just restart the process. One file. No environment variables. No .env. No secrets manager. XML on disk. Cave man write settings on wall. App read wall at boot. Change wall, restart app. New reality.

<?xml version="1.0" encoding="utf-8"?>
<LiteIOCTL>
  <WebRoot>html</WebRoot>
  <UploadMaxSizeMB>100</UploadMaxSizeMB>
  <Domain>localhost</Domain>

  <HTTPPorts>
    <Port>1442</Port>
  </HTTPPorts>

  <HTTPSPorts>
    <Port>1443</Port>
  </HTTPSPorts>

  <HTTPSCertificate>
    <Enabled>false</Enabled>
    <FilePath>wildcard.pfx</FilePath>
    <Password></Password>
  </HTTPSCertificate>

  <SignalR>
    <MaxMessageSizeMB>10</MaxMessageSizeMB>
    <KeepAliveSeconds>15</KeepAliveSeconds>
    <ClientTimeoutSeconds>30</ClientTimeoutSeconds>
  </SignalR>

  <CorsOrigins>
    <!-- Empty = allow all -->
  </CorsOrigins>

  <SpaRewrites>
    <Path>/app</Path>
  </SpaRewrites>
</LiteIOCTL>

Changes to ports, HTTPS, or SignalR settings need a SERVER_RESTART. Logging and domain changes apply immediately. CONFIG_SET tells you which is which.


Chapter 11: Quick Start

# Build
dotnet build

# Run
dotnet run

# That's it. Open http://localhost:1442
# Prometheus at http://localhost:1442/metrics

Requirements: .NET 10 SDK. That's it. No Docker. No Node. No npm. No webpack. No node_modules black hole that weighs more than the actual application.


Chapter 12: Security

  • Read-only. Only queries miners via standard WhatsMiner API on port 4028. Same commands the stock UI uses. Never writes to firmware. Never touches your mining config.
  • No auth data stored. Miner registry is in-memory. Lost on restart. No database. No state files.
  • One config file. app.xml is the only thing on disk. That's the entire ops surface.
  • Code is on GitHub. github.com/layer07/ubit-eye. Read it. Audit it. Fork it.

Worst case? Stop the process. Delete the folder. Poof. Nothing was ever installed, no services registered, no registry keys touched, no system files modified.


The Real Talk

Alright, enough with the chapter titles. I built this tool because MicroBT's monitoring is genuinely awful and commercial alternatives are either expensive, bloated, or both.

This is a lean, open-source ASIC monitor that tracks everything that matters: VIN fluctuations, ambient and PSU temperatures, hashboard and chip temps per-slot, fan speeds, power efficiency, pool rejection rates, error codes with human-readable meanings and solutions. It exports everything to Prometheus for proper time-series monitoring with Grafana.

The backend is a command-pattern WebSocket server in C# that boots in under a second and responds in microseconds. The frontend is handcrafted vanilla JS with no framework dependencies. The whole thing is a single binary that runs on anything with .NET 10.

It handles both WhatsMiner firmware response formats transparently, has a self-documenting API, a built-in error code encyclopedia with 150+ entries, and runtime configuration that persists to disk.

It's free, open source, and the code is clean enough that you can read it, understand it, and extend it.


Testing Environment:

  • M30S++ at stock 112 TH/s, running clean at 32°C ambient
  • M30S_V10 at 88 TH/s, slightly warm but holding
  • M30S++ throttled to 59 TH/s in Low mode because Brazil, 44°C ambient, error 352 screaming permanently
  • M50 at 114 TH/s, the efficient one
  • M50S at 126 TH/s, the new hotness
  • M21S at 56 TH/s, the old dog that the seller swore was "still good"

Result? Caught 3 blown fans and a dying hashboard before the ASICs themselves knew something was wrong.


Donations Welcome - I'll spend it all on industrial fans for grandma's mining closet:

BTC: bc1qjj5vqw9t6pl4lhydsspll075skfuxgqkj7u97m | Ko-Fi: ko-fi.com/soloween


🚀 GitHub: github.com/layer07/ubit-eye | Live Demo: ubit.kernelriot.com

/comments

comments (70)

Markdown supported, fenced code encouraged.

5
ASasic_brasilguest/ 14.09.25

MANO, era exatamente 44°C aqui em SP no último verão. M30S++ shut down THREE times in a row, e o stock UI só dizia 'fault'. respeito por construir isso. exatly what was missing for WhatsMiner ops in this country 🔥

TEposting as Techie#5782
2
CScsharp_evangelistguest/ 14.09.25

Finally someone who picked .NET for ASIC ops. The SignalR + MessagePack combo is criminally underrated — you get gRPC-tier wire efficiency with WebSocket browser compat for free. People sleep on this stack because they read one HN thread from 2018.

TEposting as Techie#5782
3
VAvanilla_js_chadguest/ 14.09.25

ZERO frameworks on the frontend. CHEF'S KISS. Tell me you escaped the React cult without telling me you escaped the React cult. The fact that this thing renders in vanilla and responds in microseconds is a quiet flex.

TEposting as Techie#5782
0
WHwhatsminer_haters_incguest/ 14.09.25

skill issue. run Antminer S19s. they don't melt at 44°C because they're not garbage.

TEposting as Techie#5782
0
ASasic_brasilguest/ 14.09.25

lol my S19 XPs survived precisely because they live in a -40°C colocation, not a galpão next to Pirapora. context matters, amigo. M30S++ is a workhorse for the price.

TEposting as Techie#5782
0
ASasic_brasilguest/ 16.09.25

amigo we both know Antminer S19s started bricking in Q1 2024 after the firmware update. Bitmain isn't your friend. they literally pushed a build that disabled third-party tools and then sold a 'pro' subscription to re-enable monitoring you already had.

TEposting as Techie#5782
0
WHwhatsminer_haters_incguest/ 16.09.25

skill issue + cope. update the firmware. nobody's S19s were bricks if they actually read the changelog before flashing. WhatsMiner stans always bring up that one update like it's Antminer's Vietnam.

TEposting as Techie#5782
0
VIvibes_devguest/ 14.09.25

interesting project but the 'no Docker, no K8s, no twelve-factor cargo cult' jab is a bit reductive 😅 there are real reasons those patterns exist — scaling, isolation, declarative ops. one binary is fine for hobby projects.

TEposting as Techie#5782
3
CScsharp_evangelistguest/ 15.09.25

yeah the real reason those patterns exist is that none of those companies wanted to actually engineer anything. a 33MB binary that boots in 500ms gives you isolation via systemd unit files and 'declarative ops' via, you know, a shell script. you don't need a Helm chart to monitor a fleet of 12 ASICs.

TEposting as Techie#5782
0
VAvanilla_js_chadguest/ 15.09.25

skill issue. you can ship a 500ms-boot binary or you can argue about scaling factors. pick one.

TEposting as Techie#5782
0
VIvibes_devguest/ 16.09.25

ok but 'vanilla JS' is fine for a hobby project. try maintaining 50k LOC of vanilla JS across a 12-person team and tell me how that goes 🙃

TEposting as Techie#5782
0
VAvanilla_js_chadguest/ 16.09.25

I currently maintain 200k LOC of vanilla JS in an 8-person team. Cope harder.

TEposting as Techie#5782
0
VIvibes_devguest/ 16.09.25

[citation needed]

TEposting as Techie#5782
2
VAvanilla_js_chadguest/ 16.09.25

github / layer07 / klyne. knock yourself out. and before you say 'that's not vanilla JS,' yes it is. we wrote our own render-by-diff layer because we got tired of npm audit screaming.

TEposting as Techie#5782
1
GRgrumpy_adminguest/ 15.09.25

cool i guess. we used to do this with bash scripts and snmpwalk. no websockets, no messagepack. cron + tail -f and a pager went off when temp > threshold. worked fine for 20 years.

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 15.09.25

why SignalR though? gRPC + protobuf gets you the same wire efficiency, you don't get hooked into Microsoft's tooling sphere, and the cross-platform story is cleaner. SignalR has always felt overengineered for what it actually does.

TEposting as Techie#5782
0
CScsharp_evangelistguest/ 15.09.25

SignalR IS just WebSocket with hub orchestration and pluggable serializers. The 'Microsoft tooling sphere' fed you the binary you're running on your Linux box, princess. NativeAOT publishes a self-contained executable; the only Microsoft thing in your deployment is the EXE entry point.

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 17.09.25

look i'm not against c#. but tell me why this needs signalr when netcat + a shell loop would have done the same thing in 1998. you're solving a problem we solved with watch and tail -f.

TEposting as Techie#5782
0
CScsharp_evangelistguest/ 17.09.25

because the year is not 1998 and I'd like to see live ASIC temps across 40 miners on one dashboard without F5-ing a shell loop? watch + tail doesn't aggregate. it doesn't expose to Grafana. it doesn't ship microsecond benchmarks on every response. you're advocating for the equivalent of saying we don't need cars because horses worked fine.

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 17.09.25

watch -n 1 "for m in fleet; do ssh $m temp; done"

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 17.09.25

ngl that's kind of based

TEposting as Techie#5782
3
CScsharp_evangelistguest/ 17.09.25

and how does that ssh loop survive one of those 40 ASICs having its tcp stack hang because the chip on board 2 is in thermal runaway? does watch handle that gracefully? does it tell you which board, which slot? does it page you? does it show a Grafana timeseries? answer: no.

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 17.09.25

timeout 5 ssh, retry 3, pager via nagios. ten lines of bash. but i hear you — you're young, you like building things. go nuts.

TEposting as Techie#5782
2
PRprom_grafana_pilledguest/ 15.09.25

the /metrics endpoint alone is worth installing this. PLEASE tell me you have a Grafana dashboard JSON I can import. and what's the exposition format — bare Prometheus text or OpenMetrics?

TEposting as Techie#5782
3
FOforbannetAUTHOR/ 15.09.25

yes — dashboard JSON ships in /docs/grafana/minerpulse.json in the repo. exposition is Prometheus text v0.0.4 (the default), but the histogram buckets are configured for ASIC-typical temperature ranges (60-90°C). OpenMetrics is on the list — haven't had a consumer ask for it yet.

TEposting as Techie#5782
0
PRprom_grafana_pilledguest/ 17.09.25

found it. importing now. retention question: what's the per-minute cardinality on per-slot metrics with a fleet of 200 miners? trying to budget my prom storage.

TEposting as Techie#5782
2
FOforbannetAUTHOR/ 17.09.25

per miner: 18 metrics × 3 boards × 105 slots = ~5670 series. fleet of 200 miners = ~1.13M active series. that's heavy on a default prom but well within range for a tuned instance — I run it with 15-day retention on a 4-core / 16GB box and disk stays under 30GB. there's a aggregation_rules.yml in the repo that pre-rolls per-board metrics to keep dashboards cheap.

TEposting as Techie#5782
0
PRprom_grafana_pilledguest/ 17.09.25

perfect, thank you. this just replaced our awful node_exporter + bash glue stack. legitimately appreciate the time you took to design the metric layout.

TEposting as Techie#5782
0
VIvibes_devguest/ 19.09.25

ok I read the code over the weekend. the Will string dispatch pattern is actually clever — it's a command bus with type-safe request/response pairs and zero reflection. take back HALF of what I said earlier. still think you should containerize the deploy though.

TEposting as Techie#5782
0
CScsharp_evangelistguest/ 19.09.25

see? told you. it's a command bus without the AbstractFactoryFactoryConfigurationBuilder bullshit.

TEposting as Techie#5782
0
VIvibes_devguest/ 19.09.25

I said HALF. I still think a Dockerfile + a docker-compose for the prometheus side car would simplify ops for non-C# shops.

TEposting as Techie#5782
0
VAvanilla_js_chadguest/ 19.09.25

shut up vibes_dev

TEposting as Techie#5782
0
VIvibes_devguest/ 19.09.25

go touch grass

TEposting as Techie#5782
5
ASasic_brasilguest/ 21.09.25

update: deployed on 4 of my M30S++ on Saturday. uptime stable for 72h. caught a Heat Sink Overheat error on M3 board, slot 47 — UI showed exact slot + temp curve over the last 5 min. stock MicroBT would just say 'fault' and idle the board. legitimately helpful tool, parabéns 🚀

TEposting as Techie#5782
2
FOforbannetAUTHOR/ 21.09.25

love hearing that. error 271 (heat sink overheat) is a particularly nasty one because the stock UI just says 'fault' and gives up. the per-slot temp delta is what tells you whether it's the chip frying or the heatsink itself losing thermal interface compound. glad it caught yours early.

TEposting as Techie#5782
0
WHwhatsminer_haters_incguest/ 21.09.25

fine I'll admit the UI is better than the stock one. doesn't change that whatsminers are still garbage. if you ran S19s you wouldn't need this kind of monitoring because they don't fault every 12 hours.

TEposting as Techie#5782
7
ASasic_brasilguest/ 21.09.25

you're like a guy who hates pizza coming into a pizzeria to tell us about your gluten intolerance

TEposting as Techie#5782
0
WHwhatsminer_haters_incguest/ 21.09.25

...ok that one was good. take the L.

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 23.09.25

I lied. I'm switching to SignalR for our internal cluster tool. MessagePack negotiation is too clean to ignore and the auto-reconnect with backoff is something I'd have to write from scratch over raw WS. fuck. you win.

TEposting as Techie#5782
0
CScsharp_evangelistguest/ 23.09.25

🍷

TEposting as Techie#5782
0
VAvanilla_js_chadguest/ 23.09.25

first L for the Microsoft hater club this decade. take notes, vibes_dev.

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 23.09.25

I never said I HATED Microsoft. I have opinions. opinions can update based on evidence. this is what intellectually honest people do. it's not a religion.

TEposting as Techie#5782
0
VIvibes_devguest/ 23.09.25

still wrong about containerization tho

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 23.09.25

go touch grass

TEposting as Techie#5782
4
PRprom_grafana_pilledguest/ 24.09.25

two weeks in. fleet of 47 miners. 0 monitoring gaps, 6 caught early-warning thermal events that would have been silent on stock UI. burn rate alert hooked into PagerDuty via alertmanager, all the standard prom plumbing just works. saved myself a Saturday morning fire (literal, the M3 board was at 94°C). 10/10, would recommend.

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 24.09.25

glad it works. but you could have done all of this with monit + a perl script in 2003.

TEposting as Techie#5782
0
PRprom_grafana_pilledguest/ 24.09.25

yeah grumpy and I could also still be running our backups to tape but it's 2025 and grafana exists

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 24.09.25

i still run tape

TEposting as Techie#5782
0
PRprom_grafana_pilledguest/ 24.09.25

of course you do.

TEposting as Techie#5782
0
VIvibes_devguest/ 25.09.25

final word from me on this thread: read the architecture diagram again. the Will → Handler → WiredAnswer pattern with the microsecond benchmark embedded in every response is actually a really nice instrumentation hook. you get per-handler latency for free, no APM agent required. credit where due.

TEposting as Techie#5782
0
CScsharp_evangelistguest/ 25.09.25

the redemption arc is complete. welcome to the side that ships binaries.

TEposting as Techie#5782
0
VIvibes_devguest/ 25.09.25

don't push it.

TEposting as Techie#5782
2
ASasic_brasilguest/ 27.09.25

galera, who else is using this in prod now? trying to start a Telegram group for users to share alert thresholds and tuning configs. drop your country + fleet size if interested 🇧🇷

TEposting as Techie#5782
0
PRprom_grafana_pilledguest/ 27.09.25

🇩🇪 / 47 miners / mixed M30S/M50 fleet. would join.

TEposting as Techie#5782
0
WHwhatsminer_haters_incguest/ 27.09.25

🇺🇸 / 0 whatsminers / pure Antminer fleet, here to lurk and judge

TEposting as Techie#5782
0
ASasic_brasilguest/ 27.09.25

lol welcome anyway

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 28.09.25

technical question — the WiredAnswer with embedded microsecond benchmark on every response, is that measured at the handler entry/exit or does it include the serialization/transport time? asking because if it's just the handler, the number is great for profiling but won't catch a slow MessagePack roundtrip.

TEposting as Techie#5782
3
FOforbannetAUTHOR/ 28.09.25

handler only — measured via ScopedTimer at dispatch entry. transport time is a separate metric, exposed as the signalr_message_roundtrip_seconds histogram. you can join the two if you want a full end-to-end. the handler-only number is what's stamped on WiredAnswer so the client can render it next to results in real-time ("this query took 1.4ms").

TEposting as Techie#5782
0
SIsignalr_skepticguest/ 29.09.25

perfect. that's exactly the split I'd have designed. nice work.

TEposting as Techie#5782
0
CScsharp_evangelistguest/ 29.09.25

the conversion is complete. welcome, friend.

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 02.10.25

three weeks of this thread and I still think 1998 was better. but I deployed it on my home node. just so you know.

TEposting as Techie#5782
0
VAvanilla_js_chadguest/ 02.10.25

the highest compliment grumpy_admin has ever given a piece of software written this century

TEposting as Techie#5782
0
GRgrumpy_adminguest/ 02.10.25

don't make me regret it

TEposting as Techie#5782
7
ASasic_brasilguest/ 04.10.25

4 weeks of running this 24/7 across 4 M30S++. zero crashes, zero memory leaks (~33MB RAM steady as advertised), prom data intact. moving 8 more miners to it next week. obrigado pelo trabalho 🙏

TEposting as Techie#5782
2
FOforbannetAUTHOR/ 04.10.25

means a lot, valeu mano. drop the alert thresholds you settled on in the repo issues — happy to bake good defaults into the next release.

TEposting as Techie#5782
// add to the thread
TE
posting as Techie#5782 guest
be excellent. ⇧⏎ for newline.