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