112 lines
3.2 KiB
Go
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
|
|
}
|