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_max = int(arc_stats['c_max'])
265 arc_size = f_bytes(arc_stats['size'])
266 arc_perc = f_perc(arc_stats['size'], arc_max)
267 data_size = f_bytes(arc_stats['data_size'])
268 meta_size = f_bytes(arc_stats['metadata_size'])
269 dnode_size = f_bytes(arc_stats['dnode_size'])
271 info_form = ('ARC: {0} ({1}) Data: {2} Meta: {3} Dnode: {4}')
272 info_line = info_form.format(arc_size, arc_perc, data_size, meta_size,
274 info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
275 info_line = GRAPH_INDENT+info_spc+info_line
277 graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
279 arc_perc = float(int(arc_stats['size'])/arc_max)
280 data_perc = float(int(arc_stats['data_size'])/arc_max)
281 meta_perc = float(int(arc_stats['metadata_size'])/arc_max)
282 dnode_perc = float(int(arc_stats['dnode_size'])/arc_max)
283 total_ticks = float(arc_perc)*GRAPH_WIDTH
284 data_ticks = data_perc*GRAPH_WIDTH
285 meta_ticks = meta_perc*GRAPH_WIDTH
286 dnode_ticks = dnode_perc*GRAPH_WIDTH
287 other_ticks = total_ticks-(data_ticks+meta_ticks+dnode_ticks)
289 core_form = 'D'*int(data_ticks)+'M'*int(meta_ticks)+'N'*int(dnode_ticks)+\
291 core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
292 core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
294 for line in ('', info_line, graph_line, core_line, graph_line, ''):
298 def f_bytes(byte_string):
299 """Return human-readable representation of a byte value in
300 powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
301 points. Values smaller than one KiB are returned without
302 decimal points. Note "bytes" is a reserved keyword.
305 prefixes = ([2**80, "YiB"], # yobibytes (yotta)
306 [2**70, "ZiB"], # zebibytes (zetta)
307 [2**60, "EiB"], # exbibytes (exa)
308 [2**50, "PiB"], # pebibytes (peta)
309 [2**40, "TiB"], # tebibytes (tera)
310 [2**30, "GiB"], # gibibytes (giga)
311 [2**20, "MiB"], # mebibytes (mega)
312 [2**10, "KiB"]) # kibibytes (kilo)
314 bites = int(byte_string)
317 for limit, unit in prefixes:
320 value = bites / limit
323 result = '{0:.1f} {1}'.format(value, unit)
325 result = '{0} Bytes'.format(bites)
330 def f_hits(hits_string):
331 """Create a human-readable representation of the number of hits.
332 The single-letter symbols used are SI to avoid the confusion caused
333 by the different "short scale" and "long scale" representations in
334 English, which use the same words for different values. See
335 https://en.wikipedia.org/wiki/Names_of_large_numbers and:
336 https://physics.nist.gov/cuu/Units/prefixes.html
339 numbers = ([10**24, 'Y'], # yotta (septillion)
340 [10**21, 'Z'], # zetta (sextillion)
341 [10**18, 'E'], # exa (quintrillion)
342 [10**15, 'P'], # peta (quadrillion)
343 [10**12, 'T'], # tera (trillion)
344 [10**9, 'G'], # giga (billion)
345 [10**6, 'M'], # mega (million)
346 [10**3, 'k']) # kilo (thousand)
348 hits = int(hits_string)
351 for limit, symbol in numbers:
357 result = "%0.1f%s" % (value, symbol)
364 def f_perc(value1, value2):
365 """Calculate percentage and return in human-readable form. If
366 rounding produces the result '0.0' though the first number is
367 not zero, include a 'less-than' symbol to avoid confusion.
368 Division by zero is handled by returning 'n/a'; no error
377 except ZeroDivisionError:
380 result = '{0:0.1f} %'.format(perc)
382 if result == '0.0 %' and v1 > 0:
388 def format_raw_line(name, value):
389 """For the --raw option for the tunable and SPL outputs, decide on the
390 correct formatting based on the --alternate flag.
394 result = '{0}{1}={2}'.format(INDENT, name, value)
396 # Right-align the value within the line length if it fits,
397 # otherwise just separate it from the name by a single space.
398 fit = LINE_LENGTH - len(INDENT) - len(name)
399 overflow = len(value) + 1
400 w = max(fit, overflow)
401 result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
407 """Collect information on the ZFS subsystem. The step does not perform any
408 further processing, giving us the option to only work on what is actually
409 needed. The name "kstat" is a holdover from the Solaris utility of the same
415 for section in SECTION_PATHS.values():
416 if section not in result:
417 result[section] = load_kstats(section)
422 def get_version(request):
423 """Get the version number of ZFS or SPL on this machine for header.
424 Returns an error string, but does not raise an error, if we can't
425 get the ZFS/SPL version.
428 if request not in ('spl', 'zfs'):
429 error_msg = '(ERROR: "{0}" requested)'.format(request)
432 return get_version_impl(request)
436 """Print the initial heading with date and time as well as info on the
437 kernel and ZFS versions. This is not called for the graph.
440 # datetime is now recommended over time but we keep the exact formatting
441 # from the older version of arc_summary in case there are scripts
442 # that expect it in this way
443 daydate = time.strftime(DATE_FORMAT)
444 spc_date = LINE_LENGTH-len(daydate)
445 sys_version = os.uname()
447 sys_msg = sys_version.sysname+' '+sys_version.release
448 zfs = get_version('zfs')
449 spc_zfs = LINE_LENGTH-len(zfs)
451 machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
452 spl = get_version('spl')
453 spc_spl = LINE_LENGTH-len(spl)
455 print('\n'+('-'*LINE_LENGTH))
456 print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
457 print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
458 print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
461 def print_raw(kstats_dict):
462 """Print all available data from the system in a minimally sorted format.
463 This can be used as a source to be piped through 'grep'.
466 sections = sorted(kstats_dict.keys())
468 for section in sections:
470 print('\n{0}:'.format(section.upper()))
471 lines = sorted(kstats_dict[section])
474 name, value = cleanup_line(line)
475 print(format_raw_line(name, value))
477 # Tunables and SPL must be handled separately because they come from a
478 # different source and have descriptions the user might request
484 def isolate_section(section_name, kstats_dict):
485 """From the complete information on all sections, retrieve only those
490 section_data = kstats_dict[section_name]
492 print('ERROR: Data on {0} not available'.format(section_data))
495 section_dict = dict(cleanup_line(l) for l in section_data)
500 # Formatted output helper functions
503 def prt_1(text, value):
504 """Print text and one value, no indent"""
505 spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
506 print('{0}{spc}{1}'.format(text, value, spc=spc))
509 def prt_i1(text, value):
510 """Print text and one value, with indent"""
511 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
512 print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
515 def prt_2(text, value1, value2):
516 """Print text and two values, no indent"""
517 values = '{0:>9} {1:>9}'.format(value1, value2)
518 spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
519 print('{0}{spc} {1}'.format(text, values, spc=spc))
522 def prt_i2(text, value1, value2):
523 """Print text and two values, with indent"""
524 values = '{0:>9} {1:>9}'.format(value1, value2)
525 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
526 print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
529 # The section output concentrates on important parameters instead of
530 # being exhaustive (that is what the --raw parameter is for)
533 def section_arc(kstats_dict):
534 """Give basic information on the ARC, MRU and MFU. This is the first
535 and most used section.
538 arc_stats = isolate_section('arcstats', kstats_dict)
540 memory_all = arc_stats['memory_all_bytes']
541 memory_free = arc_stats['memory_free_bytes']
542 memory_avail = arc_stats['memory_available_bytes']
543 arc_size = arc_stats['size']
544 arc_target_size = arc_stats['c']
545 arc_max = arc_stats['c_max']
546 arc_min = arc_stats['c_min']
547 dnode_limit = arc_stats['arc_dnode_limit']
550 prt_i1('Total memory size:', f_bytes(memory_all))
551 prt_i2('Min target size:', f_perc(arc_min, memory_all), f_bytes(arc_min))
552 prt_i2('Max target size:', f_perc(arc_max, memory_all), f_bytes(arc_max))
553 prt_i2('Target size (adaptive):',
554 f_perc(arc_size, arc_max), f_bytes(arc_target_size))
555 prt_i2('Current size:', f_perc(arc_size, arc_max), f_bytes(arc_size))
556 prt_i1('Free memory size:', f_bytes(memory_free))
557 prt_i1('Available memory size:', f_bytes(memory_avail))
560 compressed_size = arc_stats['compressed_size']
561 overhead_size = arc_stats['overhead_size']
562 bonus_size = arc_stats['bonus_size']
563 dnode_size = arc_stats['dnode_size']
564 dbuf_size = arc_stats['dbuf_size']
565 hdr_size = arc_stats['hdr_size']
566 l2_hdr_size = arc_stats['l2_hdr_size']
567 abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
569 prt_1('ARC structural breakdown (current size):', f_bytes(arc_size))
570 prt_i2('Compressed size:',
571 f_perc(compressed_size, arc_size), f_bytes(compressed_size))
572 prt_i2('Overhead size:',
573 f_perc(overhead_size, arc_size), f_bytes(overhead_size))
574 prt_i2('Bonus size:',
575 f_perc(bonus_size, arc_size), f_bytes(bonus_size))
576 prt_i2('Dnode size:',
577 f_perc(dnode_size, arc_size), f_bytes(dnode_size))
579 f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
580 prt_i2('Header size:',
581 f_perc(hdr_size, arc_size), f_bytes(hdr_size))
582 prt_i2('L2 header size:',
583 f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
584 prt_i2('ABD chunk waste size:',
585 f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
588 meta = arc_stats['meta']
591 data_size = arc_stats['data_size']
592 metadata_size = arc_stats['metadata_size']
593 anon_data = arc_stats['anon_data']
594 anon_metadata = arc_stats['anon_metadata']
595 mfu_data = arc_stats['mfu_data']
596 mfu_metadata = arc_stats['mfu_metadata']
597 mfu_edata = arc_stats['mfu_evictable_data']
598 mfu_emetadata = arc_stats['mfu_evictable_metadata']
599 mru_data = arc_stats['mru_data']
600 mru_metadata = arc_stats['mru_metadata']
601 mru_edata = arc_stats['mru_evictable_data']
602 mru_emetadata = arc_stats['mru_evictable_metadata']
603 mfug_data = arc_stats['mfu_ghost_data']
604 mfug_metadata = arc_stats['mfu_ghost_metadata']
605 mrug_data = arc_stats['mru_ghost_data']
606 mrug_metadata = arc_stats['mru_ghost_metadata']
607 unc_data = arc_stats['uncached_data']
608 unc_metadata = arc_stats['uncached_metadata']
609 caches_size = int(anon_data)+int(anon_metadata)+\
610 int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
611 int(unc_data)+int(unc_metadata)
613 prt_1('ARC types breakdown (compressed + overhead):', f_bytes(caches_size))
615 f_perc(data_size, caches_size), f_bytes(data_size))
616 prt_i2('Metadata size:',
617 f_perc(metadata_size, caches_size), f_bytes(metadata_size))
620 prt_1('ARC states breakdown (compressed + overhead):', f_bytes(caches_size))
621 prt_i2('Anonymous data size:',
622 f_perc(anon_data, caches_size), f_bytes(anon_data))
623 prt_i2('Anonymous metadata size:',
624 f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
626 v = (s-int(pd))*(s-int(meta))/s
627 prt_i2('MFU data target:', f_perc(v, s),
628 f_bytes(v / 65536 * caches_size / 65536))
629 prt_i2('MFU data size:',
630 f_perc(mfu_data, caches_size), f_bytes(mfu_data))
631 prt_i2('MFU evictable data size:',
632 f_perc(mfu_edata, caches_size), f_bytes(mfu_edata))
633 prt_i1('MFU ghost data size:', f_bytes(mfug_data))
634 v = (s-int(pm))*int(meta)/s
635 prt_i2('MFU metadata target:', f_perc(v, s),
636 f_bytes(v / 65536 * caches_size / 65536))
637 prt_i2('MFU metadata size:',
638 f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
639 prt_i2('MFU evictable metadata size:',
640 f_perc(mfu_emetadata, caches_size), f_bytes(mfu_emetadata))
641 prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
642 v = int(pd)*(s-int(meta))/s
643 prt_i2('MRU data target:', f_perc(v, s),
644 f_bytes(v / 65536 * caches_size / 65536))
645 prt_i2('MRU data size:',
646 f_perc(mru_data, caches_size), f_bytes(mru_data))
647 prt_i2('MRU evictable data size:',
648 f_perc(mru_edata, caches_size), f_bytes(mru_edata))
649 prt_i1('MRU ghost data size:', f_bytes(mrug_data))
650 v = int(pm)*int(meta)/s
651 prt_i2('MRU metadata target:', f_perc(v, s),
652 f_bytes(v / 65536 * caches_size / 65536))
653 prt_i2('MRU metadata size:',
654 f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
655 prt_i2('MRU evictable metadata size:',
656 f_perc(mru_emetadata, caches_size), f_bytes(mru_emetadata))
657 prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
658 prt_i2('Uncached data size:',
659 f_perc(unc_data, caches_size), f_bytes(unc_data))
660 prt_i2('Uncached metadata size:',
661 f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
664 print('ARC hash breakdown:')
665 prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
666 prt_i2('Elements current:',
667 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
668 f_hits(arc_stats['hash_elements']))
669 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
671 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
672 prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
676 prt_i1('Memory throttles:', arc_stats['memory_throttle_count'])
677 prt_i1('Memory direct reclaims:', arc_stats['memory_direct_count'])
678 prt_i1('Memory indirect reclaims:', arc_stats['memory_indirect_count'])
679 prt_i1('Deleted:', f_hits(arc_stats['deleted']))
680 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
681 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
682 prt_i1('Eviction skips due to L2 writes:',
683 f_hits(arc_stats['evict_l2_skip']))
684 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
685 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
686 prt_i2('L2 eligible MFU evictions:',
687 f_perc(arc_stats['evict_l2_eligible_mfu'],
688 arc_stats['evict_l2_eligible']),
689 f_bytes(arc_stats['evict_l2_eligible_mfu']))
690 prt_i2('L2 eligible MRU evictions:',
691 f_perc(arc_stats['evict_l2_eligible_mru'],
692 arc_stats['evict_l2_eligible']),
693 f_bytes(arc_stats['evict_l2_eligible_mru']))
694 prt_i1('L2 ineligible evictions:',
695 f_bytes(arc_stats['evict_l2_ineligible']))
699 def section_archits(kstats_dict):
700 """Print information on how the caches are accessed ("arc hits").
703 arc_stats = isolate_section('arcstats', kstats_dict)
704 all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
705 int(arc_stats['misses'])
707 prt_1('ARC total accesses:', f_hits(all_accesses))
708 ta_todo = (('Total hits:', arc_stats['hits']),
709 ('Total I/O hits:', arc_stats['iohits']),
710 ('Total misses:', arc_stats['misses']))
711 for title, value in ta_todo:
712 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
715 dd_total = int(arc_stats['demand_data_hits']) +\
716 int(arc_stats['demand_data_iohits']) +\
717 int(arc_stats['demand_data_misses'])
718 prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
720 dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
721 ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
722 ('Demand data misses:', arc_stats['demand_data_misses']))
723 for title, value in dd_todo:
724 prt_i2(title, f_perc(value, dd_total), f_hits(value))
727 dm_total = int(arc_stats['demand_metadata_hits']) +\
728 int(arc_stats['demand_metadata_iohits']) +\
729 int(arc_stats['demand_metadata_misses'])
730 prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
732 dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
733 ('Demand metadata I/O hits:',
734 arc_stats['demand_metadata_iohits']),
735 ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
736 for title, value in dm_todo:
737 prt_i2(title, f_perc(value, dm_total), f_hits(value))
740 pd_total = int(arc_stats['prefetch_data_hits']) +\
741 int(arc_stats['prefetch_data_iohits']) +\
742 int(arc_stats['prefetch_data_misses'])
743 prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
745 pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
746 ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
747 ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
748 for title, value in pd_todo:
749 prt_i2(title, f_perc(value, pd_total), f_hits(value))
752 pm_total = int(arc_stats['prefetch_metadata_hits']) +\
753 int(arc_stats['prefetch_metadata_iohits']) +\
754 int(arc_stats['prefetch_metadata_misses'])
755 prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
757 pm_todo = (('Prefetch metadata hits:',
758 arc_stats['prefetch_metadata_hits']),
759 ('Prefetch metadata I/O hits:',
760 arc_stats['prefetch_metadata_iohits']),
761 ('Prefetch metadata misses:',
762 arc_stats['prefetch_metadata_misses']))
763 for title, value in pm_todo:
764 prt_i2(title, f_perc(value, pm_total), f_hits(value))
767 all_prefetches = int(arc_stats['predictive_prefetch'])+\
768 int(arc_stats['prescient_prefetch'])
769 prt_2('ARC predictive prefetches:',
770 f_perc(arc_stats['predictive_prefetch'], all_prefetches),
771 f_hits(arc_stats['predictive_prefetch']))
772 prt_i2('Demand hits after predictive:',
773 f_perc(arc_stats['demand_hit_predictive_prefetch'],
774 arc_stats['predictive_prefetch']),
775 f_hits(arc_stats['demand_hit_predictive_prefetch']))
776 prt_i2('Demand I/O hits after predictive:',
777 f_perc(arc_stats['demand_iohit_predictive_prefetch'],
778 arc_stats['predictive_prefetch']),
779 f_hits(arc_stats['demand_iohit_predictive_prefetch']))
780 never = int(arc_stats['predictive_prefetch']) -\
781 int(arc_stats['demand_hit_predictive_prefetch']) -\
782 int(arc_stats['demand_iohit_predictive_prefetch'])
783 prt_i2('Never demanded after predictive:',
784 f_perc(never, arc_stats['predictive_prefetch']),
788 prt_2('ARC prescient prefetches:',
789 f_perc(arc_stats['prescient_prefetch'], all_prefetches),
790 f_hits(arc_stats['prescient_prefetch']))
791 prt_i2('Demand hits after prescient:',
792 f_perc(arc_stats['demand_hit_prescient_prefetch'],
793 arc_stats['prescient_prefetch']),
794 f_hits(arc_stats['demand_hit_prescient_prefetch']))
795 prt_i2('Demand I/O hits after prescient:',
796 f_perc(arc_stats['demand_iohit_prescient_prefetch'],
797 arc_stats['prescient_prefetch']),
798 f_hits(arc_stats['demand_iohit_prescient_prefetch']))
799 never = int(arc_stats['prescient_prefetch'])-\
800 int(arc_stats['demand_hit_prescient_prefetch'])-\
801 int(arc_stats['demand_iohit_prescient_prefetch'])
802 prt_i2('Never demanded after prescient:',
803 f_perc(never, arc_stats['prescient_prefetch']),
807 print('ARC states hits of all accesses:')
808 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
809 ('Most recently used (MRU):', arc_stats['mru_hits']),
810 ('Most frequently used (MFU) ghost:',
811 arc_stats['mfu_ghost_hits']),
812 ('Most recently used (MRU) ghost:',
813 arc_stats['mru_ghost_hits']),
814 ('Uncached:', arc_stats['uncached_hits']))
815 for title, value in cl_todo:
816 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
820 def section_dmu(kstats_dict):
821 """Collect information on the DMU"""
823 zfetch_stats = isolate_section('zfetchstats', kstats_dict)
825 zfetch_access_total = int(zfetch_stats['hits']) +\
826 int(zfetch_stats['future']) + int(zfetch_stats['stride']) +\
827 int(zfetch_stats['past']) + int(zfetch_stats['misses'])
829 prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
830 prt_i2('Stream hits:',
831 f_perc(zfetch_stats['hits'], zfetch_access_total),
832 f_hits(zfetch_stats['hits']))
833 future = int(zfetch_stats['future']) + int(zfetch_stats['stride'])
834 prt_i2('Hits ahead of stream:', f_perc(future, zfetch_access_total),
836 prt_i2('Hits behind stream:',
837 f_perc(zfetch_stats['past'], zfetch_access_total),
838 f_hits(zfetch_stats['past']))
839 prt_i2('Stream misses:',
840 f_perc(zfetch_stats['misses'], zfetch_access_total),
841 f_hits(zfetch_stats['misses']))
842 prt_i2('Streams limit reached:',
843 f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
844 f_hits(zfetch_stats['max_streams']))
845 prt_i1('Stream strides:', f_hits(zfetch_stats['stride']))
846 prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
850 def section_l2arc(kstats_dict):
851 """Collect information on L2ARC device if present. If not, tell user
852 that we're skipping the section.
855 # The L2ARC statistics live in the same section as the normal ARC stuff
856 arc_stats = isolate_section('arcstats', kstats_dict)
858 if arc_stats['l2_size'] == '0':
859 print('L2ARC not detected, skipping section\n')
862 l2_errors = int(arc_stats['l2_writes_error']) +\
863 int(arc_stats['l2_cksum_bad']) +\
864 int(arc_stats['l2_io_error'])
866 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
872 prt_1('L2ARC status:', health)
874 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
875 ('Free on write:', 'l2_free_on_write'),
876 ('R/W clashes:', 'l2_rw_clash'),
877 ('Bad checksums:', 'l2_cksum_bad'),
878 ('Read errors:', 'l2_io_error'),
879 ('Write errors:', 'l2_writes_error'))
881 for title, value in l2_todo:
882 prt_i1(title, f_hits(arc_stats[value]))
885 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
886 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
887 f_bytes(arc_stats['l2_asize']))
888 prt_i2('Header size:',
889 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
890 f_bytes(arc_stats['l2_hdr_size']))
891 prt_i2('MFU allocated size:',
892 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
893 f_bytes(arc_stats['l2_mfu_asize']))
894 prt_i2('MRU allocated size:',
895 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
896 f_bytes(arc_stats['l2_mru_asize']))
897 prt_i2('Prefetch allocated size:',
898 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
899 f_bytes(arc_stats['l2_prefetch_asize']))
900 prt_i2('Data (buffer content) allocated size:',
901 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
902 f_bytes(arc_stats['l2_bufc_data_asize']))
903 prt_i2('Metadata (buffer content) allocated size:',
904 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
905 f_bytes(arc_stats['l2_bufc_metadata_asize']))
908 prt_1('L2ARC breakdown:', f_hits(l2_access_total))
910 f_perc(arc_stats['l2_hits'], l2_access_total),
911 f_hits(arc_stats['l2_hits']))
912 prt_i2('Miss ratio:',
913 f_perc(arc_stats['l2_misses'], l2_access_total),
914 f_hits(arc_stats['l2_misses']))
919 f_bytes(arc_stats['l2_read_bytes']),
920 f_hits(arc_stats['l2_hits']))
922 f_bytes(arc_stats['l2_write_bytes']),
923 f_hits(arc_stats['l2_writes_sent']))
926 print('L2ARC evicts:')
927 prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
928 prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
933 """Print the SPL parameters, if requested with alternative format
934 and/or descriptions. This does not use kstats.
937 if sys.platform.startswith('freebsd'):
938 # No SPL support in FreeBSD
941 spls = get_spl_params()
942 keylist = sorted(spls.keys())
943 print('Solaris Porting Layer (SPL):')
946 descriptions = get_descriptions('spl')
953 print(INDENT+'#', descriptions[key])
955 print(INDENT+'# (No description found)') # paranoid
957 print(format_raw_line(key, value))
962 def section_tunables(*_):
963 """Print the tunables, if requested with alternative format and/or
964 descriptions. This does not use kstasts.
967 tunables = get_tunable_params()
968 keylist = sorted(tunables.keys())
972 descriptions = get_descriptions('zfs')
975 value = tunables[key]
979 print(INDENT+'#', descriptions[key])
981 print(INDENT+'# (No description found)') # paranoid
983 print(format_raw_line(key, value))
988 def section_zil(kstats_dict):
989 """Collect information on the ZFS Intent Log. Some of the information
990 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
993 zil_stats = isolate_section('zil', kstats_dict)
995 prt_1('ZIL committed transactions:',
996 f_hits(zil_stats['zil_itx_count']))
997 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
998 prt_i1('Flushes to stable storage:',
999 f_hits(zil_stats['zil_commit_writer_count']))
1000 prt_i2('Transactions to SLOG storage pool:',
1001 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
1002 f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1003 prt_i2('Transactions to non-SLOG storage pool:',
1004 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1005 f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1009 section_calls = {'arc': section_arc,
1010 'archits': section_archits,
1012 'l2arc': section_l2arc,
1014 'tunables': section_tunables,
1019 """Run program. The options to draw a graph and to print all data raw are
1020 treated separately because they come with their own call.
1023 kstats = get_kstats()
1037 section_calls[ARGS.section](kstats)
1039 print('Error: Section "{0}" unknown'.format(ARGS.section))
1043 print('WARNING: Pages are deprecated, please use "--section"\n')
1045 pages_to_calls = {1: 'arc',
1053 call = pages_to_calls[ARGS.page]
1055 print('Error: Page "{0}" not supported'.format(ARGS.page))
1058 section_calls[call](kstats)
1061 # If no parameters were given, we print all sections. We might want to
1062 # change the sequence by hand
1063 calls = sorted(section_calls.keys())
1065 for section in calls:
1066 section_calls[section](kstats)
1071 if __name__ == '__main__':