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