package metrics // The ZFS ARC — the read cache in RAM — read live from /proc/spl/kstat/zfs/arcstats on a // machine that has the module loaded, and left out of the sample everywhere else. The kstat // file is counters since boot; the hit rate here is over the sampling interval, which is the // number that says whether the cache is doing its job *now*. The lifetime figures and the // pool's caching tiers (L2ARC, SLOG, special vdevs) are the storage package's business. import ( "strconv" "strings" "time" ) const arcstatsPath = "/proc/spl/kstat/zfs/arcstats" // ARC is the read cache as one sample sees it. Sizes are bytes. type ARC struct { Size uint64 `json:"size"` Target uint64 `json:"target"` // c: what the ARC is currently allowed to hold Min uint64 `json:"min"` // c_min: the floor it will not shrink below Max uint64 `json:"max"` // c_max: the ceiling (zfs_arc_max, or half of RAM) // what is in it MRU uint64 `json:"mru"` // recently used once MFU uint64 `json:"mfu"` // used more than once Anon uint64 `json:"anon"` // in flight: dirty, not yet on disk Data uint64 `json:"data"` // file contents Metadata uint64 `json:"metadata"` // dnodes, indirect blocks, dbufs, bonus buffers MetaUsed uint64 `json:"meta_used"` Headers uint64 `json:"headers"` Compressed uint64 `json:"compressed"` Uncompressed uint64 `json:"uncompressed"` MRUGhost uint64 `json:"mru_ghost"` // what it recently evicted and still remembers MFUGhost uint64 `json:"mfu_ghost"` // L2ARC, the cache devices (0 when there are none) L2Size uint64 `json:"l2_size"` L2ASize uint64 `json:"l2_asize"` // as stored on the device, after compression // over this interval ReadsPerSec float64 `json:"reads_per_sec"` MissesPerSec float64 `json:"misses_per_sec"` HitPct float64 `json:"hit_pct"` // of the interval's reads; an idle interval keeps the last one HitPctKnown bool `json:"hit_pct_known"` // false until an interval has had any reads L2HitPct float64 `json:"l2_hit_pct"` // since boot Hits uint64 `json:"hits"` Misses uint64 `json:"misses"` LifetimeHitPct float64 `json:"lifetime_hit_pct"` // pressure NoGrow bool `json:"no_grow"` // arc_no_grow: memory pressure is stopping it growing Throttles uint64 `json:"throttles"` // memory_throttle_count since boot Prunes uint64 `json:"prunes"` // arc_prune: metadata evictions asked of the filesystems EvictSkip uint64 `json:"evict_skip"` } type arcPrev struct { hits, misses, l2hits, l2misses uint64 hitPct float64 known bool at time.Time } // readKstat parses a kstat table ("name type data" rows after two header lines). func readKstat(p string) map[string]uint64 { s := readStr(p) if s == "" { return nil } out := map[string]uint64{} for _, line := range strings.Split(s, "\n") { f := strings.Fields(line) if len(f) != 3 || f[1] != "4" { // type 4 is KSTAT_DATA_UINT64 continue } v, err := strconv.ParseUint(f[2], 10, 64) if err == nil { out[f[0]] = v } } return out } // arc reads the ARC; nil when this machine has no ZFS module loaded. func (c *Collector) arc(now time.Time) *ARC { k := readKstat(arcstatsPath) if k == nil { return nil } a := &ARC{Size: k["size"], Target: k["c"], Min: k["c_min"], Max: k["c_max"], MRU: k["mru_size"], MFU: k["mfu_size"], Anon: k["anon_size"], Data: k["data_size"], Metadata: k["metadata_size"], MetaUsed: k["arc_meta_used"], Headers: k["hdr_size"], Compressed: k["compressed_size"], Uncompressed: k["uncompressed_size"], MRUGhost: k["mru_ghost_size"], MFUGhost: k["mfu_ghost_size"], L2Size: k["l2_size"], L2ASize: k["l2_asize"], Hits: k["hits"], Misses: k["misses"], NoGrow: k["arc_no_grow"] != 0, Throttles: k["memory_throttle_count"], Prunes: k["arc_prune"], EvictSkip: k["evict_skip"]} if n := a.Hits + a.Misses; n > 0 { a.LifetimeHitPct = round1(float64(a.Hits) / float64(n) * 100) } p := c.prevARC if !p.at.IsZero() && a.Hits >= p.hits && a.Misses >= p.misses { if dt := now.Sub(p.at).Seconds(); dt > 0 { hits, misses := a.Hits-p.hits, a.Misses-p.misses a.ReadsPerSec = round1(float64(hits+misses) / dt) a.MissesPerSec = round1(float64(misses) / dt) if hits+misses > 0 { p.hitPct, p.known = round1(float64(hits)/float64(hits+misses)*100), true } if l2 := (k["l2_hits"] - p.l2hits) + (k["l2_misses"] - p.l2misses); l2 > 0 && k["l2_hits"] >= p.l2hits { a.L2HitPct = round1(float64(k["l2_hits"]-p.l2hits) / float64(l2) * 100) } } } a.HitPct, a.HitPctKnown = p.hitPct, p.known c.prevARC = arcPrev{hits: a.Hits, misses: a.Misses, l2hits: k["l2_hits"], l2misses: k["l2_misses"], hitPct: p.hitPct, known: p.known, at: now} return a }