Add scale limit constants, stale-agent indexed query, and tests.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Extract fleet caps (128 hosts, sem=16, 250ms coalesce, syscheck=20) into
named constants with Go tests; wire ListStaleOnlineAgents for stale sweeps
instead of full ListAgents scans.
This commit is contained in:
AetherForge
2026-06-07 06:40:12 -07:00
parent 148c9e2248
commit 72ae457cca
12 changed files with 240 additions and 9 deletions

View File

@@ -0,0 +1,10 @@
package api
import "time"
// WebSocket fleet telemetry scale limits.
const (
StatsBatchCoalesceInterval = 250 * time.Millisecond
StaleAgentSweepInterval = 45 * time.Second
StaleAgentThreshold = 3 * time.Minute
)

View File

@@ -0,0 +1,18 @@
package api
import (
"testing"
"time"
)
func TestScaleLimitConstants(t *testing.T) {
if StatsBatchCoalesceInterval != 250*time.Millisecond {
t.Fatalf("StatsBatchCoalesceInterval = %v, want 250ms", StatsBatchCoalesceInterval)
}
if StaleAgentSweepInterval != 45*time.Second {
t.Fatalf("StaleAgentSweepInterval = %v, want 45s", StaleAgentSweepInterval)
}
if StaleAgentThreshold != 3*time.Minute {
t.Fatalf("StaleAgentThreshold = %v, want 3m", StaleAgentThreshold)
}
}

View File

@@ -0,0 +1,30 @@
package db
import (
"time"
"crypto-miner-server/internal/models"
)
// ListStaleOnlineAgents returns online agents whose last_seen is older than
// the given threshold. Uses indexed status+last_seen filters instead of a full
// table scan.
func (d *Database) ListStaleOnlineAgents(olderThan time.Duration) ([]*models.Agent, error) {
cutoff := time.Now().Add(-olderThan)
query := `SELECT ` + agentSelectCols + ` FROM agents WHERE status = 'online' AND last_seen < ? ORDER BY last_seen ASC`
rows, err := d.Query(query, cutoff)
if err != nil {
return nil, err
}
defer rows.Close()
var agents []*models.Agent
for rows.Next() {
a, err := d.scanAgent(rows)
if err != nil {
return nil, err
}
agents = append(agents, a)
}
return agents, rows.Err()
}

View File

@@ -0,0 +1,127 @@
package db
import (
"fmt"
"testing"
"time"
"crypto-miner-server/internal/models"
)
func TestListStaleOnlineAgents_IndexedQuery(t *testing.T) {
d, err := New(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer d.Close()
staleTime := time.Now().Add(-5 * time.Minute)
freshTime := time.Now()
agents := []*models.Agent{
{ID: "stale-online", Name: "stale", Status: "online", LastSeen: staleTime},
{ID: "fresh-online", Name: "fresh", Status: "online", LastSeen: freshTime},
{ID: "stale-offline", Name: "off", Status: "offline", LastSeen: staleTime},
}
for _, a := range agents {
if err := d.UpsertAgent(a); err != nil {
t.Fatal(err)
}
}
got, err := d.ListStaleOnlineAgents(3 * time.Minute)
if err != nil {
t.Fatal(err)
}
if len(got) != 1 {
t.Fatalf("ListStaleOnlineAgents: want 1 stale online, got %d", len(got))
}
if got[0].ID != "stale-online" {
t.Fatalf("got %q, want stale-online", got[0].ID)
}
}
func TestMarkStaleAgentsOffline(t *testing.T) {
d, err := New(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer d.Close()
staleTime := time.Now().Add(-5 * time.Minute)
freshTime := time.Now()
for _, a := range []*models.Agent{
{ID: "stale-online", Name: "stale", Status: "online", LastSeen: staleTime},
{ID: "fresh-online", Name: "fresh", Status: "online", LastSeen: freshTime},
} {
if err := d.UpsertAgent(a); err != nil {
t.Fatal(err)
}
}
n, err := d.MarkStaleAgentsOffline(3 * time.Minute)
if err != nil {
t.Fatal(err)
}
if n != 1 {
t.Fatalf("MarkStaleAgentsOffline: want 1 updated, got %d", n)
}
fresh, err := d.GetAgent("fresh-online")
if err != nil {
t.Fatal(err)
}
if fresh.Status != "online" {
t.Fatalf("fresh-online status = %q, want online", fresh.Status)
}
stale, err := d.GetAgent("stale-online")
if err != nil {
t.Fatal(err)
}
if stale.Status != "offline" {
t.Fatalf("stale-online status = %q, want offline", stale.Status)
}
}
func TestListStaleOnlineAgents_AtScale(t *testing.T) {
d, err := New(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer d.Close()
const total = 500
staleTime := time.Now().Add(-5 * time.Minute)
for i := 0; i < total; i++ {
status := "offline"
lastSeen := time.Now()
if i%5 == 0 {
status = "online"
lastSeen = staleTime
}
a := &models.Agent{
ID: fmt.Sprintf("agent-%d", i),
Name: "n",
Status: status,
LastSeen: lastSeen,
}
if err := d.UpsertAgent(a); err != nil {
t.Fatal(err)
}
}
start := time.Now()
got, err := d.ListStaleOnlineAgents(3 * time.Minute)
elapsed := time.Since(start)
if err != nil {
t.Fatal(err)
}
if len(got) != total/5 {
t.Fatalf("want %d stale online, got %d", total/5, len(got))
}
if elapsed > 2*time.Second {
t.Fatalf("ListStaleOnlineAgents too slow at %d rows: %v", total, elapsed)
}
}