aboutsummaryrefslogtreecommitdiff
path: root/cmd/arc_summary/arc_summary3
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/arc_summary/arc_summary3')
-rwxr-xr-xcmd/arc_summary/arc_summary3943
1 files changed, 943 insertions, 0 deletions
diff --git a/cmd/arc_summary/arc_summary3 b/cmd/arc_summary/arc_summary3
new file mode 100755
index 000000000000..c920b8e5395d
--- /dev/null
+++ b/cmd/arc_summary/arc_summary3
@@ -0,0 +1,943 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2008 Ben Rockwood <benr@cuddletech.com>,
+# Copyright (c) 2010 Martin Matuska <mm@FreeBSD.org>,
+# Copyright (c) 2010-2011 Jason J. Hellenthal <jhell@DataIX.net>,
+# Copyright (c) 2017 Scot W. Stevenson <scot.stevenson@gmail.com>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+"""Print statistics on the ZFS ARC Cache and other information
+
+Provides basic information on the ARC, its efficiency, the L2ARC (if present),
+the Data Management Unit (DMU), Virtual Devices (VDEVs), and tunables. See
+the in-source documentation and code at
+https://github.com/zfsonlinux/zfs/blob/master/module/zfs/arc.c for details.
+The original introduction to arc_summary can be found at
+http://cuddletech.com/?p=454
+"""
+
+import argparse
+import os
+import subprocess
+import sys
+import time
+
+DESCRIPTION = 'Print ARC and other statistics for ZFS on Linux'
+INDENT = ' '*8
+LINE_LENGTH = 72
+DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
+TITLE = 'ZFS Subsystem Report'
+
+SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
+SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
+
+# Tunables and SPL are handled separately because they come from
+# different sources
+SECTION_PATHS = {'arc': 'arcstats',
+ 'dmu': 'dmu_tx',
+ 'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
+ 'vdev': 'vdev_cache_stats',
+ 'xuio': 'xuio_stats',
+ 'zfetch': 'zfetchstats',
+ 'zil': 'zil'}
+
+parser = argparse.ArgumentParser(description=DESCRIPTION)
+parser.add_argument('-a', '--alternate', action='store_true', default=False,
+ help='use alternate formatting for tunables and SPL',
+ dest='alt')
+parser.add_argument('-d', '--description', action='store_true', default=False,
+ help='print descriptions with tunables and SPL',
+ dest='desc')
+parser.add_argument('-g', '--graph', action='store_true', default=False,
+ help='print graph on ARC use and exit', dest='graph')
+parser.add_argument('-p', '--page', type=int, dest='page',
+ help='print page by number (DEPRECATED, use "-s")')
+parser.add_argument('-r', '--raw', action='store_true', default=False,
+ help='dump all available data with minimal formatting',
+ dest='raw')
+parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
+ARGS = parser.parse_args()
+
+
+if sys.platform.startswith('freebsd'):
+ # Requires py36-sysctl on FreeBSD
+ import sysctl
+
+ VDEV_CACHE_SIZE = 'vdev.cache_size'
+
+ def load_kstats(section):
+ base = 'kstat.zfs.misc.{section}.'.format(section=section)
+ # base is removed from the name
+ fmt = lambda kstat: '{name} : {value}'.format(name=kstat.name[len(base):],
+ value=kstat.value)
+ return [fmt(kstat) for kstat in sysctl.filter(base)]
+
+ def get_params(base):
+ cut = 8 # = len('vfs.zfs.')
+ return {ctl.name[cut:]: str(ctl.value) for ctl in sysctl.filter(base)}
+
+ def get_tunable_params():
+ return get_params('vfs.zfs')
+
+ def get_vdev_params():
+ return get_params('vfs.zfs.vdev')
+
+ def get_version_impl(request):
+ # FreeBSD reports versions for zpl and spa instead of zfs and spl.
+ name = {'zfs': 'zpl',
+ 'spl': 'spa'}[request]
+ mib = 'vfs.zfs.version.{}'.format(name)
+ version = sysctl.filter(mib)[0].value
+ return '{} version {}'.format(name, version)
+
+ def get_descriptions(_request):
+ # py-sysctl doesn't give descriptions, so we have to shell out.
+ command = ['sysctl', '-d', 'vfs.zfs']
+
+ # The recommended way to do this is with subprocess.run(). However,
+ # some installed versions of Python are < 3.5, so we offer them
+ # the option of doing it the old way (for now)
+ if 'run' in dir(subprocess):
+ info = subprocess.run(command, stdout=subprocess.PIPE,
+ universal_newlines=True)
+ lines = info.stdout.split('\n')
+ else:
+ info = subprocess.check_output(command, universal_newlines=True)
+ lines = info.split('\n')
+
+ def fmt(line):
+ name, desc = line.split(':', 1)
+ return (name.strip(), desc.strip())
+
+ return dict([fmt(line) for line in lines if len(line) > 0])
+
+
+elif sys.platform.startswith('linux'):
+ KSTAT_PATH = '/proc/spl/kstat/zfs'
+ SPL_PATH = '/sys/module/spl/parameters'
+ TUNABLES_PATH = '/sys/module/zfs/parameters'
+
+ VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
+
+ def load_kstats(section):
+ path = os.path.join(KSTAT_PATH, section)
+ with open(path) as f:
+ return list(f)[2:] # Get rid of header
+
+ def get_params(basepath):
+ """Collect information on the Solaris Porting Layer (SPL) or the
+ tunables, depending on the PATH given. Does not check if PATH is
+ legal.
+ """
+ result = {}
+ for name in os.listdir(basepath):
+ path = os.path.join(basepath, name)
+ with open(path) as f:
+ value = f.read()
+ result[name] = value.strip()
+ return result
+
+ def get_spl_params():
+ return get_params(SPL_PATH)
+
+ def get_tunable_params():
+ return get_params(TUNABLES_PATH)
+
+ def get_vdev_params():
+ return get_params(TUNABLES_PATH)
+
+ def get_version_impl(request):
+ # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
+ # the version information. We switch to /sys/module/{spl,zfs}/version
+ # to make sure we get what is really loaded in the kernel
+ command = ["cat", "/sys/module/{0}/version".format(request)]
+ req = request.upper()
+
+ # The recommended way to do this is with subprocess.run(). However,
+ # some installed versions of Python are < 3.5, so we offer them
+ # the option of doing it the old way (for now)
+ if 'run' in dir(subprocess):
+ info = subprocess.run(command, stdout=subprocess.PIPE,
+ universal_newlines=True)
+ version = info.stdout.strip()
+ else:
+ info = subprocess.check_output(command, universal_newlines=True)
+ version = info.strip()
+
+ return version
+
+ def get_descriptions(request):
+ """Get the descriptions of the Solaris Porting Layer (SPL) or the
+ tunables, return with minimal formatting.
+ """
+
+ if request not in ('spl', 'zfs'):
+ print('ERROR: description of "{0}" requested)'.format(request))
+ sys.exit(1)
+
+ descs = {}
+ target_prefix = 'parm:'
+
+ # We would prefer to do this with /sys/modules -- see the discussion at
+ # get_version() -- but there isn't a way to get the descriptions from
+ # there, so we fall back on modinfo
+ command = ["/sbin/modinfo", request, "-0"]
+
+ # The recommended way to do this is with subprocess.run(). However,
+ # some installed versions of Python are < 3.5, so we offer them
+ # the option of doing it the old way (for now)
+ info = ''
+
+ try:
+
+ if 'run' in dir(subprocess):
+ info = subprocess.run(command, stdout=subprocess.PIPE,
+ universal_newlines=True)
+ raw_output = info.stdout.split('\0')
+ else:
+ info = subprocess.check_output(command,
+ universal_newlines=True)
+ raw_output = info.split('\0')
+
+ except subprocess.CalledProcessError:
+ print("Error: Descriptions not available",
+ "(can't access kernel module)")
+ sys.exit(1)
+
+ for line in raw_output:
+
+ if not line.startswith(target_prefix):
+ continue
+
+ line = line[len(target_prefix):].strip()
+ name, raw_desc = line.split(':', 1)
+ desc = raw_desc.rsplit('(', 1)[0]
+
+ if desc == '':
+ desc = '(No description found)'
+
+ descs[name.strip()] = desc.strip()
+
+ return descs
+
+
+def cleanup_line(single_line):
+ """Format a raw line of data from /proc and isolate the name value
+ part, returning a tuple with each. Currently, this gets rid of the
+ middle '4'. For example "arc_no_grow 4 0" returns the tuple
+ ("arc_no_grow", "0").
+ """
+ name, _, value = single_line.split()
+
+ return name, value
+
+
+def draw_graph(kstats_dict):
+ """Draw a primitive graph representing the basic information on the
+ ARC -- its size and the proportion used by MFU and MRU -- and quit.
+ We use max size of the ARC to calculate how full it is. This is a
+ very rough representation.
+ """
+
+ arc_stats = isolate_section('arcstats', kstats_dict)
+
+ GRAPH_INDENT = ' '*4
+ GRAPH_WIDTH = 60
+ arc_size = f_bytes(arc_stats['size'])
+ arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
+ mfu_size = f_bytes(arc_stats['mfu_size'])
+ mru_size = f_bytes(arc_stats['mru_size'])
+ meta_limit = f_bytes(arc_stats['arc_meta_limit'])
+ meta_size = f_bytes(arc_stats['arc_meta_used'])
+ dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
+ dnode_size = f_bytes(arc_stats['dnode_size'])
+
+ info_form = ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} ({5}) '
+ 'DNODE {6} ({7})')
+ info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
+ meta_size, meta_limit, dnode_size,
+ dnode_limit)
+ info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
+ info_line = GRAPH_INDENT+info_spc+info_line
+
+ graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
+
+ mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
+ mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
+ arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
+ total_ticks = float(arc_perc)*GRAPH_WIDTH
+ mfu_ticks = mfu_perc*GRAPH_WIDTH
+ mru_ticks = mru_perc*GRAPH_WIDTH
+ other_ticks = total_ticks-(mfu_ticks+mru_ticks)
+
+ core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
+ core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
+ core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
+
+ for line in ('', info_line, graph_line, core_line, graph_line, ''):
+ print(line)
+
+
+def f_bytes(byte_string):
+ """Return human-readable representation of a byte value in
+ powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
+ points. Values smaller than one KiB are returned without
+ decimal points. Note "bytes" is a reserved keyword.
+ """
+
+ prefixes = ([2**80, "YiB"], # yobibytes (yotta)
+ [2**70, "ZiB"], # zebibytes (zetta)
+ [2**60, "EiB"], # exbibytes (exa)
+ [2**50, "PiB"], # pebibytes (peta)
+ [2**40, "TiB"], # tebibytes (tera)
+ [2**30, "GiB"], # gibibytes (giga)
+ [2**20, "MiB"], # mebibytes (mega)
+ [2**10, "KiB"]) # kibibytes (kilo)
+
+ bites = int(byte_string)
+
+ if bites >= 2**10:
+ for limit, unit in prefixes:
+
+ if bites >= limit:
+ value = bites / limit
+ break
+
+ result = '{0:.1f} {1}'.format(value, unit)
+ else:
+ result = '{0} Bytes'.format(bites)
+
+ return result
+
+
+def f_hits(hits_string):
+ """Create a human-readable representation of the number of hits.
+ The single-letter symbols used are SI to avoid the confusion caused
+ by the different "short scale" and "long scale" representations in
+ English, which use the same words for different values. See
+ https://en.wikipedia.org/wiki/Names_of_large_numbers and:
+ https://physics.nist.gov/cuu/Units/prefixes.html
+ """
+
+ numbers = ([10**24, 'Y'], # yotta (septillion)
+ [10**21, 'Z'], # zetta (sextillion)
+ [10**18, 'E'], # exa (quintrillion)
+ [10**15, 'P'], # peta (quadrillion)
+ [10**12, 'T'], # tera (trillion)
+ [10**9, 'G'], # giga (billion)
+ [10**6, 'M'], # mega (million)
+ [10**3, 'k']) # kilo (thousand)
+
+ hits = int(hits_string)
+
+ if hits >= 1000:
+ for limit, symbol in numbers:
+
+ if hits >= limit:
+ value = hits/limit
+ break
+
+ result = "%0.1f%s" % (value, symbol)
+ else:
+ result = "%d" % hits
+
+ return result
+
+
+def f_perc(value1, value2):
+ """Calculate percentage and return in human-readable form. If
+ rounding produces the result '0.0' though the first number is
+ not zero, include a 'less-than' symbol to avoid confusion.
+ Division by zero is handled by returning 'n/a'; no error
+ is called.
+ """
+
+ v1 = float(value1)
+ v2 = float(value2)
+
+ try:
+ perc = 100 * v1/v2
+ except ZeroDivisionError:
+ result = 'n/a'
+ else:
+ result = '{0:0.1f} %'.format(perc)
+
+ if result == '0.0 %' and v1 > 0:
+ result = '< 0.1 %'
+
+ return result
+
+
+def format_raw_line(name, value):
+ """For the --raw option for the tunable and SPL outputs, decide on the
+ correct formatting based on the --alternate flag.
+ """
+
+ if ARGS.alt:
+ result = '{0}{1}={2}'.format(INDENT, name, value)
+ else:
+ spc = LINE_LENGTH-(len(INDENT)+len(value))
+ result = '{0}{1:<{spc}}{2}'.format(INDENT, name, value, spc=spc)
+
+ return result
+
+
+def get_kstats():
+ """Collect information on the ZFS subsystem. The step does not perform any
+ further processing, giving us the option to only work on what is actually
+ needed. The name "kstat" is a holdover from the Solaris utility of the same
+ name.
+ """
+
+ result = {}
+
+ for section in SECTION_PATHS.values():
+ if section not in result:
+ result[section] = load_kstats(section)
+
+ return result
+
+
+def get_version(request):
+ """Get the version number of ZFS or SPL on this machine for header.
+ Returns an error string, but does not raise an error, if we can't
+ get the ZFS/SPL version.
+ """
+
+ if request not in ('spl', 'zfs'):
+ error_msg = '(ERROR: "{0}" requested)'.format(request)
+ return error_msg
+
+ return get_version_impl(request)
+
+
+def print_header():
+ """Print the initial heading with date and time as well as info on the
+ kernel and ZFS versions. This is not called for the graph.
+ """
+
+ # datetime is now recommended over time but we keep the exact formatting
+ # from the older version of arc_summary in case there are scripts
+ # that expect it in this way
+ daydate = time.strftime(DATE_FORMAT)
+ spc_date = LINE_LENGTH-len(daydate)
+ sys_version = os.uname()
+
+ sys_msg = sys_version.sysname+' '+sys_version.release
+ zfs = get_version('zfs')
+ spc_zfs = LINE_LENGTH-len(zfs)
+
+ machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
+ spl = get_version('spl')
+ spc_spl = LINE_LENGTH-len(spl)
+
+ print('\n'+('-'*LINE_LENGTH))
+ print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
+ print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
+ print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
+
+
+def print_raw(kstats_dict):
+ """Print all available data from the system in a minimally sorted format.
+ This can be used as a source to be piped through 'grep'.
+ """
+
+ sections = sorted(kstats_dict.keys())
+
+ for section in sections:
+
+ print('\n{0}:'.format(section.upper()))
+ lines = sorted(kstats_dict[section])
+
+ for line in lines:
+ name, value = cleanup_line(line)
+ print(format_raw_line(name, value))
+
+ # Tunables and SPL must be handled separately because they come from a
+ # different source and have descriptions the user might request
+ print()
+ section_spl()
+ section_tunables()
+
+
+def isolate_section(section_name, kstats_dict):
+ """From the complete information on all sections, retrieve only those
+ for one section.
+ """
+
+ try:
+ section_data = kstats_dict[section_name]
+ except KeyError:
+ print('ERROR: Data on {0} not available'.format(section_data))
+ sys.exit(1)
+
+ section_dict = dict(cleanup_line(l) for l in section_data)
+
+ return section_dict
+
+
+# Formatted output helper functions
+
+
+def prt_1(text, value):
+ """Print text and one value, no indent"""
+ spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
+ print('{0}{spc}{1}'.format(text, value, spc=spc))
+
+
+def prt_i1(text, value):
+ """Print text and one value, with indent"""
+ spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
+ print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
+
+
+def prt_2(text, value1, value2):
+ """Print text and two values, no indent"""
+ values = '{0:>9} {1:>9}'.format(value1, value2)
+ spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
+ print('{0}{spc} {1}'.format(text, values, spc=spc))
+
+
+def prt_i2(text, value1, value2):
+ """Print text and two values, with indent"""
+ values = '{0:>9} {1:>9}'.format(value1, value2)
+ spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
+ print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
+
+
+# The section output concentrates on important parameters instead of
+# being exhaustive (that is what the --raw parameter is for)
+
+
+def section_arc(kstats_dict):
+ """Give basic information on the ARC, MRU and MFU. This is the first
+ and most used section.
+ """
+
+ arc_stats = isolate_section('arcstats', kstats_dict)
+
+ throttle = arc_stats['memory_throttle_count']
+
+ if throttle == '0':
+ health = 'HEALTHY'
+ else:
+ health = 'THROTTLED'
+
+ prt_1('ARC status:', health)
+ prt_i1('Memory throttle count:', throttle)
+ print()
+
+ arc_size = arc_stats['size']
+ arc_target_size = arc_stats['c']
+ arc_max = arc_stats['c_max']
+ arc_min = arc_stats['c_min']
+ mfu_size = arc_stats['mfu_size']
+ mru_size = arc_stats['mru_size']
+ meta_limit = arc_stats['arc_meta_limit']
+ meta_size = arc_stats['arc_meta_used']
+ dnode_limit = arc_stats['arc_dnode_limit']
+ dnode_size = arc_stats['dnode_size']
+ target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
+
+ prt_2('ARC size (current):',
+ f_perc(arc_size, arc_max), f_bytes(arc_size))
+ prt_i2('Target size (adaptive):',
+ f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
+ prt_i2('Min size (hard limit):',
+ f_perc(arc_min, arc_max), f_bytes(arc_min))
+ prt_i2('Max size (high water):',
+ target_size_ratio, f_bytes(arc_max))
+ caches_size = int(mfu_size)+int(mru_size)
+ prt_i2('Most Frequently Used (MFU) cache size:',
+ f_perc(mfu_size, caches_size), f_bytes(mfu_size))
+ prt_i2('Most Recently Used (MRU) cache size:',
+ f_perc(mru_size, caches_size), f_bytes(mru_size))
+ prt_i2('Metadata cache size (hard limit):',
+ f_perc(meta_limit, arc_max), f_bytes(meta_limit))
+ prt_i2('Metadata cache size (current):',
+ f_perc(meta_size, meta_limit), f_bytes(meta_size))
+ prt_i2('Dnode cache size (hard limit):',
+ f_perc(dnode_limit, meta_limit), f_bytes(dnode_limit))
+ prt_i2('Dnode cache size (current):',
+ f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
+ print()
+
+ print('ARC hash breakdown:')
+ prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
+ prt_i2('Elements current:',
+ f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
+ f_hits(arc_stats['hash_elements']))
+ prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
+
+ prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
+ prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
+ print()
+
+ print('ARC misc:')
+ prt_i1('Deleted:', f_hits(arc_stats['deleted']))
+ prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
+ prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
+ print()
+
+
+def section_archits(kstats_dict):
+ """Print information on how the caches are accessed ("arc hits").
+ """
+
+ arc_stats = isolate_section('arcstats', kstats_dict)
+ all_accesses = int(arc_stats['hits'])+int(arc_stats['misses'])
+ actual_hits = int(arc_stats['mfu_hits'])+int(arc_stats['mru_hits'])
+
+ prt_1('ARC total accesses (hits + misses):', f_hits(all_accesses))
+ ta_todo = (('Cache hit ratio:', arc_stats['hits']),
+ ('Cache miss ratio:', arc_stats['misses']),
+ ('Actual hit ratio (MFU + MRU hits):', actual_hits))
+
+ for title, value in ta_todo:
+ prt_i2(title, f_perc(value, all_accesses), f_hits(value))
+
+ dd_total = int(arc_stats['demand_data_hits']) +\
+ int(arc_stats['demand_data_misses'])
+ prt_i2('Data demand efficiency:',
+ f_perc(arc_stats['demand_data_hits'], dd_total),
+ f_hits(dd_total))
+
+ dp_total = int(arc_stats['prefetch_data_hits']) +\
+ int(arc_stats['prefetch_data_misses'])
+ prt_i2('Data prefetch efficiency:',
+ f_perc(arc_stats['prefetch_data_hits'], dp_total),
+ f_hits(dp_total))
+
+ known_hits = int(arc_stats['mfu_hits']) +\
+ int(arc_stats['mru_hits']) +\
+ int(arc_stats['mfu_ghost_hits']) +\
+ int(arc_stats['mru_ghost_hits'])
+
+ anon_hits = int(arc_stats['hits'])-known_hits
+
+ print()
+ print('Cache hits by cache type:')
+ cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
+ ('Most recently used (MRU):', arc_stats['mru_hits']),
+ ('Most frequently used (MFU) ghost:',
+ arc_stats['mfu_ghost_hits']),
+ ('Most recently used (MRU) ghost:',
+ arc_stats['mru_ghost_hits']))
+
+ for title, value in cl_todo:
+ prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
+
+ # For some reason, anon_hits can turn negative, which is weird. Until we
+ # have figured out why this happens, we just hide the problem, following
+ # the behavior of the original arc_summary.
+ if anon_hits >= 0:
+ prt_i2('Anonymously used:',
+ f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
+
+ print()
+ print('Cache hits by data type:')
+ dt_todo = (('Demand data:', arc_stats['demand_data_hits']),
+ ('Demand prefetch data:', arc_stats['prefetch_data_hits']),
+ ('Demand metadata:', arc_stats['demand_metadata_hits']),
+ ('Demand prefetch metadata:',
+ arc_stats['prefetch_metadata_hits']))
+
+ for title, value in dt_todo:
+ prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
+
+ print()
+ print('Cache misses by data type:')
+ dm_todo = (('Demand data:', arc_stats['demand_data_misses']),
+ ('Demand prefetch data:',
+ arc_stats['prefetch_data_misses']),
+ ('Demand metadata:', arc_stats['demand_metadata_misses']),
+ ('Demand prefetch metadata:',
+ arc_stats['prefetch_metadata_misses']))
+
+ for title, value in dm_todo:
+ prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
+
+ print()
+
+
+def section_dmu(kstats_dict):
+ """Collect information on the DMU"""
+
+ zfetch_stats = isolate_section('zfetchstats', kstats_dict)
+
+ zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
+
+ prt_1('DMU prefetch efficiency:', f_hits(zfetch_access_total))
+ prt_i2('Hit ratio:', f_perc(zfetch_stats['hits'], zfetch_access_total),
+ f_hits(zfetch_stats['hits']))
+ prt_i2('Miss ratio:', f_perc(zfetch_stats['misses'], zfetch_access_total),
+ f_hits(zfetch_stats['misses']))
+ print()
+
+
+def section_l2arc(kstats_dict):
+ """Collect information on L2ARC device if present. If not, tell user
+ that we're skipping the section.
+ """
+
+ # The L2ARC statistics live in the same section as the normal ARC stuff
+ arc_stats = isolate_section('arcstats', kstats_dict)
+
+ if arc_stats['l2_size'] == '0':
+ print('L2ARC not detected, skipping section\n')
+ return
+
+ l2_errors = int(arc_stats['l2_writes_error']) +\
+ int(arc_stats['l2_cksum_bad']) +\
+ int(arc_stats['l2_io_error'])
+
+ l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
+ health = 'HEALTHY'
+
+ if l2_errors > 0:
+ health = 'DEGRADED'
+
+ prt_1('L2ARC status:', health)
+
+ l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
+ ('Free on write:', 'l2_free_on_write'),
+ ('R/W clashes:', 'l2_rw_clash'),
+ ('Bad checksums:', 'l2_cksum_bad'),
+ ('I/O errors:', 'l2_io_error'))
+
+ for title, value in l2_todo:
+ prt_i1(title, f_hits(arc_stats[value]))
+
+ print()
+ prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
+ prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
+ f_bytes(arc_stats['l2_asize']))
+ prt_i2('Header size:',
+ f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
+ f_bytes(arc_stats['l2_hdr_size']))
+
+ print()
+ prt_1('L2ARC breakdown:', f_hits(l2_access_total))
+ prt_i2('Hit ratio:',
+ f_perc(arc_stats['l2_hits'], l2_access_total),
+ f_hits(arc_stats['l2_hits']))
+ prt_i2('Miss ratio:',
+ f_perc(arc_stats['l2_misses'], l2_access_total),
+ f_hits(arc_stats['l2_misses']))
+ prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
+
+ print()
+ print('L2ARC writes:')
+
+ if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
+ prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
+ prt_i2('Done ratio:',
+ f_perc(arc_stats['l2_writes_done'],
+ arc_stats['l2_writes_sent']),
+ f_hits(arc_stats['l2_writes_done']))
+ prt_i2('Error ratio:',
+ f_perc(arc_stats['l2_writes_error'],
+ arc_stats['l2_writes_sent']),
+ f_hits(arc_stats['l2_writes_error']))
+ else:
+ prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
+
+ print()
+ print('L2ARC evicts:')
+ prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
+ prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
+ print()
+
+
+def section_spl(*_):
+ """Print the SPL parameters, if requested with alternative format
+ and/or descriptions. This does not use kstats.
+ """
+
+ if sys.platform.startswith('freebsd'):
+ # No SPL support in FreeBSD
+ return
+
+ spls = get_spl_params()
+ keylist = sorted(spls.keys())
+ print('Solaris Porting Layer (SPL):')
+
+ if ARGS.desc:
+ descriptions = get_descriptions('spl')
+
+ for key in keylist:
+ value = spls[key]
+
+ if ARGS.desc:
+ try:
+ print(INDENT+'#', descriptions[key])
+ except KeyError:
+ print(INDENT+'# (No description found)') # paranoid
+
+ print(format_raw_line(key, value))
+
+ print()
+
+
+def section_tunables(*_):
+ """Print the tunables, if requested with alternative format and/or
+ descriptions. This does not use kstasts.
+ """
+
+ tunables = get_tunable_params()
+ keylist = sorted(tunables.keys())
+ print('Tunables:')
+
+ if ARGS.desc:
+ descriptions = get_descriptions('zfs')
+
+ for key in keylist:
+ value = tunables[key]
+
+ if ARGS.desc:
+ try:
+ print(INDENT+'#', descriptions[key])
+ except KeyError:
+ print(INDENT+'# (No description found)') # paranoid
+
+ print(format_raw_line(key, value))
+
+ print()
+
+
+def section_vdev(kstats_dict):
+ """Collect information on VDEV caches"""
+
+ # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
+ # harmful. When this is the case, we just skip the whole entry. See
+ # https://github.com/zfsonlinux/zfs/blob/master/module/zfs/vdev_cache.c
+ # for details
+ tunables = get_vdev_params()
+
+ if tunables[VDEV_CACHE_SIZE] == '0':
+ print('VDEV cache disabled, skipping section\n')
+ return
+
+ vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
+
+ vdev_cache_total = int(vdev_stats['hits']) +\
+ int(vdev_stats['misses']) +\
+ int(vdev_stats['delegations'])
+
+ prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
+ prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
+ f_hits(vdev_stats['hits']))
+ prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
+ f_hits(vdev_stats['misses']))
+ prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
+ f_hits(vdev_stats['delegations']))
+ print()
+
+
+def section_zil(kstats_dict):
+ """Collect information on the ZFS Intent Log. Some of the information
+ taken from https://github.com/zfsonlinux/zfs/blob/master/include/sys/zil.h
+ """
+
+ zil_stats = isolate_section('zil', kstats_dict)
+
+ prt_1('ZIL committed transactions:',
+ f_hits(zil_stats['zil_itx_count']))
+ prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
+ prt_i1('Flushes to stable storage:',
+ f_hits(zil_stats['zil_commit_writer_count']))
+ prt_i2('Transactions to SLOG storage pool:',
+ f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
+ f_hits(zil_stats['zil_itx_metaslab_slog_count']))
+ prt_i2('Transactions to non-SLOG storage pool:',
+ f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
+ f_hits(zil_stats['zil_itx_metaslab_normal_count']))
+ print()
+
+
+section_calls = {'arc': section_arc,
+ 'archits': section_archits,
+ 'dmu': section_dmu,
+ 'l2arc': section_l2arc,
+ 'spl': section_spl,
+ 'tunables': section_tunables,
+ 'vdev': section_vdev,
+ 'zil': section_zil}
+
+
+def main():
+ """Run program. The options to draw a graph and to print all data raw are
+ treated separately because they come with their own call.
+ """
+
+ kstats = get_kstats()
+
+ if ARGS.graph:
+ draw_graph(kstats)
+ sys.exit(0)
+
+ print_header()
+
+ if ARGS.raw:
+ print_raw(kstats)
+
+ elif ARGS.section:
+
+ try:
+ section_calls[ARGS.section](kstats)
+ except KeyError:
+ print('Error: Section "{0}" unknown'.format(ARGS.section))
+ sys.exit(1)
+
+ elif ARGS.page:
+ print('WARNING: Pages are deprecated, please use "--section"\n')
+
+ pages_to_calls = {1: 'arc',
+ 2: 'archits',
+ 3: 'l2arc',
+ 4: 'dmu',
+ 5: 'vdev',
+ 6: 'tunables'}
+
+ try:
+ call = pages_to_calls[ARGS.page]
+ except KeyError:
+ print('Error: Page "{0}" not supported'.format(ARGS.page))
+ sys.exit(1)
+ else:
+ section_calls[call](kstats)
+
+ else:
+ # If no parameters were given, we print all sections. We might want to
+ # change the sequence by hand
+ calls = sorted(section_calls.keys())
+
+ for section in calls:
+ section_calls[section](kstats)
+
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ main()