xref: /freebsd/sys/contrib/openzfs/cmd/arc_summary (revision df58e8b1506f241670be86a560fb6e8432043aee)
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    uncompressed_size = arc_stats['uncompressed_size']
563    overhead_size = arc_stats['overhead_size']
564    bonus_size = arc_stats['bonus_size']
565    dnode_size = arc_stats['dnode_size']
566    dbuf_size = arc_stats['dbuf_size']
567    hdr_size = arc_stats['hdr_size']
568    l2_hdr_size = arc_stats['l2_hdr_size']
569    abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
570
571    prt_1('ARC structural breakdown (current size):', f_bytes(arc_size))
572    prt_i2('Compressed size:',
573           f_perc(compressed_size, arc_size), f_bytes(compressed_size))
574    prt_i2('Overhead size:',
575           f_perc(overhead_size, arc_size), f_bytes(overhead_size))
576    prt_i2('Bonus size:',
577           f_perc(bonus_size, arc_size), f_bytes(bonus_size))
578    prt_i2('Dnode size:',
579           f_perc(dnode_size, arc_size), f_bytes(dnode_size))
580    prt_i2('Dbuf size:',
581           f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
582    prt_i2('Header size:',
583           f_perc(hdr_size, arc_size), f_bytes(hdr_size))
584    prt_i2('L2 header size:',
585           f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
586    prt_i2('ABD chunk waste size:',
587           f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
588    print()
589
590    meta = arc_stats['meta']
591    pd = arc_stats['pd']
592    pm = arc_stats['pm']
593    data_size = arc_stats['data_size']
594    metadata_size = arc_stats['metadata_size']
595    anon_data = arc_stats['anon_data']
596    anon_metadata = arc_stats['anon_metadata']
597    mfu_data = arc_stats['mfu_data']
598    mfu_metadata = arc_stats['mfu_metadata']
599    mfu_edata = arc_stats['mfu_evictable_data']
600    mfu_emetadata = arc_stats['mfu_evictable_metadata']
601    mru_data = arc_stats['mru_data']
602    mru_metadata = arc_stats['mru_metadata']
603    mru_edata = arc_stats['mru_evictable_data']
604    mru_emetadata = arc_stats['mru_evictable_metadata']
605    mfug_data = arc_stats['mfu_ghost_data']
606    mfug_metadata = arc_stats['mfu_ghost_metadata']
607    mrug_data = arc_stats['mru_ghost_data']
608    mrug_metadata = arc_stats['mru_ghost_metadata']
609    unc_data = arc_stats['uncached_data']
610    unc_metadata = arc_stats['uncached_metadata']
611    caches_size = int(anon_data)+int(anon_metadata)+\
612        int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
613        int(unc_data)+int(unc_metadata)
614
615    prt_1('ARC types breakdown (compressed + overhead):', f_bytes(caches_size))
616    prt_i2('Data size:',
617           f_perc(data_size, caches_size), f_bytes(data_size))
618    prt_i2('Metadata size:',
619           f_perc(metadata_size, caches_size), f_bytes(metadata_size))
620    print()
621
622    prt_1('ARC states breakdown (compressed + overhead):', f_bytes(caches_size))
623    prt_i2('Anonymous data size:',
624           f_perc(anon_data, caches_size), f_bytes(anon_data))
625    prt_i2('Anonymous metadata size:',
626           f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
627    s = 4294967296
628    v = (s-int(pd))*(s-int(meta))/s
629    prt_i2('MFU data target:', f_perc(v, s),
630        f_bytes(v / 65536 * caches_size / 65536))
631    prt_i2('MFU data size:',
632           f_perc(mfu_data, caches_size), f_bytes(mfu_data))
633    prt_i2('MFU evictable data size:',
634           f_perc(mfu_edata, caches_size), f_bytes(mfu_edata))
635    prt_i1('MFU ghost data size:', f_bytes(mfug_data))
636    v = (s-int(pm))*int(meta)/s
637    prt_i2('MFU metadata target:', f_perc(v, s),
638        f_bytes(v / 65536 * caches_size / 65536))
639    prt_i2('MFU metadata size:',
640           f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
641    prt_i2('MFU evictable metadata size:',
642           f_perc(mfu_emetadata, caches_size), f_bytes(mfu_emetadata))
643    prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
644    v = int(pd)*(s-int(meta))/s
645    prt_i2('MRU data target:', f_perc(v, s),
646        f_bytes(v / 65536 * caches_size / 65536))
647    prt_i2('MRU data size:',
648           f_perc(mru_data, caches_size), f_bytes(mru_data))
649    prt_i2('MRU evictable data size:',
650           f_perc(mru_edata, caches_size), f_bytes(mru_edata))
651    prt_i1('MRU ghost data size:', f_bytes(mrug_data))
652    v = int(pm)*int(meta)/s
653    prt_i2('MRU metadata target:', f_perc(v, s),
654        f_bytes(v / 65536 * caches_size / 65536))
655    prt_i2('MRU metadata size:',
656           f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
657    prt_i2('MRU evictable metadata size:',
658           f_perc(mru_emetadata, caches_size), f_bytes(mru_emetadata))
659    prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
660    prt_i2('Uncached data size:',
661           f_perc(unc_data, caches_size), f_bytes(unc_data))
662    prt_i2('Uncached metadata size:',
663           f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
664    print()
665
666    print('ARC hash breakdown:')
667    prt_i1('Elements:', f_hits(arc_stats['hash_elements']))
668    prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
669
670    prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
671    prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
672    print()
673
674    print('ARC misc:')
675    prt_i2('Uncompressed size:', f_perc(uncompressed_size, compressed_size),
676           f_bytes(uncompressed_size))
677    prt_i1('Memory throttles:', arc_stats['memory_throttle_count'])
678    prt_i1('Memory direct reclaims:', arc_stats['memory_direct_count'])
679    prt_i1('Memory indirect reclaims:', arc_stats['memory_indirect_count'])
680    prt_i1('Deleted:', f_hits(arc_stats['deleted']))
681    prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
682    prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
683    prt_i1('Eviction skips due to L2 writes:',
684           f_hits(arc_stats['evict_l2_skip']))
685    prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
686    prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
687    prt_i2('L2 eligible MFU evictions:',
688           f_perc(arc_stats['evict_l2_eligible_mfu'],
689           arc_stats['evict_l2_eligible']),
690           f_bytes(arc_stats['evict_l2_eligible_mfu']))
691    prt_i2('L2 eligible MRU evictions:',
692           f_perc(arc_stats['evict_l2_eligible_mru'],
693           arc_stats['evict_l2_eligible']),
694           f_bytes(arc_stats['evict_l2_eligible_mru']))
695    prt_i1('L2 ineligible evictions:',
696           f_bytes(arc_stats['evict_l2_ineligible']))
697    print()
698
699
700def section_archits(kstats_dict):
701    """Print information on how the caches are accessed ("arc hits").
702    """
703
704    arc_stats = isolate_section('arcstats', kstats_dict)
705    all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
706        int(arc_stats['misses'])
707
708    prt_1('ARC total accesses:', f_hits(all_accesses))
709    ta_todo = (('Total hits:', arc_stats['hits']),
710               ('Total I/O hits:', arc_stats['iohits']),
711               ('Total misses:', arc_stats['misses']))
712    for title, value in ta_todo:
713        prt_i2(title, f_perc(value, all_accesses), f_hits(value))
714    print()
715
716    dd_total = int(arc_stats['demand_data_hits']) +\
717        int(arc_stats['demand_data_iohits']) +\
718        int(arc_stats['demand_data_misses'])
719    prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
720         f_hits(dd_total))
721    dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
722               ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
723               ('Demand data misses:', arc_stats['demand_data_misses']))
724    for title, value in dd_todo:
725        prt_i2(title, f_perc(value, dd_total), f_hits(value))
726    print()
727
728    dm_total = int(arc_stats['demand_metadata_hits']) +\
729        int(arc_stats['demand_metadata_iohits']) +\
730        int(arc_stats['demand_metadata_misses'])
731    prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
732          f_hits(dm_total))
733    dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
734               ('Demand metadata I/O hits:',
735                arc_stats['demand_metadata_iohits']),
736               ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
737    for title, value in dm_todo:
738        prt_i2(title, f_perc(value, dm_total), f_hits(value))
739    print()
740
741    pd_total = int(arc_stats['prefetch_data_hits']) +\
742        int(arc_stats['prefetch_data_iohits']) +\
743        int(arc_stats['prefetch_data_misses'])
744    prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
745          f_hits(pd_total))
746    pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
747               ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
748               ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
749    for title, value in pd_todo:
750        prt_i2(title, f_perc(value, pd_total), f_hits(value))
751    print()
752
753    pm_total = int(arc_stats['prefetch_metadata_hits']) +\
754        int(arc_stats['prefetch_metadata_iohits']) +\
755        int(arc_stats['prefetch_metadata_misses'])
756    prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
757          f_hits(pm_total))
758    pm_todo = (('Prefetch metadata hits:',
759                arc_stats['prefetch_metadata_hits']),
760               ('Prefetch metadata I/O hits:',
761                arc_stats['prefetch_metadata_iohits']),
762               ('Prefetch metadata misses:',
763                arc_stats['prefetch_metadata_misses']))
764    for title, value in pm_todo:
765        prt_i2(title, f_perc(value, pm_total), f_hits(value))
766    print()
767
768    all_prefetches = int(arc_stats['predictive_prefetch'])+\
769        int(arc_stats['prescient_prefetch'])
770    prt_2('ARC predictive prefetches:',
771           f_perc(arc_stats['predictive_prefetch'], all_prefetches),
772           f_hits(arc_stats['predictive_prefetch']))
773    prt_i2('Demand hits after predictive:',
774           f_perc(arc_stats['demand_hit_predictive_prefetch'],
775                  arc_stats['predictive_prefetch']),
776           f_hits(arc_stats['demand_hit_predictive_prefetch']))
777    prt_i2('Demand I/O hits after predictive:',
778           f_perc(arc_stats['demand_iohit_predictive_prefetch'],
779                  arc_stats['predictive_prefetch']),
780           f_hits(arc_stats['demand_iohit_predictive_prefetch']))
781    never = int(arc_stats['predictive_prefetch']) -\
782        int(arc_stats['demand_hit_predictive_prefetch']) -\
783        int(arc_stats['demand_iohit_predictive_prefetch'])
784    prt_i2('Never demanded after predictive:',
785           f_perc(never, arc_stats['predictive_prefetch']),
786           f_hits(never))
787    print()
788
789    prt_2('ARC prescient prefetches:',
790           f_perc(arc_stats['prescient_prefetch'], all_prefetches),
791           f_hits(arc_stats['prescient_prefetch']))
792    prt_i2('Demand hits after prescient:',
793           f_perc(arc_stats['demand_hit_prescient_prefetch'],
794                  arc_stats['prescient_prefetch']),
795           f_hits(arc_stats['demand_hit_prescient_prefetch']))
796    prt_i2('Demand I/O hits after prescient:',
797           f_perc(arc_stats['demand_iohit_prescient_prefetch'],
798                  arc_stats['prescient_prefetch']),
799           f_hits(arc_stats['demand_iohit_prescient_prefetch']))
800    never = int(arc_stats['prescient_prefetch'])-\
801        int(arc_stats['demand_hit_prescient_prefetch'])-\
802        int(arc_stats['demand_iohit_prescient_prefetch'])
803    prt_i2('Never demanded after prescient:',
804           f_perc(never, arc_stats['prescient_prefetch']),
805           f_hits(never))
806    print()
807
808    print('ARC states hits of all accesses:')
809    cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
810               ('Most recently used (MRU):', arc_stats['mru_hits']),
811               ('Most frequently used (MFU) ghost:',
812                arc_stats['mfu_ghost_hits']),
813               ('Most recently used (MRU) ghost:',
814                arc_stats['mru_ghost_hits']),
815               ('Uncached:', arc_stats['uncached_hits']))
816    for title, value in cl_todo:
817        prt_i2(title, f_perc(value, all_accesses), f_hits(value))
818    print()
819
820
821def section_dmu(kstats_dict):
822    """Collect information on the DMU"""
823
824    zfetch_stats = isolate_section('zfetchstats', kstats_dict)
825
826    zfetch_access_total = int(zfetch_stats['hits']) +\
827        int(zfetch_stats['future']) + int(zfetch_stats['stride']) +\
828        int(zfetch_stats['past']) + int(zfetch_stats['misses'])
829
830    prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
831    prt_i2('Stream hits:',
832           f_perc(zfetch_stats['hits'], zfetch_access_total),
833           f_hits(zfetch_stats['hits']))
834    future = int(zfetch_stats['future']) + int(zfetch_stats['stride'])
835    prt_i2('Hits ahead of stream:', f_perc(future, zfetch_access_total),
836           f_hits(future))
837    prt_i2('Hits behind stream:',
838           f_perc(zfetch_stats['past'], zfetch_access_total),
839           f_hits(zfetch_stats['past']))
840    prt_i2('Stream misses:',
841           f_perc(zfetch_stats['misses'], zfetch_access_total),
842           f_hits(zfetch_stats['misses']))
843    prt_i2('Streams limit reached:',
844           f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
845           f_hits(zfetch_stats['max_streams']))
846    prt_i1('Stream strides:', f_hits(zfetch_stats['stride']))
847    prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
848    print()
849
850
851def section_l2arc(kstats_dict):
852    """Collect information on L2ARC device if present. If not, tell user
853    that we're skipping the section.
854    """
855
856    # The L2ARC statistics live in the same section as the normal ARC stuff
857    arc_stats = isolate_section('arcstats', kstats_dict)
858
859    if arc_stats['l2_size'] == '0':
860        print('L2ARC not detected, skipping section\n')
861        return
862
863    l2_errors = int(arc_stats['l2_writes_error']) +\
864        int(arc_stats['l2_cksum_bad']) +\
865        int(arc_stats['l2_io_error'])
866
867    l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
868    health = 'HEALTHY'
869
870    if l2_errors > 0:
871        health = 'DEGRADED'
872
873    prt_1('L2ARC status:', health)
874
875    l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
876               ('Free on write:', 'l2_free_on_write'),
877               ('R/W clashes:', 'l2_rw_clash'),
878               ('Bad checksums:', 'l2_cksum_bad'),
879               ('Read errors:', 'l2_io_error'),
880               ('Write errors:', 'l2_writes_error'))
881
882    for title, value in l2_todo:
883        prt_i1(title, f_hits(arc_stats[value]))
884
885    print()
886    prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
887    prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
888           f_bytes(arc_stats['l2_asize']))
889    prt_i2('Header size:',
890           f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
891           f_bytes(arc_stats['l2_hdr_size']))
892    prt_i2('MFU allocated size:',
893           f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
894           f_bytes(arc_stats['l2_mfu_asize']))
895    prt_i2('MRU allocated size:',
896           f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
897           f_bytes(arc_stats['l2_mru_asize']))
898    prt_i2('Prefetch allocated size:',
899           f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
900           f_bytes(arc_stats['l2_prefetch_asize']))
901    prt_i2('Data (buffer content) allocated size:',
902           f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
903           f_bytes(arc_stats['l2_bufc_data_asize']))
904    prt_i2('Metadata (buffer content) allocated size:',
905           f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
906           f_bytes(arc_stats['l2_bufc_metadata_asize']))
907
908    print()
909    prt_1('L2ARC breakdown:', f_hits(l2_access_total))
910    prt_i2('Hit ratio:',
911           f_perc(arc_stats['l2_hits'], l2_access_total),
912           f_hits(arc_stats['l2_hits']))
913    prt_i2('Miss ratio:',
914           f_perc(arc_stats['l2_misses'], l2_access_total),
915           f_hits(arc_stats['l2_misses']))
916
917    print()
918    print('L2ARC I/O:')
919    prt_i2('Reads:',
920           f_bytes(arc_stats['l2_read_bytes']),
921           f_hits(arc_stats['l2_hits']))
922    prt_i2('Writes:',
923           f_bytes(arc_stats['l2_write_bytes']),
924           f_hits(arc_stats['l2_writes_sent']))
925
926    print()
927    print('L2ARC evicts:')
928    prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
929    prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
930    print()
931
932
933def section_spl(*_):
934    """Print the SPL parameters, if requested with alternative format
935    and/or descriptions. This does not use kstats.
936    """
937
938    if sys.platform.startswith('freebsd'):
939        # No SPL support in FreeBSD
940        return
941
942    spls = get_spl_params()
943    keylist = sorted(spls.keys())
944    print('Solaris Porting Layer (SPL):')
945
946    if ARGS.desc:
947        descriptions = get_descriptions('spl')
948
949    for key in keylist:
950        value = spls[key]
951
952        if ARGS.desc:
953            try:
954                print(INDENT+'#', descriptions[key])
955            except KeyError:
956                print(INDENT+'# (No description found)')  # paranoid
957
958        print(format_raw_line(key, value))
959
960    print()
961
962
963def section_tunables(*_):
964    """Print the tunables, if requested with alternative format and/or
965    descriptions. This does not use kstasts.
966    """
967
968    tunables = get_tunable_params()
969    keylist = sorted(tunables.keys())
970    print('Tunables:')
971
972    if ARGS.desc:
973        descriptions = get_descriptions('zfs')
974
975    for key in keylist:
976        value = tunables[key]
977
978        if ARGS.desc:
979            try:
980                print(INDENT+'#', descriptions[key])
981            except KeyError:
982                print(INDENT+'# (No description found)')  # paranoid
983
984        print(format_raw_line(key, value))
985
986    print()
987
988
989def section_zil(kstats_dict):
990    """Collect information on the ZFS Intent Log. Some of the information
991    taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
992    """
993
994    zil_stats = isolate_section('zil', kstats_dict)
995
996    prt_1('ZIL committed transactions:',
997          f_hits(zil_stats['zil_itx_count']))
998    prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
999    prt_i1('Flushes to stable storage:',
1000           f_hits(zil_stats['zil_commit_writer_count']))
1001    prt_i2('Transactions to SLOG storage pool:',
1002           f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
1003           f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1004    prt_i2('Transactions to non-SLOG storage pool:',
1005           f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1006           f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1007    print()
1008
1009
1010section_calls = {'arc': section_arc,
1011                 'archits': section_archits,
1012                 'dmu': section_dmu,
1013                 'l2arc': section_l2arc,
1014                 'spl': section_spl,
1015                 'tunables': section_tunables,
1016                 'zil': section_zil}
1017
1018
1019def main():
1020    """Run program. The options to draw a graph and to print all data raw are
1021    treated separately because they come with their own call.
1022    """
1023
1024    kstats = get_kstats()
1025
1026    if ARGS.graph:
1027        draw_graph(kstats)
1028        sys.exit(0)
1029
1030    print_header()
1031
1032    if ARGS.raw:
1033        print_raw(kstats)
1034
1035    elif ARGS.section:
1036
1037        try:
1038            section_calls[ARGS.section](kstats)
1039        except KeyError:
1040            print('Error: Section "{0}" unknown'.format(ARGS.section))
1041            sys.exit(1)
1042
1043    elif ARGS.page:
1044        print('WARNING: Pages are deprecated, please use "--section"\n')
1045
1046        pages_to_calls = {1: 'arc',
1047                          2: 'archits',
1048                          3: 'l2arc',
1049                          4: 'dmu',
1050                          5: 'vdev',
1051                          6: 'tunables'}
1052
1053        try:
1054            call = pages_to_calls[ARGS.page]
1055        except KeyError:
1056            print('Error: Page "{0}" not supported'.format(ARGS.page))
1057            sys.exit(1)
1058        else:
1059            section_calls[call](kstats)
1060
1061    else:
1062        # If no parameters were given, we print all sections. We might want to
1063        # change the sequence by hand
1064        calls = sorted(section_calls.keys())
1065
1066        for section in calls:
1067            section_calls[section](kstats)
1068
1069    sys.exit(0)
1070
1071
1072if __name__ == '__main__':
1073    main()
1074