3 # Copyright (c) 2008 Ben Rockwood <benr@cuddletech.com>,
4 # Copyright (c) 2010 Martin Matuska <mm@FreeBSD.org>,
5 # Copyright (c) 2010-2011 Jason J. Hellenthal <jhell@DataIX.net>,
6 # Copyright (c) 2017 Scot W. Stevenson <scot.stevenson@gmail.com>
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
13 # 1. Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
15 # 2. Redistributions in binary form must reproduce the above copyright
16 # notice, this list of conditions and the following disclaimer in the
17 # documentation and/or other materials provided with the distribution.
19 # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
23 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 """Print statistics on the ZFS ARC Cache and other information
32 Provides basic information on the ARC, its efficiency, the L2ARC (if present),
33 the Data Management Unit (DMU), Virtual Devices (VDEVs), and tunables. See
34 the in-source documentation and code at
35 https://github.com/openzfs/zfs/blob/master/module/zfs/arc.c for details.
36 The original introduction to arc_summary can be found at
37 http://cuddletech.com/?p=454
47 # We can't use env -S portably, and we need python3 -u to handle pipes in
48 # the shell abruptly closing the way we want to, so...
50 if isinstance(sys.__stderr__.buffer, io.BufferedWriter):
51 os.execv(sys.executable, [sys.executable, "-u"] + sys.argv)
53 DESCRIPTION = 'Print ARC and other statistics for OpenZFS'
56 DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
57 TITLE = 'ZFS Subsystem Report'
59 SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
60 SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
62 # Tunables and SPL are handled separately because they come from
64 SECTION_PATHS = {'arc': 'arcstats',
66 'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
67 'vdev': 'vdev_cache_stats',
68 'zfetch': 'zfetchstats',
71 parser = argparse.ArgumentParser(description=DESCRIPTION)
72 parser.add_argument('-a', '--alternate', action='store_true', default=False,
73 help='use alternate formatting for tunables and SPL',
75 parser.add_argument('-d', '--description', action='store_true', default=False,
76 help='print descriptions with tunables and SPL',
78 parser.add_argument('-g', '--graph', action='store_true', default=False,
79 help='print graph on ARC use and exit', dest='graph')
80 parser.add_argument('-p', '--page', type=int, dest='page',
81 help='print page by number (DEPRECATED, use "-s")')
82 parser.add_argument('-r', '--raw', action='store_true', default=False,
83 help='dump all available data with minimal formatting',
85 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
86 ARGS = parser.parse_args()
89 if sys.platform.startswith('freebsd'):
90 # Requires py36-sysctl on FreeBSD
93 VDEV_CACHE_SIZE = 'vdev.cache_size'
96 return ctl.type != sysctl.CTLTYPE_NODE
98 def namefmt(ctl, base='vfs.zfs.'):
99 # base is removed from the name
101 return ctl.name[cut:]
103 def load_kstats(section):
104 base = 'kstat.zfs.misc.{section}.'.format(section=section)
105 fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
107 kstats = sysctl.filter(base)
108 return [fmt(kstat) for kstat in kstats if is_value(kstat)]
110 def get_params(base):
111 ctls = sysctl.filter(base)
112 return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
114 def get_tunable_params():
115 return get_params('vfs.zfs')
117 def get_vdev_params():
118 return get_params('vfs.zfs.vdev')
120 def get_version_impl(request):
121 # FreeBSD reports versions for zpl and spa instead of zfs and spl.
122 name = {'zfs': 'zpl',
123 'spl': 'spa'}[request]
124 mib = 'vfs.zfs.version.{}'.format(name)
125 version = sysctl.filter(mib)[0].value
126 return '{} version {}'.format(name, version)
128 def get_descriptions(_request):
129 ctls = sysctl.filter('vfs.zfs')
130 return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
133 elif sys.platform.startswith('linux'):
134 KSTAT_PATH = '/proc/spl/kstat/zfs'
135 SPL_PATH = '/sys/module/spl/parameters'
136 TUNABLES_PATH = '/sys/module/zfs/parameters'
138 VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
140 def load_kstats(section):
141 path = os.path.join(KSTAT_PATH, section)
142 with open(path) as f:
143 return list(f)[2:] # Get rid of header
145 def get_params(basepath):
146 """Collect information on the Solaris Porting Layer (SPL) or the
147 tunables, depending on the PATH given. Does not check if PATH is
151 for name in os.listdir(basepath):
152 path = os.path.join(basepath, name)
153 with open(path) as f:
155 result[name] = value.strip()
158 def get_spl_params():
159 return get_params(SPL_PATH)
161 def get_tunable_params():
162 return get_params(TUNABLES_PATH)
164 def get_vdev_params():
165 return get_params(TUNABLES_PATH)
167 def get_version_impl(request):
168 # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
169 # the version information. We switch to /sys/module/{spl,zfs}/version
170 # to make sure we get what is really loaded in the kernel
172 with open("/sys/module/{}/version".format(request)) as f:
173 return f.read().strip()
177 def get_descriptions(request):
178 """Get the descriptions of the Solaris Porting Layer (SPL) or the
179 tunables, return with minimal formatting.
182 if request not in ('spl', 'zfs'):
183 print('ERROR: description of "{0}" requested)'.format(request))
187 target_prefix = 'parm:'
189 # We would prefer to do this with /sys/modules -- see the discussion at
190 # get_version() -- but there isn't a way to get the descriptions from
191 # there, so we fall back on modinfo
192 command = ["/sbin/modinfo", request, "-0"]
198 info = subprocess.run(command, stdout=subprocess.PIPE,
199 check=True, universal_newlines=True)
200 raw_output = info.stdout.split('\0')
202 except subprocess.CalledProcessError:
203 print("Error: Descriptions not available",
204 "(can't access kernel module)")
207 for line in raw_output:
209 if not line.startswith(target_prefix):
212 line = line[len(target_prefix):].strip()
213 name, raw_desc = line.split(':', 1)
214 desc = raw_desc.rsplit('(', 1)[0]
217 desc = '(No description found)'
219 descs[name.strip()] = desc.strip()
223 def handle_unraisableException(exc_type, exc_value=None, exc_traceback=None,
224 err_msg=None, object=None):
225 handle_Exception(exc_type, object, exc_traceback)
227 def handle_Exception(ex_cls, ex, tb):
228 if ex_cls is KeyboardInterrupt:
231 if ex_cls is BrokenPipeError:
232 # It turns out that while sys.exit() triggers an exception
233 # not handled message on Python 3.8+, os._exit() does not.
236 if ex_cls is OSError:
237 if ex.errno == errno.ENOTCONN:
242 if hasattr(sys,'unraisablehook'): # Python 3.8+
243 sys.unraisablehook = handle_unraisableException
244 sys.excepthook = handle_Exception
247 def cleanup_line(single_line):
248 """Format a raw line of data from /proc and isolate the name value
249 part, returning a tuple with each. Currently, this gets rid of the
250 middle '4'. For example "arc_no_grow 4 0" returns the tuple
251 ("arc_no_grow", "0").
253 name, _, value = single_line.split()
258 def draw_graph(kstats_dict):
259 """Draw a primitive graph representing the basic information on the
260 ARC -- its size and the proportion used by MFU and MRU -- and quit.
261 We use max size of the ARC to calculate how full it is. This is a
262 very rough representation.
265 arc_stats = isolate_section('arcstats', kstats_dict)
269 arc_size = f_bytes(arc_stats['size'])
270 arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
271 mfu_size = f_bytes(arc_stats['mfu_size'])
272 mru_size = f_bytes(arc_stats['mru_size'])
273 meta_size = f_bytes(arc_stats['arc_meta_used'])
274 dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
275 dnode_size = f_bytes(arc_stats['dnode_size'])
277 info_form = ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} '
279 info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
280 meta_size, dnode_size, dnode_limit)
281 info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
282 info_line = GRAPH_INDENT+info_spc+info_line
284 graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
286 mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
287 mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
288 arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
289 total_ticks = float(arc_perc)*GRAPH_WIDTH
290 mfu_ticks = mfu_perc*GRAPH_WIDTH
291 mru_ticks = mru_perc*GRAPH_WIDTH
292 other_ticks = total_ticks-(mfu_ticks+mru_ticks)
294 core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
295 core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
296 core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
298 for line in ('', info_line, graph_line, core_line, graph_line, ''):
302 def f_bytes(byte_string):
303 """Return human-readable representation of a byte value in
304 powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
305 points. Values smaller than one KiB are returned without
306 decimal points. Note "bytes" is a reserved keyword.
309 prefixes = ([2**80, "YiB"], # yobibytes (yotta)
310 [2**70, "ZiB"], # zebibytes (zetta)
311 [2**60, "EiB"], # exbibytes (exa)
312 [2**50, "PiB"], # pebibytes (peta)
313 [2**40, "TiB"], # tebibytes (tera)
314 [2**30, "GiB"], # gibibytes (giga)
315 [2**20, "MiB"], # mebibytes (mega)
316 [2**10, "KiB"]) # kibibytes (kilo)
318 bites = int(byte_string)
321 for limit, unit in prefixes:
324 value = bites / limit
327 result = '{0:.1f} {1}'.format(value, unit)
329 result = '{0} Bytes'.format(bites)
334 def f_hits(hits_string):
335 """Create a human-readable representation of the number of hits.
336 The single-letter symbols used are SI to avoid the confusion caused
337 by the different "short scale" and "long scale" representations in
338 English, which use the same words for different values. See
339 https://en.wikipedia.org/wiki/Names_of_large_numbers and:
340 https://physics.nist.gov/cuu/Units/prefixes.html
343 numbers = ([10**24, 'Y'], # yotta (septillion)
344 [10**21, 'Z'], # zetta (sextillion)
345 [10**18, 'E'], # exa (quintrillion)
346 [10**15, 'P'], # peta (quadrillion)
347 [10**12, 'T'], # tera (trillion)
348 [10**9, 'G'], # giga (billion)
349 [10**6, 'M'], # mega (million)
350 [10**3, 'k']) # kilo (thousand)
352 hits = int(hits_string)
355 for limit, symbol in numbers:
361 result = "%0.1f%s" % (value, symbol)
368 def f_perc(value1, value2):
369 """Calculate percentage and return in human-readable form. If
370 rounding produces the result '0.0' though the first number is
371 not zero, include a 'less-than' symbol to avoid confusion.
372 Division by zero is handled by returning 'n/a'; no error
381 except ZeroDivisionError:
384 result = '{0:0.1f} %'.format(perc)
386 if result == '0.0 %' and v1 > 0:
392 def format_raw_line(name, value):
393 """For the --raw option for the tunable and SPL outputs, decide on the
394 correct formatting based on the --alternate flag.
398 result = '{0}{1}={2}'.format(INDENT, name, value)
400 # Right-align the value within the line length if it fits,
401 # otherwise just separate it from the name by a single space.
402 fit = LINE_LENGTH - len(INDENT) - len(name)
403 overflow = len(value) + 1
404 w = max(fit, overflow)
405 result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
411 """Collect information on the ZFS subsystem. The step does not perform any
412 further processing, giving us the option to only work on what is actually
413 needed. The name "kstat" is a holdover from the Solaris utility of the same
419 for section in SECTION_PATHS.values():
420 if section not in result:
421 result[section] = load_kstats(section)
426 def get_version(request):
427 """Get the version number of ZFS or SPL on this machine for header.
428 Returns an error string, but does not raise an error, if we can't
429 get the ZFS/SPL version.
432 if request not in ('spl', 'zfs'):
433 error_msg = '(ERROR: "{0}" requested)'.format(request)
436 return get_version_impl(request)
440 """Print the initial heading with date and time as well as info on the
441 kernel and ZFS versions. This is not called for the graph.
444 # datetime is now recommended over time but we keep the exact formatting
445 # from the older version of arc_summary in case there are scripts
446 # that expect it in this way
447 daydate = time.strftime(DATE_FORMAT)
448 spc_date = LINE_LENGTH-len(daydate)
449 sys_version = os.uname()
451 sys_msg = sys_version.sysname+' '+sys_version.release
452 zfs = get_version('zfs')
453 spc_zfs = LINE_LENGTH-len(zfs)
455 machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
456 spl = get_version('spl')
457 spc_spl = LINE_LENGTH-len(spl)
459 print('\n'+('-'*LINE_LENGTH))
460 print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
461 print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
462 print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
465 def print_raw(kstats_dict):
466 """Print all available data from the system in a minimally sorted format.
467 This can be used as a source to be piped through 'grep'.
470 sections = sorted(kstats_dict.keys())
472 for section in sections:
474 print('\n{0}:'.format(section.upper()))
475 lines = sorted(kstats_dict[section])
478 name, value = cleanup_line(line)
479 print(format_raw_line(name, value))
481 # Tunables and SPL must be handled separately because they come from a
482 # different source and have descriptions the user might request
488 def isolate_section(section_name, kstats_dict):
489 """From the complete information on all sections, retrieve only those
494 section_data = kstats_dict[section_name]
496 print('ERROR: Data on {0} not available'.format(section_data))
499 section_dict = dict(cleanup_line(l) for l in section_data)
504 # Formatted output helper functions
507 def prt_1(text, value):
508 """Print text and one value, no indent"""
509 spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
510 print('{0}{spc}{1}'.format(text, value, spc=spc))
513 def prt_i1(text, value):
514 """Print text and one value, with indent"""
515 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
516 print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
519 def prt_2(text, value1, value2):
520 """Print text and two values, no indent"""
521 values = '{0:>9} {1:>9}'.format(value1, value2)
522 spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
523 print('{0}{spc} {1}'.format(text, values, spc=spc))
526 def prt_i2(text, value1, value2):
527 """Print text and two values, with indent"""
528 values = '{0:>9} {1:>9}'.format(value1, value2)
529 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
530 print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
533 # The section output concentrates on important parameters instead of
534 # being exhaustive (that is what the --raw parameter is for)
537 def section_arc(kstats_dict):
538 """Give basic information on the ARC, MRU and MFU. This is the first
539 and most used section.
542 arc_stats = isolate_section('arcstats', kstats_dict)
544 throttle = arc_stats['memory_throttle_count']
551 prt_1('ARC status:', health)
552 prt_i1('Memory throttle count:', throttle)
555 arc_size = arc_stats['size']
556 arc_target_size = arc_stats['c']
557 arc_max = arc_stats['c_max']
558 arc_min = arc_stats['c_min']
559 meta = arc_stats['meta']
562 anon_data = arc_stats['anon_data']
563 anon_metadata = arc_stats['anon_metadata']
564 mfu_data = arc_stats['mfu_data']
565 mfu_metadata = arc_stats['mfu_metadata']
566 mru_data = arc_stats['mru_data']
567 mru_metadata = arc_stats['mru_metadata']
568 mfug_data = arc_stats['mfu_ghost_data']
569 mfug_metadata = arc_stats['mfu_ghost_metadata']
570 mrug_data = arc_stats['mru_ghost_data']
571 mrug_metadata = arc_stats['mru_ghost_metadata']
572 unc_data = arc_stats['uncached_data']
573 unc_metadata = arc_stats['uncached_metadata']
574 bonus_size = arc_stats['bonus_size']
575 dnode_limit = arc_stats['arc_dnode_limit']
576 dnode_size = arc_stats['dnode_size']
577 dbuf_size = arc_stats['dbuf_size']
578 hdr_size = arc_stats['hdr_size']
579 l2_hdr_size = arc_stats['l2_hdr_size']
580 abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
581 target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
583 prt_2('ARC size (current):',
584 f_perc(arc_size, arc_max), f_bytes(arc_size))
585 prt_i2('Target size (adaptive):',
586 f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
587 prt_i2('Min size (hard limit):',
588 f_perc(arc_min, arc_max), f_bytes(arc_min))
589 prt_i2('Max size (high water):',
590 target_size_ratio, f_bytes(arc_max))
591 caches_size = int(anon_data)+int(anon_metadata)+\
592 int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
593 int(unc_data)+int(unc_metadata)
594 prt_i2('Anonymous data size:',
595 f_perc(anon_data, caches_size), f_bytes(anon_data))
596 prt_i2('Anonymous metadata size:',
597 f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
599 v = (s-int(pd))*(s-int(meta))/s
600 prt_i2('MFU data target:', f_perc(v, s),
601 f_bytes(v / 65536 * caches_size / 65536))
602 prt_i2('MFU data size:',
603 f_perc(mfu_data, caches_size), f_bytes(mfu_data))
604 prt_i1('MFU ghost data size:', f_bytes(mfug_data))
605 v = (s-int(pm))*int(meta)/s
606 prt_i2('MFU metadata target:', f_perc(v, s),
607 f_bytes(v / 65536 * caches_size / 65536))
608 prt_i2('MFU metadata size:',
609 f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
610 prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
611 v = int(pd)*(s-int(meta))/s
612 prt_i2('MRU data target:', f_perc(v, s),
613 f_bytes(v / 65536 * caches_size / 65536))
614 prt_i2('MRU data size:',
615 f_perc(mru_data, caches_size), f_bytes(mru_data))
616 prt_i1('MRU ghost data size:', f_bytes(mrug_data))
617 v = int(pm)*int(meta)/s
618 prt_i2('MRU metadata target:', f_perc(v, s),
619 f_bytes(v / 65536 * caches_size / 65536))
620 prt_i2('MRU metadata size:',
621 f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
622 prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
623 prt_i2('Uncached data size:',
624 f_perc(unc_data, caches_size), f_bytes(unc_data))
625 prt_i2('Uncached metadata size:',
626 f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
627 prt_i2('Bonus size:',
628 f_perc(bonus_size, arc_size), f_bytes(bonus_size))
629 prt_i2('Dnode cache target:',
630 f_perc(dnode_limit, arc_max), f_bytes(dnode_limit))
631 prt_i2('Dnode cache size:',
632 f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
634 f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
635 prt_i2('Header size:',
636 f_perc(hdr_size, arc_size), f_bytes(hdr_size))
637 prt_i2('L2 header size:',
638 f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
639 prt_i2('ABD chunk waste size:',
640 f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
643 print('ARC hash breakdown:')
644 prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
645 prt_i2('Elements current:',
646 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
647 f_hits(arc_stats['hash_elements']))
648 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
650 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
651 prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
655 prt_i1('Deleted:', f_hits(arc_stats['deleted']))
656 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
657 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
658 prt_i1('Eviction skips due to L2 writes:',
659 f_hits(arc_stats['evict_l2_skip']))
660 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
661 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
662 prt_i2('L2 eligible MFU evictions:',
663 f_perc(arc_stats['evict_l2_eligible_mfu'],
664 arc_stats['evict_l2_eligible']),
665 f_bytes(arc_stats['evict_l2_eligible_mfu']))
666 prt_i2('L2 eligible MRU evictions:',
667 f_perc(arc_stats['evict_l2_eligible_mru'],
668 arc_stats['evict_l2_eligible']),
669 f_bytes(arc_stats['evict_l2_eligible_mru']))
670 prt_i1('L2 ineligible evictions:',
671 f_bytes(arc_stats['evict_l2_ineligible']))
675 def section_archits(kstats_dict):
676 """Print information on how the caches are accessed ("arc hits").
679 arc_stats = isolate_section('arcstats', kstats_dict)
680 all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
681 int(arc_stats['misses'])
683 prt_1('ARC total accesses:', f_hits(all_accesses))
684 ta_todo = (('Total hits:', arc_stats['hits']),
685 ('Total I/O hits:', arc_stats['iohits']),
686 ('Total misses:', arc_stats['misses']))
687 for title, value in ta_todo:
688 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
691 dd_total = int(arc_stats['demand_data_hits']) +\
692 int(arc_stats['demand_data_iohits']) +\
693 int(arc_stats['demand_data_misses'])
694 prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
696 dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
697 ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
698 ('Demand data misses:', arc_stats['demand_data_misses']))
699 for title, value in dd_todo:
700 prt_i2(title, f_perc(value, dd_total), f_hits(value))
703 dm_total = int(arc_stats['demand_metadata_hits']) +\
704 int(arc_stats['demand_metadata_iohits']) +\
705 int(arc_stats['demand_metadata_misses'])
706 prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
708 dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
709 ('Demand metadata I/O hits:',
710 arc_stats['demand_metadata_iohits']),
711 ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
712 for title, value in dm_todo:
713 prt_i2(title, f_perc(value, dm_total), f_hits(value))
716 pd_total = int(arc_stats['prefetch_data_hits']) +\
717 int(arc_stats['prefetch_data_iohits']) +\
718 int(arc_stats['prefetch_data_misses'])
719 prt_2('ARC prefetch metadata accesses:', f_perc(pd_total, all_accesses),
721 pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
722 ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
723 ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
724 for title, value in pd_todo:
725 prt_i2(title, f_perc(value, pd_total), f_hits(value))
728 pm_total = int(arc_stats['prefetch_metadata_hits']) +\
729 int(arc_stats['prefetch_metadata_iohits']) +\
730 int(arc_stats['prefetch_metadata_misses'])
731 prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
733 pm_todo = (('Prefetch metadata hits:',
734 arc_stats['prefetch_metadata_hits']),
735 ('Prefetch metadata I/O hits:',
736 arc_stats['prefetch_metadata_iohits']),
737 ('Prefetch metadata misses:',
738 arc_stats['prefetch_metadata_misses']))
739 for title, value in pm_todo:
740 prt_i2(title, f_perc(value, pm_total), f_hits(value))
743 all_prefetches = int(arc_stats['predictive_prefetch'])+\
744 int(arc_stats['prescient_prefetch'])
745 prt_2('ARC predictive prefetches:',
746 f_perc(arc_stats['predictive_prefetch'], all_prefetches),
747 f_hits(arc_stats['predictive_prefetch']))
748 prt_i2('Demand hits after predictive:',
749 f_perc(arc_stats['demand_hit_predictive_prefetch'],
750 arc_stats['predictive_prefetch']),
751 f_hits(arc_stats['demand_hit_predictive_prefetch']))
752 prt_i2('Demand I/O hits after predictive:',
753 f_perc(arc_stats['demand_iohit_predictive_prefetch'],
754 arc_stats['predictive_prefetch']),
755 f_hits(arc_stats['demand_iohit_predictive_prefetch']))
756 never = int(arc_stats['predictive_prefetch']) -\
757 int(arc_stats['demand_hit_predictive_prefetch']) -\
758 int(arc_stats['demand_iohit_predictive_prefetch'])
759 prt_i2('Never demanded after predictive:',
760 f_perc(never, arc_stats['predictive_prefetch']),
764 prt_2('ARC prescient prefetches:',
765 f_perc(arc_stats['prescient_prefetch'], all_prefetches),
766 f_hits(arc_stats['prescient_prefetch']))
767 prt_i2('Demand hits after prescient:',
768 f_perc(arc_stats['demand_hit_prescient_prefetch'],
769 arc_stats['prescient_prefetch']),
770 f_hits(arc_stats['demand_hit_prescient_prefetch']))
771 prt_i2('Demand I/O hits after prescient:',
772 f_perc(arc_stats['demand_iohit_prescient_prefetch'],
773 arc_stats['prescient_prefetch']),
774 f_hits(arc_stats['demand_iohit_prescient_prefetch']))
775 never = int(arc_stats['prescient_prefetch'])-\
776 int(arc_stats['demand_hit_prescient_prefetch'])-\
777 int(arc_stats['demand_iohit_prescient_prefetch'])
778 prt_i2('Never demanded after prescient:',
779 f_perc(never, arc_stats['prescient_prefetch']),
783 print('ARC states hits of all accesses:')
784 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
785 ('Most recently used (MRU):', arc_stats['mru_hits']),
786 ('Most frequently used (MFU) ghost:',
787 arc_stats['mfu_ghost_hits']),
788 ('Most recently used (MRU) ghost:',
789 arc_stats['mru_ghost_hits']),
790 ('Uncached:', arc_stats['uncached_hits']))
791 for title, value in cl_todo:
792 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
796 def section_dmu(kstats_dict):
797 """Collect information on the DMU"""
799 zfetch_stats = isolate_section('zfetchstats', kstats_dict)
801 zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
803 prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
804 prt_i2('Stream hits:',
805 f_perc(zfetch_stats['hits'], zfetch_access_total),
806 f_hits(zfetch_stats['hits']))
807 prt_i2('Stream misses:',
808 f_perc(zfetch_stats['misses'], zfetch_access_total),
809 f_hits(zfetch_stats['misses']))
810 prt_i2('Streams limit reached:',
811 f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
812 f_hits(zfetch_stats['max_streams']))
813 prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
817 def section_l2arc(kstats_dict):
818 """Collect information on L2ARC device if present. If not, tell user
819 that we're skipping the section.
822 # The L2ARC statistics live in the same section as the normal ARC stuff
823 arc_stats = isolate_section('arcstats', kstats_dict)
825 if arc_stats['l2_size'] == '0':
826 print('L2ARC not detected, skipping section\n')
829 l2_errors = int(arc_stats['l2_writes_error']) +\
830 int(arc_stats['l2_cksum_bad']) +\
831 int(arc_stats['l2_io_error'])
833 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
839 prt_1('L2ARC status:', health)
841 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
842 ('Free on write:', 'l2_free_on_write'),
843 ('R/W clashes:', 'l2_rw_clash'),
844 ('Bad checksums:', 'l2_cksum_bad'),
845 ('I/O errors:', 'l2_io_error'))
847 for title, value in l2_todo:
848 prt_i1(title, f_hits(arc_stats[value]))
851 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
852 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
853 f_bytes(arc_stats['l2_asize']))
854 prt_i2('Header size:',
855 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
856 f_bytes(arc_stats['l2_hdr_size']))
857 prt_i2('MFU allocated size:',
858 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
859 f_bytes(arc_stats['l2_mfu_asize']))
860 prt_i2('MRU allocated size:',
861 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
862 f_bytes(arc_stats['l2_mru_asize']))
863 prt_i2('Prefetch allocated size:',
864 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
865 f_bytes(arc_stats['l2_prefetch_asize']))
866 prt_i2('Data (buffer content) allocated size:',
867 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
868 f_bytes(arc_stats['l2_bufc_data_asize']))
869 prt_i2('Metadata (buffer content) allocated size:',
870 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
871 f_bytes(arc_stats['l2_bufc_metadata_asize']))
874 prt_1('L2ARC breakdown:', f_hits(l2_access_total))
876 f_perc(arc_stats['l2_hits'], l2_access_total),
877 f_hits(arc_stats['l2_hits']))
878 prt_i2('Miss ratio:',
879 f_perc(arc_stats['l2_misses'], l2_access_total),
880 f_hits(arc_stats['l2_misses']))
881 prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
884 print('L2ARC writes:')
886 if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
887 prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
888 prt_i2('Done ratio:',
889 f_perc(arc_stats['l2_writes_done'],
890 arc_stats['l2_writes_sent']),
891 f_hits(arc_stats['l2_writes_done']))
892 prt_i2('Error ratio:',
893 f_perc(arc_stats['l2_writes_error'],
894 arc_stats['l2_writes_sent']),
895 f_hits(arc_stats['l2_writes_error']))
897 prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
900 print('L2ARC evicts:')
901 prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
902 prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
907 """Print the SPL parameters, if requested with alternative format
908 and/or descriptions. This does not use kstats.
911 if sys.platform.startswith('freebsd'):
912 # No SPL support in FreeBSD
915 spls = get_spl_params()
916 keylist = sorted(spls.keys())
917 print('Solaris Porting Layer (SPL):')
920 descriptions = get_descriptions('spl')
927 print(INDENT+'#', descriptions[key])
929 print(INDENT+'# (No description found)') # paranoid
931 print(format_raw_line(key, value))
936 def section_tunables(*_):
937 """Print the tunables, if requested with alternative format and/or
938 descriptions. This does not use kstasts.
941 tunables = get_tunable_params()
942 keylist = sorted(tunables.keys())
946 descriptions = get_descriptions('zfs')
949 value = tunables[key]
953 print(INDENT+'#', descriptions[key])
955 print(INDENT+'# (No description found)') # paranoid
957 print(format_raw_line(key, value))
962 def section_vdev(kstats_dict):
963 """Collect information on VDEV caches"""
965 # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
966 # harmful. When this is the case, we just skip the whole entry. See
967 # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
969 tunables = get_vdev_params()
971 if tunables[VDEV_CACHE_SIZE] == '0':
972 print('VDEV cache disabled, skipping section\n')
975 vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
977 vdev_cache_total = int(vdev_stats['hits']) +\
978 int(vdev_stats['misses']) +\
979 int(vdev_stats['delegations'])
981 prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
982 prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
983 f_hits(vdev_stats['hits']))
984 prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
985 f_hits(vdev_stats['misses']))
986 prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
987 f_hits(vdev_stats['delegations']))
991 def section_zil(kstats_dict):
992 """Collect information on the ZFS Intent Log. Some of the information
993 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
996 zil_stats = isolate_section('zil', kstats_dict)
998 prt_1('ZIL committed transactions:',
999 f_hits(zil_stats['zil_itx_count']))
1000 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
1001 prt_i1('Flushes to stable storage:',
1002 f_hits(zil_stats['zil_commit_writer_count']))
1003 prt_i2('Transactions to SLOG storage pool:',
1004 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
1005 f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1006 prt_i2('Transactions to non-SLOG storage pool:',
1007 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1008 f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1012 section_calls = {'arc': section_arc,
1013 'archits': section_archits,
1015 'l2arc': section_l2arc,
1017 'tunables': section_tunables,
1018 'vdev': section_vdev,
1023 """Run program. The options to draw a graph and to print all data raw are
1024 treated separately because they come with their own call.
1027 kstats = get_kstats()
1041 section_calls[ARGS.section](kstats)
1043 print('Error: Section "{0}" unknown'.format(ARGS.section))
1047 print('WARNING: Pages are deprecated, please use "--section"\n')
1049 pages_to_calls = {1: 'arc',
1057 call = pages_to_calls[ARGS.page]
1059 print('Error: Page "{0}" not supported'.format(ARGS.page))
1062 section_calls[call](kstats)
1065 # If no parameters were given, we print all sections. We might want to
1066 # change the sequence by hand
1067 calls = sorted(section_calls.keys())
1069 for section in calls:
1070 section_calls[section](kstats)
1075 if __name__ == '__main__':