ZTS: Add additional exceptions
[zfs.git] / cmd / arc_summary
blobc24d400fa39a9c4310c71cdbdaf3f8b368c9f433
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 = 70
264     arc_max = int(arc_stats['c_max'])
265     arc_size = f_bytes(arc_stats['size'])
266     arc_perc = f_perc(arc_stats['size'], arc_max)
267     data_size = f_bytes(arc_stats['data_size'])
268     meta_size = f_bytes(arc_stats['metadata_size'])
269     dnode_size = f_bytes(arc_stats['dnode_size'])
271     info_form = ('ARC: {0} ({1}) Data: {2} Meta: {3} Dnode: {4}')
272     info_line = info_form.format(arc_size, arc_perc, data_size, meta_size,
273                                  dnode_size)
274     info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
275     info_line = GRAPH_INDENT+info_spc+info_line
277     graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
279     arc_perc = float(int(arc_stats['size'])/arc_max)
280     data_perc = float(int(arc_stats['data_size'])/arc_max)
281     meta_perc = float(int(arc_stats['metadata_size'])/arc_max)
282     dnode_perc = float(int(arc_stats['dnode_size'])/arc_max)
283     total_ticks = float(arc_perc)*GRAPH_WIDTH
284     data_ticks = data_perc*GRAPH_WIDTH
285     meta_ticks = meta_perc*GRAPH_WIDTH
286     dnode_ticks = dnode_perc*GRAPH_WIDTH
287     other_ticks = total_ticks-(data_ticks+meta_ticks+dnode_ticks)
289     core_form = 'D'*int(data_ticks)+'M'*int(meta_ticks)+'N'*int(dnode_ticks)+\
290         'O'*int(other_ticks)
291     core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
292     core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
294     for line in ('', info_line, graph_line, core_line, graph_line, ''):
295         print(line)
298 def f_bytes(byte_string):
299     """Return human-readable representation of a byte value in
300     powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
301     points. Values smaller than one KiB are returned without
302     decimal points. Note "bytes" is a reserved keyword.
303     """
305     prefixes = ([2**80, "YiB"],   # yobibytes (yotta)
306                 [2**70, "ZiB"],   # zebibytes (zetta)
307                 [2**60, "EiB"],   # exbibytes (exa)
308                 [2**50, "PiB"],   # pebibytes (peta)
309                 [2**40, "TiB"],   # tebibytes (tera)
310                 [2**30, "GiB"],   # gibibytes (giga)
311                 [2**20, "MiB"],   # mebibytes (mega)
312                 [2**10, "KiB"])   # kibibytes (kilo)
314     bites = int(byte_string)
316     if bites >= 2**10:
317         for limit, unit in prefixes:
319             if bites >= limit:
320                 value = bites / limit
321                 break
323         result = '{0:.1f} {1}'.format(value, unit)
324     else:
325         result = '{0} Bytes'.format(bites)
327     return result
330 def f_hits(hits_string):
331     """Create a human-readable representation of the number of hits.
332     The single-letter symbols used are SI to avoid the confusion caused
333     by the different "short scale" and "long scale" representations in
334     English, which use the same words for different values. See
335     https://en.wikipedia.org/wiki/Names_of_large_numbers and:
336     https://physics.nist.gov/cuu/Units/prefixes.html
337     """
339     numbers = ([10**24, 'Y'],  # yotta (septillion)
340                [10**21, 'Z'],  # zetta (sextillion)
341                [10**18, 'E'],  # exa   (quintrillion)
342                [10**15, 'P'],  # peta  (quadrillion)
343                [10**12, 'T'],  # tera  (trillion)
344                [10**9, 'G'],   # giga  (billion)
345                [10**6, 'M'],   # mega  (million)
346                [10**3, 'k'])   # kilo  (thousand)
348     hits = int(hits_string)
350     if hits >= 1000:
351         for limit, symbol in numbers:
353             if hits >= limit:
354                 value = hits/limit
355                 break
357         result = "%0.1f%s" % (value, symbol)
358     else:
359         result = "%d" % hits
361     return result
364 def f_perc(value1, value2):
365     """Calculate percentage and return in human-readable form. If
366     rounding produces the result '0.0' though the first number is
367     not zero, include a 'less-than' symbol to avoid confusion.
368     Division by zero is handled by returning 'n/a'; no error
369     is called.
370     """
372     v1 = float(value1)
373     v2 = float(value2)
375     try:
376         perc = 100 * v1/v2
377     except ZeroDivisionError:
378         result = 'n/a'
379     else:
380         result = '{0:0.1f} %'.format(perc)
382     if result == '0.0 %' and v1 > 0:
383         result = '< 0.1 %'
385     return result
388 def format_raw_line(name, value):
389     """For the --raw option for the tunable and SPL outputs, decide on the
390     correct formatting based on the --alternate flag.
391     """
393     if ARGS.alt:
394         result = '{0}{1}={2}'.format(INDENT, name, value)
395     else:
396         # Right-align the value within the line length if it fits,
397         # otherwise just separate it from the name by a single space.
398         fit = LINE_LENGTH - len(INDENT) - len(name)
399         overflow = len(value) + 1
400         w = max(fit, overflow)
401         result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
403     return result
406 def get_kstats():
407     """Collect information on the ZFS subsystem. The step does not perform any
408     further processing, giving us the option to only work on what is actually
409     needed. The name "kstat" is a holdover from the Solaris utility of the same
410     name.
411     """
413     result = {}
415     for section in SECTION_PATHS.values():
416         if section not in result:
417             result[section] = load_kstats(section)
419     return result
422 def get_version(request):
423     """Get the version number of ZFS or SPL on this machine for header.
424     Returns an error string, but does not raise an error, if we can't
425     get the ZFS/SPL version.
426     """
428     if request not in ('spl', 'zfs'):
429         error_msg = '(ERROR: "{0}" requested)'.format(request)
430         return error_msg
432     return get_version_impl(request)
435 def print_header():
436     """Print the initial heading with date and time as well as info on the
437     kernel and ZFS versions. This is not called for the graph.
438     """
440     # datetime is now recommended over time but we keep the exact formatting
441     # from the older version of arc_summary in case there are scripts
442     # that expect it in this way
443     daydate = time.strftime(DATE_FORMAT)
444     spc_date = LINE_LENGTH-len(daydate)
445     sys_version = os.uname()
447     sys_msg = sys_version.sysname+' '+sys_version.release
448     zfs = get_version('zfs')
449     spc_zfs = LINE_LENGTH-len(zfs)
451     machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
452     spl = get_version('spl')
453     spc_spl = LINE_LENGTH-len(spl)
455     print('\n'+('-'*LINE_LENGTH))
456     print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
457     print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
458     print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
461 def print_raw(kstats_dict):
462     """Print all available data from the system in a minimally sorted format.
463     This can be used as a source to be piped through 'grep'.
464     """
466     sections = sorted(kstats_dict.keys())
468     for section in sections:
470         print('\n{0}:'.format(section.upper()))
471         lines = sorted(kstats_dict[section])
473         for line in lines:
474             name, value = cleanup_line(line)
475             print(format_raw_line(name, value))
477     # Tunables and SPL must be handled separately because they come from a
478     # different source and have descriptions the user might request
479     print()
480     section_spl()
481     section_tunables()
484 def isolate_section(section_name, kstats_dict):
485     """From the complete information on all sections, retrieve only those
486     for one section.
487     """
489     try:
490         section_data = kstats_dict[section_name]
491     except KeyError:
492         print('ERROR: Data on {0} not available'.format(section_data))
493         sys.exit(1)
495     section_dict = dict(cleanup_line(l) for l in section_data)
497     return section_dict
500 # Formatted output helper functions
503 def prt_1(text, value):
504     """Print text and one value, no indent"""
505     spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
506     print('{0}{spc}{1}'.format(text, value, spc=spc))
509 def prt_i1(text, value):
510     """Print text and one value, with indent"""
511     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
512     print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
515 def prt_2(text, value1, value2):
516     """Print text and two values, no indent"""
517     values = '{0:>9}  {1:>9}'.format(value1, value2)
518     spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
519     print('{0}{spc}  {1}'.format(text, values, spc=spc))
522 def prt_i2(text, value1, value2):
523     """Print text and two values, with indent"""
524     values = '{0:>9}  {1:>9}'.format(value1, value2)
525     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
526     print(INDENT+'{0}{spc}  {1}'.format(text, values, spc=spc))
529 # The section output concentrates on important parameters instead of
530 # being exhaustive (that is what the --raw parameter is for)
533 def section_arc(kstats_dict):
534     """Give basic information on the ARC, MRU and MFU. This is the first
535     and most used section.
536     """
538     arc_stats = isolate_section('arcstats', kstats_dict)
540     memory_all = arc_stats['memory_all_bytes']
541     memory_free = arc_stats['memory_free_bytes']
542     memory_avail = arc_stats['memory_available_bytes']
543     arc_size = arc_stats['size']
544     arc_target_size = arc_stats['c']
545     arc_max = arc_stats['c_max']
546     arc_min = arc_stats['c_min']
547     dnode_limit = arc_stats['arc_dnode_limit']
549     print('ARC status:')
550     prt_i1('Total memory size:', f_bytes(memory_all))
551     prt_i2('Min target size:', f_perc(arc_min, memory_all), f_bytes(arc_min))
552     prt_i2('Max target size:', f_perc(arc_max, memory_all), f_bytes(arc_max))
553     prt_i2('Target size (adaptive):',
554            f_perc(arc_size, arc_max), f_bytes(arc_target_size))
555     prt_i2('Current size:', f_perc(arc_size, arc_max), f_bytes(arc_size))
556     prt_i1('Free memory size:', f_bytes(memory_free))
557     prt_i1('Available memory size:', f_bytes(memory_avail))
558     print()
560     compressed_size = arc_stats['compressed_size']
561     overhead_size = arc_stats['overhead_size']
562     bonus_size = arc_stats['bonus_size']
563     dnode_size = arc_stats['dnode_size']
564     dbuf_size = arc_stats['dbuf_size']
565     hdr_size = arc_stats['hdr_size']
566     l2_hdr_size = arc_stats['l2_hdr_size']
567     abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
569     prt_1('ARC structural breakdown (current size):', f_bytes(arc_size))
570     prt_i2('Compressed size:',
571            f_perc(compressed_size, arc_size), f_bytes(compressed_size))
572     prt_i2('Overhead size:',
573            f_perc(overhead_size, arc_size), f_bytes(overhead_size))
574     prt_i2('Bonus size:',
575            f_perc(bonus_size, arc_size), f_bytes(bonus_size))
576     prt_i2('Dnode size:',
577            f_perc(dnode_size, arc_size), f_bytes(dnode_size))
578     prt_i2('Dbuf size:',
579            f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
580     prt_i2('Header size:',
581            f_perc(hdr_size, arc_size), f_bytes(hdr_size))
582     prt_i2('L2 header size:',
583            f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
584     prt_i2('ABD chunk waste size:',
585            f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
586     print()
588     meta = arc_stats['meta']
589     pd = arc_stats['pd']
590     pm = arc_stats['pm']
591     data_size = arc_stats['data_size']
592     metadata_size = arc_stats['metadata_size']
593     anon_data = arc_stats['anon_data']
594     anon_metadata = arc_stats['anon_metadata']
595     mfu_data = arc_stats['mfu_data']
596     mfu_metadata = arc_stats['mfu_metadata']
597     mfu_edata = arc_stats['mfu_evictable_data']
598     mfu_emetadata = arc_stats['mfu_evictable_metadata']
599     mru_data = arc_stats['mru_data']
600     mru_metadata = arc_stats['mru_metadata']
601     mru_edata = arc_stats['mru_evictable_data']
602     mru_emetadata = arc_stats['mru_evictable_metadata']
603     mfug_data = arc_stats['mfu_ghost_data']
604     mfug_metadata = arc_stats['mfu_ghost_metadata']
605     mrug_data = arc_stats['mru_ghost_data']
606     mrug_metadata = arc_stats['mru_ghost_metadata']
607     unc_data = arc_stats['uncached_data']
608     unc_metadata = arc_stats['uncached_metadata']
609     caches_size = int(anon_data)+int(anon_metadata)+\
610         int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
611         int(unc_data)+int(unc_metadata)
613     prt_1('ARC types breakdown (compressed + overhead):', f_bytes(caches_size))
614     prt_i2('Data size:',
615            f_perc(data_size, caches_size), f_bytes(data_size))
616     prt_i2('Metadata size:',
617            f_perc(metadata_size, caches_size), f_bytes(metadata_size))
618     print()
620     prt_1('ARC states breakdown (compressed + overhead):', f_bytes(caches_size))
621     prt_i2('Anonymous data size:',
622            f_perc(anon_data, caches_size), f_bytes(anon_data))
623     prt_i2('Anonymous metadata size:',
624            f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
625     s = 4294967296
626     v = (s-int(pd))*(s-int(meta))/s
627     prt_i2('MFU data target:', f_perc(v, s),
628         f_bytes(v / 65536 * caches_size / 65536))
629     prt_i2('MFU data size:',
630            f_perc(mfu_data, caches_size), f_bytes(mfu_data))
631     prt_i2('MFU evictable data size:',
632            f_perc(mfu_edata, caches_size), f_bytes(mfu_edata))
633     prt_i1('MFU ghost data size:', f_bytes(mfug_data))
634     v = (s-int(pm))*int(meta)/s
635     prt_i2('MFU metadata target:', f_perc(v, s),
636         f_bytes(v / 65536 * caches_size / 65536))
637     prt_i2('MFU metadata size:',
638            f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
639     prt_i2('MFU evictable metadata size:',
640            f_perc(mfu_emetadata, caches_size), f_bytes(mfu_emetadata))
641     prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
642     v = int(pd)*(s-int(meta))/s
643     prt_i2('MRU data target:', f_perc(v, s),
644         f_bytes(v / 65536 * caches_size / 65536))
645     prt_i2('MRU data size:',
646            f_perc(mru_data, caches_size), f_bytes(mru_data))
647     prt_i2('MRU evictable data size:',
648            f_perc(mru_edata, caches_size), f_bytes(mru_edata))
649     prt_i1('MRU ghost data size:', f_bytes(mrug_data))
650     v = int(pm)*int(meta)/s
651     prt_i2('MRU metadata target:', f_perc(v, s),
652         f_bytes(v / 65536 * caches_size / 65536))
653     prt_i2('MRU metadata size:',
654            f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
655     prt_i2('MRU evictable metadata size:',
656            f_perc(mru_emetadata, caches_size), f_bytes(mru_emetadata))
657     prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
658     prt_i2('Uncached data size:',
659            f_perc(unc_data, caches_size), f_bytes(unc_data))
660     prt_i2('Uncached metadata size:',
661            f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
662     print()
664     print('ARC hash breakdown:')
665     prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
666     prt_i2('Elements current:',
667            f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
668            f_hits(arc_stats['hash_elements']))
669     prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
671     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
672     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
673     print()
675     print('ARC misc:')
676     prt_i1('Memory throttles:', arc_stats['memory_throttle_count'])
677     prt_i1('Memory direct reclaims:', arc_stats['memory_direct_count'])
678     prt_i1('Memory indirect reclaims:', arc_stats['memory_indirect_count'])
679     prt_i1('Deleted:', f_hits(arc_stats['deleted']))
680     prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
681     prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
682     prt_i1('Eviction skips due to L2 writes:',
683            f_hits(arc_stats['evict_l2_skip']))
684     prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
685     prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
686     prt_i2('L2 eligible MFU evictions:',
687            f_perc(arc_stats['evict_l2_eligible_mfu'],
688            arc_stats['evict_l2_eligible']),
689            f_bytes(arc_stats['evict_l2_eligible_mfu']))
690     prt_i2('L2 eligible MRU evictions:',
691            f_perc(arc_stats['evict_l2_eligible_mru'],
692            arc_stats['evict_l2_eligible']),
693            f_bytes(arc_stats['evict_l2_eligible_mru']))
694     prt_i1('L2 ineligible evictions:',
695            f_bytes(arc_stats['evict_l2_ineligible']))
696     print()
699 def section_archits(kstats_dict):
700     """Print information on how the caches are accessed ("arc hits").
701     """
703     arc_stats = isolate_section('arcstats', kstats_dict)
704     all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
705         int(arc_stats['misses'])
707     prt_1('ARC total accesses:', f_hits(all_accesses))
708     ta_todo = (('Total hits:', arc_stats['hits']),
709                ('Total I/O hits:', arc_stats['iohits']),
710                ('Total misses:', arc_stats['misses']))
711     for title, value in ta_todo:
712         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
713     print()
715     dd_total = int(arc_stats['demand_data_hits']) +\
716         int(arc_stats['demand_data_iohits']) +\
717         int(arc_stats['demand_data_misses'])
718     prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
719          f_hits(dd_total))
720     dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
721                ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
722                ('Demand data misses:', arc_stats['demand_data_misses']))
723     for title, value in dd_todo:
724         prt_i2(title, f_perc(value, dd_total), f_hits(value))
725     print()
727     dm_total = int(arc_stats['demand_metadata_hits']) +\
728         int(arc_stats['demand_metadata_iohits']) +\
729         int(arc_stats['demand_metadata_misses'])
730     prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
731           f_hits(dm_total))
732     dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
733                ('Demand metadata I/O hits:',
734                 arc_stats['demand_metadata_iohits']),
735                ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
736     for title, value in dm_todo:
737         prt_i2(title, f_perc(value, dm_total), f_hits(value))
738     print()
740     pd_total = int(arc_stats['prefetch_data_hits']) +\
741         int(arc_stats['prefetch_data_iohits']) +\
742         int(arc_stats['prefetch_data_misses'])
743     prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
744           f_hits(pd_total))
745     pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
746                ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
747                ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
748     for title, value in pd_todo:
749         prt_i2(title, f_perc(value, pd_total), f_hits(value))
750     print()
752     pm_total = int(arc_stats['prefetch_metadata_hits']) +\
753         int(arc_stats['prefetch_metadata_iohits']) +\
754         int(arc_stats['prefetch_metadata_misses'])
755     prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
756           f_hits(pm_total))
757     pm_todo = (('Prefetch metadata hits:',
758                 arc_stats['prefetch_metadata_hits']),
759                ('Prefetch metadata I/O hits:',
760                 arc_stats['prefetch_metadata_iohits']),
761                ('Prefetch metadata misses:',
762                 arc_stats['prefetch_metadata_misses']))
763     for title, value in pm_todo:
764         prt_i2(title, f_perc(value, pm_total), f_hits(value))
765     print()
767     all_prefetches = int(arc_stats['predictive_prefetch'])+\
768         int(arc_stats['prescient_prefetch'])
769     prt_2('ARC predictive prefetches:',
770            f_perc(arc_stats['predictive_prefetch'], all_prefetches),
771            f_hits(arc_stats['predictive_prefetch']))
772     prt_i2('Demand hits after predictive:',
773            f_perc(arc_stats['demand_hit_predictive_prefetch'],
774                   arc_stats['predictive_prefetch']),
775            f_hits(arc_stats['demand_hit_predictive_prefetch']))
776     prt_i2('Demand I/O hits after predictive:',
777            f_perc(arc_stats['demand_iohit_predictive_prefetch'],
778                   arc_stats['predictive_prefetch']),
779            f_hits(arc_stats['demand_iohit_predictive_prefetch']))
780     never = int(arc_stats['predictive_prefetch']) -\
781         int(arc_stats['demand_hit_predictive_prefetch']) -\
782         int(arc_stats['demand_iohit_predictive_prefetch'])
783     prt_i2('Never demanded after predictive:',
784            f_perc(never, arc_stats['predictive_prefetch']),
785            f_hits(never))
786     print()
788     prt_2('ARC prescient prefetches:',
789            f_perc(arc_stats['prescient_prefetch'], all_prefetches),
790            f_hits(arc_stats['prescient_prefetch']))
791     prt_i2('Demand hits after prescient:',
792            f_perc(arc_stats['demand_hit_prescient_prefetch'],
793                   arc_stats['prescient_prefetch']),
794            f_hits(arc_stats['demand_hit_prescient_prefetch']))
795     prt_i2('Demand I/O hits after prescient:',
796            f_perc(arc_stats['demand_iohit_prescient_prefetch'],
797                   arc_stats['prescient_prefetch']),
798            f_hits(arc_stats['demand_iohit_prescient_prefetch']))
799     never = int(arc_stats['prescient_prefetch'])-\
800         int(arc_stats['demand_hit_prescient_prefetch'])-\
801         int(arc_stats['demand_iohit_prescient_prefetch'])
802     prt_i2('Never demanded after prescient:',
803            f_perc(never, arc_stats['prescient_prefetch']),
804            f_hits(never))
805     print()
807     print('ARC states hits of all accesses:')
808     cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
809                ('Most recently used (MRU):', arc_stats['mru_hits']),
810                ('Most frequently used (MFU) ghost:',
811                 arc_stats['mfu_ghost_hits']),
812                ('Most recently used (MRU) ghost:',
813                 arc_stats['mru_ghost_hits']),
814                ('Uncached:', arc_stats['uncached_hits']))
815     for title, value in cl_todo:
816         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
817     print()
820 def section_dmu(kstats_dict):
821     """Collect information on the DMU"""
823     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
825     zfetch_access_total = int(zfetch_stats['hits']) +\
826         int(zfetch_stats['future']) + int(zfetch_stats['stride']) +\
827         int(zfetch_stats['past']) + int(zfetch_stats['misses'])
829     prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
830     prt_i2('Stream hits:',
831            f_perc(zfetch_stats['hits'], zfetch_access_total),
832            f_hits(zfetch_stats['hits']))
833     future = int(zfetch_stats['future']) + int(zfetch_stats['stride'])
834     prt_i2('Hits ahead of stream:', f_perc(future, zfetch_access_total),
835            f_hits(future))
836     prt_i2('Hits behind stream:',
837            f_perc(zfetch_stats['past'], zfetch_access_total),
838            f_hits(zfetch_stats['past']))
839     prt_i2('Stream misses:',
840            f_perc(zfetch_stats['misses'], zfetch_access_total),
841            f_hits(zfetch_stats['misses']))
842     prt_i2('Streams limit reached:',
843            f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
844            f_hits(zfetch_stats['max_streams']))
845     prt_i1('Stream strides:', f_hits(zfetch_stats['stride']))
846     prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
847     print()
850 def section_l2arc(kstats_dict):
851     """Collect information on L2ARC device if present. If not, tell user
852     that we're skipping the section.
853     """
855     # The L2ARC statistics live in the same section as the normal ARC stuff
856     arc_stats = isolate_section('arcstats', kstats_dict)
858     if arc_stats['l2_size'] == '0':
859         print('L2ARC not detected, skipping section\n')
860         return
862     l2_errors = int(arc_stats['l2_writes_error']) +\
863         int(arc_stats['l2_cksum_bad']) +\
864         int(arc_stats['l2_io_error'])
866     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
867     health = 'HEALTHY'
869     if l2_errors > 0:
870         health = 'DEGRADED'
872     prt_1('L2ARC status:', health)
874     l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
875                ('Free on write:', 'l2_free_on_write'),
876                ('R/W clashes:', 'l2_rw_clash'),
877                ('Bad checksums:', 'l2_cksum_bad'),
878                ('Read errors:', 'l2_io_error'),
879                ('Write errors:', 'l2_writes_error'))
881     for title, value in l2_todo:
882         prt_i1(title, f_hits(arc_stats[value]))
884     print()
885     prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
886     prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
887            f_bytes(arc_stats['l2_asize']))
888     prt_i2('Header size:',
889            f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
890            f_bytes(arc_stats['l2_hdr_size']))
891     prt_i2('MFU allocated size:',
892            f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
893            f_bytes(arc_stats['l2_mfu_asize']))
894     prt_i2('MRU allocated size:',
895            f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
896            f_bytes(arc_stats['l2_mru_asize']))
897     prt_i2('Prefetch allocated size:',
898            f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
899            f_bytes(arc_stats['l2_prefetch_asize']))
900     prt_i2('Data (buffer content) allocated size:',
901            f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
902            f_bytes(arc_stats['l2_bufc_data_asize']))
903     prt_i2('Metadata (buffer content) allocated size:',
904            f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
905            f_bytes(arc_stats['l2_bufc_metadata_asize']))
907     print()
908     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
909     prt_i2('Hit ratio:',
910            f_perc(arc_stats['l2_hits'], l2_access_total),
911            f_hits(arc_stats['l2_hits']))
912     prt_i2('Miss ratio:',
913            f_perc(arc_stats['l2_misses'], l2_access_total),
914            f_hits(arc_stats['l2_misses']))
916     print()
917     print('L2ARC I/O:')
918     prt_i2('Reads:',
919            f_bytes(arc_stats['l2_read_bytes']),
920            f_hits(arc_stats['l2_hits']))
921     prt_i2('Writes:',
922            f_bytes(arc_stats['l2_write_bytes']),
923            f_hits(arc_stats['l2_writes_sent']))
925     print()
926     print('L2ARC evicts:')
927     prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
928     prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
929     print()
932 def section_spl(*_):
933     """Print the SPL parameters, if requested with alternative format
934     and/or descriptions. This does not use kstats.
935     """
937     if sys.platform.startswith('freebsd'):
938         # No SPL support in FreeBSD
939         return
941     spls = get_spl_params()
942     keylist = sorted(spls.keys())
943     print('Solaris Porting Layer (SPL):')
945     if ARGS.desc:
946         descriptions = get_descriptions('spl')
948     for key in keylist:
949         value = spls[key]
951         if ARGS.desc:
952             try:
953                 print(INDENT+'#', descriptions[key])
954             except KeyError:
955                 print(INDENT+'# (No description found)')  # paranoid
957         print(format_raw_line(key, value))
959     print()
962 def section_tunables(*_):
963     """Print the tunables, if requested with alternative format and/or
964     descriptions. This does not use kstasts.
965     """
967     tunables = get_tunable_params()
968     keylist = sorted(tunables.keys())
969     print('Tunables:')
971     if ARGS.desc:
972         descriptions = get_descriptions('zfs')
974     for key in keylist:
975         value = tunables[key]
977         if ARGS.desc:
978             try:
979                 print(INDENT+'#', descriptions[key])
980             except KeyError:
981                 print(INDENT+'# (No description found)')  # paranoid
983         print(format_raw_line(key, value))
985     print()
988 def section_zil(kstats_dict):
989     """Collect information on the ZFS Intent Log. Some of the information
990     taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
991     """
993     zil_stats = isolate_section('zil', kstats_dict)
995     prt_1('ZIL committed transactions:',
996           f_hits(zil_stats['zil_itx_count']))
997     prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
998     prt_i1('Flushes to stable storage:',
999            f_hits(zil_stats['zil_commit_writer_count']))
1000     prt_i2('Transactions to SLOG storage pool:',
1001            f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
1002            f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1003     prt_i2('Transactions to non-SLOG storage pool:',
1004            f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1005            f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1006     print()
1009 section_calls = {'arc': section_arc,
1010                  'archits': section_archits,
1011                  'dmu': section_dmu,
1012                  'l2arc': section_l2arc,
1013                  'spl': section_spl,
1014                  'tunables': section_tunables,
1015                  'zil': section_zil}
1018 def main():
1019     """Run program. The options to draw a graph and to print all data raw are
1020     treated separately because they come with their own call.
1021     """
1023     kstats = get_kstats()
1025     if ARGS.graph:
1026         draw_graph(kstats)
1027         sys.exit(0)
1029     print_header()
1031     if ARGS.raw:
1032         print_raw(kstats)
1034     elif ARGS.section:
1036         try:
1037             section_calls[ARGS.section](kstats)
1038         except KeyError:
1039             print('Error: Section "{0}" unknown'.format(ARGS.section))
1040             sys.exit(1)
1042     elif ARGS.page:
1043         print('WARNING: Pages are deprecated, please use "--section"\n')
1045         pages_to_calls = {1: 'arc',
1046                           2: 'archits',
1047                           3: 'l2arc',
1048                           4: 'dmu',
1049                           5: 'vdev',
1050                           6: 'tunables'}
1052         try:
1053             call = pages_to_calls[ARGS.page]
1054         except KeyError:
1055             print('Error: Page "{0}" not supported'.format(ARGS.page))
1056             sys.exit(1)
1057         else:
1058             section_calls[call](kstats)
1060     else:
1061         # If no parameters were given, we print all sections. We might want to
1062         # change the sequence by hand
1063         calls = sorted(section_calls.keys())
1065         for section in calls:
1066             section_calls[section](kstats)
1068     sys.exit(0)
1071 if __name__ == '__main__':
1072     main()