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