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