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:', f_hits(arc_stats['hash_elements']))
666 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
668 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
669 prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
673 prt_i1('Memory throttles:', arc_stats['memory_throttle_count'])
674 prt_i1('Memory direct reclaims:', arc_stats['memory_direct_count'])
675 prt_i1('Memory indirect reclaims:', arc_stats['memory_indirect_count'])
676 prt_i1('Deleted:', f_hits(arc_stats['deleted']))
677 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
678 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
679 prt_i1('Eviction skips due to L2 writes:',
680 f_hits(arc_stats['evict_l2_skip']))
681 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
682 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
683 prt_i2('L2 eligible MFU evictions:',
684 f_perc(arc_stats['evict_l2_eligible_mfu'],
685 arc_stats['evict_l2_eligible']),
686 f_bytes(arc_stats['evict_l2_eligible_mfu']))
687 prt_i2('L2 eligible MRU evictions:',
688 f_perc(arc_stats['evict_l2_eligible_mru'],
689 arc_stats['evict_l2_eligible']),
690 f_bytes(arc_stats['evict_l2_eligible_mru']))
691 prt_i1('L2 ineligible evictions:',
692 f_bytes(arc_stats['evict_l2_ineligible']))
696 def section_archits(kstats_dict):
697 """Print information on how the caches are accessed ("arc hits").
700 arc_stats = isolate_section('arcstats', kstats_dict)
701 all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
702 int(arc_stats['misses'])
704 prt_1('ARC total accesses:', f_hits(all_accesses))
705 ta_todo = (('Total hits:', arc_stats['hits']),
706 ('Total I/O hits:', arc_stats['iohits']),
707 ('Total misses:', arc_stats['misses']))
708 for title, value in ta_todo:
709 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
712 dd_total = int(arc_stats['demand_data_hits']) +\
713 int(arc_stats['demand_data_iohits']) +\
714 int(arc_stats['demand_data_misses'])
715 prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
717 dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
718 ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
719 ('Demand data misses:', arc_stats['demand_data_misses']))
720 for title, value in dd_todo:
721 prt_i2(title, f_perc(value, dd_total), f_hits(value))
724 dm_total = int(arc_stats['demand_metadata_hits']) +\
725 int(arc_stats['demand_metadata_iohits']) +\
726 int(arc_stats['demand_metadata_misses'])
727 prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
729 dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
730 ('Demand metadata I/O hits:',
731 arc_stats['demand_metadata_iohits']),
732 ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
733 for title, value in dm_todo:
734 prt_i2(title, f_perc(value, dm_total), f_hits(value))
737 pd_total = int(arc_stats['prefetch_data_hits']) +\
738 int(arc_stats['prefetch_data_iohits']) +\
739 int(arc_stats['prefetch_data_misses'])
740 prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses),
742 pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
743 ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
744 ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
745 for title, value in pd_todo:
746 prt_i2(title, f_perc(value, pd_total), f_hits(value))
749 pm_total = int(arc_stats['prefetch_metadata_hits']) +\
750 int(arc_stats['prefetch_metadata_iohits']) +\
751 int(arc_stats['prefetch_metadata_misses'])
752 prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
754 pm_todo = (('Prefetch metadata hits:',
755 arc_stats['prefetch_metadata_hits']),
756 ('Prefetch metadata I/O hits:',
757 arc_stats['prefetch_metadata_iohits']),
758 ('Prefetch metadata misses:',
759 arc_stats['prefetch_metadata_misses']))
760 for title, value in pm_todo:
761 prt_i2(title, f_perc(value, pm_total), f_hits(value))
764 all_prefetches = int(arc_stats['predictive_prefetch'])+\
765 int(arc_stats['prescient_prefetch'])
766 prt_2('ARC predictive prefetches:',
767 f_perc(arc_stats['predictive_prefetch'], all_prefetches),
768 f_hits(arc_stats['predictive_prefetch']))
769 prt_i2('Demand hits after predictive:',
770 f_perc(arc_stats['demand_hit_predictive_prefetch'],
771 arc_stats['predictive_prefetch']),
772 f_hits(arc_stats['demand_hit_predictive_prefetch']))
773 prt_i2('Demand I/O hits after predictive:',
774 f_perc(arc_stats['demand_iohit_predictive_prefetch'],
775 arc_stats['predictive_prefetch']),
776 f_hits(arc_stats['demand_iohit_predictive_prefetch']))
777 never = int(arc_stats['predictive_prefetch']) -\
778 int(arc_stats['demand_hit_predictive_prefetch']) -\
779 int(arc_stats['demand_iohit_predictive_prefetch'])
780 prt_i2('Never demanded after predictive:',
781 f_perc(never, arc_stats['predictive_prefetch']),
785 prt_2('ARC prescient prefetches:',
786 f_perc(arc_stats['prescient_prefetch'], all_prefetches),
787 f_hits(arc_stats['prescient_prefetch']))
788 prt_i2('Demand hits after prescient:',
789 f_perc(arc_stats['demand_hit_prescient_prefetch'],
790 arc_stats['prescient_prefetch']),
791 f_hits(arc_stats['demand_hit_prescient_prefetch']))
792 prt_i2('Demand I/O hits after prescient:',
793 f_perc(arc_stats['demand_iohit_prescient_prefetch'],
794 arc_stats['prescient_prefetch']),
795 f_hits(arc_stats['demand_iohit_prescient_prefetch']))
796 never = int(arc_stats['prescient_prefetch'])-\
797 int(arc_stats['demand_hit_prescient_prefetch'])-\
798 int(arc_stats['demand_iohit_prescient_prefetch'])
799 prt_i2('Never demanded after prescient:',
800 f_perc(never, arc_stats['prescient_prefetch']),
804 print('ARC states hits of all accesses:')
805 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
806 ('Most recently used (MRU):', arc_stats['mru_hits']),
807 ('Most frequently used (MFU) ghost:',
808 arc_stats['mfu_ghost_hits']),
809 ('Most recently used (MRU) ghost:',
810 arc_stats['mru_ghost_hits']),
811 ('Uncached:', arc_stats['uncached_hits']))
812 for title, value in cl_todo:
813 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
817 def section_dmu(kstats_dict):
818 """Collect information on the DMU"""
820 zfetch_stats = isolate_section('zfetchstats', kstats_dict)
822 zfetch_access_total = int(zfetch_stats['hits']) +\
823 int(zfetch_stats['future']) + int(zfetch_stats['stride']) +\
824 int(zfetch_stats['past']) + int(zfetch_stats['misses'])
826 prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
827 prt_i2('Stream hits:',
828 f_perc(zfetch_stats['hits'], zfetch_access_total),
829 f_hits(zfetch_stats['hits']))
830 future = int(zfetch_stats['future']) + int(zfetch_stats['stride'])
831 prt_i2('Hits ahead of stream:', f_perc(future, zfetch_access_total),
833 prt_i2('Hits behind stream:',
834 f_perc(zfetch_stats['past'], zfetch_access_total),
835 f_hits(zfetch_stats['past']))
836 prt_i2('Stream misses:',
837 f_perc(zfetch_stats['misses'], zfetch_access_total),
838 f_hits(zfetch_stats['misses']))
839 prt_i2('Streams limit reached:',
840 f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
841 f_hits(zfetch_stats['max_streams']))
842 prt_i1('Stream strides:', f_hits(zfetch_stats['stride']))
843 prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
847 def section_l2arc(kstats_dict):
848 """Collect information on L2ARC device if present. If not, tell user
849 that we're skipping the section.
852 # The L2ARC statistics live in the same section as the normal ARC stuff
853 arc_stats = isolate_section('arcstats', kstats_dict)
855 if arc_stats['l2_size'] == '0':
856 print('L2ARC not detected, skipping section\n')
859 l2_errors = int(arc_stats['l2_writes_error']) +\
860 int(arc_stats['l2_cksum_bad']) +\
861 int(arc_stats['l2_io_error'])
863 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
869 prt_1('L2ARC status:', health)
871 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
872 ('Free on write:', 'l2_free_on_write'),
873 ('R/W clashes:', 'l2_rw_clash'),
874 ('Bad checksums:', 'l2_cksum_bad'),
875 ('Read errors:', 'l2_io_error'),
876 ('Write errors:', 'l2_writes_error'))
878 for title, value in l2_todo:
879 prt_i1(title, f_hits(arc_stats[value]))
882 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
883 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
884 f_bytes(arc_stats['l2_asize']))
885 prt_i2('Header size:',
886 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
887 f_bytes(arc_stats['l2_hdr_size']))
888 prt_i2('MFU allocated size:',
889 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
890 f_bytes(arc_stats['l2_mfu_asize']))
891 prt_i2('MRU allocated size:',
892 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
893 f_bytes(arc_stats['l2_mru_asize']))
894 prt_i2('Prefetch allocated size:',
895 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
896 f_bytes(arc_stats['l2_prefetch_asize']))
897 prt_i2('Data (buffer content) allocated size:',
898 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
899 f_bytes(arc_stats['l2_bufc_data_asize']))
900 prt_i2('Metadata (buffer content) allocated size:',
901 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
902 f_bytes(arc_stats['l2_bufc_metadata_asize']))
905 prt_1('L2ARC breakdown:', f_hits(l2_access_total))
907 f_perc(arc_stats['l2_hits'], l2_access_total),
908 f_hits(arc_stats['l2_hits']))
909 prt_i2('Miss ratio:',
910 f_perc(arc_stats['l2_misses'], l2_access_total),
911 f_hits(arc_stats['l2_misses']))
916 f_bytes(arc_stats['l2_read_bytes']),
917 f_hits(arc_stats['l2_hits']))
919 f_bytes(arc_stats['l2_write_bytes']),
920 f_hits(arc_stats['l2_writes_sent']))
923 print('L2ARC evicts:')
924 prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached']))
925 prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading']))
930 """Print the SPL parameters, if requested with alternative format
931 and/or descriptions. This does not use kstats.
934 if sys.platform.startswith('freebsd'):
935 # No SPL support in FreeBSD
938 spls = get_spl_params()
939 keylist = sorted(spls.keys())
940 print('Solaris Porting Layer (SPL):')
943 descriptions = get_descriptions('spl')
950 print(INDENT+'#', descriptions[key])
952 print(INDENT+'# (No description found)') # paranoid
954 print(format_raw_line(key, value))
959 def section_tunables(*_):
960 """Print the tunables, if requested with alternative format and/or
961 descriptions. This does not use kstasts.
964 tunables = get_tunable_params()
965 keylist = sorted(tunables.keys())
969 descriptions = get_descriptions('zfs')
972 value = tunables[key]
976 print(INDENT+'#', descriptions[key])
978 print(INDENT+'# (No description found)') # paranoid
980 print(format_raw_line(key, value))
985 def section_zil(kstats_dict):
986 """Collect information on the ZFS Intent Log. Some of the information
987 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
990 zil_stats = isolate_section('zil', kstats_dict)
992 prt_1('ZIL committed transactions:',
993 f_hits(zil_stats['zil_itx_count']))
994 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
995 prt_i1('Flushes to stable storage:',
996 f_hits(zil_stats['zil_commit_writer_count']))
997 prt_i2('Transactions to SLOG storage pool:',
998 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
999 f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1000 prt_i2('Transactions to non-SLOG storage pool:',
1001 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1002 f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1006 section_calls = {'arc': section_arc,
1007 'archits': section_archits,
1009 'l2arc': section_l2arc,
1011 'tunables': section_tunables,
1016 """Run program. The options to draw a graph and to print all data raw are
1017 treated separately because they come with their own call.
1020 kstats = get_kstats()
1034 section_calls[ARGS.section](kstats)
1036 print('Error: Section "{0}" unknown'.format(ARGS.section))
1040 print('WARNING: Pages are deprecated, please use "--section"\n')
1042 pages_to_calls = {1: 'arc',
1050 call = pages_to_calls[ARGS.page]
1052 print('Error: Page "{0}" not supported'.format(ARGS.page))
1055 section_calls[call](kstats)
1058 # If no parameters were given, we print all sections. We might want to
1059 # change the sequence by hand
1060 calls = sorted(section_calls.keys())
1062 for section in calls:
1063 section_calls[section](kstats)
1068 if __name__ == '__main__':