zdb: fix printf() length for uint64_t devid
[zfs.git] / cmd / arc_summary
blob9c69ec4f8cccace85ba6a80d4db0cd5629b62e05
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                  'zfetch': 'zfetchstats',
68                  'zil': 'zil'}
70 parser = argparse.ArgumentParser(description=DESCRIPTION)
71 parser.add_argument('-a', '--alternate', action='store_true', default=False,
72                     help='use alternate formatting for tunables and SPL',
73                     dest='alt')
74 parser.add_argument('-d', '--description', action='store_true', default=False,
75                     help='print descriptions with tunables and SPL',
76                     dest='desc')
77 parser.add_argument('-g', '--graph', action='store_true', default=False,
78                     help='print graph on ARC use and exit', dest='graph')
79 parser.add_argument('-p', '--page', type=int, dest='page',
80                     help='print page by number (DEPRECATED, use "-s")')
81 parser.add_argument('-r', '--raw', action='store_true', default=False,
82                     help='dump all available data with minimal formatting',
83                     dest='raw')
84 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
85 ARGS = parser.parse_args()
88 if sys.platform.startswith('freebsd'):
89     # Requires py36-sysctl on FreeBSD
90     import sysctl
92     def is_value(ctl):
93         return ctl.type != sysctl.CTLTYPE_NODE
95     def namefmt(ctl, base='vfs.zfs.'):
96         # base is removed from the name
97         cut = len(base)
98         return ctl.name[cut:]
100     def load_kstats(section):
101         base = 'kstat.zfs.misc.{section}.'.format(section=section)
102         fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
103                                                       value=kstat.value)
104         kstats = sysctl.filter(base)
105         return [fmt(kstat) for kstat in kstats if is_value(kstat)]
107     def get_params(base):
108         ctls = sysctl.filter(base)
109         return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
111     def get_tunable_params():
112         return get_params('vfs.zfs')
114     def get_vdev_params():
115         return get_params('vfs.zfs.vdev')
117     def get_version_impl(request):
118         # FreeBSD reports versions for zpl and spa instead of zfs and spl.
119         name = {'zfs': 'zpl',
120                 'spl': 'spa'}[request]
121         mib = 'vfs.zfs.version.{}'.format(name)
122         version = sysctl.filter(mib)[0].value
123         return '{} version {}'.format(name, version)
125     def get_descriptions(_request):
126         ctls = sysctl.filter('vfs.zfs')
127         return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
130 elif sys.platform.startswith('linux'):
131     KSTAT_PATH = '/proc/spl/kstat/zfs'
132     SPL_PATH = '/sys/module/spl/parameters'
133     TUNABLES_PATH = '/sys/module/zfs/parameters'
135     def load_kstats(section):
136         path = os.path.join(KSTAT_PATH, section)
137         with open(path) as f:
138             return list(f)[2:] # Get rid of header
140     def get_params(basepath):
141         """Collect information on the Solaris Porting Layer (SPL) or the
142         tunables, depending on the PATH given. Does not check if PATH is
143         legal.
144         """
145         result = {}
146         for name in os.listdir(basepath):
147             path = os.path.join(basepath, name)
148             with open(path) as f:
149                 value = f.read()
150                 result[name] = value.strip()
151         return result
153     def get_spl_params():
154         return get_params(SPL_PATH)
156     def get_tunable_params():
157         return get_params(TUNABLES_PATH)
159     def get_vdev_params():
160         return get_params(TUNABLES_PATH)
162     def get_version_impl(request):
163         # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
164         # the version information. We switch to /sys/module/{spl,zfs}/version
165         # to make sure we get what is really loaded in the kernel
166         try:
167             with open("/sys/module/{}/version".format(request)) as f:
168                 return f.read().strip()
169         except:
170             return "(unknown)"
172     def get_descriptions(request):
173         """Get the descriptions of the Solaris Porting Layer (SPL) or the
174         tunables, return with minimal formatting.
175         """
177         if request not in ('spl', 'zfs'):
178             print('ERROR: description of "{0}" requested)'.format(request))
179             sys.exit(1)
181         descs = {}
182         target_prefix = 'parm:'
184         # We would prefer to do this with /sys/modules -- see the discussion at
185         # get_version() -- but there isn't a way to get the descriptions from
186         # there, so we fall back on modinfo
187         command = ["/sbin/modinfo", request, "-0"]
189         info = ''
191         try:
193             info = subprocess.run(command, stdout=subprocess.PIPE,
194                                   check=True, universal_newlines=True)
195             raw_output = info.stdout.split('\0')
197         except subprocess.CalledProcessError:
198             print("Error: Descriptions not available",
199                   "(can't access kernel module)")
200             sys.exit(1)
202         for line in raw_output:
204             if not line.startswith(target_prefix):
205                 continue
207             line = line[len(target_prefix):].strip()
208             name, raw_desc = line.split(':', 1)
209             desc = raw_desc.rsplit('(', 1)[0]
211             if desc == '':
212                 desc = '(No description found)'
214             descs[name.strip()] = desc.strip()
216         return descs
218 def handle_unraisableException(exc_type, exc_value=None, exc_traceback=None,
219                                err_msg=None, object=None):
220    handle_Exception(exc_type, object, exc_traceback)
222 def handle_Exception(ex_cls, ex, tb):
223     if ex_cls is KeyboardInterrupt:
224         sys.exit()
226     if ex_cls is BrokenPipeError:
227         # It turns out that while sys.exit() triggers an exception
228         # not handled message on Python 3.8+, os._exit() does not.
229         os._exit(0)
231     if ex_cls is OSError:
232       if ex.errno == errno.ENOTCONN:
233         sys.exit()
235     raise ex
237 if hasattr(sys,'unraisablehook'): # Python 3.8+
238     sys.unraisablehook = handle_unraisableException
239 sys.excepthook = handle_Exception
242 def cleanup_line(single_line):
243     """Format a raw line of data from /proc and isolate the name value
244     part, returning a tuple with each. Currently, this gets rid of the
245     middle '4'. For example "arc_no_grow    4    0" returns the tuple
246     ("arc_no_grow", "0").
247     """
248     name, _, value = single_line.split()
250     return name, value
253 def draw_graph(kstats_dict):
254     """Draw a primitive graph representing the basic information on the
255     ARC -- its size and the proportion used by MFU and MRU -- and quit.
256     We use max size of the ARC to calculate how full it is. This is a
257     very rough representation.
258     """
260     arc_stats = isolate_section('arcstats', kstats_dict)
262     GRAPH_INDENT = ' '*4
263     GRAPH_WIDTH = 60
264     arc_size = f_bytes(arc_stats['size'])
265     arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
266     mfu_size = f_bytes(arc_stats['mfu_size'])
267     mru_size = f_bytes(arc_stats['mru_size'])
268     meta_size = f_bytes(arc_stats['arc_meta_used'])
269     dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
270     dnode_size = f_bytes(arc_stats['dnode_size'])
272     info_form = ('ARC: {0} ({1})  MFU: {2}  MRU: {3}  META: {4} '
273                  'DNODE {5} ({6})')
274     info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
275                                  meta_size, dnode_size, dnode_limit)
276     info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
277     info_line = GRAPH_INDENT+info_spc+info_line
279     graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
281     mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
282     mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
283     arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
284     total_ticks = float(arc_perc)*GRAPH_WIDTH
285     mfu_ticks = mfu_perc*GRAPH_WIDTH
286     mru_ticks = mru_perc*GRAPH_WIDTH
287     other_ticks = total_ticks-(mfu_ticks+mru_ticks)
289     core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
290     core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
291     core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
293     for line in ('', info_line, graph_line, core_line, graph_line, ''):
294         print(line)
297 def f_bytes(byte_string):
298     """Return human-readable representation of a byte value in
299     powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
300     points. Values smaller than one KiB are returned without
301     decimal points. Note "bytes" is a reserved keyword.
302     """
304     prefixes = ([2**80, "YiB"],   # yobibytes (yotta)
305                 [2**70, "ZiB"],   # zebibytes (zetta)
306                 [2**60, "EiB"],   # exbibytes (exa)
307                 [2**50, "PiB"],   # pebibytes (peta)
308                 [2**40, "TiB"],   # tebibytes (tera)
309                 [2**30, "GiB"],   # gibibytes (giga)
310                 [2**20, "MiB"],   # mebibytes (mega)
311                 [2**10, "KiB"])   # kibibytes (kilo)
313     bites = int(byte_string)
315     if bites >= 2**10:
316         for limit, unit in prefixes:
318             if bites >= limit:
319                 value = bites / limit
320                 break
322         result = '{0:.1f} {1}'.format(value, unit)
323     else:
324         result = '{0} Bytes'.format(bites)
326     return result
329 def f_hits(hits_string):
330     """Create a human-readable representation of the number of hits.
331     The single-letter symbols used are SI to avoid the confusion caused
332     by the different "short scale" and "long scale" representations in
333     English, which use the same words for different values. See
334     https://en.wikipedia.org/wiki/Names_of_large_numbers and:
335     https://physics.nist.gov/cuu/Units/prefixes.html
336     """
338     numbers = ([10**24, 'Y'],  # yotta (septillion)
339                [10**21, 'Z'],  # zetta (sextillion)
340                [10**18, 'E'],  # exa   (quintrillion)
341                [10**15, 'P'],  # peta  (quadrillion)
342                [10**12, 'T'],  # tera  (trillion)
343                [10**9, 'G'],   # giga  (billion)
344                [10**6, 'M'],   # mega  (million)
345                [10**3, 'k'])   # kilo  (thousand)
347     hits = int(hits_string)
349     if hits >= 1000:
350         for limit, symbol in numbers:
352             if hits >= limit:
353                 value = hits/limit
354                 break
356         result = "%0.1f%s" % (value, symbol)
357     else:
358         result = "%d" % hits
360     return result
363 def f_perc(value1, value2):
364     """Calculate percentage and return in human-readable form. If
365     rounding produces the result '0.0' though the first number is
366     not zero, include a 'less-than' symbol to avoid confusion.
367     Division by zero is handled by returning 'n/a'; no error
368     is called.
369     """
371     v1 = float(value1)
372     v2 = float(value2)
374     try:
375         perc = 100 * v1/v2
376     except ZeroDivisionError:
377         result = 'n/a'
378     else:
379         result = '{0:0.1f} %'.format(perc)
381     if result == '0.0 %' and v1 > 0:
382         result = '< 0.1 %'
384     return result
387 def format_raw_line(name, value):
388     """For the --raw option for the tunable and SPL outputs, decide on the
389     correct formatting based on the --alternate flag.
390     """
392     if ARGS.alt:
393         result = '{0}{1}={2}'.format(INDENT, name, value)
394     else:
395         # Right-align the value within the line length if it fits,
396         # otherwise just separate it from the name by a single space.
397         fit = LINE_LENGTH - len(INDENT) - len(name)
398         overflow = len(value) + 1
399         w = max(fit, overflow)
400         result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
402     return result
405 def get_kstats():
406     """Collect information on the ZFS subsystem. The step does not perform any
407     further processing, giving us the option to only work on what is actually
408     needed. The name "kstat" is a holdover from the Solaris utility of the same
409     name.
410     """
412     result = {}
414     for section in SECTION_PATHS.values():
415         if section not in result:
416             result[section] = load_kstats(section)
418     return result
421 def get_version(request):
422     """Get the version number of ZFS or SPL on this machine for header.
423     Returns an error string, but does not raise an error, if we can't
424     get the ZFS/SPL version.
425     """
427     if request not in ('spl', 'zfs'):
428         error_msg = '(ERROR: "{0}" requested)'.format(request)
429         return error_msg
431     return get_version_impl(request)
434 def print_header():
435     """Print the initial heading with date and time as well as info on the
436     kernel and ZFS versions. This is not called for the graph.
437     """
439     # datetime is now recommended over time but we keep the exact formatting
440     # from the older version of arc_summary in case there are scripts
441     # that expect it in this way
442     daydate = time.strftime(DATE_FORMAT)
443     spc_date = LINE_LENGTH-len(daydate)
444     sys_version = os.uname()
446     sys_msg = sys_version.sysname+' '+sys_version.release
447     zfs = get_version('zfs')
448     spc_zfs = LINE_LENGTH-len(zfs)
450     machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
451     spl = get_version('spl')
452     spc_spl = LINE_LENGTH-len(spl)
454     print('\n'+('-'*LINE_LENGTH))
455     print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
456     print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
457     print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
460 def print_raw(kstats_dict):
461     """Print all available data from the system in a minimally sorted format.
462     This can be used as a source to be piped through 'grep'.
463     """
465     sections = sorted(kstats_dict.keys())
467     for section in sections:
469         print('\n{0}:'.format(section.upper()))
470         lines = sorted(kstats_dict[section])
472         for line in lines:
473             name, value = cleanup_line(line)
474             print(format_raw_line(name, value))
476     # Tunables and SPL must be handled separately because they come from a
477     # different source and have descriptions the user might request
478     print()
479     section_spl()
480     section_tunables()
483 def isolate_section(section_name, kstats_dict):
484     """From the complete information on all sections, retrieve only those
485     for one section.
486     """
488     try:
489         section_data = kstats_dict[section_name]
490     except KeyError:
491         print('ERROR: Data on {0} not available'.format(section_data))
492         sys.exit(1)
494     section_dict = dict(cleanup_line(l) for l in section_data)
496     return section_dict
499 # Formatted output helper functions
502 def prt_1(text, value):
503     """Print text and one value, no indent"""
504     spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
505     print('{0}{spc}{1}'.format(text, value, spc=spc))
508 def prt_i1(text, value):
509     """Print text and one value, with indent"""
510     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
511     print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
514 def prt_2(text, value1, value2):
515     """Print text and two values, no indent"""
516     values = '{0:>9}  {1:>9}'.format(value1, value2)
517     spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
518     print('{0}{spc}  {1}'.format(text, values, spc=spc))
521 def prt_i2(text, value1, value2):
522     """Print text and two values, with indent"""
523     values = '{0:>9}  {1:>9}'.format(value1, value2)
524     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
525     print(INDENT+'{0}{spc}  {1}'.format(text, values, spc=spc))
528 # The section output concentrates on important parameters instead of
529 # being exhaustive (that is what the --raw parameter is for)
532 def section_arc(kstats_dict):
533     """Give basic information on the ARC, MRU and MFU. This is the first
534     and most used section.
535     """
537     arc_stats = isolate_section('arcstats', kstats_dict)
539     throttle = arc_stats['memory_throttle_count']
541     if throttle == '0':
542         health = 'HEALTHY'
543     else:
544         health = 'THROTTLED'
546     prt_1('ARC status:', health)
547     prt_i1('Memory throttle count:', throttle)
548     print()
550     arc_size = arc_stats['size']
551     arc_target_size = arc_stats['c']
552     arc_max = arc_stats['c_max']
553     arc_min = arc_stats['c_min']
554     meta = arc_stats['meta']
555     pd = arc_stats['pd']
556     pm = arc_stats['pm']
557     anon_data = arc_stats['anon_data']
558     anon_metadata = arc_stats['anon_metadata']
559     mfu_data = arc_stats['mfu_data']
560     mfu_metadata = arc_stats['mfu_metadata']
561     mru_data = arc_stats['mru_data']
562     mru_metadata = arc_stats['mru_metadata']
563     mfug_data = arc_stats['mfu_ghost_data']
564     mfug_metadata = arc_stats['mfu_ghost_metadata']
565     mrug_data = arc_stats['mru_ghost_data']
566     mrug_metadata = arc_stats['mru_ghost_metadata']
567     unc_data = arc_stats['uncached_data']
568     unc_metadata = arc_stats['uncached_metadata']
569     bonus_size = arc_stats['bonus_size']
570     dnode_limit = arc_stats['arc_dnode_limit']
571     dnode_size = arc_stats['dnode_size']
572     dbuf_size = arc_stats['dbuf_size']
573     hdr_size = arc_stats['hdr_size']
574     l2_hdr_size = arc_stats['l2_hdr_size']
575     abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
576     target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
578     prt_2('ARC size (current):',
579           f_perc(arc_size, arc_max), f_bytes(arc_size))
580     prt_i2('Target size (adaptive):',
581            f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
582     prt_i2('Min size (hard limit):',
583            f_perc(arc_min, arc_max), f_bytes(arc_min))
584     prt_i2('Max size (high water):',
585            target_size_ratio, f_bytes(arc_max))
586     caches_size = int(anon_data)+int(anon_metadata)+\
587         int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
588         int(unc_data)+int(unc_metadata)
589     prt_i2('Anonymous data size:',
590            f_perc(anon_data, caches_size), f_bytes(anon_data))
591     prt_i2('Anonymous metadata size:',
592            f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
593     s = 4294967296
594     v = (s-int(pd))*(s-int(meta))/s
595     prt_i2('MFU data target:', f_perc(v, s),
596         f_bytes(v / 65536 * caches_size / 65536))
597     prt_i2('MFU data size:',
598            f_perc(mfu_data, caches_size), f_bytes(mfu_data))
599     prt_i1('MFU ghost data size:', f_bytes(mfug_data))
600     v = (s-int(pm))*int(meta)/s
601     prt_i2('MFU metadata target:', f_perc(v, s),
602         f_bytes(v / 65536 * caches_size / 65536))
603     prt_i2('MFU metadata size:',
604            f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
605     prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
606     v = int(pd)*(s-int(meta))/s
607     prt_i2('MRU data target:', f_perc(v, s),
608         f_bytes(v / 65536 * caches_size / 65536))
609     prt_i2('MRU data size:',
610            f_perc(mru_data, caches_size), f_bytes(mru_data))
611     prt_i1('MRU ghost data size:', f_bytes(mrug_data))
612     v = int(pm)*int(meta)/s
613     prt_i2('MRU metadata target:', f_perc(v, s),
614         f_bytes(v / 65536 * caches_size / 65536))
615     prt_i2('MRU metadata size:',
616            f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
617     prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
618     prt_i2('Uncached data size:',
619            f_perc(unc_data, caches_size), f_bytes(unc_data))
620     prt_i2('Uncached metadata size:',
621            f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
622     prt_i2('Bonus size:',
623            f_perc(bonus_size, arc_size), f_bytes(bonus_size))
624     prt_i2('Dnode cache target:',
625            f_perc(dnode_limit, arc_max), f_bytes(dnode_limit))
626     prt_i2('Dnode cache size:',
627            f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
628     prt_i2('Dbuf size:',
629            f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
630     prt_i2('Header size:',
631            f_perc(hdr_size, arc_size), f_bytes(hdr_size))
632     prt_i2('L2 header size:',
633            f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
634     prt_i2('ABD chunk waste size:',
635            f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
636     print()
638     print('ARC hash breakdown:')
639     prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
640     prt_i2('Elements current:',
641            f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
642            f_hits(arc_stats['hash_elements']))
643     prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
645     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
646     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
647     print()
649     print('ARC misc:')
650     prt_i1('Deleted:', f_hits(arc_stats['deleted']))
651     prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
652     prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
653     prt_i1('Eviction skips due to L2 writes:',
654            f_hits(arc_stats['evict_l2_skip']))
655     prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
656     prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
657     prt_i2('L2 eligible MFU evictions:',
658            f_perc(arc_stats['evict_l2_eligible_mfu'],
659            arc_stats['evict_l2_eligible']),
660            f_bytes(arc_stats['evict_l2_eligible_mfu']))
661     prt_i2('L2 eligible MRU evictions:',
662            f_perc(arc_stats['evict_l2_eligible_mru'],
663            arc_stats['evict_l2_eligible']),
664            f_bytes(arc_stats['evict_l2_eligible_mru']))
665     prt_i1('L2 ineligible evictions:',
666            f_bytes(arc_stats['evict_l2_ineligible']))
667     print()
670 def section_archits(kstats_dict):
671     """Print information on how the caches are accessed ("arc hits").
672     """
674     arc_stats = isolate_section('arcstats', kstats_dict)
675     all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
676         int(arc_stats['misses'])
678     prt_1('ARC total accesses:', f_hits(all_accesses))
679     ta_todo = (('Total hits:', arc_stats['hits']),
680                ('Total I/O hits:', arc_stats['iohits']),
681                ('Total misses:', arc_stats['misses']))
682     for title, value in ta_todo:
683         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
684     print()
686     dd_total = int(arc_stats['demand_data_hits']) +\
687         int(arc_stats['demand_data_iohits']) +\
688         int(arc_stats['demand_data_misses'])
689     prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
690          f_hits(dd_total))
691     dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
692                ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
693                ('Demand data misses:', arc_stats['demand_data_misses']))
694     for title, value in dd_todo:
695         prt_i2(title, f_perc(value, dd_total), f_hits(value))
696     print()
698     dm_total = int(arc_stats['demand_metadata_hits']) +\
699         int(arc_stats['demand_metadata_iohits']) +\
700         int(arc_stats['demand_metadata_misses'])
701     prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
702           f_hits(dm_total))
703     dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
704                ('Demand metadata I/O hits:',
705                 arc_stats['demand_metadata_iohits']),
706                ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
707     for title, value in dm_todo:
708         prt_i2(title, f_perc(value, dm_total), f_hits(value))
709     print()
711     pd_total = int(arc_stats['prefetch_data_hits']) +\
712         int(arc_stats['prefetch_data_iohits']) +\
713         int(arc_stats['prefetch_data_misses'])
714     prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
715           f_hits(pd_total))
716     pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
717                ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
718                ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
719     for title, value in pd_todo:
720         prt_i2(title, f_perc(value, pd_total), f_hits(value))
721     print()
723     pm_total = int(arc_stats['prefetch_metadata_hits']) +\
724         int(arc_stats['prefetch_metadata_iohits']) +\
725         int(arc_stats['prefetch_metadata_misses'])
726     prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
727           f_hits(pm_total))
728     pm_todo = (('Prefetch metadata hits:',
729                 arc_stats['prefetch_metadata_hits']),
730                ('Prefetch metadata I/O hits:',
731                 arc_stats['prefetch_metadata_iohits']),
732                ('Prefetch metadata misses:',
733                 arc_stats['prefetch_metadata_misses']))
734     for title, value in pm_todo:
735         prt_i2(title, f_perc(value, pm_total), f_hits(value))
736     print()
738     all_prefetches = int(arc_stats['predictive_prefetch'])+\
739         int(arc_stats['prescient_prefetch'])
740     prt_2('ARC predictive prefetches:',
741            f_perc(arc_stats['predictive_prefetch'], all_prefetches),
742            f_hits(arc_stats['predictive_prefetch']))
743     prt_i2('Demand hits after predictive:',
744            f_perc(arc_stats['demand_hit_predictive_prefetch'],
745                   arc_stats['predictive_prefetch']),
746            f_hits(arc_stats['demand_hit_predictive_prefetch']))
747     prt_i2('Demand I/O hits after predictive:',
748            f_perc(arc_stats['demand_iohit_predictive_prefetch'],
749                   arc_stats['predictive_prefetch']),
750            f_hits(arc_stats['demand_iohit_predictive_prefetch']))
751     never = int(arc_stats['predictive_prefetch']) -\
752         int(arc_stats['demand_hit_predictive_prefetch']) -\
753         int(arc_stats['demand_iohit_predictive_prefetch'])
754     prt_i2('Never demanded after predictive:',
755            f_perc(never, arc_stats['predictive_prefetch']),
756            f_hits(never))
757     print()
759     prt_2('ARC prescient prefetches:',
760            f_perc(arc_stats['prescient_prefetch'], all_prefetches),
761            f_hits(arc_stats['prescient_prefetch']))
762     prt_i2('Demand hits after prescient:',
763            f_perc(arc_stats['demand_hit_prescient_prefetch'],
764                   arc_stats['prescient_prefetch']),
765            f_hits(arc_stats['demand_hit_prescient_prefetch']))
766     prt_i2('Demand I/O hits after prescient:',
767            f_perc(arc_stats['demand_iohit_prescient_prefetch'],
768                   arc_stats['prescient_prefetch']),
769            f_hits(arc_stats['demand_iohit_prescient_prefetch']))
770     never = int(arc_stats['prescient_prefetch'])-\
771         int(arc_stats['demand_hit_prescient_prefetch'])-\
772         int(arc_stats['demand_iohit_prescient_prefetch'])
773     prt_i2('Never demanded after prescient:',
774            f_perc(never, arc_stats['prescient_prefetch']),
775            f_hits(never))
776     print()
778     print('ARC states hits of all accesses:')
779     cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
780                ('Most recently used (MRU):', arc_stats['mru_hits']),
781                ('Most frequently used (MFU) ghost:',
782                 arc_stats['mfu_ghost_hits']),
783                ('Most recently used (MRU) ghost:',
784                 arc_stats['mru_ghost_hits']),
785                ('Uncached:', arc_stats['uncached_hits']))
786     for title, value in cl_todo:
787         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
788     print()
791 def section_dmu(kstats_dict):
792     """Collect information on the DMU"""
794     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
796     zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
798     prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
799     prt_i2('Stream hits:',
800            f_perc(zfetch_stats['hits'], zfetch_access_total),
801            f_hits(zfetch_stats['hits']))
802     prt_i2('Stream misses:',
803            f_perc(zfetch_stats['misses'], zfetch_access_total),
804            f_hits(zfetch_stats['misses']))
805     prt_i2('Streams limit reached:',
806            f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
807            f_hits(zfetch_stats['max_streams']))
808     prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
809     print()
812 def section_l2arc(kstats_dict):
813     """Collect information on L2ARC device if present. If not, tell user
814     that we're skipping the section.
815     """
817     # The L2ARC statistics live in the same section as the normal ARC stuff
818     arc_stats = isolate_section('arcstats', kstats_dict)
820     if arc_stats['l2_size'] == '0':
821         print('L2ARC not detected, skipping section\n')
822         return
824     l2_errors = int(arc_stats['l2_writes_error']) +\
825         int(arc_stats['l2_cksum_bad']) +\
826         int(arc_stats['l2_io_error'])
828     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
829     health = 'HEALTHY'
831     if l2_errors > 0:
832         health = 'DEGRADED'
834     prt_1('L2ARC status:', health)
836     l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
837                ('Free on write:', 'l2_free_on_write'),
838                ('R/W clashes:', 'l2_rw_clash'),
839                ('Bad checksums:', 'l2_cksum_bad'),
840                ('Read errors:', 'l2_io_error'),
841                ('Write errors:', 'l2_writes_error'))
843     for title, value in l2_todo:
844         prt_i1(title, f_hits(arc_stats[value]))
846     print()
847     prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
848     prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
849            f_bytes(arc_stats['l2_asize']))
850     prt_i2('Header size:',
851            f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
852            f_bytes(arc_stats['l2_hdr_size']))
853     prt_i2('MFU allocated size:',
854            f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
855            f_bytes(arc_stats['l2_mfu_asize']))
856     prt_i2('MRU allocated size:',
857            f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
858            f_bytes(arc_stats['l2_mru_asize']))
859     prt_i2('Prefetch allocated size:',
860            f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
861            f_bytes(arc_stats['l2_prefetch_asize']))
862     prt_i2('Data (buffer content) allocated size:',
863            f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
864            f_bytes(arc_stats['l2_bufc_data_asize']))
865     prt_i2('Metadata (buffer content) allocated size:',
866            f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
867            f_bytes(arc_stats['l2_bufc_metadata_asize']))
869     print()
870     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
871     prt_i2('Hit ratio:',
872            f_perc(arc_stats['l2_hits'], l2_access_total),
873            f_hits(arc_stats['l2_hits']))
874     prt_i2('Miss ratio:',
875            f_perc(arc_stats['l2_misses'], l2_access_total),
876            f_hits(arc_stats['l2_misses']))
878     print()
879     print('L2ARC I/O:')
880     prt_i2('Reads:',
881            f_bytes(arc_stats['l2_read_bytes']),
882            f_hits(arc_stats['l2_hits']))
883     prt_i2('Writes:',
884            f_bytes(arc_stats['l2_write_bytes']),
885            f_hits(arc_stats['l2_writes_sent']))
887     print()
888     print('L2ARC evicts:')
889     prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
890     prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
891     print()
894 def section_spl(*_):
895     """Print the SPL parameters, if requested with alternative format
896     and/or descriptions. This does not use kstats.
897     """
899     if sys.platform.startswith('freebsd'):
900         # No SPL support in FreeBSD
901         return
903     spls = get_spl_params()
904     keylist = sorted(spls.keys())
905     print('Solaris Porting Layer (SPL):')
907     if ARGS.desc:
908         descriptions = get_descriptions('spl')
910     for key in keylist:
911         value = spls[key]
913         if ARGS.desc:
914             try:
915                 print(INDENT+'#', descriptions[key])
916             except KeyError:
917                 print(INDENT+'# (No description found)')  # paranoid
919         print(format_raw_line(key, value))
921     print()
924 def section_tunables(*_):
925     """Print the tunables, if requested with alternative format and/or
926     descriptions. This does not use kstasts.
927     """
929     tunables = get_tunable_params()
930     keylist = sorted(tunables.keys())
931     print('Tunables:')
933     if ARGS.desc:
934         descriptions = get_descriptions('zfs')
936     for key in keylist:
937         value = tunables[key]
939         if ARGS.desc:
940             try:
941                 print(INDENT+'#', descriptions[key])
942             except KeyError:
943                 print(INDENT+'# (No description found)')  # paranoid
945         print(format_raw_line(key, value))
947     print()
950 def section_zil(kstats_dict):
951     """Collect information on the ZFS Intent Log. Some of the information
952     taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
953     """
955     zil_stats = isolate_section('zil', kstats_dict)
957     prt_1('ZIL committed transactions:',
958           f_hits(zil_stats['zil_itx_count']))
959     prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
960     prt_i1('Flushes to stable storage:',
961            f_hits(zil_stats['zil_commit_writer_count']))
962     prt_i2('Transactions to SLOG storage pool:',
963            f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
964            f_hits(zil_stats['zil_itx_metaslab_slog_count']))
965     prt_i2('Transactions to non-SLOG storage pool:',
966            f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
967            f_hits(zil_stats['zil_itx_metaslab_normal_count']))
968     print()
971 section_calls = {'arc': section_arc,
972                  'archits': section_archits,
973                  'dmu': section_dmu,
974                  'l2arc': section_l2arc,
975                  'spl': section_spl,
976                  'tunables': section_tunables,
977                  'zil': section_zil}
980 def main():
981     """Run program. The options to draw a graph and to print all data raw are
982     treated separately because they come with their own call.
983     """
985     kstats = get_kstats()
987     if ARGS.graph:
988         draw_graph(kstats)
989         sys.exit(0)
991     print_header()
993     if ARGS.raw:
994         print_raw(kstats)
996     elif ARGS.section:
998         try:
999             section_calls[ARGS.section](kstats)
1000         except KeyError:
1001             print('Error: Section "{0}" unknown'.format(ARGS.section))
1002             sys.exit(1)
1004     elif ARGS.page:
1005         print('WARNING: Pages are deprecated, please use "--section"\n')
1007         pages_to_calls = {1: 'arc',
1008                           2: 'archits',
1009                           3: 'l2arc',
1010                           4: 'dmu',
1011                           5: 'vdev',
1012                           6: 'tunables'}
1014         try:
1015             call = pages_to_calls[ARGS.page]
1016         except KeyError:
1017             print('Error: Page "{0}" not supported'.format(ARGS.page))
1018             sys.exit(1)
1019         else:
1020             section_calls[call](kstats)
1022     else:
1023         # If no parameters were given, we print all sections. We might want to
1024         # change the sequence by hand
1025         calls = sorted(section_calls.keys())
1027         for section in calls:
1028             section_calls[section](kstats)
1030     sys.exit(0)
1033 if __name__ == '__main__':
1034     main()