ops: Signal disk-full and eviction capacity pressure
When the disk (or memory) tier is at cap or the volume returns ENOSPC, ops currently look like random misses with no clear "we are dropping data." Count those events as capacity_pressure_events on /metrics and log tier plus reason so operators can tell capacity pressure from a cold cache, without changing the existing evictions counter. Link: #36
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"s1d3sw1ped/steamcache2/steamcache/logger"
|
||||
)
|
||||
|
||||
// Metrics tracks various performance and operational metrics
|
||||
@@ -28,13 +30,14 @@ type Metrics struct {
|
||||
TotalBytesSaved int64 // bytes served from cache instead of being re-downloaded from upstream
|
||||
|
||||
// Cache metrics
|
||||
MemoryCacheSize int64
|
||||
DiskCacheSize int64
|
||||
MemoryCacheHits int64
|
||||
DiskCacheHits int64
|
||||
Promotions int64
|
||||
Evictions int64
|
||||
DiskTierReady int64 // 0=pending (or unset), 1=ready or no-disk (N/A)
|
||||
MemoryCacheSize int64
|
||||
DiskCacheSize int64
|
||||
MemoryCacheHits int64
|
||||
DiskCacheHits int64
|
||||
Promotions int64
|
||||
Evictions int64
|
||||
CapacityPressureEvents int64 // soft eviction under cap and/or disk ENOSPC
|
||||
DiskTierReady int64 // 0=pending (or unset), 1=ready or no-disk (N/A)
|
||||
|
||||
// Expanded observability (upstream breakdowns, cache write failures, per-service errors)
|
||||
UpstreamErrors int64
|
||||
@@ -169,8 +172,39 @@ func (m *Metrics) GetServiceRequests(service string) int64 {
|
||||
return m.ServiceRequests[service]
|
||||
}
|
||||
|
||||
func (m *Metrics) IncrementPromotions() { atomic.AddInt64(&m.Promotions, 1) }
|
||||
func (m *Metrics) IncrementEvictions() { atomic.AddInt64(&m.Evictions, 1) }
|
||||
func (m *Metrics) IncrementPromotions() { atomic.AddInt64(&m.Promotions, 1) }
|
||||
func (m *Metrics) IncrementEvictions() { atomic.AddInt64(&m.Evictions, 1) }
|
||||
func (m *Metrics) IncrementCapacityPressureEvents() { atomic.AddInt64(&m.CapacityPressureEvents, 1) }
|
||||
|
||||
// NoteSoftEviction records one cap-pressure eviction batch that freed bytes.
|
||||
// Keeps the existing evictions counter and also increments capacity_pressure_events.
|
||||
// Nil m is safe: the log still fires so ops can grep without metrics wired.
|
||||
func NoteSoftEviction(m *Metrics, tier string, evicted uint) {
|
||||
if evicted == 0 {
|
||||
return
|
||||
}
|
||||
if m != nil {
|
||||
m.IncrementEvictions()
|
||||
m.IncrementCapacityPressureEvents()
|
||||
}
|
||||
logger.Logger.Info().
|
||||
Str("tier", tier).
|
||||
Str("reason", "eviction").
|
||||
Uint("bytes_evicted", evicted).
|
||||
Msg("cache capacity pressure")
|
||||
}
|
||||
|
||||
// NoteNoSpace records a disk Create/Write/Mkdir ENOSPC (or equivalent) event.
|
||||
func NoteNoSpace(m *Metrics, err error) {
|
||||
if m != nil {
|
||||
m.IncrementCapacityPressureEvents()
|
||||
}
|
||||
logger.Logger.Warn().
|
||||
Str("tier", "disk").
|
||||
Str("reason", "enospc").
|
||||
Err(err).
|
||||
Msg("cache capacity pressure")
|
||||
}
|
||||
|
||||
// Additional observability counters
|
||||
func (m *Metrics) IncrementUpstreamErrors() { atomic.AddInt64(&m.UpstreamErrors, 1) }
|
||||
@@ -215,32 +249,33 @@ func (m *Metrics) GetStats() *Stats {
|
||||
}
|
||||
|
||||
return &Stats{
|
||||
TotalRequests: totalRequests,
|
||||
CacheHits: cacheHits,
|
||||
CacheMisses: cacheMisses,
|
||||
CacheCoalesced: atomic.LoadInt64(&m.CacheCoalesced),
|
||||
NegativeCacheHits: atomic.LoadInt64(&m.NegativeCacheHits),
|
||||
RangeCache: atomic.LoadInt64(&m.RangeCache),
|
||||
RangeUpstream: atomic.LoadInt64(&m.RangeUpstream),
|
||||
Errors: atomic.LoadInt64(&m.Errors),
|
||||
RateLimited: atomic.LoadInt64(&m.RateLimited),
|
||||
HitRate: hitRate,
|
||||
AvgResponseTime: avgResponseTime,
|
||||
TotalBytesServed: atomic.LoadInt64(&m.TotalBytesServed),
|
||||
TotalBytesSaved: atomic.LoadInt64(&m.TotalBytesSaved),
|
||||
MemoryCacheSize: atomic.LoadInt64(&m.MemoryCacheSize),
|
||||
DiskCacheSize: atomic.LoadInt64(&m.DiskCacheSize),
|
||||
DiskTierReady: atomic.LoadInt64(&m.DiskTierReady),
|
||||
MemoryCacheHits: atomic.LoadInt64(&m.MemoryCacheHits),
|
||||
DiskCacheHits: atomic.LoadInt64(&m.DiskCacheHits),
|
||||
Promotions: atomic.LoadInt64(&m.Promotions),
|
||||
Evictions: atomic.LoadInt64(&m.Evictions),
|
||||
ServiceRequests: serviceRequests,
|
||||
UpstreamErrors: atomic.LoadInt64(&m.UpstreamErrors),
|
||||
CacheWriteFailures: atomic.LoadInt64(&m.CacheWriteFailures),
|
||||
ServiceErrors: serviceErrors,
|
||||
Uptime: time.Since(m.StartTime),
|
||||
LastResetTime: m.LastResetTime,
|
||||
TotalRequests: totalRequests,
|
||||
CacheHits: cacheHits,
|
||||
CacheMisses: cacheMisses,
|
||||
CacheCoalesced: atomic.LoadInt64(&m.CacheCoalesced),
|
||||
NegativeCacheHits: atomic.LoadInt64(&m.NegativeCacheHits),
|
||||
RangeCache: atomic.LoadInt64(&m.RangeCache),
|
||||
RangeUpstream: atomic.LoadInt64(&m.RangeUpstream),
|
||||
Errors: atomic.LoadInt64(&m.Errors),
|
||||
RateLimited: atomic.LoadInt64(&m.RateLimited),
|
||||
HitRate: hitRate,
|
||||
AvgResponseTime: avgResponseTime,
|
||||
TotalBytesServed: atomic.LoadInt64(&m.TotalBytesServed),
|
||||
TotalBytesSaved: atomic.LoadInt64(&m.TotalBytesSaved),
|
||||
MemoryCacheSize: atomic.LoadInt64(&m.MemoryCacheSize),
|
||||
DiskCacheSize: atomic.LoadInt64(&m.DiskCacheSize),
|
||||
DiskTierReady: atomic.LoadInt64(&m.DiskTierReady),
|
||||
MemoryCacheHits: atomic.LoadInt64(&m.MemoryCacheHits),
|
||||
DiskCacheHits: atomic.LoadInt64(&m.DiskCacheHits),
|
||||
Promotions: atomic.LoadInt64(&m.Promotions),
|
||||
Evictions: atomic.LoadInt64(&m.Evictions),
|
||||
CapacityPressureEvents: atomic.LoadInt64(&m.CapacityPressureEvents),
|
||||
ServiceRequests: serviceRequests,
|
||||
UpstreamErrors: atomic.LoadInt64(&m.UpstreamErrors),
|
||||
CacheWriteFailures: atomic.LoadInt64(&m.CacheWriteFailures),
|
||||
ServiceErrors: serviceErrors,
|
||||
Uptime: time.Since(m.StartTime),
|
||||
LastResetTime: m.LastResetTime,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +297,7 @@ func (m *Metrics) Reset() {
|
||||
atomic.StoreInt64(&m.DiskCacheHits, 0)
|
||||
atomic.StoreInt64(&m.Promotions, 0)
|
||||
atomic.StoreInt64(&m.Evictions, 0)
|
||||
atomic.StoreInt64(&m.CapacityPressureEvents, 0)
|
||||
atomic.StoreInt64(&m.UpstreamErrors, 0)
|
||||
atomic.StoreInt64(&m.CacheWriteFailures, 0)
|
||||
|
||||
@@ -293,18 +329,19 @@ type Stats struct {
|
||||
TotalBytesSaved int64
|
||||
MemoryCacheSize int64
|
||||
|
||||
DiskCacheSize int64
|
||||
DiskTierReady int64
|
||||
MemoryCacheHits int64
|
||||
DiskCacheHits int64
|
||||
Promotions int64
|
||||
Evictions int64
|
||||
UpstreamErrors int64
|
||||
CacheWriteFailures int64
|
||||
ServiceErrors map[string]int64
|
||||
ServiceRequests map[string]int64
|
||||
Uptime time.Duration
|
||||
LastResetTime time.Time
|
||||
DiskCacheSize int64
|
||||
DiskTierReady int64
|
||||
MemoryCacheHits int64
|
||||
DiskCacheHits int64
|
||||
Promotions int64
|
||||
Evictions int64
|
||||
CapacityPressureEvents int64
|
||||
UpstreamErrors int64
|
||||
CacheWriteFailures int64
|
||||
ServiceErrors map[string]int64
|
||||
ServiceRequests map[string]int64
|
||||
Uptime time.Duration
|
||||
LastResetTime time.Time
|
||||
}
|
||||
|
||||
// WriteText emits the Prometheus-style text metrics to the ResponseWriter.
|
||||
@@ -329,6 +366,7 @@ func WriteText(w http.ResponseWriter, stats *Stats) {
|
||||
_, _ = fmt.Fprintf(w, "disk_cache_hits %d\n", stats.DiskCacheHits)
|
||||
_, _ = fmt.Fprintf(w, "promotions %d\n", stats.Promotions)
|
||||
_, _ = fmt.Fprintf(w, "evictions %d\n", stats.Evictions)
|
||||
_, _ = fmt.Fprintf(w, "capacity_pressure_events %d\n", stats.CapacityPressureEvents)
|
||||
for svc, cnt := range stats.ServiceErrors {
|
||||
_, _ = fmt.Fprintf(w, "service_errors{service=%q} %d\n", svc, cnt)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user