Add block-update latency tracking, raw share counters, and shares bar UI

Log ZMQ→mining.notify latency in ckpool (patch 0005), expose a raw
reject counter (patch 0006), and surface both in the dashboard:

- Block latency card shows avg/last ms, wasted work, and block count
- SharesBar component shows session + all-time accepted/rejected with
  a Demon Slayer flame-slash animation on new shares
- Miners card info moved to MinersTable section header
- Latency stats and share counts persist across restarts via kv store
- Removed misleading pool-wide share stats from per-worker/user pages
  (ckpool doesn't expose per-user raw counts)
This commit is contained in:
satoshi
2026-05-11 02:12:16 +03:00
parent e622f1a81a
commit 6acff6280f
14 changed files with 555 additions and 18 deletions
+33 -10
View File
@@ -49,14 +49,23 @@ type AttemptEvent struct {
RawLine string
}
// LatencyEvent is emitted when ckpool logs the "Block update latency"
// line from our patch 0005 — the milliseconds between ZMQ trigger and
// mining.notify broadcast completion.
type LatencyEvent struct {
SeenAt time.Time
LatencyMs int64
}
// Tailer follows a log file, surviving rotation/truncation, and emits
// parsed events. Create with New, then Run in a goroutine.
type Tailer struct {
Path string
Events chan BlockEvent
Attempts chan AttemptEvent
Log *slog.Logger
PollWait time.Duration // how long to sleep between EOF polls
Path string
Events chan BlockEvent
Attempts chan AttemptEvent
Latencies chan LatencyEvent
Log *slog.Logger
PollWait time.Duration // how long to sleep between EOF polls
// LoadCursor / SaveCursor, if both non-nil, persist the read
// position across process restarts. LoadCursor returns (inode,
@@ -77,11 +86,12 @@ type Tailer struct {
func New(path string, log *slog.Logger) *Tailer {
return &Tailer{
Path: path,
Events: make(chan BlockEvent, 16),
Attempts: make(chan AttemptEvent, 16),
Log: log,
PollWait: 500 * time.Millisecond,
Path: path,
Events: make(chan BlockEvent, 16),
Attempts: make(chan AttemptEvent, 16),
Latencies: make(chan LatencyEvent, 16),
Log: log,
PollWait: 500 * time.Millisecond,
}
}
@@ -94,6 +104,9 @@ var (
// "Submitting possible block solve share diff N !"
// "Possible remote block solve diff N !"
solveDiffRE = regexp.MustCompile(`(?:Possible|Submitting[^"]*possible).*block solve.*diff\s+([0-9eE.+-]+)`)
// Matches the latency line from our patch 0005:
// "Block update latency: 92ms (ZMQ trigger to mining.notify broadcast)"
latencyRE = regexp.MustCompile(`Block update latency:\s+(\d+)ms`)
)
// Run blocks until ctx is cancelled. It opens the file, seeks to either
@@ -104,6 +117,7 @@ var (
func (t *Tailer) Run(ctx context.Context) {
defer close(t.Events)
defer close(t.Attempts)
defer close(t.Latencies)
var (
f *os.File
@@ -242,6 +256,15 @@ func (t *Tailer) Run(ctx context.Context) {
}
func (t *Tailer) handleLine(line string) {
if m := latencyRE.FindStringSubmatch(line); m != nil {
if ms, err := strconv.ParseInt(m[1], 10, 64); err == nil {
select {
case t.Latencies <- LatencyEvent{SeenAt: time.Now(), LatencyMs: ms}:
default:
}
}
return
}
if m := solveDiffRE.FindStringSubmatch(line); m != nil {
var d float64
if v, err := strconv.ParseFloat(m[1], 64); err == nil {