ops: Disk tier occupancy on /metrics
CI / vulncheck (pull_request) Successful in 19s
CI / check-and-test (pull_request) Successful in 47s
CI / vulncheck (push) Successful in 16s
CI / check-and-test (push) Successful in 46s
Release Tag / release (push) Successful in 14s

Operators could see attach-ready and capacity-pressure events but not how
full the configured disk (or memory) tier was without reading filesystems.
Expose size/capacity gauges and disk_cache_full_ratio next to disk_tier_ready.
Capacity is a config read, so it stays available while attach is pending.
This commit was merged in pull request #53.
This commit is contained in:
2026-09-09 16:06:12 +00:00
parent a3ea4806a7
commit 50ca0a071c
5 changed files with 240 additions and 2 deletions
+42 -2
View File
@@ -32,6 +32,8 @@ type Metrics struct {
// Cache metrics
MemoryCacheSize int64
DiskCacheSize int64
MemoryCacheCapacity int64 // configured memory capacity (bytes)
DiskCacheCapacity int64 // configured disk capacity (bytes); 0 when no disk
MemoryCacheHits int64
DiskCacheHits int64
Promotions int64
@@ -137,6 +139,17 @@ func (m *Metrics) SetDiskCacheSize(size int64) {
atomic.StoreInt64(&m.DiskCacheSize, size)
}
// SetMemoryCacheCapacity sets the configured memory cache capacity in bytes.
func (m *Metrics) SetMemoryCacheCapacity(capacity int64) {
atomic.StoreInt64(&m.MemoryCacheCapacity, capacity)
}
// SetDiskCacheCapacity sets the configured disk cache capacity in bytes
// (0 when no disk is configured).
func (m *Metrics) SetDiskCacheCapacity(capacity int64) {
atomic.StoreInt64(&m.DiskCacheCapacity, capacity)
}
// SetDiskTierReady sets whether the disk slow tier is attached (1) or still pending (0).
// Memory-only (no disk) also uses 1 — meaning "not waiting on disk attach". Reset does not clear this.
func (m *Metrics) SetDiskTierReady(ready int64) {
@@ -248,6 +261,11 @@ func (m *Metrics) GetStats() *Stats {
serviceErrors[k] = v
}
memoryCacheSize := atomic.LoadInt64(&m.MemoryCacheSize)
diskCacheSize := atomic.LoadInt64(&m.DiskCacheSize)
memoryCacheCapacity := atomic.LoadInt64(&m.MemoryCacheCapacity)
diskCacheCapacity := atomic.LoadInt64(&m.DiskCacheCapacity)
return &Stats{
TotalRequests: totalRequests,
CacheHits: cacheHits,
@@ -262,8 +280,11 @@ func (m *Metrics) GetStats() *Stats {
AvgResponseTime: avgResponseTime,
TotalBytesServed: atomic.LoadInt64(&m.TotalBytesServed),
TotalBytesSaved: atomic.LoadInt64(&m.TotalBytesSaved),
MemoryCacheSize: atomic.LoadInt64(&m.MemoryCacheSize),
DiskCacheSize: atomic.LoadInt64(&m.DiskCacheSize),
MemoryCacheSize: memoryCacheSize,
DiskCacheSize: diskCacheSize,
MemoryCacheCapacity: memoryCacheCapacity,
DiskCacheCapacity: diskCacheCapacity,
DiskCacheFullRatio: diskFullRatio(diskCacheSize, diskCacheCapacity),
DiskTierReady: atomic.LoadInt64(&m.DiskTierReady),
MemoryCacheHits: atomic.LoadInt64(&m.MemoryCacheHits),
DiskCacheHits: atomic.LoadInt64(&m.DiskCacheHits),
@@ -279,6 +300,19 @@ func (m *Metrics) GetStats() *Stats {
}
}
// diskFullRatio is size / capacity clamped to [0,1].
// It is 0 when no disk is configured or the capacity is 0.
func diskFullRatio(size, capacity int64) float64 {
if size <= 0 || capacity <= 0 {
return 0
}
ratio := float64(size) / float64(capacity)
if ratio > 1 {
return 1
}
return ratio
}
// Reset resets all metrics to zero
func (m *Metrics) Reset() {
atomic.StoreInt64(&m.TotalRequests, 0)
@@ -330,6 +364,9 @@ type Stats struct {
MemoryCacheSize int64
DiskCacheSize int64
MemoryCacheCapacity int64 // configured memory capacity (bytes)
DiskCacheCapacity int64 // configured disk capacity (bytes); 0 when no disk
DiskCacheFullRatio float64 // disk_cache_size / disk_cache_capacity, clamped to [0,1]; 0 when no disk or capacity is 0
DiskTierReady int64
MemoryCacheHits int64
DiskCacheHits int64
@@ -382,7 +419,10 @@ func WriteText(w http.ResponseWriter, stats *Stats) {
writeInt(w, "total_bytes_served", "Total bytes sent to clients.", "counter", stats.TotalBytesServed)
writeInt(w, "total_bytes_saved", "Bytes served from cache instead of being re-downloaded from upstream.", "counter", stats.TotalBytesSaved)
writeInt(w, "memory_cache_size", "Current memory cache size in bytes.", "gauge", stats.MemoryCacheSize)
writeInt(w, "memory_cache_capacity", "Configured memory cache capacity in bytes.", "gauge", stats.MemoryCacheCapacity)
writeInt(w, "disk_cache_size", "Current disk cache size in bytes.", "gauge", stats.DiskCacheSize)
writeInt(w, "disk_cache_capacity", "Configured disk cache capacity in bytes; 0 when no disk is configured.", "gauge", stats.DiskCacheCapacity)
writeFloat(w, "disk_cache_full_ratio", "disk_cache_size / disk_cache_capacity in [0,1]; 0 when no disk or capacity is 0.", "gauge", "%.4f", stats.DiskCacheFullRatio)
writeInt(w, "disk_tier_ready", "1 if the disk tier is attached or no disk is configured; 0 while attach is pending.", "gauge", stats.DiskTierReady)
writeFloat(w, "uptime_seconds", "Process uptime in seconds.", "gauge", "%.2f", stats.Uptime.Seconds())
}