Files
2025-12-25 20:02:59 +00:00

112 lines
3.2 KiB
Go

package storage
import (
"bufio"
"context"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/atlasos/calypso/internal/common/logger"
)
// ARCStats represents ZFS ARC (Adaptive Replacement Cache) statistics
type ARCStats struct {
HitRatio float64 `json:"hit_ratio"` // Percentage of cache hits
CacheUsage float64 `json:"cache_usage"` // Percentage of cache used
CacheSize int64 `json:"cache_size"` // Current ARC size in bytes
CacheMax int64 `json:"cache_max"` // Maximum ARC size in bytes
Hits int64 `json:"hits"` // Total cache hits
Misses int64 `json:"misses"` // Total cache misses
DemandHits int64 `json:"demand_hits"` // Demand data/metadata hits
PrefetchHits int64 `json:"prefetch_hits"` // Prefetch hits
MRUHits int64 `json:"mru_hits"` // Most Recently Used hits
MFUHits int64 `json:"mfu_hits"` // Most Frequently Used hits
CollectedAt string `json:"collected_at"` // Timestamp when stats were collected
}
// ARCService handles ZFS ARC statistics collection
type ARCService struct {
logger *logger.Logger
}
// NewARCService creates a new ARC service
func NewARCService(log *logger.Logger) *ARCService {
return &ARCService{
logger: log,
}
}
// GetARCStats reads and parses ARC statistics from /proc/spl/kstat/zfs/arcstats
func (s *ARCService) GetARCStats(ctx context.Context) (*ARCStats, error) {
stats := &ARCStats{}
// Read ARC stats file
file, err := os.Open("/proc/spl/kstat/zfs/arcstats")
if err != nil {
return nil, fmt.Errorf("failed to open arcstats file: %w", err)
}
defer file.Close()
// Parse the file
scanner := bufio.NewScanner(file)
arcData := make(map[string]int64)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Skip empty lines and header lines
if line == "" || strings.HasPrefix(line, "name") || strings.HasPrefix(line, "9") {
continue
}
// Parse lines like: "hits 4 311154"
fields := strings.Fields(line)
if len(fields) >= 3 {
key := fields[0]
// The value is in the last field (field index 2)
if value, err := strconv.ParseInt(fields[len(fields)-1], 10, 64); err == nil {
arcData[key] = value
}
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to read arcstats file: %w", err)
}
// Extract key metrics
stats.Hits = arcData["hits"]
stats.Misses = arcData["misses"]
stats.DemandHits = arcData["demand_data_hits"] + arcData["demand_metadata_hits"]
stats.PrefetchHits = arcData["prefetch_data_hits"] + arcData["prefetch_metadata_hits"]
stats.MRUHits = arcData["mru_hits"]
stats.MFUHits = arcData["mfu_hits"]
// Current ARC size (c) and max size (c_max)
stats.CacheSize = arcData["c"]
stats.CacheMax = arcData["c_max"]
// Calculate hit ratio
totalRequests := stats.Hits + stats.Misses
if totalRequests > 0 {
stats.HitRatio = float64(stats.Hits) / float64(totalRequests) * 100.0
} else {
stats.HitRatio = 0.0
}
// Calculate cache usage percentage
if stats.CacheMax > 0 {
stats.CacheUsage = float64(stats.CacheSize) / float64(stats.CacheMax) * 100.0
} else {
stats.CacheUsage = 0.0
}
// Set collection timestamp
stats.CollectedAt = time.Now().Format(time.RFC3339)
return stats, nil
}