xref: /freebsd/sys/contrib/openzfs/cmd/arc_summary (revision 15f0b8c309dea1dcb14d3e374686576ff68ac43f)
1716fd348SMartin Matuska#!/usr/bin/env python3
2716fd348SMartin Matuska#
3716fd348SMartin Matuska# Copyright (c) 2008 Ben Rockwood <benr@cuddletech.com>,
4716fd348SMartin Matuska# Copyright (c) 2010 Martin Matuska <mm@FreeBSD.org>,
5716fd348SMartin Matuska# Copyright (c) 2010-2011 Jason J. Hellenthal <jhell@DataIX.net>,
6716fd348SMartin Matuska# Copyright (c) 2017 Scot W. Stevenson <scot.stevenson@gmail.com>
7716fd348SMartin Matuska# All rights reserved.
8716fd348SMartin Matuska#
9716fd348SMartin Matuska# Redistribution and use in source and binary forms, with or without
10716fd348SMartin Matuska# modification, are permitted provided that the following conditions
11716fd348SMartin Matuska# are met:
12716fd348SMartin Matuska#
13716fd348SMartin Matuska# 1. Redistributions of source code must retain the above copyright
14716fd348SMartin Matuska#    notice, this list of conditions and the following disclaimer.
15716fd348SMartin Matuska# 2. Redistributions in binary form must reproduce the above copyright
16716fd348SMartin Matuska#    notice, this list of conditions and the following disclaimer in the
17716fd348SMartin Matuska#    documentation and/or other materials provided with the distribution.
18716fd348SMartin Matuska#
19716fd348SMartin Matuska# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20716fd348SMartin Matuska# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21716fd348SMartin Matuska# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22716fd348SMartin Matuska# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
23716fd348SMartin Matuska# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24716fd348SMartin Matuska# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25716fd348SMartin Matuska# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26716fd348SMartin Matuska# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27716fd348SMartin Matuska# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28716fd348SMartin Matuska# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29716fd348SMartin Matuska# SUCH DAMAGE.
30716fd348SMartin Matuska"""Print statistics on the ZFS ARC Cache and other information
31716fd348SMartin Matuska
32716fd348SMartin MatuskaProvides basic information on the ARC, its efficiency, the L2ARC (if present),
33716fd348SMartin Matuskathe Data Management Unit (DMU), Virtual Devices (VDEVs), and tunables. See
34716fd348SMartin Matuskathe in-source documentation and code at
35716fd348SMartin Matuskahttps://github.com/openzfs/zfs/blob/master/module/zfs/arc.c for details.
36716fd348SMartin MatuskaThe original introduction to arc_summary can be found at
37716fd348SMartin Matuskahttp://cuddletech.com/?p=454
38716fd348SMartin Matuska"""
39716fd348SMartin Matuska
40716fd348SMartin Matuskaimport argparse
41716fd348SMartin Matuskaimport os
42716fd348SMartin Matuskaimport subprocess
43716fd348SMartin Matuskaimport sys
44716fd348SMartin Matuskaimport time
45716fd348SMartin Matuskaimport errno
46716fd348SMartin Matuska
47716fd348SMartin Matuska# We can't use env -S portably, and we need python3 -u to handle pipes in
48716fd348SMartin Matuska# the shell abruptly closing the way we want to, so...
49716fd348SMartin Matuskaimport io
50716fd348SMartin Matuskaif isinstance(sys.__stderr__.buffer, io.BufferedWriter):
51716fd348SMartin Matuska    os.execv(sys.executable, [sys.executable, "-u"] + sys.argv)
52716fd348SMartin Matuska
53716fd348SMartin MatuskaDESCRIPTION = 'Print ARC and other statistics for OpenZFS'
54716fd348SMartin MatuskaINDENT = ' '*8
55716fd348SMartin MatuskaLINE_LENGTH = 72
56716fd348SMartin MatuskaDATE_FORMAT = '%a %b %d %H:%M:%S %Y'
57716fd348SMartin MatuskaTITLE = 'ZFS Subsystem Report'
58716fd348SMartin Matuska
59716fd348SMartin MatuskaSECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
60716fd348SMartin MatuskaSECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
61716fd348SMartin Matuska
62716fd348SMartin Matuska# Tunables and SPL are handled separately because they come from
63716fd348SMartin Matuska# different sources
64716fd348SMartin MatuskaSECTION_PATHS = {'arc': 'arcstats',
65716fd348SMartin Matuska                 'dmu': 'dmu_tx',
66716fd348SMartin Matuska                 'l2arc': 'arcstats',  # L2ARC stuff lives in arcstats
67716fd348SMartin Matuska                 'vdev': 'vdev_cache_stats',
68716fd348SMartin Matuska                 'zfetch': 'zfetchstats',
69716fd348SMartin Matuska                 'zil': 'zil'}
70716fd348SMartin Matuska
71716fd348SMartin Matuskaparser = argparse.ArgumentParser(description=DESCRIPTION)
72716fd348SMartin Matuskaparser.add_argument('-a', '--alternate', action='store_true', default=False,
73716fd348SMartin Matuska                    help='use alternate formatting for tunables and SPL',
74716fd348SMartin Matuska                    dest='alt')
75716fd348SMartin Matuskaparser.add_argument('-d', '--description', action='store_true', default=False,
76716fd348SMartin Matuska                    help='print descriptions with tunables and SPL',
77716fd348SMartin Matuska                    dest='desc')
78716fd348SMartin Matuskaparser.add_argument('-g', '--graph', action='store_true', default=False,
79716fd348SMartin Matuska                    help='print graph on ARC use and exit', dest='graph')
80716fd348SMartin Matuskaparser.add_argument('-p', '--page', type=int, dest='page',
81716fd348SMartin Matuska                    help='print page by number (DEPRECATED, use "-s")')
82716fd348SMartin Matuskaparser.add_argument('-r', '--raw', action='store_true', default=False,
83716fd348SMartin Matuska                    help='dump all available data with minimal formatting',
84716fd348SMartin Matuska                    dest='raw')
85716fd348SMartin Matuskaparser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
86716fd348SMartin MatuskaARGS = parser.parse_args()
87716fd348SMartin Matuska
88716fd348SMartin Matuska
89716fd348SMartin Matuskaif sys.platform.startswith('freebsd'):
90716fd348SMartin Matuska    # Requires py36-sysctl on FreeBSD
91716fd348SMartin Matuska    import sysctl
92716fd348SMartin Matuska
93716fd348SMartin Matuska    VDEV_CACHE_SIZE = 'vdev.cache_size'
94716fd348SMartin Matuska
95716fd348SMartin Matuska    def is_value(ctl):
96716fd348SMartin Matuska        return ctl.type != sysctl.CTLTYPE_NODE
97716fd348SMartin Matuska
98716fd348SMartin Matuska    def namefmt(ctl, base='vfs.zfs.'):
99716fd348SMartin Matuska        # base is removed from the name
100716fd348SMartin Matuska        cut = len(base)
101716fd348SMartin Matuska        return ctl.name[cut:]
102716fd348SMartin Matuska
103716fd348SMartin Matuska    def load_kstats(section):
104716fd348SMartin Matuska        base = 'kstat.zfs.misc.{section}.'.format(section=section)
105716fd348SMartin Matuska        fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
106716fd348SMartin Matuska                                                      value=kstat.value)
107716fd348SMartin Matuska        kstats = sysctl.filter(base)
108716fd348SMartin Matuska        return [fmt(kstat) for kstat in kstats if is_value(kstat)]
109716fd348SMartin Matuska
110716fd348SMartin Matuska    def get_params(base):
111716fd348SMartin Matuska        ctls = sysctl.filter(base)
112716fd348SMartin Matuska        return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
113716fd348SMartin Matuska
114716fd348SMartin Matuska    def get_tunable_params():
115716fd348SMartin Matuska        return get_params('vfs.zfs')
116716fd348SMartin Matuska
117716fd348SMartin Matuska    def get_vdev_params():
118716fd348SMartin Matuska        return get_params('vfs.zfs.vdev')
119716fd348SMartin Matuska
120716fd348SMartin Matuska    def get_version_impl(request):
121716fd348SMartin Matuska        # FreeBSD reports versions for zpl and spa instead of zfs and spl.
122716fd348SMartin Matuska        name = {'zfs': 'zpl',
123716fd348SMartin Matuska                'spl': 'spa'}[request]
124716fd348SMartin Matuska        mib = 'vfs.zfs.version.{}'.format(name)
125716fd348SMartin Matuska        version = sysctl.filter(mib)[0].value
126716fd348SMartin Matuska        return '{} version {}'.format(name, version)
127716fd348SMartin Matuska
128716fd348SMartin Matuska    def get_descriptions(_request):
129716fd348SMartin Matuska        ctls = sysctl.filter('vfs.zfs')
130716fd348SMartin Matuska        return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
131716fd348SMartin Matuska
132716fd348SMartin Matuska
133716fd348SMartin Matuskaelif sys.platform.startswith('linux'):
134716fd348SMartin Matuska    KSTAT_PATH = '/proc/spl/kstat/zfs'
135716fd348SMartin Matuska    SPL_PATH = '/sys/module/spl/parameters'
136716fd348SMartin Matuska    TUNABLES_PATH = '/sys/module/zfs/parameters'
137716fd348SMartin Matuska
138716fd348SMartin Matuska    VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
139716fd348SMartin Matuska
140716fd348SMartin Matuska    def load_kstats(section):
141716fd348SMartin Matuska        path = os.path.join(KSTAT_PATH, section)
142716fd348SMartin Matuska        with open(path) as f:
143716fd348SMartin Matuska            return list(f)[2:] # Get rid of header
144716fd348SMartin Matuska
145716fd348SMartin Matuska    def get_params(basepath):
146716fd348SMartin Matuska        """Collect information on the Solaris Porting Layer (SPL) or the
147716fd348SMartin Matuska        tunables, depending on the PATH given. Does not check if PATH is
148716fd348SMartin Matuska        legal.
149716fd348SMartin Matuska        """
150716fd348SMartin Matuska        result = {}
151716fd348SMartin Matuska        for name in os.listdir(basepath):
152716fd348SMartin Matuska            path = os.path.join(basepath, name)
153716fd348SMartin Matuska            with open(path) as f:
154716fd348SMartin Matuska                value = f.read()
155716fd348SMartin Matuska                result[name] = value.strip()
156716fd348SMartin Matuska        return result
157716fd348SMartin Matuska
158716fd348SMartin Matuska    def get_spl_params():
159716fd348SMartin Matuska        return get_params(SPL_PATH)
160716fd348SMartin Matuska
161716fd348SMartin Matuska    def get_tunable_params():
162716fd348SMartin Matuska        return get_params(TUNABLES_PATH)
163716fd348SMartin Matuska
164716fd348SMartin Matuska    def get_vdev_params():
165716fd348SMartin Matuska        return get_params(TUNABLES_PATH)
166716fd348SMartin Matuska
167716fd348SMartin Matuska    def get_version_impl(request):
168716fd348SMartin Matuska        # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
169716fd348SMartin Matuska        # the version information. We switch to /sys/module/{spl,zfs}/version
170716fd348SMartin Matuska        # to make sure we get what is really loaded in the kernel
171716fd348SMartin Matuska        try:
172716fd348SMartin Matuska            with open("/sys/module/{}/version".format(request)) as f:
173716fd348SMartin Matuska                return f.read().strip()
174716fd348SMartin Matuska        except:
175716fd348SMartin Matuska            return "(unknown)"
176716fd348SMartin Matuska
177716fd348SMartin Matuska    def get_descriptions(request):
178716fd348SMartin Matuska        """Get the descriptions of the Solaris Porting Layer (SPL) or the
179716fd348SMartin Matuska        tunables, return with minimal formatting.
180716fd348SMartin Matuska        """
181716fd348SMartin Matuska
182716fd348SMartin Matuska        if request not in ('spl', 'zfs'):
183716fd348SMartin Matuska            print('ERROR: description of "{0}" requested)'.format(request))
184716fd348SMartin Matuska            sys.exit(1)
185716fd348SMartin Matuska
186716fd348SMartin Matuska        descs = {}
187716fd348SMartin Matuska        target_prefix = 'parm:'
188716fd348SMartin Matuska
189716fd348SMartin Matuska        # We would prefer to do this with /sys/modules -- see the discussion at
190716fd348SMartin Matuska        # get_version() -- but there isn't a way to get the descriptions from
191716fd348SMartin Matuska        # there, so we fall back on modinfo
192716fd348SMartin Matuska        command = ["/sbin/modinfo", request, "-0"]
193716fd348SMartin Matuska
194716fd348SMartin Matuska        info = ''
195716fd348SMartin Matuska
196716fd348SMartin Matuska        try:
197716fd348SMartin Matuska
198716fd348SMartin Matuska            info = subprocess.run(command, stdout=subprocess.PIPE,
199716fd348SMartin Matuska                                  check=True, universal_newlines=True)
200716fd348SMartin Matuska            raw_output = info.stdout.split('\0')
201716fd348SMartin Matuska
202716fd348SMartin Matuska        except subprocess.CalledProcessError:
203716fd348SMartin Matuska            print("Error: Descriptions not available",
204716fd348SMartin Matuska                  "(can't access kernel module)")
205716fd348SMartin Matuska            sys.exit(1)
206716fd348SMartin Matuska
207716fd348SMartin Matuska        for line in raw_output:
208716fd348SMartin Matuska
209716fd348SMartin Matuska            if not line.startswith(target_prefix):
210716fd348SMartin Matuska                continue
211716fd348SMartin Matuska
212716fd348SMartin Matuska            line = line[len(target_prefix):].strip()
213716fd348SMartin Matuska            name, raw_desc = line.split(':', 1)
214716fd348SMartin Matuska            desc = raw_desc.rsplit('(', 1)[0]
215716fd348SMartin Matuska
216716fd348SMartin Matuska            if desc == '':
217716fd348SMartin Matuska                desc = '(No description found)'
218716fd348SMartin Matuska
219716fd348SMartin Matuska            descs[name.strip()] = desc.strip()
220716fd348SMartin Matuska
221716fd348SMartin Matuska        return descs
222716fd348SMartin Matuska
223716fd348SMartin Matuskadef handle_unraisableException(exc_type, exc_value=None, exc_traceback=None,
224716fd348SMartin Matuska                               err_msg=None, object=None):
225716fd348SMartin Matuska   handle_Exception(exc_type, object, exc_traceback)
226716fd348SMartin Matuska
227716fd348SMartin Matuskadef handle_Exception(ex_cls, ex, tb):
228716fd348SMartin Matuska    if ex_cls is KeyboardInterrupt:
229716fd348SMartin Matuska        sys.exit()
230716fd348SMartin Matuska
231716fd348SMartin Matuska    if ex_cls is BrokenPipeError:
232716fd348SMartin Matuska        # It turns out that while sys.exit() triggers an exception
233716fd348SMartin Matuska        # not handled message on Python 3.8+, os._exit() does not.
234716fd348SMartin Matuska        os._exit(0)
235716fd348SMartin Matuska
236716fd348SMartin Matuska    if ex_cls is OSError:
237716fd348SMartin Matuska      if ex.errno == errno.ENOTCONN:
238716fd348SMartin Matuska        sys.exit()
239716fd348SMartin Matuska
240716fd348SMartin Matuska    raise ex
241716fd348SMartin Matuska
242716fd348SMartin Matuskaif hasattr(sys,'unraisablehook'): # Python 3.8+
243716fd348SMartin Matuska    sys.unraisablehook = handle_unraisableException
244716fd348SMartin Matuskasys.excepthook = handle_Exception
245716fd348SMartin Matuska
246716fd348SMartin Matuska
247716fd348SMartin Matuskadef cleanup_line(single_line):
248716fd348SMartin Matuska    """Format a raw line of data from /proc and isolate the name value
249716fd348SMartin Matuska    part, returning a tuple with each. Currently, this gets rid of the
250716fd348SMartin Matuska    middle '4'. For example "arc_no_grow    4    0" returns the tuple
251716fd348SMartin Matuska    ("arc_no_grow", "0").
252716fd348SMartin Matuska    """
253716fd348SMartin Matuska    name, _, value = single_line.split()
254716fd348SMartin Matuska
255716fd348SMartin Matuska    return name, value
256716fd348SMartin Matuska
257716fd348SMartin Matuska
258716fd348SMartin Matuskadef draw_graph(kstats_dict):
259716fd348SMartin Matuska    """Draw a primitive graph representing the basic information on the
260716fd348SMartin Matuska    ARC -- its size and the proportion used by MFU and MRU -- and quit.
261716fd348SMartin Matuska    We use max size of the ARC to calculate how full it is. This is a
262716fd348SMartin Matuska    very rough representation.
263716fd348SMartin Matuska    """
264716fd348SMartin Matuska
265716fd348SMartin Matuska    arc_stats = isolate_section('arcstats', kstats_dict)
266716fd348SMartin Matuska
267716fd348SMartin Matuska    GRAPH_INDENT = ' '*4
268716fd348SMartin Matuska    GRAPH_WIDTH = 60
269716fd348SMartin Matuska    arc_size = f_bytes(arc_stats['size'])
270716fd348SMartin Matuska    arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
271716fd348SMartin Matuska    mfu_size = f_bytes(arc_stats['mfu_size'])
272716fd348SMartin Matuska    mru_size = f_bytes(arc_stats['mru_size'])
273716fd348SMartin Matuska    meta_limit = f_bytes(arc_stats['arc_meta_limit'])
274716fd348SMartin Matuska    meta_size = f_bytes(arc_stats['arc_meta_used'])
275716fd348SMartin Matuska    dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
276716fd348SMartin Matuska    dnode_size = f_bytes(arc_stats['dnode_size'])
277716fd348SMartin Matuska
278716fd348SMartin Matuska    info_form = ('ARC: {0} ({1})  MFU: {2}  MRU: {3}  META: {4} ({5}) '
279716fd348SMartin Matuska                 'DNODE {6} ({7})')
280716fd348SMartin Matuska    info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
281716fd348SMartin Matuska                                 meta_size, meta_limit, dnode_size,
282716fd348SMartin Matuska                                 dnode_limit)
283716fd348SMartin Matuska    info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
284716fd348SMartin Matuska    info_line = GRAPH_INDENT+info_spc+info_line
285716fd348SMartin Matuska
286716fd348SMartin Matuska    graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
287716fd348SMartin Matuska
288716fd348SMartin Matuska    mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
289716fd348SMartin Matuska    mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
290716fd348SMartin Matuska    arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
291716fd348SMartin Matuska    total_ticks = float(arc_perc)*GRAPH_WIDTH
292716fd348SMartin Matuska    mfu_ticks = mfu_perc*GRAPH_WIDTH
293716fd348SMartin Matuska    mru_ticks = mru_perc*GRAPH_WIDTH
294716fd348SMartin Matuska    other_ticks = total_ticks-(mfu_ticks+mru_ticks)
295716fd348SMartin Matuska
296716fd348SMartin Matuska    core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
297716fd348SMartin Matuska    core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
298716fd348SMartin Matuska    core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
299716fd348SMartin Matuska
300716fd348SMartin Matuska    for line in ('', info_line, graph_line, core_line, graph_line, ''):
301716fd348SMartin Matuska        print(line)
302716fd348SMartin Matuska
303716fd348SMartin Matuska
304716fd348SMartin Matuskadef f_bytes(byte_string):
305716fd348SMartin Matuska    """Return human-readable representation of a byte value in
306716fd348SMartin Matuska    powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
307716fd348SMartin Matuska    points. Values smaller than one KiB are returned without
308716fd348SMartin Matuska    decimal points. Note "bytes" is a reserved keyword.
309716fd348SMartin Matuska    """
310716fd348SMartin Matuska
311716fd348SMartin Matuska    prefixes = ([2**80, "YiB"],   # yobibytes (yotta)
312716fd348SMartin Matuska                [2**70, "ZiB"],   # zebibytes (zetta)
313716fd348SMartin Matuska                [2**60, "EiB"],   # exbibytes (exa)
314716fd348SMartin Matuska                [2**50, "PiB"],   # pebibytes (peta)
315716fd348SMartin Matuska                [2**40, "TiB"],   # tebibytes (tera)
316716fd348SMartin Matuska                [2**30, "GiB"],   # gibibytes (giga)
317716fd348SMartin Matuska                [2**20, "MiB"],   # mebibytes (mega)
318716fd348SMartin Matuska                [2**10, "KiB"])   # kibibytes (kilo)
319716fd348SMartin Matuska
320716fd348SMartin Matuska    bites = int(byte_string)
321716fd348SMartin Matuska
322716fd348SMartin Matuska    if bites >= 2**10:
323716fd348SMartin Matuska        for limit, unit in prefixes:
324716fd348SMartin Matuska
325716fd348SMartin Matuska            if bites >= limit:
326716fd348SMartin Matuska                value = bites / limit
327716fd348SMartin Matuska                break
328716fd348SMartin Matuska
329716fd348SMartin Matuska        result = '{0:.1f} {1}'.format(value, unit)
330716fd348SMartin Matuska    else:
331716fd348SMartin Matuska        result = '{0} Bytes'.format(bites)
332716fd348SMartin Matuska
333716fd348SMartin Matuska    return result
334716fd348SMartin Matuska
335716fd348SMartin Matuska
336716fd348SMartin Matuskadef f_hits(hits_string):
337716fd348SMartin Matuska    """Create a human-readable representation of the number of hits.
338716fd348SMartin Matuska    The single-letter symbols used are SI to avoid the confusion caused
339716fd348SMartin Matuska    by the different "short scale" and "long scale" representations in
340716fd348SMartin Matuska    English, which use the same words for different values. See
341716fd348SMartin Matuska    https://en.wikipedia.org/wiki/Names_of_large_numbers and:
342716fd348SMartin Matuska    https://physics.nist.gov/cuu/Units/prefixes.html
343716fd348SMartin Matuska    """
344716fd348SMartin Matuska
345716fd348SMartin Matuska    numbers = ([10**24, 'Y'],  # yotta (septillion)
346716fd348SMartin Matuska               [10**21, 'Z'],  # zetta (sextillion)
347716fd348SMartin Matuska               [10**18, 'E'],  # exa   (quintrillion)
348716fd348SMartin Matuska               [10**15, 'P'],  # peta  (quadrillion)
349716fd348SMartin Matuska               [10**12, 'T'],  # tera  (trillion)
350716fd348SMartin Matuska               [10**9, 'G'],   # giga  (billion)
351716fd348SMartin Matuska               [10**6, 'M'],   # mega  (million)
352716fd348SMartin Matuska               [10**3, 'k'])   # kilo  (thousand)
353716fd348SMartin Matuska
354716fd348SMartin Matuska    hits = int(hits_string)
355716fd348SMartin Matuska
356716fd348SMartin Matuska    if hits >= 1000:
357716fd348SMartin Matuska        for limit, symbol in numbers:
358716fd348SMartin Matuska
359716fd348SMartin Matuska            if hits >= limit:
360716fd348SMartin Matuska                value = hits/limit
361716fd348SMartin Matuska                break
362716fd348SMartin Matuska
363716fd348SMartin Matuska        result = "%0.1f%s" % (value, symbol)
364716fd348SMartin Matuska    else:
365716fd348SMartin Matuska        result = "%d" % hits
366716fd348SMartin Matuska
367716fd348SMartin Matuska    return result
368716fd348SMartin Matuska
369716fd348SMartin Matuska
370716fd348SMartin Matuskadef f_perc(value1, value2):
371716fd348SMartin Matuska    """Calculate percentage and return in human-readable form. If
372716fd348SMartin Matuska    rounding produces the result '0.0' though the first number is
373716fd348SMartin Matuska    not zero, include a 'less-than' symbol to avoid confusion.
374716fd348SMartin Matuska    Division by zero is handled by returning 'n/a'; no error
375716fd348SMartin Matuska    is called.
376716fd348SMartin Matuska    """
377716fd348SMartin Matuska
378716fd348SMartin Matuska    v1 = float(value1)
379716fd348SMartin Matuska    v2 = float(value2)
380716fd348SMartin Matuska
381716fd348SMartin Matuska    try:
382716fd348SMartin Matuska        perc = 100 * v1/v2
383716fd348SMartin Matuska    except ZeroDivisionError:
384716fd348SMartin Matuska        result = 'n/a'
385716fd348SMartin Matuska    else:
386716fd348SMartin Matuska        result = '{0:0.1f} %'.format(perc)
387716fd348SMartin Matuska
388716fd348SMartin Matuska    if result == '0.0 %' and v1 > 0:
389716fd348SMartin Matuska        result = '< 0.1 %'
390716fd348SMartin Matuska
391716fd348SMartin Matuska    return result
392716fd348SMartin Matuska
393716fd348SMartin Matuska
394716fd348SMartin Matuskadef format_raw_line(name, value):
395716fd348SMartin Matuska    """For the --raw option for the tunable and SPL outputs, decide on the
396716fd348SMartin Matuska    correct formatting based on the --alternate flag.
397716fd348SMartin Matuska    """
398716fd348SMartin Matuska
399716fd348SMartin Matuska    if ARGS.alt:
400716fd348SMartin Matuska        result = '{0}{1}={2}'.format(INDENT, name, value)
401716fd348SMartin Matuska    else:
402716fd348SMartin Matuska        # Right-align the value within the line length if it fits,
403716fd348SMartin Matuska        # otherwise just separate it from the name by a single space.
404716fd348SMartin Matuska        fit = LINE_LENGTH - len(INDENT) - len(name)
405716fd348SMartin Matuska        overflow = len(value) + 1
406716fd348SMartin Matuska        w = max(fit, overflow)
407716fd348SMartin Matuska        result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
408716fd348SMartin Matuska
409716fd348SMartin Matuska    return result
410716fd348SMartin Matuska
411716fd348SMartin Matuska
412716fd348SMartin Matuskadef get_kstats():
413716fd348SMartin Matuska    """Collect information on the ZFS subsystem. The step does not perform any
414716fd348SMartin Matuska    further processing, giving us the option to only work on what is actually
415716fd348SMartin Matuska    needed. The name "kstat" is a holdover from the Solaris utility of the same
416716fd348SMartin Matuska    name.
417716fd348SMartin Matuska    """
418716fd348SMartin Matuska
419716fd348SMartin Matuska    result = {}
420716fd348SMartin Matuska
421716fd348SMartin Matuska    for section in SECTION_PATHS.values():
422716fd348SMartin Matuska        if section not in result:
423716fd348SMartin Matuska            result[section] = load_kstats(section)
424716fd348SMartin Matuska
425716fd348SMartin Matuska    return result
426716fd348SMartin Matuska
427716fd348SMartin Matuska
428716fd348SMartin Matuskadef get_version(request):
429716fd348SMartin Matuska    """Get the version number of ZFS or SPL on this machine for header.
430716fd348SMartin Matuska    Returns an error string, but does not raise an error, if we can't
431716fd348SMartin Matuska    get the ZFS/SPL version.
432716fd348SMartin Matuska    """
433716fd348SMartin Matuska
434716fd348SMartin Matuska    if request not in ('spl', 'zfs'):
435716fd348SMartin Matuska        error_msg = '(ERROR: "{0}" requested)'.format(request)
436716fd348SMartin Matuska        return error_msg
437716fd348SMartin Matuska
438716fd348SMartin Matuska    return get_version_impl(request)
439716fd348SMartin Matuska
440716fd348SMartin Matuska
441716fd348SMartin Matuskadef print_header():
442716fd348SMartin Matuska    """Print the initial heading with date and time as well as info on the
443716fd348SMartin Matuska    kernel and ZFS versions. This is not called for the graph.
444716fd348SMartin Matuska    """
445716fd348SMartin Matuska
446716fd348SMartin Matuska    # datetime is now recommended over time but we keep the exact formatting
447716fd348SMartin Matuska    # from the older version of arc_summary in case there are scripts
448716fd348SMartin Matuska    # that expect it in this way
449716fd348SMartin Matuska    daydate = time.strftime(DATE_FORMAT)
450716fd348SMartin Matuska    spc_date = LINE_LENGTH-len(daydate)
451716fd348SMartin Matuska    sys_version = os.uname()
452716fd348SMartin Matuska
453716fd348SMartin Matuska    sys_msg = sys_version.sysname+' '+sys_version.release
454716fd348SMartin Matuska    zfs = get_version('zfs')
455716fd348SMartin Matuska    spc_zfs = LINE_LENGTH-len(zfs)
456716fd348SMartin Matuska
457716fd348SMartin Matuska    machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
458716fd348SMartin Matuska    spl = get_version('spl')
459716fd348SMartin Matuska    spc_spl = LINE_LENGTH-len(spl)
460716fd348SMartin Matuska
461716fd348SMartin Matuska    print('\n'+('-'*LINE_LENGTH))
462716fd348SMartin Matuska    print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
463716fd348SMartin Matuska    print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
464716fd348SMartin Matuska    print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
465716fd348SMartin Matuska
466716fd348SMartin Matuska
467716fd348SMartin Matuskadef print_raw(kstats_dict):
468716fd348SMartin Matuska    """Print all available data from the system in a minimally sorted format.
469716fd348SMartin Matuska    This can be used as a source to be piped through 'grep'.
470716fd348SMartin Matuska    """
471716fd348SMartin Matuska
472716fd348SMartin Matuska    sections = sorted(kstats_dict.keys())
473716fd348SMartin Matuska
474716fd348SMartin Matuska    for section in sections:
475716fd348SMartin Matuska
476716fd348SMartin Matuska        print('\n{0}:'.format(section.upper()))
477716fd348SMartin Matuska        lines = sorted(kstats_dict[section])
478716fd348SMartin Matuska
479716fd348SMartin Matuska        for line in lines:
480716fd348SMartin Matuska            name, value = cleanup_line(line)
481716fd348SMartin Matuska            print(format_raw_line(name, value))
482716fd348SMartin Matuska
483716fd348SMartin Matuska    # Tunables and SPL must be handled separately because they come from a
484716fd348SMartin Matuska    # different source and have descriptions the user might request
485716fd348SMartin Matuska    print()
486716fd348SMartin Matuska    section_spl()
487716fd348SMartin Matuska    section_tunables()
488716fd348SMartin Matuska
489716fd348SMartin Matuska
490716fd348SMartin Matuskadef isolate_section(section_name, kstats_dict):
491716fd348SMartin Matuska    """From the complete information on all sections, retrieve only those
492716fd348SMartin Matuska    for one section.
493716fd348SMartin Matuska    """
494716fd348SMartin Matuska
495716fd348SMartin Matuska    try:
496716fd348SMartin Matuska        section_data = kstats_dict[section_name]
497716fd348SMartin Matuska    except KeyError:
498716fd348SMartin Matuska        print('ERROR: Data on {0} not available'.format(section_data))
499716fd348SMartin Matuska        sys.exit(1)
500716fd348SMartin Matuska
501716fd348SMartin Matuska    section_dict = dict(cleanup_line(l) for l in section_data)
502716fd348SMartin Matuska
503716fd348SMartin Matuska    return section_dict
504716fd348SMartin Matuska
505716fd348SMartin Matuska
506716fd348SMartin Matuska# Formatted output helper functions
507716fd348SMartin Matuska
508716fd348SMartin Matuska
509716fd348SMartin Matuskadef prt_1(text, value):
510716fd348SMartin Matuska    """Print text and one value, no indent"""
511716fd348SMartin Matuska    spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
512716fd348SMartin Matuska    print('{0}{spc}{1}'.format(text, value, spc=spc))
513716fd348SMartin Matuska
514716fd348SMartin Matuska
515716fd348SMartin Matuskadef prt_i1(text, value):
516716fd348SMartin Matuska    """Print text and one value, with indent"""
517716fd348SMartin Matuska    spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
518716fd348SMartin Matuska    print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
519716fd348SMartin Matuska
520716fd348SMartin Matuska
521716fd348SMartin Matuskadef prt_2(text, value1, value2):
522716fd348SMartin Matuska    """Print text and two values, no indent"""
523716fd348SMartin Matuska    values = '{0:>9}  {1:>9}'.format(value1, value2)
524716fd348SMartin Matuska    spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
525716fd348SMartin Matuska    print('{0}{spc}  {1}'.format(text, values, spc=spc))
526716fd348SMartin Matuska
527716fd348SMartin Matuska
528716fd348SMartin Matuskadef prt_i2(text, value1, value2):
529716fd348SMartin Matuska    """Print text and two values, with indent"""
530716fd348SMartin Matuska    values = '{0:>9}  {1:>9}'.format(value1, value2)
531716fd348SMartin Matuska    spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
532716fd348SMartin Matuska    print(INDENT+'{0}{spc}  {1}'.format(text, values, spc=spc))
533716fd348SMartin Matuska
534716fd348SMartin Matuska
535716fd348SMartin Matuska# The section output concentrates on important parameters instead of
536716fd348SMartin Matuska# being exhaustive (that is what the --raw parameter is for)
537716fd348SMartin Matuska
538716fd348SMartin Matuska
539716fd348SMartin Matuskadef section_arc(kstats_dict):
540716fd348SMartin Matuska    """Give basic information on the ARC, MRU and MFU. This is the first
541716fd348SMartin Matuska    and most used section.
542716fd348SMartin Matuska    """
543716fd348SMartin Matuska
544716fd348SMartin Matuska    arc_stats = isolate_section('arcstats', kstats_dict)
545716fd348SMartin Matuska
546716fd348SMartin Matuska    throttle = arc_stats['memory_throttle_count']
547716fd348SMartin Matuska
548716fd348SMartin Matuska    if throttle == '0':
549716fd348SMartin Matuska        health = 'HEALTHY'
550716fd348SMartin Matuska    else:
551716fd348SMartin Matuska        health = 'THROTTLED'
552716fd348SMartin Matuska
553716fd348SMartin Matuska    prt_1('ARC status:', health)
554716fd348SMartin Matuska    prt_i1('Memory throttle count:', throttle)
555716fd348SMartin Matuska    print()
556716fd348SMartin Matuska
557716fd348SMartin Matuska    arc_size = arc_stats['size']
558716fd348SMartin Matuska    arc_target_size = arc_stats['c']
559716fd348SMartin Matuska    arc_max = arc_stats['c_max']
560716fd348SMartin Matuska    arc_min = arc_stats['c_min']
561*15f0b8c3SMartin Matuska    anon_size = arc_stats['anon_size']
562716fd348SMartin Matuska    mfu_size = arc_stats['mfu_size']
563716fd348SMartin Matuska    mru_size = arc_stats['mru_size']
564*15f0b8c3SMartin Matuska    mfug_size = arc_stats['mfu_ghost_size']
565*15f0b8c3SMartin Matuska    mrug_size = arc_stats['mru_ghost_size']
566*15f0b8c3SMartin Matuska    unc_size = arc_stats['uncached_size']
567716fd348SMartin Matuska    meta_limit = arc_stats['arc_meta_limit']
568716fd348SMartin Matuska    meta_size = arc_stats['arc_meta_used']
569716fd348SMartin Matuska    dnode_limit = arc_stats['arc_dnode_limit']
570716fd348SMartin Matuska    dnode_size = arc_stats['dnode_size']
571716fd348SMartin Matuska    target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
572716fd348SMartin Matuska
573716fd348SMartin Matuska    prt_2('ARC size (current):',
574716fd348SMartin Matuska          f_perc(arc_size, arc_max), f_bytes(arc_size))
575716fd348SMartin Matuska    prt_i2('Target size (adaptive):',
576716fd348SMartin Matuska           f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
577716fd348SMartin Matuska    prt_i2('Min size (hard limit):',
578716fd348SMartin Matuska           f_perc(arc_min, arc_max), f_bytes(arc_min))
579716fd348SMartin Matuska    prt_i2('Max size (high water):',
580716fd348SMartin Matuska           target_size_ratio, f_bytes(arc_max))
581*15f0b8c3SMartin Matuska    caches_size = int(anon_size)+int(mfu_size)+int(mru_size)+int(unc_size)
582*15f0b8c3SMartin Matuska    prt_i2('Anonymouns data size:',
583*15f0b8c3SMartin Matuska           f_perc(anon_size, caches_size), f_bytes(anon_size))
584716fd348SMartin Matuska    prt_i2('Most Frequently Used (MFU) cache size:',
585716fd348SMartin Matuska           f_perc(mfu_size, caches_size), f_bytes(mfu_size))
586716fd348SMartin Matuska    prt_i2('Most Recently Used (MRU) cache size:',
587716fd348SMartin Matuska           f_perc(mru_size, caches_size), f_bytes(mru_size))
588*15f0b8c3SMartin Matuska    prt_i1('Most Frequently Used (MFU) ghost size:', f_bytes(mfug_size))
589*15f0b8c3SMartin Matuska    prt_i1('Most Recently Used (MRU) ghost size:', f_bytes(mrug_size))
590*15f0b8c3SMartin Matuska    prt_i2('Uncached data size:',
591*15f0b8c3SMartin Matuska           f_perc(unc_size, caches_size), f_bytes(unc_size))
592716fd348SMartin Matuska    prt_i2('Metadata cache size (hard limit):',
593716fd348SMartin Matuska           f_perc(meta_limit, arc_max), f_bytes(meta_limit))
594716fd348SMartin Matuska    prt_i2('Metadata cache size (current):',
595716fd348SMartin Matuska           f_perc(meta_size, meta_limit), f_bytes(meta_size))
596716fd348SMartin Matuska    prt_i2('Dnode cache size (hard limit):',
597716fd348SMartin Matuska           f_perc(dnode_limit, meta_limit), f_bytes(dnode_limit))
598716fd348SMartin Matuska    prt_i2('Dnode cache size (current):',
599716fd348SMartin Matuska           f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
600716fd348SMartin Matuska    print()
601716fd348SMartin Matuska
602716fd348SMartin Matuska    print('ARC hash breakdown:')
603716fd348SMartin Matuska    prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
604716fd348SMartin Matuska    prt_i2('Elements current:',
605716fd348SMartin Matuska           f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
606716fd348SMartin Matuska           f_hits(arc_stats['hash_elements']))
607716fd348SMartin Matuska    prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
608716fd348SMartin Matuska
609716fd348SMartin Matuska    prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
610716fd348SMartin Matuska    prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
611716fd348SMartin Matuska    print()
612716fd348SMartin Matuska
613716fd348SMartin Matuska    print('ARC misc:')
614716fd348SMartin Matuska    prt_i1('Deleted:', f_hits(arc_stats['deleted']))
615716fd348SMartin Matuska    prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
616716fd348SMartin Matuska    prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
617716fd348SMartin Matuska    prt_i1('Eviction skips due to L2 writes:',
618716fd348SMartin Matuska           f_hits(arc_stats['evict_l2_skip']))
619716fd348SMartin Matuska    prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
620716fd348SMartin Matuska    prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
621716fd348SMartin Matuska    prt_i2('L2 eligible MFU evictions:',
622716fd348SMartin Matuska           f_perc(arc_stats['evict_l2_eligible_mfu'],
623716fd348SMartin Matuska           arc_stats['evict_l2_eligible']),
624716fd348SMartin Matuska           f_bytes(arc_stats['evict_l2_eligible_mfu']))
625716fd348SMartin Matuska    prt_i2('L2 eligible MRU evictions:',
626716fd348SMartin Matuska           f_perc(arc_stats['evict_l2_eligible_mru'],
627716fd348SMartin Matuska           arc_stats['evict_l2_eligible']),
628716fd348SMartin Matuska           f_bytes(arc_stats['evict_l2_eligible_mru']))
629716fd348SMartin Matuska    prt_i1('L2 ineligible evictions:',
630716fd348SMartin Matuska           f_bytes(arc_stats['evict_l2_ineligible']))
631716fd348SMartin Matuska    print()
632716fd348SMartin Matuska
633716fd348SMartin Matuska
634716fd348SMartin Matuskadef section_archits(kstats_dict):
635716fd348SMartin Matuska    """Print information on how the caches are accessed ("arc hits").
636716fd348SMartin Matuska    """
637716fd348SMartin Matuska
638716fd348SMartin Matuska    arc_stats = isolate_section('arcstats', kstats_dict)
639*15f0b8c3SMartin Matuska    all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
640*15f0b8c3SMartin Matuska        int(arc_stats['misses'])
641716fd348SMartin Matuska
642*15f0b8c3SMartin Matuska    prt_1('ARC total accesses:', f_hits(all_accesses))
643*15f0b8c3SMartin Matuska    ta_todo = (('Total hits:', arc_stats['hits']),
644*15f0b8c3SMartin Matuska               ('Total I/O hits:', arc_stats['iohits']),
645*15f0b8c3SMartin Matuska               ('Total misses:', arc_stats['misses']))
646716fd348SMartin Matuska    for title, value in ta_todo:
647716fd348SMartin Matuska        prt_i2(title, f_perc(value, all_accesses), f_hits(value))
648*15f0b8c3SMartin Matuska    print()
649716fd348SMartin Matuska
650716fd348SMartin Matuska    dd_total = int(arc_stats['demand_data_hits']) +\
651*15f0b8c3SMartin Matuska        int(arc_stats['demand_data_iohits']) +\
652716fd348SMartin Matuska        int(arc_stats['demand_data_misses'])
653*15f0b8c3SMartin Matuska    prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
654716fd348SMartin Matuska         f_hits(dd_total))
655*15f0b8c3SMartin Matuska    dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
656*15f0b8c3SMartin Matuska               ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
657*15f0b8c3SMartin Matuska               ('Demand data misses:', arc_stats['demand_data_misses']))
658*15f0b8c3SMartin Matuska    for title, value in dd_todo:
659*15f0b8c3SMartin Matuska        prt_i2(title, f_perc(value, dd_total), f_hits(value))
660716fd348SMartin Matuska    print()
661*15f0b8c3SMartin Matuska
662*15f0b8c3SMartin Matuska    dm_total = int(arc_stats['demand_metadata_hits']) +\
663*15f0b8c3SMartin Matuska        int(arc_stats['demand_metadata_iohits']) +\
664*15f0b8c3SMartin Matuska        int(arc_stats['demand_metadata_misses'])
665*15f0b8c3SMartin Matuska    prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
666*15f0b8c3SMartin Matuska          f_hits(dm_total))
667*15f0b8c3SMartin Matuska    dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
668*15f0b8c3SMartin Matuska               ('Demand metadata I/O hits:',
669*15f0b8c3SMartin Matuska                arc_stats['demand_metadata_iohits']),
670*15f0b8c3SMartin Matuska               ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
671*15f0b8c3SMartin Matuska    for title, value in dm_todo:
672*15f0b8c3SMartin Matuska        prt_i2(title, f_perc(value, dm_total), f_hits(value))
673*15f0b8c3SMartin Matuska    print()
674*15f0b8c3SMartin Matuska
675*15f0b8c3SMartin Matuska    pd_total = int(arc_stats['prefetch_data_hits']) +\
676*15f0b8c3SMartin Matuska        int(arc_stats['prefetch_data_iohits']) +\
677*15f0b8c3SMartin Matuska        int(arc_stats['prefetch_data_misses'])
678*15f0b8c3SMartin Matuska    prt_2('ARC prefetch metadata accesses:', f_perc(pd_total, all_accesses),
679*15f0b8c3SMartin Matuska          f_hits(pd_total))
680*15f0b8c3SMartin Matuska    pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
681*15f0b8c3SMartin Matuska               ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
682*15f0b8c3SMartin Matuska               ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
683*15f0b8c3SMartin Matuska    for title, value in pd_todo:
684*15f0b8c3SMartin Matuska        prt_i2(title, f_perc(value, pd_total), f_hits(value))
685*15f0b8c3SMartin Matuska    print()
686*15f0b8c3SMartin Matuska
687*15f0b8c3SMartin Matuska    pm_total = int(arc_stats['prefetch_metadata_hits']) +\
688*15f0b8c3SMartin Matuska        int(arc_stats['prefetch_metadata_iohits']) +\
689*15f0b8c3SMartin Matuska        int(arc_stats['prefetch_metadata_misses'])
690*15f0b8c3SMartin Matuska    prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
691*15f0b8c3SMartin Matuska          f_hits(pm_total))
692*15f0b8c3SMartin Matuska    pm_todo = (('Prefetch metadata hits:',
693*15f0b8c3SMartin Matuska                arc_stats['prefetch_metadata_hits']),
694*15f0b8c3SMartin Matuska               ('Prefetch metadata I/O hits:',
695*15f0b8c3SMartin Matuska                arc_stats['prefetch_metadata_iohits']),
696*15f0b8c3SMartin Matuska               ('Prefetch metadata misses:',
697*15f0b8c3SMartin Matuska                arc_stats['prefetch_metadata_misses']))
698*15f0b8c3SMartin Matuska    for title, value in pm_todo:
699*15f0b8c3SMartin Matuska        prt_i2(title, f_perc(value, pm_total), f_hits(value))
700*15f0b8c3SMartin Matuska    print()
701*15f0b8c3SMartin Matuska
702*15f0b8c3SMartin Matuska    all_prefetches = int(arc_stats['predictive_prefetch'])+\
703*15f0b8c3SMartin Matuska        int(arc_stats['prescient_prefetch'])
704*15f0b8c3SMartin Matuska    prt_2('ARC predictive prefetches:',
705*15f0b8c3SMartin Matuska           f_perc(arc_stats['predictive_prefetch'], all_prefetches),
706*15f0b8c3SMartin Matuska           f_hits(arc_stats['predictive_prefetch']))
707*15f0b8c3SMartin Matuska    prt_i2('Demand hits after predictive:',
708*15f0b8c3SMartin Matuska           f_perc(arc_stats['demand_hit_predictive_prefetch'],
709*15f0b8c3SMartin Matuska                  arc_stats['predictive_prefetch']),
710*15f0b8c3SMartin Matuska           f_hits(arc_stats['demand_hit_predictive_prefetch']))
711*15f0b8c3SMartin Matuska    prt_i2('Demand I/O hits after predictive:',
712*15f0b8c3SMartin Matuska           f_perc(arc_stats['demand_iohit_predictive_prefetch'],
713*15f0b8c3SMartin Matuska                  arc_stats['predictive_prefetch']),
714*15f0b8c3SMartin Matuska           f_hits(arc_stats['demand_iohit_predictive_prefetch']))
715*15f0b8c3SMartin Matuska    never = int(arc_stats['predictive_prefetch']) -\
716*15f0b8c3SMartin Matuska        int(arc_stats['demand_hit_predictive_prefetch']) -\
717*15f0b8c3SMartin Matuska        int(arc_stats['demand_iohit_predictive_prefetch'])
718*15f0b8c3SMartin Matuska    prt_i2('Never demanded after predictive:',
719*15f0b8c3SMartin Matuska           f_perc(never, arc_stats['predictive_prefetch']),
720*15f0b8c3SMartin Matuska           f_hits(never))
721*15f0b8c3SMartin Matuska    print()
722*15f0b8c3SMartin Matuska
723*15f0b8c3SMartin Matuska    prt_2('ARC prescient prefetches:',
724*15f0b8c3SMartin Matuska           f_perc(arc_stats['prescient_prefetch'], all_prefetches),
725*15f0b8c3SMartin Matuska           f_hits(arc_stats['prescient_prefetch']))
726*15f0b8c3SMartin Matuska    prt_i2('Demand hits after prescient:',
727*15f0b8c3SMartin Matuska           f_perc(arc_stats['demand_hit_prescient_prefetch'],
728*15f0b8c3SMartin Matuska                  arc_stats['prescient_prefetch']),
729*15f0b8c3SMartin Matuska           f_hits(arc_stats['demand_hit_prescient_prefetch']))
730*15f0b8c3SMartin Matuska    prt_i2('Demand I/O hits after prescient:',
731*15f0b8c3SMartin Matuska           f_perc(arc_stats['demand_iohit_prescient_prefetch'],
732*15f0b8c3SMartin Matuska                  arc_stats['prescient_prefetch']),
733*15f0b8c3SMartin Matuska           f_hits(arc_stats['demand_iohit_prescient_prefetch']))
734*15f0b8c3SMartin Matuska    never = int(arc_stats['prescient_prefetch'])-\
735*15f0b8c3SMartin Matuska        int(arc_stats['demand_hit_prescient_prefetch'])-\
736*15f0b8c3SMartin Matuska        int(arc_stats['demand_iohit_prescient_prefetch'])
737*15f0b8c3SMartin Matuska    prt_i2('Never demanded after prescient:',
738*15f0b8c3SMartin Matuska           f_perc(never, arc_stats['prescient_prefetch']),
739*15f0b8c3SMartin Matuska           f_hits(never))
740*15f0b8c3SMartin Matuska    print()
741*15f0b8c3SMartin Matuska
742*15f0b8c3SMartin Matuska    print('ARC states hits of all accesses:')
743716fd348SMartin Matuska    cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
744716fd348SMartin Matuska               ('Most recently used (MRU):', arc_stats['mru_hits']),
745716fd348SMartin Matuska               ('Most frequently used (MFU) ghost:',
746716fd348SMartin Matuska                arc_stats['mfu_ghost_hits']),
747716fd348SMartin Matuska               ('Most recently used (MRU) ghost:',
748*15f0b8c3SMartin Matuska                arc_stats['mru_ghost_hits']),
749*15f0b8c3SMartin Matuska               ('Uncached:', arc_stats['uncached_hits']))
750716fd348SMartin Matuska    for title, value in cl_todo:
751*15f0b8c3SMartin Matuska        prt_i2(title, f_perc(value, all_accesses), f_hits(value))
752716fd348SMartin Matuska    print()
753716fd348SMartin Matuska
754716fd348SMartin Matuska
755716fd348SMartin Matuskadef section_dmu(kstats_dict):
756716fd348SMartin Matuska    """Collect information on the DMU"""
757716fd348SMartin Matuska
758716fd348SMartin Matuska    zfetch_stats = isolate_section('zfetchstats', kstats_dict)
759716fd348SMartin Matuska
760716fd348SMartin Matuska    zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
761716fd348SMartin Matuska
762*15f0b8c3SMartin Matuska    prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
763*15f0b8c3SMartin Matuska    prt_i2('Stream hits:',
764*15f0b8c3SMartin Matuska           f_perc(zfetch_stats['hits'], zfetch_access_total),
765716fd348SMartin Matuska           f_hits(zfetch_stats['hits']))
766*15f0b8c3SMartin Matuska    prt_i2('Stream misses:',
767*15f0b8c3SMartin Matuska           f_perc(zfetch_stats['misses'], zfetch_access_total),
768716fd348SMartin Matuska           f_hits(zfetch_stats['misses']))
769*15f0b8c3SMartin Matuska    prt_i2('Streams limit reached:',
770*15f0b8c3SMartin Matuska           f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
771*15f0b8c3SMartin Matuska           f_hits(zfetch_stats['max_streams']))
772*15f0b8c3SMartin Matuska    prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
773716fd348SMartin Matuska    print()
774716fd348SMartin Matuska
775716fd348SMartin Matuska
776716fd348SMartin Matuskadef section_l2arc(kstats_dict):
777716fd348SMartin Matuska    """Collect information on L2ARC device if present. If not, tell user
778716fd348SMartin Matuska    that we're skipping the section.
779716fd348SMartin Matuska    """
780716fd348SMartin Matuska
781716fd348SMartin Matuska    # The L2ARC statistics live in the same section as the normal ARC stuff
782716fd348SMartin Matuska    arc_stats = isolate_section('arcstats', kstats_dict)
783716fd348SMartin Matuska
784716fd348SMartin Matuska    if arc_stats['l2_size'] == '0':
785716fd348SMartin Matuska        print('L2ARC not detected, skipping section\n')
786716fd348SMartin Matuska        return
787716fd348SMartin Matuska
788716fd348SMartin Matuska    l2_errors = int(arc_stats['l2_writes_error']) +\
789716fd348SMartin Matuska        int(arc_stats['l2_cksum_bad']) +\
790716fd348SMartin Matuska        int(arc_stats['l2_io_error'])
791716fd348SMartin Matuska
792716fd348SMartin Matuska    l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
793716fd348SMartin Matuska    health = 'HEALTHY'
794716fd348SMartin Matuska
795716fd348SMartin Matuska    if l2_errors > 0:
796716fd348SMartin Matuska        health = 'DEGRADED'
797716fd348SMartin Matuska
798716fd348SMartin Matuska    prt_1('L2ARC status:', health)
799716fd348SMartin Matuska
800716fd348SMartin Matuska    l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
801716fd348SMartin Matuska               ('Free on write:', 'l2_free_on_write'),
802716fd348SMartin Matuska               ('R/W clashes:', 'l2_rw_clash'),
803716fd348SMartin Matuska               ('Bad checksums:', 'l2_cksum_bad'),
804716fd348SMartin Matuska               ('I/O errors:', 'l2_io_error'))
805716fd348SMartin Matuska
806716fd348SMartin Matuska    for title, value in l2_todo:
807716fd348SMartin Matuska        prt_i1(title, f_hits(arc_stats[value]))
808716fd348SMartin Matuska
809716fd348SMartin Matuska    print()
810716fd348SMartin Matuska    prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
811716fd348SMartin Matuska    prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
812716fd348SMartin Matuska           f_bytes(arc_stats['l2_asize']))
813716fd348SMartin Matuska    prt_i2('Header size:',
814716fd348SMartin Matuska           f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
815716fd348SMartin Matuska           f_bytes(arc_stats['l2_hdr_size']))
816716fd348SMartin Matuska    prt_i2('MFU allocated size:',
817716fd348SMartin Matuska           f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
818716fd348SMartin Matuska           f_bytes(arc_stats['l2_mfu_asize']))
819716fd348SMartin Matuska    prt_i2('MRU allocated size:',
820716fd348SMartin Matuska           f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
821716fd348SMartin Matuska           f_bytes(arc_stats['l2_mru_asize']))
822716fd348SMartin Matuska    prt_i2('Prefetch allocated size:',
823716fd348SMartin Matuska           f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
824716fd348SMartin Matuska           f_bytes(arc_stats['l2_prefetch_asize']))
825716fd348SMartin Matuska    prt_i2('Data (buffer content) allocated size:',
826716fd348SMartin Matuska           f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
827716fd348SMartin Matuska           f_bytes(arc_stats['l2_bufc_data_asize']))
828716fd348SMartin Matuska    prt_i2('Metadata (buffer content) allocated size:',
829716fd348SMartin Matuska           f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
830716fd348SMartin Matuska           f_bytes(arc_stats['l2_bufc_metadata_asize']))
831716fd348SMartin Matuska
832716fd348SMartin Matuska    print()
833716fd348SMartin Matuska    prt_1('L2ARC breakdown:', f_hits(l2_access_total))
834716fd348SMartin Matuska    prt_i2('Hit ratio:',
835716fd348SMartin Matuska           f_perc(arc_stats['l2_hits'], l2_access_total),
836716fd348SMartin Matuska           f_hits(arc_stats['l2_hits']))
837716fd348SMartin Matuska    prt_i2('Miss ratio:',
838716fd348SMartin Matuska           f_perc(arc_stats['l2_misses'], l2_access_total),
839716fd348SMartin Matuska           f_hits(arc_stats['l2_misses']))
840716fd348SMartin Matuska    prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
841716fd348SMartin Matuska
842716fd348SMartin Matuska    print()
843716fd348SMartin Matuska    print('L2ARC writes:')
844716fd348SMartin Matuska
845716fd348SMartin Matuska    if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
846716fd348SMartin Matuska        prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
847716fd348SMartin Matuska        prt_i2('Done ratio:',
848716fd348SMartin Matuska               f_perc(arc_stats['l2_writes_done'],
849716fd348SMartin Matuska                      arc_stats['l2_writes_sent']),
850716fd348SMartin Matuska               f_hits(arc_stats['l2_writes_done']))
851716fd348SMartin Matuska        prt_i2('Error ratio:',
852716fd348SMartin Matuska               f_perc(arc_stats['l2_writes_error'],
853716fd348SMartin Matuska                      arc_stats['l2_writes_sent']),
854716fd348SMartin Matuska               f_hits(arc_stats['l2_writes_error']))
855716fd348SMartin Matuska    else:
856716fd348SMartin Matuska        prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
857716fd348SMartin Matuska
858716fd348SMartin Matuska    print()
859716fd348SMartin Matuska    print('L2ARC evicts:')
860716fd348SMartin Matuska    prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
861716fd348SMartin Matuska    prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
862716fd348SMartin Matuska    print()
863716fd348SMartin Matuska
864716fd348SMartin Matuska
865716fd348SMartin Matuskadef section_spl(*_):
866716fd348SMartin Matuska    """Print the SPL parameters, if requested with alternative format
867716fd348SMartin Matuska    and/or descriptions. This does not use kstats.
868716fd348SMartin Matuska    """
869716fd348SMartin Matuska
870716fd348SMartin Matuska    if sys.platform.startswith('freebsd'):
871716fd348SMartin Matuska        # No SPL support in FreeBSD
872716fd348SMartin Matuska        return
873716fd348SMartin Matuska
874716fd348SMartin Matuska    spls = get_spl_params()
875716fd348SMartin Matuska    keylist = sorted(spls.keys())
876716fd348SMartin Matuska    print('Solaris Porting Layer (SPL):')
877716fd348SMartin Matuska
878716fd348SMartin Matuska    if ARGS.desc:
879716fd348SMartin Matuska        descriptions = get_descriptions('spl')
880716fd348SMartin Matuska
881716fd348SMartin Matuska    for key in keylist:
882716fd348SMartin Matuska        value = spls[key]
883716fd348SMartin Matuska
884716fd348SMartin Matuska        if ARGS.desc:
885716fd348SMartin Matuska            try:
886716fd348SMartin Matuska                print(INDENT+'#', descriptions[key])
887716fd348SMartin Matuska            except KeyError:
888716fd348SMartin Matuska                print(INDENT+'# (No description found)')  # paranoid
889716fd348SMartin Matuska
890716fd348SMartin Matuska        print(format_raw_line(key, value))
891716fd348SMartin Matuska
892716fd348SMartin Matuska    print()
893716fd348SMartin Matuska
894716fd348SMartin Matuska
895716fd348SMartin Matuskadef section_tunables(*_):
896716fd348SMartin Matuska    """Print the tunables, if requested with alternative format and/or
897716fd348SMartin Matuska    descriptions. This does not use kstasts.
898716fd348SMartin Matuska    """
899716fd348SMartin Matuska
900716fd348SMartin Matuska    tunables = get_tunable_params()
901716fd348SMartin Matuska    keylist = sorted(tunables.keys())
902716fd348SMartin Matuska    print('Tunables:')
903716fd348SMartin Matuska
904716fd348SMartin Matuska    if ARGS.desc:
905716fd348SMartin Matuska        descriptions = get_descriptions('zfs')
906716fd348SMartin Matuska
907716fd348SMartin Matuska    for key in keylist:
908716fd348SMartin Matuska        value = tunables[key]
909716fd348SMartin Matuska
910716fd348SMartin Matuska        if ARGS.desc:
911716fd348SMartin Matuska            try:
912716fd348SMartin Matuska                print(INDENT+'#', descriptions[key])
913716fd348SMartin Matuska            except KeyError:
914716fd348SMartin Matuska                print(INDENT+'# (No description found)')  # paranoid
915716fd348SMartin Matuska
916716fd348SMartin Matuska        print(format_raw_line(key, value))
917716fd348SMartin Matuska
918716fd348SMartin Matuska    print()
919716fd348SMartin Matuska
920716fd348SMartin Matuska
921716fd348SMartin Matuskadef section_vdev(kstats_dict):
922716fd348SMartin Matuska    """Collect information on VDEV caches"""
923716fd348SMartin Matuska
924716fd348SMartin Matuska    # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
925716fd348SMartin Matuska    # harmful. When this is the case, we just skip the whole entry. See
926716fd348SMartin Matuska    # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
927716fd348SMartin Matuska    # for details
928716fd348SMartin Matuska    tunables = get_vdev_params()
929716fd348SMartin Matuska
930716fd348SMartin Matuska    if tunables[VDEV_CACHE_SIZE] == '0':
931716fd348SMartin Matuska        print('VDEV cache disabled, skipping section\n')
932716fd348SMartin Matuska        return
933716fd348SMartin Matuska
934716fd348SMartin Matuska    vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
935716fd348SMartin Matuska
936716fd348SMartin Matuska    vdev_cache_total = int(vdev_stats['hits']) +\
937716fd348SMartin Matuska        int(vdev_stats['misses']) +\
938716fd348SMartin Matuska        int(vdev_stats['delegations'])
939716fd348SMartin Matuska
940716fd348SMartin Matuska    prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
941716fd348SMartin Matuska    prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
942716fd348SMartin Matuska           f_hits(vdev_stats['hits']))
943716fd348SMartin Matuska    prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
944716fd348SMartin Matuska           f_hits(vdev_stats['misses']))
945716fd348SMartin Matuska    prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
946716fd348SMartin Matuska           f_hits(vdev_stats['delegations']))
947716fd348SMartin Matuska    print()
948716fd348SMartin Matuska
949716fd348SMartin Matuska
950716fd348SMartin Matuskadef section_zil(kstats_dict):
951716fd348SMartin Matuska    """Collect information on the ZFS Intent Log. Some of the information
952716fd348SMartin Matuska    taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
953716fd348SMartin Matuska    """
954716fd348SMartin Matuska
955716fd348SMartin Matuska    zil_stats = isolate_section('zil', kstats_dict)
956716fd348SMartin Matuska
957716fd348SMartin Matuska    prt_1('ZIL committed transactions:',
958716fd348SMartin Matuska          f_hits(zil_stats['zil_itx_count']))
959716fd348SMartin Matuska    prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
960716fd348SMartin Matuska    prt_i1('Flushes to stable storage:',
961716fd348SMartin Matuska           f_hits(zil_stats['zil_commit_writer_count']))
962716fd348SMartin Matuska    prt_i2('Transactions to SLOG storage pool:',
963716fd348SMartin Matuska           f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
964716fd348SMartin Matuska           f_hits(zil_stats['zil_itx_metaslab_slog_count']))
965716fd348SMartin Matuska    prt_i2('Transactions to non-SLOG storage pool:',
966716fd348SMartin Matuska           f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
967716fd348SMartin Matuska           f_hits(zil_stats['zil_itx_metaslab_normal_count']))
968716fd348SMartin Matuska    print()
969716fd348SMartin Matuska
970716fd348SMartin Matuska
971716fd348SMartin Matuskasection_calls = {'arc': section_arc,
972716fd348SMartin Matuska                 'archits': section_archits,
973716fd348SMartin Matuska                 'dmu': section_dmu,
974716fd348SMartin Matuska                 'l2arc': section_l2arc,
975716fd348SMartin Matuska                 'spl': section_spl,
976716fd348SMartin Matuska                 'tunables': section_tunables,
977716fd348SMartin Matuska                 'vdev': section_vdev,
978716fd348SMartin Matuska                 'zil': section_zil}
979716fd348SMartin Matuska
980716fd348SMartin Matuska
981716fd348SMartin Matuskadef main():
982716fd348SMartin Matuska    """Run program. The options to draw a graph and to print all data raw are
983716fd348SMartin Matuska    treated separately because they come with their own call.
984716fd348SMartin Matuska    """
985716fd348SMartin Matuska
986716fd348SMartin Matuska    kstats = get_kstats()
987716fd348SMartin Matuska
988716fd348SMartin Matuska    if ARGS.graph:
989716fd348SMartin Matuska        draw_graph(kstats)
990716fd348SMartin Matuska        sys.exit(0)
991716fd348SMartin Matuska
992716fd348SMartin Matuska    print_header()
993716fd348SMartin Matuska
994716fd348SMartin Matuska    if ARGS.raw:
995716fd348SMartin Matuska        print_raw(kstats)
996716fd348SMartin Matuska
997716fd348SMartin Matuska    elif ARGS.section:
998716fd348SMartin Matuska
999716fd348SMartin Matuska        try:
1000716fd348SMartin Matuska            section_calls[ARGS.section](kstats)
1001716fd348SMartin Matuska        except KeyError:
1002716fd348SMartin Matuska            print('Error: Section "{0}" unknown'.format(ARGS.section))
1003716fd348SMartin Matuska            sys.exit(1)
1004716fd348SMartin Matuska
1005716fd348SMartin Matuska    elif ARGS.page:
1006716fd348SMartin Matuska        print('WARNING: Pages are deprecated, please use "--section"\n')
1007716fd348SMartin Matuska
1008716fd348SMartin Matuska        pages_to_calls = {1: 'arc',
1009716fd348SMartin Matuska                          2: 'archits',
1010716fd348SMartin Matuska                          3: 'l2arc',
1011716fd348SMartin Matuska                          4: 'dmu',
1012716fd348SMartin Matuska                          5: 'vdev',
1013716fd348SMartin Matuska                          6: 'tunables'}
1014716fd348SMartin Matuska
1015716fd348SMartin Matuska        try:
1016716fd348SMartin Matuska            call = pages_to_calls[ARGS.page]
1017716fd348SMartin Matuska        except KeyError:
1018716fd348SMartin Matuska            print('Error: Page "{0}" not supported'.format(ARGS.page))
1019716fd348SMartin Matuska            sys.exit(1)
1020716fd348SMartin Matuska        else:
1021716fd348SMartin Matuska            section_calls[call](kstats)
1022716fd348SMartin Matuska
1023716fd348SMartin Matuska    else:
1024716fd348SMartin Matuska        # If no parameters were given, we print all sections. We might want to
1025716fd348SMartin Matuska        # change the sequence by hand
1026716fd348SMartin Matuska        calls = sorted(section_calls.keys())
1027716fd348SMartin Matuska
1028716fd348SMartin Matuska        for section in calls:
1029716fd348SMartin Matuska            section_calls[section](kstats)
1030716fd348SMartin Matuska
1031716fd348SMartin Matuska    sys.exit(0)
1032716fd348SMartin Matuska
1033716fd348SMartin Matuska
1034716fd348SMartin Matuskaif __name__ == '__main__':
1035716fd348SMartin Matuska    main()
1036