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