ops: Signal disk-tier attach pending vs ready
Large disk caches can look memory-only/broken during async DiskFS attach. Expose disk_tier_ready in /metrics, pending/ready logs for disk-only and mixed modes, and X-SteamCache-Disk-Tier on /lancache-heartbeat. GetMetrics skips blocking disk.Size() while attach is pending so /metrics stays usable. Closes #33
This commit is contained in:
@@ -1057,6 +1057,12 @@ func TestDiskOnlyDelayedAttach(t *testing.T) {
|
||||
t.Errorf("during init window, expected ErrNotFound from disk-only tiered Create (no slow), got %v", err)
|
||||
}
|
||||
|
||||
// Disk tier is pending while the attach goroutine is in the Size barrier.
|
||||
// GetMetrics must return quickly (it skips disk.Size() while pending) and report 0.
|
||||
if got := sc.GetMetrics().DiskTierReady; got != 0 {
|
||||
t.Errorf("during pending attach, DiskTierReady=%d, want 0", got)
|
||||
}
|
||||
|
||||
// Wait the barrier (exercises the attach go's Size wait)
|
||||
_ = sc.disk.Size()
|
||||
|
||||
@@ -1083,6 +1089,91 @@ func TestDiskOnlyDelayedAttach(t *testing.T) {
|
||||
} else {
|
||||
rc.Close()
|
||||
}
|
||||
|
||||
// After attach, the disk tier must be marked ready (1)
|
||||
if got := sc.GetMetrics().DiskTierReady; got != 1 {
|
||||
t.Errorf("post-attach DiskTierReady=%d, want 1 (ready)", got)
|
||||
}
|
||||
|
||||
// /metrics text output includes the disk_tier_ready line
|
||||
rec := httptest.NewRecorder()
|
||||
metrics.WriteText(rec, sc.GetMetrics())
|
||||
if !bytes.Contains(rec.Body.Bytes(), []byte("disk_tier_ready 1")) {
|
||||
t.Errorf("WriteText output missing \"disk_tier_ready 1\": %q", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiskTierSignalMemoryOnly covers memory-only mode: DiskTierReady=1 (N/A, not
|
||||
// waiting on disk attach) and heartbeat header X-SteamCache-Disk-Tier: disabled.
|
||||
func TestDiskTierSignalMemoryOnly(t *testing.T) {
|
||||
sc, err := New("127.0.0.1:0", "1MB", "0", t.TempDir(), "", "lru", "lru", 10, 5, "0", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("New memory-only: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { sc.Shutdown() })
|
||||
|
||||
if got := sc.GetMetrics().DiskTierReady; got != 1 {
|
||||
t.Errorf("DiskTierReady=%d, want 1 (memory-only = N/A/not pending)", got)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "/lancache-heartbeat", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
sc.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Errorf("heartbeat status=%d, want 204", rec.Code)
|
||||
}
|
||||
if got := rec.Header().Get("X-SteamCache-Disk-Tier"); got != "disabled" {
|
||||
t.Errorf("X-SteamCache-Disk-Tier=%q, want disabled", got)
|
||||
}
|
||||
if got := rec.Header().Get("X-LanCache-Processed-By"); got != "SteamCache2" {
|
||||
t.Errorf("X-LanCache-Processed-By=%q, want SteamCache2", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiskTierSignalMixedPendingReady covers mixed mode: DiskTierReady=0 (header
|
||||
// pending) while the disk attach is in the Size barrier, then DiskTierReady=1
|
||||
// (header ready) after the barrier opens and the attach goroutine sets SetSlow.
|
||||
func TestDiskTierSignalMixedPendingReady(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
diskPath := filepath.Join(td, "disk")
|
||||
if err := os.MkdirAll(diskPath, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sc, err := New("127.0.0.1:0", "1MB", "10MB", diskPath, "", "lru", "lru", 10, 1, "0", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("New mixed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { sc.Shutdown() })
|
||||
|
||||
// Immediately in the pending window
|
||||
if got := sc.GetMetrics().DiskTierReady; got != 0 {
|
||||
t.Errorf("immediate DiskTierReady=%d, want 0 (pending)", got)
|
||||
}
|
||||
req := httptest.NewRequest("GET", "/lancache-heartbeat", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
sc.ServeHTTP(rec, req)
|
||||
if got := rec.Header().Get("X-SteamCache-Disk-Tier"); got != "pending" {
|
||||
t.Errorf("heartbeat header=%q, want pending", got)
|
||||
}
|
||||
|
||||
// Wait the barrier, then retry until the attach goroutine flips the flag
|
||||
_ = sc.disk.Size()
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if sc.GetMetrics().DiskTierReady == 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if got := sc.GetMetrics().DiskTierReady; got != 1 {
|
||||
t.Fatalf("DiskTierReady=%d after barrier, want 1 (ready)", got)
|
||||
}
|
||||
|
||||
rec2 := httptest.NewRecorder()
|
||||
sc.ServeHTTP(rec2, httptest.NewRequest("GET", "/lancache-heartbeat", nil))
|
||||
if got := rec2.Header().Get("X-SteamCache-Disk-Tier"); got != "ready" {
|
||||
t.Errorf("heartbeat header=%q, want ready", got)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Phase 2: narrow black-box tests for the new wrapper types ---
|
||||
|
||||
Reference in New Issue
Block a user