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