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 'zfetch': 'zfetchstats',
70 parser = argparse.ArgumentParser(description=DESCRIPTION)
71 parser.add_argument('-a', '--alternate', action='store_true', default=False,
72 help='use alternate formatting for tunables and SPL',
74 parser.add_argument('-d', '--description', action='store_true', default=False,
75 help='print descriptions with tunables and SPL',
77 parser.add_argument('-g', '--graph', action='store_true', default=False,
78 help='print graph on ARC use and exit', dest='graph')
79 parser.add_argument('-p', '--page', type=int, dest='page',
80 help='print page by number (DEPRECATED, use "-s")')
81 parser.add_argument('-r', '--raw', action='store_true', default=False,
82 help='dump all available data with minimal formatting',
84 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
85 ARGS = parser.parse_args()
88 if sys.platform.startswith('freebsd'):
89 # Requires py36-sysctl on FreeBSD
93 return ctl.type != sysctl.CTLTYPE_NODE
95 def namefmt(ctl, base='vfs.zfs.'):
96 # base is removed from the name
100 def load_kstats(section):
101 base = 'kstat.zfs.misc.{section}.'.format(section=section)
102 fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
104 kstats = sysctl.filter(base)
105 return [fmt(kstat) for kstat in kstats if is_value(kstat)]
107 def get_params(base):
108 ctls = sysctl.filter(base)
109 return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
111 def get_tunable_params():
112 return get_params('vfs.zfs')
114 def get_vdev_params():
115 return get_params('vfs.zfs.vdev')
117 def get_version_impl(request):
118 # FreeBSD reports versions for zpl and spa instead of zfs and spl.
119 name = {'zfs': 'zpl',
120 'spl': 'spa'}[request]
121 mib = 'vfs.zfs.version.{}'.format(name)
122 version = sysctl.filter(mib)[0].value
123 return '{} version {}'.format(name, version)
125 def get_descriptions(_request):
126 ctls = sysctl.filter('vfs.zfs')
127 return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
130 elif sys.platform.startswith('linux'):
131 KSTAT_PATH = '/proc/spl/kstat/zfs'
132 SPL_PATH = '/sys/module/spl/parameters'
133 TUNABLES_PATH = '/sys/module/zfs/parameters'
135 def load_kstats(section):
136 path = os.path.join(KSTAT_PATH, section)
137 with open(path) as f:
138 return list(f)[2:] # Get rid of header
140 def get_params(basepath):
141 """Collect information on the Solaris Porting Layer (SPL) or the
142 tunables, depending on the PATH given. Does not check if PATH is
146 for name in os.listdir(basepath):
147 path = os.path.join(basepath, name)
148 with open(path) as f:
150 result[name] = value.strip()
153 def get_spl_params():
154 return get_params(SPL_PATH)
156 def get_tunable_params():
157 return get_params(TUNABLES_PATH)
159 def get_vdev_params():
160 return get_params(TUNABLES_PATH)
162 def get_version_impl(request):
163 # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
164 # the version information. We switch to /sys/module/{spl,zfs}/version
165 # to make sure we get what is really loaded in the kernel
167 with open("/sys/module/{}/version".format(request)) as f:
168 return f.read().strip()
172 def get_descriptions(request):
173 """Get the descriptions of the Solaris Porting Layer (SPL) or the
174 tunables, return with minimal formatting.
177 if request not in ('spl', 'zfs'):
178 print('ERROR: description of "{0}" requested)'.format(request))
182 target_prefix = 'parm:'
184 # We would prefer to do this with /sys/modules -- see the discussion at
185 # get_version() -- but there isn't a way to get the descriptions from
186 # there, so we fall back on modinfo
187 command = ["/sbin/modinfo", request, "-0"]
193 info = subprocess.run(command, stdout=subprocess.PIPE,
194 check=True, universal_newlines=True)
195 raw_output = info.stdout.split('\0')
197 except subprocess.CalledProcessError:
198 print("Error: Descriptions not available",
199 "(can't access kernel module)")
202 for line in raw_output:
204 if not line.startswith(target_prefix):
207 line = line[len(target_prefix):].strip()
208 name, raw_desc = line.split(':', 1)
209 desc = raw_desc.rsplit('(', 1)[0]
212 desc = '(No description found)'
214 descs[name.strip()] = desc.strip()
218 def handle_unraisableException(exc_type, exc_value=None, exc_traceback=None,
219 err_msg=None, object=None):
220 handle_Exception(exc_type, object, exc_traceback)
222 def handle_Exception(ex_cls, ex, tb):
223 if ex_cls is KeyboardInterrupt:
226 if ex_cls is BrokenPipeError:
227 # It turns out that while sys.exit() triggers an exception
228 # not handled message on Python 3.8+, os._exit() does not.
231 if ex_cls is OSError:
232 if ex.errno == errno.ENOTCONN:
237 if hasattr(sys,'unraisablehook'): # Python 3.8+
238 sys.unraisablehook = handle_unraisableException
239 sys.excepthook = handle_Exception
242 def cleanup_line(single_line):
243 """Format a raw line of data from /proc and isolate the name value
244 part, returning a tuple with each. Currently, this gets rid of the
245 middle '4'. For example "arc_no_grow 4 0" returns the tuple
246 ("arc_no_grow", "0").
248 name, _, value = single_line.split()
253 def draw_graph(kstats_dict):
254 """Draw a primitive graph representing the basic information on the
255 ARC -- its size and the proportion used by MFU and MRU -- and quit.
256 We use max size of the ARC to calculate how full it is. This is a
257 very rough representation.
260 arc_stats = isolate_section('arcstats', kstats_dict)
264 arc_size = f_bytes(arc_stats['size'])
265 arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
266 mfu_size = f_bytes(arc_stats['mfu_size'])
267 mru_size = f_bytes(arc_stats['mru_size'])
268 meta_size = f_bytes(arc_stats['arc_meta_used'])
269 dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
270 dnode_size = f_bytes(arc_stats['dnode_size'])
272 info_form = ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} '
274 info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
275 meta_size, dnode_size, dnode_limit)
276 info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
277 info_line = GRAPH_INDENT+info_spc+info_line
279 graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
281 mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
282 mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
283 arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
284 total_ticks = float(arc_perc)*GRAPH_WIDTH
285 mfu_ticks = mfu_perc*GRAPH_WIDTH
286 mru_ticks = mru_perc*GRAPH_WIDTH
287 other_ticks = total_ticks-(mfu_ticks+mru_ticks)
289 core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
290 core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
291 core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
293 for line in ('', info_line, graph_line, core_line, graph_line, ''):
297 def f_bytes(byte_string):
298 """Return human-readable representation of a byte value in
299 powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
300 points. Values smaller than one KiB are returned without
301 decimal points. Note "bytes" is a reserved keyword.
304 prefixes = ([2**80, "YiB"], # yobibytes (yotta)
305 [2**70, "ZiB"], # zebibytes (zetta)
306 [2**60, "EiB"], # exbibytes (exa)
307 [2**50, "PiB"], # pebibytes (peta)
308 [2**40, "TiB"], # tebibytes (tera)
309 [2**30, "GiB"], # gibibytes (giga)
310 [2**20, "MiB"], # mebibytes (mega)
311 [2**10, "KiB"]) # kibibytes (kilo)
313 bites = int(byte_string)
316 for limit, unit in prefixes:
319 value = bites / limit
322 result = '{0:.1f} {1}'.format(value, unit)
324 result = '{0} Bytes'.format(bites)
329 def f_hits(hits_string):
330 """Create a human-readable representation of the number of hits.
331 The single-letter symbols used are SI to avoid the confusion caused
332 by the different "short scale" and "long scale" representations in
333 English, which use the same words for different values. See
334 https://en.wikipedia.org/wiki/Names_of_large_numbers and:
335 https://physics.nist.gov/cuu/Units/prefixes.html
338 numbers = ([10**24, 'Y'], # yotta (septillion)
339 [10**21, 'Z'], # zetta (sextillion)
340 [10**18, 'E'], # exa (quintrillion)
341 [10**15, 'P'], # peta (quadrillion)
342 [10**12, 'T'], # tera (trillion)
343 [10**9, 'G'], # giga (billion)
344 [10**6, 'M'], # mega (million)
345 [10**3, 'k']) # kilo (thousand)
347 hits = int(hits_string)
350 for limit, symbol in numbers:
356 result = "%0.1f%s" % (value, symbol)
363 def f_perc(value1, value2):
364 """Calculate percentage and return in human-readable form. If
365 rounding produces the result '0.0' though the first number is
366 not zero, include a 'less-than' symbol to avoid confusion.
367 Division by zero is handled by returning 'n/a'; no error
376 except ZeroDivisionError:
379 result = '{0:0.1f} %'.format(perc)
381 if result == '0.0 %' and v1 > 0:
387 def format_raw_line(name, value):
388 """For the --raw option for the tunable and SPL outputs, decide on the
389 correct formatting based on the --alternate flag.
393 result = '{0}{1}={2}'.format(INDENT, name, value)
395 # Right-align the value within the line length if it fits,
396 # otherwise just separate it from the name by a single space.
397 fit = LINE_LENGTH - len(INDENT) - len(name)
398 overflow = len(value) + 1
399 w = max(fit, overflow)
400 result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
406 """Collect information on the ZFS subsystem. The step does not perform any
407 further processing, giving us the option to only work on what is actually
408 needed. The name "kstat" is a holdover from the Solaris utility of the same
414 for section in SECTION_PATHS.values():
415 if section not in result:
416 result[section] = load_kstats(section)
421 def get_version(request):
422 """Get the version number of ZFS or SPL on this machine for header.
423 Returns an error string, but does not raise an error, if we can't
424 get the ZFS/SPL version.
427 if request not in ('spl', 'zfs'):
428 error_msg = '(ERROR: "{0}" requested)'.format(request)
431 return get_version_impl(request)
435 """Print the initial heading with date and time as well as info on the
436 kernel and ZFS versions. This is not called for the graph.
439 # datetime is now recommended over time but we keep the exact formatting
440 # from the older version of arc_summary in case there are scripts
441 # that expect it in this way
442 daydate = time.strftime(DATE_FORMAT)
443 spc_date = LINE_LENGTH-len(daydate)
444 sys_version = os.uname()
446 sys_msg = sys_version.sysname+' '+sys_version.release
447 zfs = get_version('zfs')
448 spc_zfs = LINE_LENGTH-len(zfs)
450 machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
451 spl = get_version('spl')
452 spc_spl = LINE_LENGTH-len(spl)
454 print('\n'+('-'*LINE_LENGTH))
455 print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
456 print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
457 print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
460 def print_raw(kstats_dict):
461 """Print all available data from the system in a minimally sorted format.
462 This can be used as a source to be piped through 'grep'.
465 sections = sorted(kstats_dict.keys())
467 for section in sections:
469 print('\n{0}:'.format(section.upper()))
470 lines = sorted(kstats_dict[section])
473 name, value = cleanup_line(line)
474 print(format_raw_line(name, value))
476 # Tunables and SPL must be handled separately because they come from a
477 # different source and have descriptions the user might request
483 def isolate_section(section_name, kstats_dict):
484 """From the complete information on all sections, retrieve only those
489 section_data = kstats_dict[section_name]
491 print('ERROR: Data on {0} not available'.format(section_data))
494 section_dict = dict(cleanup_line(l) for l in section_data)
499 # Formatted output helper functions
502 def prt_1(text, value):
503 """Print text and one value, no indent"""
504 spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
505 print('{0}{spc}{1}'.format(text, value, spc=spc))
508 def prt_i1(text, value):
509 """Print text and one value, with indent"""
510 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
511 print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
514 def prt_2(text, value1, value2):
515 """Print text and two values, no indent"""
516 values = '{0:>9} {1:>9}'.format(value1, value2)
517 spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
518 print('{0}{spc} {1}'.format(text, values, spc=spc))
521 def prt_i2(text, value1, value2):
522 """Print text and two values, with indent"""
523 values = '{0:>9} {1:>9}'.format(value1, value2)
524 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
525 print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
528 # The section output concentrates on important parameters instead of
529 # being exhaustive (that is what the --raw parameter is for)
532 def section_arc(kstats_dict):
533 """Give basic information on the ARC, MRU and MFU. This is the first
534 and most used section.
537 arc_stats = isolate_section('arcstats', kstats_dict)
539 throttle = arc_stats['memory_throttle_count']
546 prt_1('ARC status:', health)
547 prt_i1('Memory throttle count:', throttle)
550 arc_size = arc_stats['size']
551 arc_target_size = arc_stats['c']
552 arc_max = arc_stats['c_max']
553 arc_min = arc_stats['c_min']
554 meta = arc_stats['meta']
557 anon_data = arc_stats['anon_data']
558 anon_metadata = arc_stats['anon_metadata']
559 mfu_data = arc_stats['mfu_data']
560 mfu_metadata = arc_stats['mfu_metadata']
561 mru_data = arc_stats['mru_data']
562 mru_metadata = arc_stats['mru_metadata']
563 mfug_data = arc_stats['mfu_ghost_data']
564 mfug_metadata = arc_stats['mfu_ghost_metadata']
565 mrug_data = arc_stats['mru_ghost_data']
566 mrug_metadata = arc_stats['mru_ghost_metadata']
567 unc_data = arc_stats['uncached_data']
568 unc_metadata = arc_stats['uncached_metadata']
569 bonus_size = arc_stats['bonus_size']
570 dnode_limit = arc_stats['arc_dnode_limit']
571 dnode_size = arc_stats['dnode_size']
572 dbuf_size = arc_stats['dbuf_size']
573 hdr_size = arc_stats['hdr_size']
574 l2_hdr_size = arc_stats['l2_hdr_size']
575 abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
576 target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
578 prt_2('ARC size (current):',
579 f_perc(arc_size, arc_max), f_bytes(arc_size))
580 prt_i2('Target size (adaptive):',
581 f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
582 prt_i2('Min size (hard limit):',
583 f_perc(arc_min, arc_max), f_bytes(arc_min))
584 prt_i2('Max size (high water):',
585 target_size_ratio, f_bytes(arc_max))
586 caches_size = int(anon_data)+int(anon_metadata)+\
587 int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
588 int(unc_data)+int(unc_metadata)
589 prt_i2('Anonymous data size:',
590 f_perc(anon_data, caches_size), f_bytes(anon_data))
591 prt_i2('Anonymous metadata size:',
592 f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
594 v = (s-int(pd))*(s-int(meta))/s
595 prt_i2('MFU data target:', f_perc(v, s),
596 f_bytes(v / 65536 * caches_size / 65536))
597 prt_i2('MFU data size:',
598 f_perc(mfu_data, caches_size), f_bytes(mfu_data))
599 prt_i1('MFU ghost data size:', f_bytes(mfug_data))
600 v = (s-int(pm))*int(meta)/s
601 prt_i2('MFU metadata target:', f_perc(v, s),
602 f_bytes(v / 65536 * caches_size / 65536))
603 prt_i2('MFU metadata size:',
604 f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
605 prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
606 v = int(pd)*(s-int(meta))/s
607 prt_i2('MRU data target:', f_perc(v, s),
608 f_bytes(v / 65536 * caches_size / 65536))
609 prt_i2('MRU data size:',
610 f_perc(mru_data, caches_size), f_bytes(mru_data))
611 prt_i1('MRU ghost data size:', f_bytes(mrug_data))
612 v = int(pm)*int(meta)/s
613 prt_i2('MRU metadata target:', f_perc(v, s),
614 f_bytes(v / 65536 * caches_size / 65536))
615 prt_i2('MRU metadata size:',
616 f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
617 prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
618 prt_i2('Uncached data size:',
619 f_perc(unc_data, caches_size), f_bytes(unc_data))
620 prt_i2('Uncached metadata size:',
621 f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
622 prt_i2('Bonus size:',
623 f_perc(bonus_size, arc_size), f_bytes(bonus_size))
624 prt_i2('Dnode cache target:',
625 f_perc(dnode_limit, arc_max), f_bytes(dnode_limit))
626 prt_i2('Dnode cache size:',
627 f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
629 f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
630 prt_i2('Header size:',
631 f_perc(hdr_size, arc_size), f_bytes(hdr_size))
632 prt_i2('L2 header size:',
633 f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
634 prt_i2('ABD chunk waste size:',
635 f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
638 print('ARC hash breakdown:')
639 prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
640 prt_i2('Elements current:',
641 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
642 f_hits(arc_stats['hash_elements']))
643 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
645 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
646 prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
650 prt_i1('Deleted:', f_hits(arc_stats['deleted']))
651 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
652 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
653 prt_i1('Eviction skips due to L2 writes:',
654 f_hits(arc_stats['evict_l2_skip']))
655 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
656 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
657 prt_i2('L2 eligible MFU evictions:',
658 f_perc(arc_stats['evict_l2_eligible_mfu'],
659 arc_stats['evict_l2_eligible']),
660 f_bytes(arc_stats['evict_l2_eligible_mfu']))
661 prt_i2('L2 eligible MRU evictions:',
662 f_perc(arc_stats['evict_l2_eligible_mru'],
663 arc_stats['evict_l2_eligible']),
664 f_bytes(arc_stats['evict_l2_eligible_mru']))
665 prt_i1('L2 ineligible evictions:',
666 f_bytes(arc_stats['evict_l2_ineligible']))
670 def section_archits(kstats_dict):
671 """Print information on how the caches are accessed ("arc hits").
674 arc_stats = isolate_section('arcstats', kstats_dict)
675 all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
676 int(arc_stats['misses'])
678 prt_1('ARC total accesses:', f_hits(all_accesses))
679 ta_todo = (('Total hits:', arc_stats['hits']),
680 ('Total I/O hits:', arc_stats['iohits']),
681 ('Total misses:', arc_stats['misses']))
682 for title, value in ta_todo:
683 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
686 dd_total = int(arc_stats['demand_data_hits']) +\
687 int(arc_stats['demand_data_iohits']) +\
688 int(arc_stats['demand_data_misses'])
689 prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
691 dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
692 ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
693 ('Demand data misses:', arc_stats['demand_data_misses']))
694 for title, value in dd_todo:
695 prt_i2(title, f_perc(value, dd_total), f_hits(value))
698 dm_total = int(arc_stats['demand_metadata_hits']) +\
699 int(arc_stats['demand_metadata_iohits']) +\
700 int(arc_stats['demand_metadata_misses'])
701 prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
703 dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
704 ('Demand metadata I/O hits:',
705 arc_stats['demand_metadata_iohits']),
706 ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
707 for title, value in dm_todo:
708 prt_i2(title, f_perc(value, dm_total), f_hits(value))
711 pd_total = int(arc_stats['prefetch_data_hits']) +\
712 int(arc_stats['prefetch_data_iohits']) +\
713 int(arc_stats['prefetch_data_misses'])
714 prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
716 pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
717 ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
718 ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
719 for title, value in pd_todo:
720 prt_i2(title, f_perc(value, pd_total), f_hits(value))
723 pm_total = int(arc_stats['prefetch_metadata_hits']) +\
724 int(arc_stats['prefetch_metadata_iohits']) +\
725 int(arc_stats['prefetch_metadata_misses'])
726 prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
728 pm_todo = (('Prefetch metadata hits:',
729 arc_stats['prefetch_metadata_hits']),
730 ('Prefetch metadata I/O hits:',
731 arc_stats['prefetch_metadata_iohits']),
732 ('Prefetch metadata misses:',
733 arc_stats['prefetch_metadata_misses']))
734 for title, value in pm_todo:
735 prt_i2(title, f_perc(value, pm_total), f_hits(value))
738 all_prefetches = int(arc_stats['predictive_prefetch'])+\
739 int(arc_stats['prescient_prefetch'])
740 prt_2('ARC predictive prefetches:',
741 f_perc(arc_stats['predictive_prefetch'], all_prefetches),
742 f_hits(arc_stats['predictive_prefetch']))
743 prt_i2('Demand hits after predictive:',
744 f_perc(arc_stats['demand_hit_predictive_prefetch'],
745 arc_stats['predictive_prefetch']),
746 f_hits(arc_stats['demand_hit_predictive_prefetch']))
747 prt_i2('Demand I/O hits after predictive:',
748 f_perc(arc_stats['demand_iohit_predictive_prefetch'],
749 arc_stats['predictive_prefetch']),
750 f_hits(arc_stats['demand_iohit_predictive_prefetch']))
751 never = int(arc_stats['predictive_prefetch']) -\
752 int(arc_stats['demand_hit_predictive_prefetch']) -\
753 int(arc_stats['demand_iohit_predictive_prefetch'])
754 prt_i2('Never demanded after predictive:',
755 f_perc(never, arc_stats['predictive_prefetch']),
759 prt_2('ARC prescient prefetches:',
760 f_perc(arc_stats['prescient_prefetch'], all_prefetches),
761 f_hits(arc_stats['prescient_prefetch']))
762 prt_i2('Demand hits after prescient:',
763 f_perc(arc_stats['demand_hit_prescient_prefetch'],
764 arc_stats['prescient_prefetch']),
765 f_hits(arc_stats['demand_hit_prescient_prefetch']))
766 prt_i2('Demand I/O hits after prescient:',
767 f_perc(arc_stats['demand_iohit_prescient_prefetch'],
768 arc_stats['prescient_prefetch']),
769 f_hits(arc_stats['demand_iohit_prescient_prefetch']))
770 never = int(arc_stats['prescient_prefetch'])-\
771 int(arc_stats['demand_hit_prescient_prefetch'])-\
772 int(arc_stats['demand_iohit_prescient_prefetch'])
773 prt_i2('Never demanded after prescient:',
774 f_perc(never, arc_stats['prescient_prefetch']),
778 print('ARC states hits of all accesses:')
779 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
780 ('Most recently used (MRU):', arc_stats['mru_hits']),
781 ('Most frequently used (MFU) ghost:',
782 arc_stats['mfu_ghost_hits']),
783 ('Most recently used (MRU) ghost:',
784 arc_stats['mru_ghost_hits']),
785 ('Uncached:', arc_stats['uncached_hits']))
786 for title, value in cl_todo:
787 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
791 def section_dmu(kstats_dict):
792 """Collect information on the DMU"""
794 zfetch_stats = isolate_section('zfetchstats', kstats_dict)
796 zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
798 prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
799 prt_i2('Stream hits:',
800 f_perc(zfetch_stats['hits'], zfetch_access_total),
801 f_hits(zfetch_stats['hits']))
802 prt_i2('Stream misses:',
803 f_perc(zfetch_stats['misses'], zfetch_access_total),
804 f_hits(zfetch_stats['misses']))
805 prt_i2('Streams limit reached:',
806 f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
807 f_hits(zfetch_stats['max_streams']))
808 prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
812 def section_l2arc(kstats_dict):
813 """Collect information on L2ARC device if present. If not, tell user
814 that we're skipping the section.
817 # The L2ARC statistics live in the same section as the normal ARC stuff
818 arc_stats = isolate_section('arcstats', kstats_dict)
820 if arc_stats['l2_size'] == '0':
821 print('L2ARC not detected, skipping section\n')
824 l2_errors = int(arc_stats['l2_writes_error']) +\
825 int(arc_stats['l2_cksum_bad']) +\
826 int(arc_stats['l2_io_error'])
828 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
834 prt_1('L2ARC status:', health)
836 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
837 ('Free on write:', 'l2_free_on_write'),
838 ('R/W clashes:', 'l2_rw_clash'),
839 ('Bad checksums:', 'l2_cksum_bad'),
840 ('Read errors:', 'l2_io_error'),
841 ('Write errors:', 'l2_writes_error'))
843 for title, value in l2_todo:
844 prt_i1(title, f_hits(arc_stats[value]))
847 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
848 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
849 f_bytes(arc_stats['l2_asize']))
850 prt_i2('Header size:',
851 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
852 f_bytes(arc_stats['l2_hdr_size']))
853 prt_i2('MFU allocated size:',
854 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
855 f_bytes(arc_stats['l2_mfu_asize']))
856 prt_i2('MRU allocated size:',
857 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
858 f_bytes(arc_stats['l2_mru_asize']))
859 prt_i2('Prefetch allocated size:',
860 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
861 f_bytes(arc_stats['l2_prefetch_asize']))
862 prt_i2('Data (buffer content) allocated size:',
863 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
864 f_bytes(arc_stats['l2_bufc_data_asize']))
865 prt_i2('Metadata (buffer content) allocated size:',
866 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
867 f_bytes(arc_stats['l2_bufc_metadata_asize']))
870 prt_1('L2ARC breakdown:', f_hits(l2_access_total))
872 f_perc(arc_stats['l2_hits'], l2_access_total),
873 f_hits(arc_stats['l2_hits']))
874 prt_i2('Miss ratio:',
875 f_perc(arc_stats['l2_misses'], l2_access_total),
876 f_hits(arc_stats['l2_misses']))
881 f_bytes(arc_stats['l2_read_bytes']),
882 f_hits(arc_stats['l2_hits']))
884 f_bytes(arc_stats['l2_write_bytes']),
885 f_hits(arc_stats['l2_writes_sent']))
888 print('L2ARC evicts:')
889 prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
890 prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
895 """Print the SPL parameters, if requested with alternative format
896 and/or descriptions. This does not use kstats.
899 if sys.platform.startswith('freebsd'):
900 # No SPL support in FreeBSD
903 spls = get_spl_params()
904 keylist = sorted(spls.keys())
905 print('Solaris Porting Layer (SPL):')
908 descriptions = get_descriptions('spl')
915 print(INDENT+'#', descriptions[key])
917 print(INDENT+'# (No description found)') # paranoid
919 print(format_raw_line(key, value))
924 def section_tunables(*_):
925 """Print the tunables, if requested with alternative format and/or
926 descriptions. This does not use kstasts.
929 tunables = get_tunable_params()
930 keylist = sorted(tunables.keys())
934 descriptions = get_descriptions('zfs')
937 value = tunables[key]
941 print(INDENT+'#', descriptions[key])
943 print(INDENT+'# (No description found)') # paranoid
945 print(format_raw_line(key, value))
950 def section_zil(kstats_dict):
951 """Collect information on the ZFS Intent Log. Some of the information
952 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
955 zil_stats = isolate_section('zil', kstats_dict)
957 prt_1('ZIL committed transactions:',
958 f_hits(zil_stats['zil_itx_count']))
959 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
960 prt_i1('Flushes to stable storage:',
961 f_hits(zil_stats['zil_commit_writer_count']))
962 prt_i2('Transactions to SLOG storage pool:',
963 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
964 f_hits(zil_stats['zil_itx_metaslab_slog_count']))
965 prt_i2('Transactions to non-SLOG storage pool:',
966 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
967 f_hits(zil_stats['zil_itx_metaslab_normal_count']))
971 section_calls = {'arc': section_arc,
972 'archits': section_archits,
974 'l2arc': section_l2arc,
976 'tunables': section_tunables,
981 """Run program. The options to draw a graph and to print all data raw are
982 treated separately because they come with their own call.
985 kstats = get_kstats()
999 section_calls[ARGS.section](kstats)
1001 print('Error: Section "{0}" unknown'.format(ARGS.section))
1005 print('WARNING: Pages are deprecated, please use "--section"\n')
1007 pages_to_calls = {1: 'arc',
1015 call = pages_to_calls[ARGS.page]
1017 print('Error: Page "{0}" not supported'.format(ARGS.page))
1020 section_calls[call](kstats)
1023 # If no parameters were given, we print all sections. We might want to
1024 # change the sequence by hand
1025 calls = sorted(section_calls.keys())
1027 for section in calls:
1028 section_calls[section](kstats)
1033 if __name__ == '__main__':