flush: only detect lack of flush support in one place
[zfs.git] / cmd / arc_summary
blob72381d266e64e318e776367783d32d5aba3b5062
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:', f_hits(arc_stats['hash_elements']))
666     prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
668     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
669     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
670     print()
672     print('ARC misc:')
673     prt_i1('Memory throttles:', arc_stats['memory_throttle_count'])
674     prt_i1('Memory direct reclaims:', arc_stats['memory_direct_count'])
675     prt_i1('Memory indirect reclaims:', arc_stats['memory_indirect_count'])
676     prt_i1('Deleted:', f_hits(arc_stats['deleted']))
677     prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
678     prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
679     prt_i1('Eviction skips due to L2 writes:',
680            f_hits(arc_stats['evict_l2_skip']))
681     prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
682     prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
683     prt_i2('L2 eligible MFU evictions:',
684            f_perc(arc_stats['evict_l2_eligible_mfu'],
685            arc_stats['evict_l2_eligible']),
686            f_bytes(arc_stats['evict_l2_eligible_mfu']))
687     prt_i2('L2 eligible MRU evictions:',
688            f_perc(arc_stats['evict_l2_eligible_mru'],
689            arc_stats['evict_l2_eligible']),
690            f_bytes(arc_stats['evict_l2_eligible_mru']))
691     prt_i1('L2 ineligible evictions:',
692            f_bytes(arc_stats['evict_l2_ineligible']))
693     print()
696 def section_archits(kstats_dict):
697     """Print information on how the caches are accessed ("arc hits").
698     """
700     arc_stats = isolate_section('arcstats', kstats_dict)
701     all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
702         int(arc_stats['misses'])
704     prt_1('ARC total accesses:', f_hits(all_accesses))
705     ta_todo = (('Total hits:', arc_stats['hits']),
706                ('Total I/O hits:', arc_stats['iohits']),
707                ('Total misses:', arc_stats['misses']))
708     for title, value in ta_todo:
709         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
710     print()
712     dd_total = int(arc_stats['demand_data_hits']) +\
713         int(arc_stats['demand_data_iohits']) +\
714         int(arc_stats['demand_data_misses'])
715     prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
716          f_hits(dd_total))
717     dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
718                ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
719                ('Demand data misses:', arc_stats['demand_data_misses']))
720     for title, value in dd_todo:
721         prt_i2(title, f_perc(value, dd_total), f_hits(value))
722     print()
724     dm_total = int(arc_stats['demand_metadata_hits']) +\
725         int(arc_stats['demand_metadata_iohits']) +\
726         int(arc_stats['demand_metadata_misses'])
727     prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
728           f_hits(dm_total))
729     dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
730                ('Demand metadata I/O hits:',
731                 arc_stats['demand_metadata_iohits']),
732                ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
733     for title, value in dm_todo:
734         prt_i2(title, f_perc(value, dm_total), f_hits(value))
735     print()
737     pd_total = int(arc_stats['prefetch_data_hits']) +\
738         int(arc_stats['prefetch_data_iohits']) +\
739         int(arc_stats['prefetch_data_misses'])
740     prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
741           f_hits(pd_total))
742     pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
743                ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
744                ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
745     for title, value in pd_todo:
746         prt_i2(title, f_perc(value, pd_total), f_hits(value))
747     print()
749     pm_total = int(arc_stats['prefetch_metadata_hits']) +\
750         int(arc_stats['prefetch_metadata_iohits']) +\
751         int(arc_stats['prefetch_metadata_misses'])
752     prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
753           f_hits(pm_total))
754     pm_todo = (('Prefetch metadata hits:',
755                 arc_stats['prefetch_metadata_hits']),
756                ('Prefetch metadata I/O hits:',
757                 arc_stats['prefetch_metadata_iohits']),
758                ('Prefetch metadata misses:',
759                 arc_stats['prefetch_metadata_misses']))
760     for title, value in pm_todo:
761         prt_i2(title, f_perc(value, pm_total), f_hits(value))
762     print()
764     all_prefetches = int(arc_stats['predictive_prefetch'])+\
765         int(arc_stats['prescient_prefetch'])
766     prt_2('ARC predictive prefetches:',
767            f_perc(arc_stats['predictive_prefetch'], all_prefetches),
768            f_hits(arc_stats['predictive_prefetch']))
769     prt_i2('Demand hits after predictive:',
770            f_perc(arc_stats['demand_hit_predictive_prefetch'],
771                   arc_stats['predictive_prefetch']),
772            f_hits(arc_stats['demand_hit_predictive_prefetch']))
773     prt_i2('Demand I/O hits after predictive:',
774            f_perc(arc_stats['demand_iohit_predictive_prefetch'],
775                   arc_stats['predictive_prefetch']),
776            f_hits(arc_stats['demand_iohit_predictive_prefetch']))
777     never = int(arc_stats['predictive_prefetch']) -\
778         int(arc_stats['demand_hit_predictive_prefetch']) -\
779         int(arc_stats['demand_iohit_predictive_prefetch'])
780     prt_i2('Never demanded after predictive:',
781            f_perc(never, arc_stats['predictive_prefetch']),
782            f_hits(never))
783     print()
785     prt_2('ARC prescient prefetches:',
786            f_perc(arc_stats['prescient_prefetch'], all_prefetches),
787            f_hits(arc_stats['prescient_prefetch']))
788     prt_i2('Demand hits after prescient:',
789            f_perc(arc_stats['demand_hit_prescient_prefetch'],
790                   arc_stats['prescient_prefetch']),
791            f_hits(arc_stats['demand_hit_prescient_prefetch']))
792     prt_i2('Demand I/O hits after prescient:',
793            f_perc(arc_stats['demand_iohit_prescient_prefetch'],
794                   arc_stats['prescient_prefetch']),
795            f_hits(arc_stats['demand_iohit_prescient_prefetch']))
796     never = int(arc_stats['prescient_prefetch'])-\
797         int(arc_stats['demand_hit_prescient_prefetch'])-\
798         int(arc_stats['demand_iohit_prescient_prefetch'])
799     prt_i2('Never demanded after prescient:',
800            f_perc(never, arc_stats['prescient_prefetch']),
801            f_hits(never))
802     print()
804     print('ARC states hits of all accesses:')
805     cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
806                ('Most recently used (MRU):', arc_stats['mru_hits']),
807                ('Most frequently used (MFU) ghost:',
808                 arc_stats['mfu_ghost_hits']),
809                ('Most recently used (MRU) ghost:',
810                 arc_stats['mru_ghost_hits']),
811                ('Uncached:', arc_stats['uncached_hits']))
812     for title, value in cl_todo:
813         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
814     print()
817 def section_dmu(kstats_dict):
818     """Collect information on the DMU"""
820     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
822     zfetch_access_total = int(zfetch_stats['hits']) +\
823         int(zfetch_stats['future']) + int(zfetch_stats['stride']) +\
824         int(zfetch_stats['past']) + int(zfetch_stats['misses'])
826     prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
827     prt_i2('Stream hits:',
828            f_perc(zfetch_stats['hits'], zfetch_access_total),
829            f_hits(zfetch_stats['hits']))
830     future = int(zfetch_stats['future']) + int(zfetch_stats['stride'])
831     prt_i2('Hits ahead of stream:', f_perc(future, zfetch_access_total),
832            f_hits(future))
833     prt_i2('Hits behind stream:',
834            f_perc(zfetch_stats['past'], zfetch_access_total),
835            f_hits(zfetch_stats['past']))
836     prt_i2('Stream misses:',
837            f_perc(zfetch_stats['misses'], zfetch_access_total),
838            f_hits(zfetch_stats['misses']))
839     prt_i2('Streams limit reached:',
840            f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
841            f_hits(zfetch_stats['max_streams']))
842     prt_i1('Stream strides:', f_hits(zfetch_stats['stride']))
843     prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
844     print()
847 def section_l2arc(kstats_dict):
848     """Collect information on L2ARC device if present. If not, tell user
849     that we're skipping the section.
850     """
852     # The L2ARC statistics live in the same section as the normal ARC stuff
853     arc_stats = isolate_section('arcstats', kstats_dict)
855     if arc_stats['l2_size'] == '0':
856         print('L2ARC not detected, skipping section\n')
857         return
859     l2_errors = int(arc_stats['l2_writes_error']) +\
860         int(arc_stats['l2_cksum_bad']) +\
861         int(arc_stats['l2_io_error'])
863     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
864     health = 'HEALTHY'
866     if l2_errors > 0:
867         health = 'DEGRADED'
869     prt_1('L2ARC status:', health)
871     l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
872                ('Free on write:', 'l2_free_on_write'),
873                ('R/W clashes:', 'l2_rw_clash'),
874                ('Bad checksums:', 'l2_cksum_bad'),
875                ('Read errors:', 'l2_io_error'),
876                ('Write errors:', 'l2_writes_error'))
878     for title, value in l2_todo:
879         prt_i1(title, f_hits(arc_stats[value]))
881     print()
882     prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
883     prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
884            f_bytes(arc_stats['l2_asize']))
885     prt_i2('Header size:',
886            f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
887            f_bytes(arc_stats['l2_hdr_size']))
888     prt_i2('MFU allocated size:',
889            f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
890            f_bytes(arc_stats['l2_mfu_asize']))
891     prt_i2('MRU allocated size:',
892            f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
893            f_bytes(arc_stats['l2_mru_asize']))
894     prt_i2('Prefetch allocated size:',
895            f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
896            f_bytes(arc_stats['l2_prefetch_asize']))
897     prt_i2('Data (buffer content) allocated size:',
898            f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
899            f_bytes(arc_stats['l2_bufc_data_asize']))
900     prt_i2('Metadata (buffer content) allocated size:',
901            f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
902            f_bytes(arc_stats['l2_bufc_metadata_asize']))
904     print()
905     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
906     prt_i2('Hit ratio:',
907            f_perc(arc_stats['l2_hits'], l2_access_total),
908            f_hits(arc_stats['l2_hits']))
909     prt_i2('Miss ratio:',
910            f_perc(arc_stats['l2_misses'], l2_access_total),
911            f_hits(arc_stats['l2_misses']))
913     print()
914     print('L2ARC I/O:')
915     prt_i2('Reads:',
916            f_bytes(arc_stats['l2_read_bytes']),
917            f_hits(arc_stats['l2_hits']))
918     prt_i2('Writes:',
919            f_bytes(arc_stats['l2_write_bytes']),
920            f_hits(arc_stats['l2_writes_sent']))
922     print()
923     print('L2ARC evicts:')
924     prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
925     prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
926     print()
929 def section_spl(*_):
930     """Print the SPL parameters, if requested with alternative format
931     and/or descriptions. This does not use kstats.
932     """
934     if sys.platform.startswith('freebsd'):
935         # No SPL support in FreeBSD
936         return
938     spls = get_spl_params()
939     keylist = sorted(spls.keys())
940     print('Solaris Porting Layer (SPL):')
942     if ARGS.desc:
943         descriptions = get_descriptions('spl')
945     for key in keylist:
946         value = spls[key]
948         if ARGS.desc:
949             try:
950                 print(INDENT+'#', descriptions[key])
951             except KeyError:
952                 print(INDENT+'# (No description found)')  # paranoid
954         print(format_raw_line(key, value))
956     print()
959 def section_tunables(*_):
960     """Print the tunables, if requested with alternative format and/or
961     descriptions. This does not use kstasts.
962     """
964     tunables = get_tunable_params()
965     keylist = sorted(tunables.keys())
966     print('Tunables:')
968     if ARGS.desc:
969         descriptions = get_descriptions('zfs')
971     for key in keylist:
972         value = tunables[key]
974         if ARGS.desc:
975             try:
976                 print(INDENT+'#', descriptions[key])
977             except KeyError:
978                 print(INDENT+'# (No description found)')  # paranoid
980         print(format_raw_line(key, value))
982     print()
985 def section_zil(kstats_dict):
986     """Collect information on the ZFS Intent Log. Some of the information
987     taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
988     """
990     zil_stats = isolate_section('zil', kstats_dict)
992     prt_1('ZIL committed transactions:',
993           f_hits(zil_stats['zil_itx_count']))
994     prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
995     prt_i1('Flushes to stable storage:',
996            f_hits(zil_stats['zil_commit_writer_count']))
997     prt_i2('Transactions to SLOG storage pool:',
998            f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
999            f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1000     prt_i2('Transactions to non-SLOG storage pool:',
1001            f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1002            f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1003     print()
1006 section_calls = {'arc': section_arc,
1007                  'archits': section_archits,
1008                  'dmu': section_dmu,
1009                  'l2arc': section_l2arc,
1010                  'spl': section_spl,
1011                  'tunables': section_tunables,
1012                  'zil': section_zil}
1015 def main():
1016     """Run program. The options to draw a graph and to print all data raw are
1017     treated separately because they come with their own call.
1018     """
1020     kstats = get_kstats()
1022     if ARGS.graph:
1023         draw_graph(kstats)
1024         sys.exit(0)
1026     print_header()
1028     if ARGS.raw:
1029         print_raw(kstats)
1031     elif ARGS.section:
1033         try:
1034             section_calls[ARGS.section](kstats)
1035         except KeyError:
1036             print('Error: Section "{0}" unknown'.format(ARGS.section))
1037             sys.exit(1)
1039     elif ARGS.page:
1040         print('WARNING: Pages are deprecated, please use "--section"\n')
1042         pages_to_calls = {1: 'arc',
1043                           2: 'archits',
1044                           3: 'l2arc',
1045                           4: 'dmu',
1046                           5: 'vdev',
1047                           6: 'tunables'}
1049         try:
1050             call = pages_to_calls[ARGS.page]
1051         except KeyError:
1052             print('Error: Page "{0}" not supported'.format(ARGS.page))
1053             sys.exit(1)
1054         else:
1055             section_calls[call](kstats)
1057     else:
1058         # If no parameters were given, we print all sections. We might want to
1059         # change the sequence by hand
1060         calls = sorted(section_calls.keys())
1062         for section in calls:
1063             section_calls[section](kstats)
1065     sys.exit(0)
1068 if __name__ == '__main__':
1069     main()