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