@@ -171,6 +171,10 @@ class IOPriority(enum.IntEnum):
171171svmem = namedtuple (
172172 'svmem' , ['total' , 'available' , 'percent' , 'used' , 'free' ,
173173 'active' , 'inactive' , 'buffers' , 'cached' , 'shared' , 'slab' ])
174+ # psutil.zfs_arc_stats()
175+ szfsarc = namedtuple (
176+ 'szfsarc' , ['enabled' , 'min' , 'max' , 'compressed' , 'uncompressed' ,
177+ 'size' , 'header' , 'anon' , 'mfu' , 'mru' , 'other' ])
174178# psutil.disk_io_counters()
175179sdiskio = namedtuple (
176180 'sdiskio' , ['read_count' , 'write_count' ,
@@ -415,13 +419,15 @@ def calculate_avail_vmem(mems):
415419 return int (avail )
416420
417421
418- def virtual_memory ():
422+ def virtual_memory (bool : include_zfs_arc = False ):
419423 """Report virtual memory stats.
420- This implementation mimicks procps-ng-3.3.12, aka "free" CLI tool:
424+ This implementation mimics procps-ng-3.3.12, aka "free" CLI tool:
421425 https://gitlab.com/procps-ng/procps/blob/
422426 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791
423427 The returned values are supposed to match both "free" and "vmstat -s"
424428 CLI tools.
429+ If specifying the `include_zfs_arc` parameter, the implementation mimics
430+ "htop" CLI tool instead of "free" CLI tool.
425431 """
426432 missing_fields = []
427433 mems = {}
@@ -523,6 +529,22 @@ def virtual_memory():
523529 # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764
524530 avail = free
525531
532+ # ZFS ARC memory consumption is not reported by /proc/meminfo.
533+ # Specifying `include_zfs_arc` will include reclaimable ZFS ARC
534+ # memory in the returned values.
535+ # N.B. this will make psutil match the output of "htop" instead
536+ # of "free" CLI tool.
537+ # See:
538+ # https://www.reddit.com/r/zfs/comments/ha0p7f/understanding_arcstat_and_free/
539+ # https://github.com/openzfs/zfs/issues/10255
540+ if include_zfs_arc :
541+ zfs = zfs_arc_stats ()
542+ if zfs .enabled :
543+ shrinkable_size = max (zfs .size - zfs .min , 0 )
544+ used -= shrinkable_size
545+ cached += shrinkable_size
546+ avail += shrinkable_size
547+
526548 percent = usage_percent ((total - avail ), total , round_ = 1 )
527549
528550 # Warn about missing metrics which are set to 0.
@@ -603,6 +625,64 @@ def swap_memory():
603625 return _common .sswap (total , used , free , percent , sin , sout )
604626
605627
628+ def zfs_arc_stats ():
629+ """Return ZFS ARC (Adaptive Replacement Cache) stats."""
630+ missing_fields = []
631+ mems = {}
632+
633+ def get_if_available (key ):
634+ try :
635+ return mems [key ] / 1024
636+ except KeyError :
637+ missing_fields .append (key )
638+ return 0
639+
640+
641+ with open_binary ('%s/spl/kstat/zfs/arcstats' % get_procfs_path ()) as f :
642+ for line in f :
643+ fields = line .split ()
644+ mems [fields [0 ]] = int (fields [1 ])
645+
646+ zfs_min = get_if_available (b'c_min' )
647+ zfs_max = get_if_available (b'c_max' )
648+ zfs_compressed = get_if_available (b'compressed_size' )
649+ zfs_uncompressed = get_if_available (b'uncompressed_size' )
650+ zfs_size = get_if_available (b'size' )
651+ zfs_header = get_if_available (b'hdr_size' )
652+ dbuf_size = get_if_available (b'dbuf_size' )
653+ dnode_size = get_if_available (b'dnode_size' )
654+ bonus_size = get_if_available (b'bonus_size' )
655+ zfs_anon = get_if_available (b'anon_size' )
656+ zfs_mfu = get_if_available (b'mfu_size' )
657+ zfs_mru = get_if_available (b'mru_size' )
658+
659+ zfs_enabled = zfs .size > 0
660+ zfs_other = dbuf_size + dnode_size + bonus_size if all ([dbuf_size , dnode_size , bonus_size ]) else 0
661+
662+ # Warn about missing metrics which are set to 0.
663+ if missing_fields :
664+ msg = "%s memory stats couldn't be determined and %s set to 0" % (
665+ ", " .join (missing_fields ),
666+ "was" if len (missing_fields ) == 1 else "were" ,
667+ )
668+ warnings .warn (msg , RuntimeWarning , stacklevel = 2 )
669+
670+ return szfsarc (
671+ zfs_enabled ,
672+ zfs_min ,
673+ zfs_max ,
674+ zfs_compressed ,
675+ zfs_uncompressed ,
676+ zfs_size ,
677+ zfs_header ,
678+ zfs_anon ,
679+ zfs_mfu ,
680+ zfs_mru ,
681+ zfs_other
682+ )
683+
684+
685+
606686# =====================================================================
607687# --- CPU
608688# =====================================================================
0 commit comments