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