Apply arc_shrink_shift to ARC above arc_c_min
[zfs.git] / cmd / arc_summary
blob4f275813d9735c281f098e9ebc95f6e21af45bb6
1 #!/usr/bin/env python3
3 # Copyright (c) 2008 Ben Rockwood <benr@cuddletech.com>,
4 # Copyright (c) 2010 Martin Matuska <mm@FreeBSD.org>,
5 # Copyright (c) 2010-2011 Jason J. Hellenthal <jhell@DataIX.net>,
6 # Copyright (c) 2017 Scot W. Stevenson <scot.stevenson@gmail.com>
7 # All rights reserved.
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
11 # are met:
13 # 1. Redistributions of source code must retain the above copyright
14 #    notice, this list of conditions and the following disclaimer.
15 # 2. Redistributions in binary form must reproduce the above copyright
16 #    notice, this list of conditions and the following disclaimer in the
17 #    documentation and/or other materials provided with the distribution.
19 # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 # ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
23 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 # SUCH DAMAGE.
30 """Print statistics on the ZFS ARC Cache and other information
32 Provides basic information on the ARC, its efficiency, the L2ARC (if present),
33 the Data Management Unit (DMU), Virtual Devices (VDEVs), and tunables. See
34 the in-source documentation and code at
35 https://github.com/openzfs/zfs/blob/master/module/zfs/arc.c for details.
36 The original introduction to arc_summary can be found at
37 http://cuddletech.com/?p=454
38 """
40 import argparse
41 import os
42 import subprocess
43 import sys
44 import time
45 import errno
47 # We can't use env -S portably, and we need python3 -u to handle pipes in
48 # the shell abruptly closing the way we want to, so...
49 import io
50 if isinstance(sys.__stderr__.buffer, io.BufferedWriter):
51     os.execv(sys.executable, [sys.executable, "-u"] + sys.argv)
53 DESCRIPTION = 'Print ARC and other statistics for OpenZFS'
54 INDENT = ' '*8
55 LINE_LENGTH = 72
56 DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
57 TITLE = 'ZFS Subsystem Report'
59 SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
60 SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
62 # Tunables and SPL are handled separately because they come from
63 # different sources
64 SECTION_PATHS = {'arc': 'arcstats',
65                  'dmu': 'dmu_tx',
66                  'l2arc': 'arcstats',  # L2ARC stuff lives in arcstats
67                  'vdev': 'vdev_cache_stats',
68                  'zfetch': 'zfetchstats',
69                  'zil': 'zil'}
71 parser = argparse.ArgumentParser(description=DESCRIPTION)
72 parser.add_argument('-a', '--alternate', action='store_true', default=False,
73                     help='use alternate formatting for tunables and SPL',
74                     dest='alt')
75 parser.add_argument('-d', '--description', action='store_true', default=False,
76                     help='print descriptions with tunables and SPL',
77                     dest='desc')
78 parser.add_argument('-g', '--graph', action='store_true', default=False,
79                     help='print graph on ARC use and exit', dest='graph')
80 parser.add_argument('-p', '--page', type=int, dest='page',
81                     help='print page by number (DEPRECATED, use "-s")')
82 parser.add_argument('-r', '--raw', action='store_true', default=False,
83                     help='dump all available data with minimal formatting',
84                     dest='raw')
85 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
86 ARGS = parser.parse_args()
89 if sys.platform.startswith('freebsd'):
90     # Requires py36-sysctl on FreeBSD
91     import sysctl
93     VDEV_CACHE_SIZE = 'vdev.cache_size'
95     def is_value(ctl):
96         return ctl.type != sysctl.CTLTYPE_NODE
98     def namefmt(ctl, base='vfs.zfs.'):
99         # base is removed from the name
100         cut = len(base)
101         return ctl.name[cut:]
103     def load_kstats(section):
104         base = 'kstat.zfs.misc.{section}.'.format(section=section)
105         fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
106                                                       value=kstat.value)
107         kstats = sysctl.filter(base)
108         return [fmt(kstat) for kstat in kstats if is_value(kstat)]
110     def get_params(base):
111         ctls = sysctl.filter(base)
112         return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
114     def get_tunable_params():
115         return get_params('vfs.zfs')
117     def get_vdev_params():
118         return get_params('vfs.zfs.vdev')
120     def get_version_impl(request):
121         # FreeBSD reports versions for zpl and spa instead of zfs and spl.
122         name = {'zfs': 'zpl',
123                 'spl': 'spa'}[request]
124         mib = 'vfs.zfs.version.{}'.format(name)
125         version = sysctl.filter(mib)[0].value
126         return '{} version {}'.format(name, version)
128     def get_descriptions(_request):
129         ctls = sysctl.filter('vfs.zfs')
130         return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
133 elif sys.platform.startswith('linux'):
134     KSTAT_PATH = '/proc/spl/kstat/zfs'
135     SPL_PATH = '/sys/module/spl/parameters'
136     TUNABLES_PATH = '/sys/module/zfs/parameters'
138     VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
140     def load_kstats(section):
141         path = os.path.join(KSTAT_PATH, section)
142         with open(path) as f:
143             return list(f)[2:] # Get rid of header
145     def get_params(basepath):
146         """Collect information on the Solaris Porting Layer (SPL) or the
147         tunables, depending on the PATH given. Does not check if PATH is
148         legal.
149         """
150         result = {}
151         for name in os.listdir(basepath):
152             path = os.path.join(basepath, name)
153             with open(path) as f:
154                 value = f.read()
155                 result[name] = value.strip()
156         return result
158     def get_spl_params():
159         return get_params(SPL_PATH)
161     def get_tunable_params():
162         return get_params(TUNABLES_PATH)
164     def get_vdev_params():
165         return get_params(TUNABLES_PATH)
167     def get_version_impl(request):
168         # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
169         # the version information. We switch to /sys/module/{spl,zfs}/version
170         # to make sure we get what is really loaded in the kernel
171         try:
172             with open("/sys/module/{}/version".format(request)) as f:
173                 return f.read().strip()
174         except:
175             return "(unknown)"
177     def get_descriptions(request):
178         """Get the descriptions of the Solaris Porting Layer (SPL) or the
179         tunables, return with minimal formatting.
180         """
182         if request not in ('spl', 'zfs'):
183             print('ERROR: description of "{0}" requested)'.format(request))
184             sys.exit(1)
186         descs = {}
187         target_prefix = 'parm:'
189         # We would prefer to do this with /sys/modules -- see the discussion at
190         # get_version() -- but there isn't a way to get the descriptions from
191         # there, so we fall back on modinfo
192         command = ["/sbin/modinfo", request, "-0"]
194         info = ''
196         try:
198             info = subprocess.run(command, stdout=subprocess.PIPE,
199                                   check=True, universal_newlines=True)
200             raw_output = info.stdout.split('\0')
202         except subprocess.CalledProcessError:
203             print("Error: Descriptions not available",
204                   "(can't access kernel module)")
205             sys.exit(1)
207         for line in raw_output:
209             if not line.startswith(target_prefix):
210                 continue
212             line = line[len(target_prefix):].strip()
213             name, raw_desc = line.split(':', 1)
214             desc = raw_desc.rsplit('(', 1)[0]
216             if desc == '':
217                 desc = '(No description found)'
219             descs[name.strip()] = desc.strip()
221         return descs
223 def handle_unraisableException(exc_type, exc_value=None, exc_traceback=None,
224                                err_msg=None, object=None):
225    handle_Exception(exc_type, object, exc_traceback)
227 def handle_Exception(ex_cls, ex, tb):
228     if ex_cls is KeyboardInterrupt:
229         sys.exit()
231     if ex_cls is BrokenPipeError:
232         # It turns out that while sys.exit() triggers an exception
233         # not handled message on Python 3.8+, os._exit() does not.
234         os._exit(0)
236     if ex_cls is OSError:
237       if ex.errno == errno.ENOTCONN:
238         sys.exit()
240     raise ex
242 if hasattr(sys,'unraisablehook'): # Python 3.8+
243     sys.unraisablehook = handle_unraisableException
244 sys.excepthook = handle_Exception
247 def cleanup_line(single_line):
248     """Format a raw line of data from /proc and isolate the name value
249     part, returning a tuple with each. Currently, this gets rid of the
250     middle '4'. For example "arc_no_grow    4    0" returns the tuple
251     ("arc_no_grow", "0").
252     """
253     name, _, value = single_line.split()
255     return name, value
258 def draw_graph(kstats_dict):
259     """Draw a primitive graph representing the basic information on the
260     ARC -- its size and the proportion used by MFU and MRU -- and quit.
261     We use max size of the ARC to calculate how full it is. This is a
262     very rough representation.
263     """
265     arc_stats = isolate_section('arcstats', kstats_dict)
267     GRAPH_INDENT = ' '*4
268     GRAPH_WIDTH = 60
269     arc_size = f_bytes(arc_stats['size'])
270     arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
271     mfu_size = f_bytes(arc_stats['mfu_size'])
272     mru_size = f_bytes(arc_stats['mru_size'])
273     meta_limit = f_bytes(arc_stats['arc_meta_limit'])
274     meta_size = f_bytes(arc_stats['arc_meta_used'])
275     dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
276     dnode_size = f_bytes(arc_stats['dnode_size'])
278     info_form = ('ARC: {0} ({1})  MFU: {2}  MRU: {3}  META: {4} ({5}) '
279                  'DNODE {6} ({7})')
280     info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
281                                  meta_size, meta_limit, dnode_size,
282                                  dnode_limit)
283     info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
284     info_line = GRAPH_INDENT+info_spc+info_line
286     graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
288     mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
289     mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
290     arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
291     total_ticks = float(arc_perc)*GRAPH_WIDTH
292     mfu_ticks = mfu_perc*GRAPH_WIDTH
293     mru_ticks = mru_perc*GRAPH_WIDTH
294     other_ticks = total_ticks-(mfu_ticks+mru_ticks)
296     core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
297     core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
298     core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
300     for line in ('', info_line, graph_line, core_line, graph_line, ''):
301         print(line)
304 def f_bytes(byte_string):
305     """Return human-readable representation of a byte value in
306     powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
307     points. Values smaller than one KiB are returned without
308     decimal points. Note "bytes" is a reserved keyword.
309     """
311     prefixes = ([2**80, "YiB"],   # yobibytes (yotta)
312                 [2**70, "ZiB"],   # zebibytes (zetta)
313                 [2**60, "EiB"],   # exbibytes (exa)
314                 [2**50, "PiB"],   # pebibytes (peta)
315                 [2**40, "TiB"],   # tebibytes (tera)
316                 [2**30, "GiB"],   # gibibytes (giga)
317                 [2**20, "MiB"],   # mebibytes (mega)
318                 [2**10, "KiB"])   # kibibytes (kilo)
320     bites = int(byte_string)
322     if bites >= 2**10:
323         for limit, unit in prefixes:
325             if bites >= limit:
326                 value = bites / limit
327                 break
329         result = '{0:.1f} {1}'.format(value, unit)
330     else:
331         result = '{0} Bytes'.format(bites)
333     return result
336 def f_hits(hits_string):
337     """Create a human-readable representation of the number of hits.
338     The single-letter symbols used are SI to avoid the confusion caused
339     by the different "short scale" and "long scale" representations in
340     English, which use the same words for different values. See
341     https://en.wikipedia.org/wiki/Names_of_large_numbers and:
342     https://physics.nist.gov/cuu/Units/prefixes.html
343     """
345     numbers = ([10**24, 'Y'],  # yotta (septillion)
346                [10**21, 'Z'],  # zetta (sextillion)
347                [10**18, 'E'],  # exa   (quintrillion)
348                [10**15, 'P'],  # peta  (quadrillion)
349                [10**12, 'T'],  # tera  (trillion)
350                [10**9, 'G'],   # giga  (billion)
351                [10**6, 'M'],   # mega  (million)
352                [10**3, 'k'])   # kilo  (thousand)
354     hits = int(hits_string)
356     if hits >= 1000:
357         for limit, symbol in numbers:
359             if hits >= limit:
360                 value = hits/limit
361                 break
363         result = "%0.1f%s" % (value, symbol)
364     else:
365         result = "%d" % hits
367     return result
370 def f_perc(value1, value2):
371     """Calculate percentage and return in human-readable form. If
372     rounding produces the result '0.0' though the first number is
373     not zero, include a 'less-than' symbol to avoid confusion.
374     Division by zero is handled by returning 'n/a'; no error
375     is called.
376     """
378     v1 = float(value1)
379     v2 = float(value2)
381     try:
382         perc = 100 * v1/v2
383     except ZeroDivisionError:
384         result = 'n/a'
385     else:
386         result = '{0:0.1f} %'.format(perc)
388     if result == '0.0 %' and v1 > 0:
389         result = '< 0.1 %'
391     return result
394 def format_raw_line(name, value):
395     """For the --raw option for the tunable and SPL outputs, decide on the
396     correct formatting based on the --alternate flag.
397     """
399     if ARGS.alt:
400         result = '{0}{1}={2}'.format(INDENT, name, value)
401     else:
402         # Right-align the value within the line length if it fits,
403         # otherwise just separate it from the name by a single space.
404         fit = LINE_LENGTH - len(INDENT) - len(name)
405         overflow = len(value) + 1
406         w = max(fit, overflow)
407         result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
409     return result
412 def get_kstats():
413     """Collect information on the ZFS subsystem. The step does not perform any
414     further processing, giving us the option to only work on what is actually
415     needed. The name "kstat" is a holdover from the Solaris utility of the same
416     name.
417     """
419     result = {}
421     for section in SECTION_PATHS.values():
422         if section not in result:
423             result[section] = load_kstats(section)
425     return result
428 def get_version(request):
429     """Get the version number of ZFS or SPL on this machine for header.
430     Returns an error string, but does not raise an error, if we can't
431     get the ZFS/SPL version.
432     """
434     if request not in ('spl', 'zfs'):
435         error_msg = '(ERROR: "{0}" requested)'.format(request)
436         return error_msg
438     return get_version_impl(request)
441 def print_header():
442     """Print the initial heading with date and time as well as info on the
443     kernel and ZFS versions. This is not called for the graph.
444     """
446     # datetime is now recommended over time but we keep the exact formatting
447     # from the older version of arc_summary in case there are scripts
448     # that expect it in this way
449     daydate = time.strftime(DATE_FORMAT)
450     spc_date = LINE_LENGTH-len(daydate)
451     sys_version = os.uname()
453     sys_msg = sys_version.sysname+' '+sys_version.release
454     zfs = get_version('zfs')
455     spc_zfs = LINE_LENGTH-len(zfs)
457     machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
458     spl = get_version('spl')
459     spc_spl = LINE_LENGTH-len(spl)
461     print('\n'+('-'*LINE_LENGTH))
462     print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
463     print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
464     print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
467 def print_raw(kstats_dict):
468     """Print all available data from the system in a minimally sorted format.
469     This can be used as a source to be piped through 'grep'.
470     """
472     sections = sorted(kstats_dict.keys())
474     for section in sections:
476         print('\n{0}:'.format(section.upper()))
477         lines = sorted(kstats_dict[section])
479         for line in lines:
480             name, value = cleanup_line(line)
481             print(format_raw_line(name, value))
483     # Tunables and SPL must be handled separately because they come from a
484     # different source and have descriptions the user might request
485     print()
486     section_spl()
487     section_tunables()
490 def isolate_section(section_name, kstats_dict):
491     """From the complete information on all sections, retrieve only those
492     for one section.
493     """
495     try:
496         section_data = kstats_dict[section_name]
497     except KeyError:
498         print('ERROR: Data on {0} not available'.format(section_data))
499         sys.exit(1)
501     section_dict = dict(cleanup_line(l) for l in section_data)
503     return section_dict
506 # Formatted output helper functions
509 def prt_1(text, value):
510     """Print text and one value, no indent"""
511     spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
512     print('{0}{spc}{1}'.format(text, value, spc=spc))
515 def prt_i1(text, value):
516     """Print text and one value, with indent"""
517     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
518     print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
521 def prt_2(text, value1, value2):
522     """Print text and two values, no indent"""
523     values = '{0:>9}  {1:>9}'.format(value1, value2)
524     spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
525     print('{0}{spc}  {1}'.format(text, values, spc=spc))
528 def prt_i2(text, value1, value2):
529     """Print text and two values, with indent"""
530     values = '{0:>9}  {1:>9}'.format(value1, value2)
531     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
532     print(INDENT+'{0}{spc}  {1}'.format(text, values, spc=spc))
535 # The section output concentrates on important parameters instead of
536 # being exhaustive (that is what the --raw parameter is for)
539 def section_arc(kstats_dict):
540     """Give basic information on the ARC, MRU and MFU. This is the first
541     and most used section.
542     """
544     arc_stats = isolate_section('arcstats', kstats_dict)
546     throttle = arc_stats['memory_throttle_count']
548     if throttle == '0':
549         health = 'HEALTHY'
550     else:
551         health = 'THROTTLED'
553     prt_1('ARC status:', health)
554     prt_i1('Memory throttle count:', throttle)
555     print()
557     arc_size = arc_stats['size']
558     arc_target_size = arc_stats['c']
559     arc_max = arc_stats['c_max']
560     arc_min = arc_stats['c_min']
561     mfu_size = arc_stats['mfu_size']
562     mru_size = arc_stats['mru_size']
563     meta_limit = arc_stats['arc_meta_limit']
564     meta_size = arc_stats['arc_meta_used']
565     dnode_limit = arc_stats['arc_dnode_limit']
566     dnode_size = arc_stats['dnode_size']
567     target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
569     prt_2('ARC size (current):',
570           f_perc(arc_size, arc_max), f_bytes(arc_size))
571     prt_i2('Target size (adaptive):',
572            f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
573     prt_i2('Min size (hard limit):',
574            f_perc(arc_min, arc_max), f_bytes(arc_min))
575     prt_i2('Max size (high water):',
576            target_size_ratio, f_bytes(arc_max))
577     caches_size = int(mfu_size)+int(mru_size)
578     prt_i2('Most Frequently Used (MFU) cache size:',
579            f_perc(mfu_size, caches_size), f_bytes(mfu_size))
580     prt_i2('Most Recently Used (MRU) cache size:',
581            f_perc(mru_size, caches_size), f_bytes(mru_size))
582     prt_i2('Metadata cache size (hard limit):',
583            f_perc(meta_limit, arc_max), f_bytes(meta_limit))
584     prt_i2('Metadata cache size (current):',
585            f_perc(meta_size, meta_limit), f_bytes(meta_size))
586     prt_i2('Dnode cache size (hard limit):',
587            f_perc(dnode_limit, meta_limit), f_bytes(dnode_limit))
588     prt_i2('Dnode cache size (current):',
589            f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
590     print()
592     print('ARC hash breakdown:')
593     prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
594     prt_i2('Elements current:',
595            f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
596            f_hits(arc_stats['hash_elements']))
597     prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
599     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
600     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
601     print()
603     print('ARC misc:')
604     prt_i1('Deleted:', f_hits(arc_stats['deleted']))
605     prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
606     prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
607     prt_i1('Eviction skips due to L2 writes:',
608            f_hits(arc_stats['evict_l2_skip']))
609     prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
610     prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
611     prt_i2('L2 eligible MFU evictions:',
612            f_perc(arc_stats['evict_l2_eligible_mfu'],
613            arc_stats['evict_l2_eligible']),
614            f_bytes(arc_stats['evict_l2_eligible_mfu']))
615     prt_i2('L2 eligible MRU evictions:',
616            f_perc(arc_stats['evict_l2_eligible_mru'],
617            arc_stats['evict_l2_eligible']),
618            f_bytes(arc_stats['evict_l2_eligible_mru']))
619     prt_i1('L2 ineligible evictions:',
620            f_bytes(arc_stats['evict_l2_ineligible']))
621     print()
624 def section_archits(kstats_dict):
625     """Print information on how the caches are accessed ("arc hits").
626     """
628     arc_stats = isolate_section('arcstats', kstats_dict)
629     all_accesses = int(arc_stats['hits'])+int(arc_stats['misses'])
630     actual_hits = int(arc_stats['mfu_hits'])+int(arc_stats['mru_hits'])
632     prt_1('ARC total accesses (hits + misses):', f_hits(all_accesses))
633     ta_todo = (('Cache hit ratio:', arc_stats['hits']),
634                ('Cache miss ratio:', arc_stats['misses']),
635                ('Actual hit ratio (MFU + MRU hits):', actual_hits))
637     for title, value in ta_todo:
638         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
640     dd_total = int(arc_stats['demand_data_hits']) +\
641         int(arc_stats['demand_data_misses'])
642     prt_i2('Data demand efficiency:',
643            f_perc(arc_stats['demand_data_hits'], dd_total),
644            f_hits(dd_total))
646     dp_total = int(arc_stats['prefetch_data_hits']) +\
647         int(arc_stats['prefetch_data_misses'])
648     prt_i2('Data prefetch efficiency:',
649            f_perc(arc_stats['prefetch_data_hits'], dp_total),
650            f_hits(dp_total))
652     known_hits = int(arc_stats['mfu_hits']) +\
653         int(arc_stats['mru_hits']) +\
654         int(arc_stats['mfu_ghost_hits']) +\
655         int(arc_stats['mru_ghost_hits'])
657     anon_hits = int(arc_stats['hits'])-known_hits
659     print()
660     print('Cache hits by cache type:')
661     cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
662                ('Most recently used (MRU):', arc_stats['mru_hits']),
663                ('Most frequently used (MFU) ghost:',
664                 arc_stats['mfu_ghost_hits']),
665                ('Most recently used (MRU) ghost:',
666                 arc_stats['mru_ghost_hits']))
668     for title, value in cl_todo:
669         prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
671     # For some reason, anon_hits can turn negative, which is weird. Until we
672     # have figured out why this happens, we just hide the problem, following
673     # the behavior of the original arc_summary.
674     if anon_hits >= 0:
675         prt_i2('Anonymously used:',
676                f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
678     print()
679     print('Cache hits by data type:')
680     dt_todo = (('Demand data:', arc_stats['demand_data_hits']),
681                ('Demand prefetch data:', arc_stats['prefetch_data_hits']),
682                ('Demand metadata:', arc_stats['demand_metadata_hits']),
683                ('Demand prefetch metadata:',
684                 arc_stats['prefetch_metadata_hits']))
686     for title, value in dt_todo:
687         prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
689     print()
690     print('Cache misses by data type:')
691     dm_todo = (('Demand data:', arc_stats['demand_data_misses']),
692                ('Demand prefetch data:',
693                 arc_stats['prefetch_data_misses']),
694                ('Demand metadata:', arc_stats['demand_metadata_misses']),
695                ('Demand prefetch metadata:',
696                 arc_stats['prefetch_metadata_misses']))
698     for title, value in dm_todo:
699         prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
701     print()
704 def section_dmu(kstats_dict):
705     """Collect information on the DMU"""
707     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
709     zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
711     prt_1('DMU prefetch efficiency:', f_hits(zfetch_access_total))
712     prt_i2('Hit ratio:', f_perc(zfetch_stats['hits'], zfetch_access_total),
713            f_hits(zfetch_stats['hits']))
714     prt_i2('Miss ratio:', f_perc(zfetch_stats['misses'], zfetch_access_total),
715            f_hits(zfetch_stats['misses']))
716     print()
719 def section_l2arc(kstats_dict):
720     """Collect information on L2ARC device if present. If not, tell user
721     that we're skipping the section.
722     """
724     # The L2ARC statistics live in the same section as the normal ARC stuff
725     arc_stats = isolate_section('arcstats', kstats_dict)
727     if arc_stats['l2_size'] == '0':
728         print('L2ARC not detected, skipping section\n')
729         return
731     l2_errors = int(arc_stats['l2_writes_error']) +\
732         int(arc_stats['l2_cksum_bad']) +\
733         int(arc_stats['l2_io_error'])
735     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
736     health = 'HEALTHY'
738     if l2_errors > 0:
739         health = 'DEGRADED'
741     prt_1('L2ARC status:', health)
743     l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
744                ('Free on write:', 'l2_free_on_write'),
745                ('R/W clashes:', 'l2_rw_clash'),
746                ('Bad checksums:', 'l2_cksum_bad'),
747                ('I/O errors:', 'l2_io_error'))
749     for title, value in l2_todo:
750         prt_i1(title, f_hits(arc_stats[value]))
752     print()
753     prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
754     prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
755            f_bytes(arc_stats['l2_asize']))
756     prt_i2('Header size:',
757            f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
758            f_bytes(arc_stats['l2_hdr_size']))
759     prt_i2('MFU allocated size:',
760            f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
761            f_bytes(arc_stats['l2_mfu_asize']))
762     prt_i2('MRU allocated size:',
763            f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
764            f_bytes(arc_stats['l2_mru_asize']))
765     prt_i2('Prefetch allocated size:',
766            f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
767            f_bytes(arc_stats['l2_prefetch_asize']))
768     prt_i2('Data (buffer content) allocated size:',
769            f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
770            f_bytes(arc_stats['l2_bufc_data_asize']))
771     prt_i2('Metadata (buffer content) allocated size:',
772            f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
773            f_bytes(arc_stats['l2_bufc_metadata_asize']))
775     print()
776     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
777     prt_i2('Hit ratio:',
778            f_perc(arc_stats['l2_hits'], l2_access_total),
779            f_hits(arc_stats['l2_hits']))
780     prt_i2('Miss ratio:',
781            f_perc(arc_stats['l2_misses'], l2_access_total),
782            f_hits(arc_stats['l2_misses']))
783     prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
785     print()
786     print('L2ARC writes:')
788     if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
789         prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
790         prt_i2('Done ratio:',
791                f_perc(arc_stats['l2_writes_done'],
792                       arc_stats['l2_writes_sent']),
793                f_hits(arc_stats['l2_writes_done']))
794         prt_i2('Error ratio:',
795                f_perc(arc_stats['l2_writes_error'],
796                       arc_stats['l2_writes_sent']),
797                f_hits(arc_stats['l2_writes_error']))
798     else:
799         prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
801     print()
802     print('L2ARC evicts:')
803     prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
804     prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
805     print()
808 def section_spl(*_):
809     """Print the SPL parameters, if requested with alternative format
810     and/or descriptions. This does not use kstats.
811     """
813     if sys.platform.startswith('freebsd'):
814         # No SPL support in FreeBSD
815         return
817     spls = get_spl_params()
818     keylist = sorted(spls.keys())
819     print('Solaris Porting Layer (SPL):')
821     if ARGS.desc:
822         descriptions = get_descriptions('spl')
824     for key in keylist:
825         value = spls[key]
827         if ARGS.desc:
828             try:
829                 print(INDENT+'#', descriptions[key])
830             except KeyError:
831                 print(INDENT+'# (No description found)')  # paranoid
833         print(format_raw_line(key, value))
835     print()
838 def section_tunables(*_):
839     """Print the tunables, if requested with alternative format and/or
840     descriptions. This does not use kstasts.
841     """
843     tunables = get_tunable_params()
844     keylist = sorted(tunables.keys())
845     print('Tunables:')
847     if ARGS.desc:
848         descriptions = get_descriptions('zfs')
850     for key in keylist:
851         value = tunables[key]
853         if ARGS.desc:
854             try:
855                 print(INDENT+'#', descriptions[key])
856             except KeyError:
857                 print(INDENT+'# (No description found)')  # paranoid
859         print(format_raw_line(key, value))
861     print()
864 def section_vdev(kstats_dict):
865     """Collect information on VDEV caches"""
867     # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
868     # harmful. When this is the case, we just skip the whole entry. See
869     # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
870     # for details
871     tunables = get_vdev_params()
873     if tunables[VDEV_CACHE_SIZE] == '0':
874         print('VDEV cache disabled, skipping section\n')
875         return
877     vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
879     vdev_cache_total = int(vdev_stats['hits']) +\
880         int(vdev_stats['misses']) +\
881         int(vdev_stats['delegations'])
883     prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
884     prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
885            f_hits(vdev_stats['hits']))
886     prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
887            f_hits(vdev_stats['misses']))
888     prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
889            f_hits(vdev_stats['delegations']))
890     print()
893 def section_zil(kstats_dict):
894     """Collect information on the ZFS Intent Log. Some of the information
895     taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
896     """
898     zil_stats = isolate_section('zil', kstats_dict)
900     prt_1('ZIL committed transactions:',
901           f_hits(zil_stats['zil_itx_count']))
902     prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
903     prt_i1('Flushes to stable storage:',
904            f_hits(zil_stats['zil_commit_writer_count']))
905     prt_i2('Transactions to SLOG storage pool:',
906            f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
907            f_hits(zil_stats['zil_itx_metaslab_slog_count']))
908     prt_i2('Transactions to non-SLOG storage pool:',
909            f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
910            f_hits(zil_stats['zil_itx_metaslab_normal_count']))
911     print()
914 section_calls = {'arc': section_arc,
915                  'archits': section_archits,
916                  'dmu': section_dmu,
917                  'l2arc': section_l2arc,
918                  'spl': section_spl,
919                  'tunables': section_tunables,
920                  'vdev': section_vdev,
921                  'zil': section_zil}
924 def main():
925     """Run program. The options to draw a graph and to print all data raw are
926     treated separately because they come with their own call.
927     """
929     kstats = get_kstats()
931     if ARGS.graph:
932         draw_graph(kstats)
933         sys.exit(0)
935     print_header()
937     if ARGS.raw:
938         print_raw(kstats)
940     elif ARGS.section:
942         try:
943             section_calls[ARGS.section](kstats)
944         except KeyError:
945             print('Error: Section "{0}" unknown'.format(ARGS.section))
946             sys.exit(1)
948     elif ARGS.page:
949         print('WARNING: Pages are deprecated, please use "--section"\n')
951         pages_to_calls = {1: 'arc',
952                           2: 'archits',
953                           3: 'l2arc',
954                           4: 'dmu',
955                           5: 'vdev',
956                           6: 'tunables'}
958         try:
959             call = pages_to_calls[ARGS.page]
960         except KeyError:
961             print('Error: Page "{0}" not supported'.format(ARGS.page))
962             sys.exit(1)
963         else:
964             section_calls[call](kstats)
966     else:
967         # If no parameters were given, we print all sections. We might want to
968         # change the sequence by hand
969         calls = sorted(section_calls.keys())
971         for section in calls:
972             section_calls[section](kstats)
974     sys.exit(0)
977 if __name__ == '__main__':
978     main()