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
46 DESCRIPTION = 'Print ARC and other statistics for OpenZFS'
49 DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
50 TITLE = 'ZFS Subsystem Report'
52 SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
53 SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
55 # Tunables and SPL are handled separately because they come from
57 SECTION_PATHS = {'arc': 'arcstats',
59 'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
60 'vdev': 'vdev_cache_stats',
61 'zfetch': 'zfetchstats',
64 parser = argparse.ArgumentParser(description=DESCRIPTION)
65 parser.add_argument('-a', '--alternate', action='store_true', default=False,
66 help='use alternate formatting for tunables and SPL',
68 parser.add_argument('-d', '--description', action='store_true', default=False,
69 help='print descriptions with tunables and SPL',
71 parser.add_argument('-g', '--graph', action='store_true', default=False,
72 help='print graph on ARC use and exit', dest='graph')
73 parser.add_argument('-p', '--page', type=int, dest='page',
74 help='print page by number (DEPRECATED, use "-s")')
75 parser.add_argument('-r', '--raw', action='store_true', default=False,
76 help='dump all available data with minimal formatting',
78 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
79 ARGS = parser.parse_args()
82 if sys.platform.startswith('freebsd'):
83 # Requires py36-sysctl on FreeBSD
86 VDEV_CACHE_SIZE = 'vdev.cache_size'
89 return ctl.type != sysctl.CTLTYPE_NODE
91 def namefmt(ctl, base='vfs.zfs.'):
92 # base is removed from the name
96 def load_kstats(section):
97 base = 'kstat.zfs.misc.{section}.'.format(section=section)
98 fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
100 kstats = sysctl.filter(base)
101 return [fmt(kstat) for kstat in kstats if is_value(kstat)]
103 def get_params(base):
104 ctls = sysctl.filter(base)
105 return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
107 def get_tunable_params():
108 return get_params('vfs.zfs')
110 def get_vdev_params():
111 return get_params('vfs.zfs.vdev')
113 def get_version_impl(request):
114 # FreeBSD reports versions for zpl and spa instead of zfs and spl.
115 name = {'zfs': 'zpl',
116 'spl': 'spa'}[request]
117 mib = 'vfs.zfs.version.{}'.format(name)
118 version = sysctl.filter(mib)[0].value
119 return '{} version {}'.format(name, version)
121 def get_descriptions(_request):
122 ctls = sysctl.filter('vfs.zfs')
123 return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
126 elif sys.platform.startswith('linux'):
127 KSTAT_PATH = '/proc/spl/kstat/zfs'
128 SPL_PATH = '/sys/module/spl/parameters'
129 TUNABLES_PATH = '/sys/module/zfs/parameters'
131 VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
133 def load_kstats(section):
134 path = os.path.join(KSTAT_PATH, section)
135 with open(path) as f:
136 return list(f)[2:] # Get rid of header
138 def get_params(basepath):
139 """Collect information on the Solaris Porting Layer (SPL) or the
140 tunables, depending on the PATH given. Does not check if PATH is
144 for name in os.listdir(basepath):
145 path = os.path.join(basepath, name)
146 with open(path) as f:
148 result[name] = value.strip()
151 def get_spl_params():
152 return get_params(SPL_PATH)
154 def get_tunable_params():
155 return get_params(TUNABLES_PATH)
157 def get_vdev_params():
158 return get_params(TUNABLES_PATH)
160 def get_version_impl(request):
161 # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
162 # the version information. We switch to /sys/module/{spl,zfs}/version
163 # to make sure we get what is really loaded in the kernel
165 with open("/sys/module/{}/version".format(request)) as f:
166 return f.read().strip()
170 def get_descriptions(request):
171 """Get the descriptions of the Solaris Porting Layer (SPL) or the
172 tunables, return with minimal formatting.
175 if request not in ('spl', 'zfs'):
176 print('ERROR: description of "{0}" requested)'.format(request))
180 target_prefix = 'parm:'
182 # We would prefer to do this with /sys/modules -- see the discussion at
183 # get_version() -- but there isn't a way to get the descriptions from
184 # there, so we fall back on modinfo
185 command = ["/sbin/modinfo", request, "-0"]
187 # The recommended way to do this is with subprocess.run(). However,
188 # some installed versions of Python are < 3.5, so we offer them
189 # the option of doing it the old way (for now)
194 if 'run' in dir(subprocess):
195 info = subprocess.run(command, stdout=subprocess.PIPE,
196 universal_newlines=True)
197 raw_output = info.stdout.split('\0')
199 info = subprocess.check_output(command,
200 universal_newlines=True)
201 raw_output = info.split('\0')
203 except subprocess.CalledProcessError:
204 print("Error: Descriptions not available",
205 "(can't access kernel module)")
208 for line in raw_output:
210 if not line.startswith(target_prefix):
213 line = line[len(target_prefix):].strip()
214 name, raw_desc = line.split(':', 1)
215 desc = raw_desc.rsplit('(', 1)[0]
218 desc = '(No description found)'
220 descs[name.strip()] = desc.strip()
225 def cleanup_line(single_line):
226 """Format a raw line of data from /proc and isolate the name value
227 part, returning a tuple with each. Currently, this gets rid of the
228 middle '4'. For example "arc_no_grow 4 0" returns the tuple
229 ("arc_no_grow", "0").
231 name, _, value = single_line.split()
236 def draw_graph(kstats_dict):
237 """Draw a primitive graph representing the basic information on the
238 ARC -- its size and the proportion used by MFU and MRU -- and quit.
239 We use max size of the ARC to calculate how full it is. This is a
240 very rough representation.
243 arc_stats = isolate_section('arcstats', kstats_dict)
247 arc_size = f_bytes(arc_stats['size'])
248 arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
249 mfu_size = f_bytes(arc_stats['mfu_size'])
250 mru_size = f_bytes(arc_stats['mru_size'])
251 meta_limit = f_bytes(arc_stats['arc_meta_limit'])
252 meta_size = f_bytes(arc_stats['arc_meta_used'])
253 dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
254 dnode_size = f_bytes(arc_stats['dnode_size'])
256 info_form = ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} ({5}) '
258 info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
259 meta_size, meta_limit, dnode_size,
261 info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
262 info_line = GRAPH_INDENT+info_spc+info_line
264 graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
266 mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
267 mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
268 arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
269 total_ticks = float(arc_perc)*GRAPH_WIDTH
270 mfu_ticks = mfu_perc*GRAPH_WIDTH
271 mru_ticks = mru_perc*GRAPH_WIDTH
272 other_ticks = total_ticks-(mfu_ticks+mru_ticks)
274 core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
275 core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
276 core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
278 for line in ('', info_line, graph_line, core_line, graph_line, ''):
282 def f_bytes(byte_string):
283 """Return human-readable representation of a byte value in
284 powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
285 points. Values smaller than one KiB are returned without
286 decimal points. Note "bytes" is a reserved keyword.
289 prefixes = ([2**80, "YiB"], # yobibytes (yotta)
290 [2**70, "ZiB"], # zebibytes (zetta)
291 [2**60, "EiB"], # exbibytes (exa)
292 [2**50, "PiB"], # pebibytes (peta)
293 [2**40, "TiB"], # tebibytes (tera)
294 [2**30, "GiB"], # gibibytes (giga)
295 [2**20, "MiB"], # mebibytes (mega)
296 [2**10, "KiB"]) # kibibytes (kilo)
298 bites = int(byte_string)
301 for limit, unit in prefixes:
304 value = bites / limit
307 result = '{0:.1f} {1}'.format(value, unit)
309 result = '{0} Bytes'.format(bites)
314 def f_hits(hits_string):
315 """Create a human-readable representation of the number of hits.
316 The single-letter symbols used are SI to avoid the confusion caused
317 by the different "short scale" and "long scale" representations in
318 English, which use the same words for different values. See
319 https://en.wikipedia.org/wiki/Names_of_large_numbers and:
320 https://physics.nist.gov/cuu/Units/prefixes.html
323 numbers = ([10**24, 'Y'], # yotta (septillion)
324 [10**21, 'Z'], # zetta (sextillion)
325 [10**18, 'E'], # exa (quintrillion)
326 [10**15, 'P'], # peta (quadrillion)
327 [10**12, 'T'], # tera (trillion)
328 [10**9, 'G'], # giga (billion)
329 [10**6, 'M'], # mega (million)
330 [10**3, 'k']) # kilo (thousand)
332 hits = int(hits_string)
335 for limit, symbol in numbers:
341 result = "%0.1f%s" % (value, symbol)
348 def f_perc(value1, value2):
349 """Calculate percentage and return in human-readable form. If
350 rounding produces the result '0.0' though the first number is
351 not zero, include a 'less-than' symbol to avoid confusion.
352 Division by zero is handled by returning 'n/a'; no error
361 except ZeroDivisionError:
364 result = '{0:0.1f} %'.format(perc)
366 if result == '0.0 %' and v1 > 0:
372 def format_raw_line(name, value):
373 """For the --raw option for the tunable and SPL outputs, decide on the
374 correct formatting based on the --alternate flag.
378 result = '{0}{1}={2}'.format(INDENT, name, value)
380 # Right-align the value within the line length if it fits,
381 # otherwise just separate it from the name by a single space.
382 fit = LINE_LENGTH - len(INDENT) - len(name)
383 overflow = len(value) + 1
384 w = max(fit, overflow)
385 result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
391 """Collect information on the ZFS subsystem. The step does not perform any
392 further processing, giving us the option to only work on what is actually
393 needed. The name "kstat" is a holdover from the Solaris utility of the same
399 for section in SECTION_PATHS.values():
400 if section not in result:
401 result[section] = load_kstats(section)
406 def get_version(request):
407 """Get the version number of ZFS or SPL on this machine for header.
408 Returns an error string, but does not raise an error, if we can't
409 get the ZFS/SPL version.
412 if request not in ('spl', 'zfs'):
413 error_msg = '(ERROR: "{0}" requested)'.format(request)
416 return get_version_impl(request)
420 """Print the initial heading with date and time as well as info on the
421 kernel and ZFS versions. This is not called for the graph.
424 # datetime is now recommended over time but we keep the exact formatting
425 # from the older version of arc_summary in case there are scripts
426 # that expect it in this way
427 daydate = time.strftime(DATE_FORMAT)
428 spc_date = LINE_LENGTH-len(daydate)
429 sys_version = os.uname()
431 sys_msg = sys_version.sysname+' '+sys_version.release
432 zfs = get_version('zfs')
433 spc_zfs = LINE_LENGTH-len(zfs)
435 machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
436 spl = get_version('spl')
437 spc_spl = LINE_LENGTH-len(spl)
439 print('\n'+('-'*LINE_LENGTH))
440 print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
441 print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
442 print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
445 def print_raw(kstats_dict):
446 """Print all available data from the system in a minimally sorted format.
447 This can be used as a source to be piped through 'grep'.
450 sections = sorted(kstats_dict.keys())
452 for section in sections:
454 print('\n{0}:'.format(section.upper()))
455 lines = sorted(kstats_dict[section])
458 name, value = cleanup_line(line)
459 print(format_raw_line(name, value))
461 # Tunables and SPL must be handled separately because they come from a
462 # different source and have descriptions the user might request
468 def isolate_section(section_name, kstats_dict):
469 """From the complete information on all sections, retrieve only those
474 section_data = kstats_dict[section_name]
476 print('ERROR: Data on {0} not available'.format(section_data))
479 section_dict = dict(cleanup_line(l) for l in section_data)
484 # Formatted output helper functions
487 def prt_1(text, value):
488 """Print text and one value, no indent"""
489 spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
490 print('{0}{spc}{1}'.format(text, value, spc=spc))
493 def prt_i1(text, value):
494 """Print text and one value, with indent"""
495 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
496 print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
499 def prt_2(text, value1, value2):
500 """Print text and two values, no indent"""
501 values = '{0:>9} {1:>9}'.format(value1, value2)
502 spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
503 print('{0}{spc} {1}'.format(text, values, spc=spc))
506 def prt_i2(text, value1, value2):
507 """Print text and two values, with indent"""
508 values = '{0:>9} {1:>9}'.format(value1, value2)
509 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
510 print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
513 # The section output concentrates on important parameters instead of
514 # being exhaustive (that is what the --raw parameter is for)
517 def section_arc(kstats_dict):
518 """Give basic information on the ARC, MRU and MFU. This is the first
519 and most used section.
522 arc_stats = isolate_section('arcstats', kstats_dict)
524 throttle = arc_stats['memory_throttle_count']
531 prt_1('ARC status:', health)
532 prt_i1('Memory throttle count:', throttle)
535 arc_size = arc_stats['size']
536 arc_target_size = arc_stats['c']
537 arc_max = arc_stats['c_max']
538 arc_min = arc_stats['c_min']
539 mfu_size = arc_stats['mfu_size']
540 mru_size = arc_stats['mru_size']
541 meta_limit = arc_stats['arc_meta_limit']
542 meta_size = arc_stats['arc_meta_used']
543 dnode_limit = arc_stats['arc_dnode_limit']
544 dnode_size = arc_stats['dnode_size']
545 target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
547 prt_2('ARC size (current):',
548 f_perc(arc_size, arc_max), f_bytes(arc_size))
549 prt_i2('Target size (adaptive):',
550 f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
551 prt_i2('Min size (hard limit):',
552 f_perc(arc_min, arc_max), f_bytes(arc_min))
553 prt_i2('Max size (high water):',
554 target_size_ratio, f_bytes(arc_max))
555 caches_size = int(mfu_size)+int(mru_size)
556 prt_i2('Most Frequently Used (MFU) cache size:',
557 f_perc(mfu_size, caches_size), f_bytes(mfu_size))
558 prt_i2('Most Recently Used (MRU) cache size:',
559 f_perc(mru_size, caches_size), f_bytes(mru_size))
560 prt_i2('Metadata cache size (hard limit):',
561 f_perc(meta_limit, arc_max), f_bytes(meta_limit))
562 prt_i2('Metadata cache size (current):',
563 f_perc(meta_size, meta_limit), f_bytes(meta_size))
564 prt_i2('Dnode cache size (hard limit):',
565 f_perc(dnode_limit, meta_limit), f_bytes(dnode_limit))
566 prt_i2('Dnode cache size (current):',
567 f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
570 print('ARC hash breakdown:')
571 prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
572 prt_i2('Elements current:',
573 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
574 f_hits(arc_stats['hash_elements']))
575 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
577 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
578 prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
582 prt_i1('Deleted:', f_hits(arc_stats['deleted']))
583 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
584 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
585 prt_i1('Eviction skips due to L2 writes:',
586 f_hits(arc_stats['evict_l2_skip']))
587 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
588 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
589 prt_i2('L2 eligible MFU evictions:',
590 f_perc(arc_stats['evict_l2_eligible_mfu'],
591 arc_stats['evict_l2_eligible']),
592 f_bytes(arc_stats['evict_l2_eligible_mfu']))
593 prt_i2('L2 eligible MRU evictions:',
594 f_perc(arc_stats['evict_l2_eligible_mru'],
595 arc_stats['evict_l2_eligible']),
596 f_bytes(arc_stats['evict_l2_eligible_mru']))
597 prt_i1('L2 ineligible evictions:',
598 f_bytes(arc_stats['evict_l2_ineligible']))
602 def section_archits(kstats_dict):
603 """Print information on how the caches are accessed ("arc hits").
606 arc_stats = isolate_section('arcstats', kstats_dict)
607 all_accesses = int(arc_stats['hits'])+int(arc_stats['misses'])
608 actual_hits = int(arc_stats['mfu_hits'])+int(arc_stats['mru_hits'])
610 prt_1('ARC total accesses (hits + misses):', f_hits(all_accesses))
611 ta_todo = (('Cache hit ratio:', arc_stats['hits']),
612 ('Cache miss ratio:', arc_stats['misses']),
613 ('Actual hit ratio (MFU + MRU hits):', actual_hits))
615 for title, value in ta_todo:
616 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
618 dd_total = int(arc_stats['demand_data_hits']) +\
619 int(arc_stats['demand_data_misses'])
620 prt_i2('Data demand efficiency:',
621 f_perc(arc_stats['demand_data_hits'], dd_total),
624 dp_total = int(arc_stats['prefetch_data_hits']) +\
625 int(arc_stats['prefetch_data_misses'])
626 prt_i2('Data prefetch efficiency:',
627 f_perc(arc_stats['prefetch_data_hits'], dp_total),
630 known_hits = int(arc_stats['mfu_hits']) +\
631 int(arc_stats['mru_hits']) +\
632 int(arc_stats['mfu_ghost_hits']) +\
633 int(arc_stats['mru_ghost_hits'])
635 anon_hits = int(arc_stats['hits'])-known_hits
638 print('Cache hits by cache type:')
639 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
640 ('Most recently used (MRU):', arc_stats['mru_hits']),
641 ('Most frequently used (MFU) ghost:',
642 arc_stats['mfu_ghost_hits']),
643 ('Most recently used (MRU) ghost:',
644 arc_stats['mru_ghost_hits']))
646 for title, value in cl_todo:
647 prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
649 # For some reason, anon_hits can turn negative, which is weird. Until we
650 # have figured out why this happens, we just hide the problem, following
651 # the behavior of the original arc_summary.
653 prt_i2('Anonymously used:',
654 f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
657 print('Cache hits by data type:')
658 dt_todo = (('Demand data:', arc_stats['demand_data_hits']),
659 ('Demand prefetch data:', arc_stats['prefetch_data_hits']),
660 ('Demand metadata:', arc_stats['demand_metadata_hits']),
661 ('Demand prefetch metadata:',
662 arc_stats['prefetch_metadata_hits']))
664 for title, value in dt_todo:
665 prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
668 print('Cache misses by data type:')
669 dm_todo = (('Demand data:', arc_stats['demand_data_misses']),
670 ('Demand prefetch data:',
671 arc_stats['prefetch_data_misses']),
672 ('Demand metadata:', arc_stats['demand_metadata_misses']),
673 ('Demand prefetch metadata:',
674 arc_stats['prefetch_metadata_misses']))
676 for title, value in dm_todo:
677 prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
682 def section_dmu(kstats_dict):
683 """Collect information on the DMU"""
685 zfetch_stats = isolate_section('zfetchstats', kstats_dict)
687 zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
689 prt_1('DMU prefetch efficiency:', f_hits(zfetch_access_total))
690 prt_i2('Hit ratio:', f_perc(zfetch_stats['hits'], zfetch_access_total),
691 f_hits(zfetch_stats['hits']))
692 prt_i2('Miss ratio:', f_perc(zfetch_stats['misses'], zfetch_access_total),
693 f_hits(zfetch_stats['misses']))
697 def section_l2arc(kstats_dict):
698 """Collect information on L2ARC device if present. If not, tell user
699 that we're skipping the section.
702 # The L2ARC statistics live in the same section as the normal ARC stuff
703 arc_stats = isolate_section('arcstats', kstats_dict)
705 if arc_stats['l2_size'] == '0':
706 print('L2ARC not detected, skipping section\n')
709 l2_errors = int(arc_stats['l2_writes_error']) +\
710 int(arc_stats['l2_cksum_bad']) +\
711 int(arc_stats['l2_io_error'])
713 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
719 prt_1('L2ARC status:', health)
721 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
722 ('Free on write:', 'l2_free_on_write'),
723 ('R/W clashes:', 'l2_rw_clash'),
724 ('Bad checksums:', 'l2_cksum_bad'),
725 ('I/O errors:', 'l2_io_error'))
727 for title, value in l2_todo:
728 prt_i1(title, f_hits(arc_stats[value]))
731 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
732 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
733 f_bytes(arc_stats['l2_asize']))
734 prt_i2('Header size:',
735 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
736 f_bytes(arc_stats['l2_hdr_size']))
737 prt_i2('MFU allocated size:',
738 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
739 f_bytes(arc_stats['l2_mfu_asize']))
740 prt_i2('MRU allocated size:',
741 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
742 f_bytes(arc_stats['l2_mru_asize']))
743 prt_i2('Prefetch allocated size:',
744 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
745 f_bytes(arc_stats['l2_prefetch_asize']))
746 prt_i2('Data (buffer content) allocated size:',
747 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
748 f_bytes(arc_stats['l2_bufc_data_asize']))
749 prt_i2('Metadata (buffer content) allocated size:',
750 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
751 f_bytes(arc_stats['l2_bufc_metadata_asize']))
754 prt_1('L2ARC breakdown:', f_hits(l2_access_total))
756 f_perc(arc_stats['l2_hits'], l2_access_total),
757 f_hits(arc_stats['l2_hits']))
758 prt_i2('Miss ratio:',
759 f_perc(arc_stats['l2_misses'], l2_access_total),
760 f_hits(arc_stats['l2_misses']))
761 prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
764 print('L2ARC writes:')
766 if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
767 prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
768 prt_i2('Done ratio:',
769 f_perc(arc_stats['l2_writes_done'],
770 arc_stats['l2_writes_sent']),
771 f_hits(arc_stats['l2_writes_done']))
772 prt_i2('Error ratio:',
773 f_perc(arc_stats['l2_writes_error'],
774 arc_stats['l2_writes_sent']),
775 f_hits(arc_stats['l2_writes_error']))
777 prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
780 print('L2ARC evicts:')
781 prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
782 prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
787 """Print the SPL parameters, if requested with alternative format
788 and/or descriptions. This does not use kstats.
791 if sys.platform.startswith('freebsd'):
792 # No SPL support in FreeBSD
795 spls = get_spl_params()
796 keylist = sorted(spls.keys())
797 print('Solaris Porting Layer (SPL):')
800 descriptions = get_descriptions('spl')
807 print(INDENT+'#', descriptions[key])
809 print(INDENT+'# (No description found)') # paranoid
811 print(format_raw_line(key, value))
816 def section_tunables(*_):
817 """Print the tunables, if requested with alternative format and/or
818 descriptions. This does not use kstasts.
821 tunables = get_tunable_params()
822 keylist = sorted(tunables.keys())
826 descriptions = get_descriptions('zfs')
829 value = tunables[key]
833 print(INDENT+'#', descriptions[key])
835 print(INDENT+'# (No description found)') # paranoid
837 print(format_raw_line(key, value))
842 def section_vdev(kstats_dict):
843 """Collect information on VDEV caches"""
845 # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
846 # harmful. When this is the case, we just skip the whole entry. See
847 # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
849 tunables = get_vdev_params()
851 if tunables[VDEV_CACHE_SIZE] == '0':
852 print('VDEV cache disabled, skipping section\n')
855 vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
857 vdev_cache_total = int(vdev_stats['hits']) +\
858 int(vdev_stats['misses']) +\
859 int(vdev_stats['delegations'])
861 prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
862 prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
863 f_hits(vdev_stats['hits']))
864 prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
865 f_hits(vdev_stats['misses']))
866 prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
867 f_hits(vdev_stats['delegations']))
871 def section_zil(kstats_dict):
872 """Collect information on the ZFS Intent Log. Some of the information
873 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
876 zil_stats = isolate_section('zil', kstats_dict)
878 prt_1('ZIL committed transactions:',
879 f_hits(zil_stats['zil_itx_count']))
880 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
881 prt_i1('Flushes to stable storage:',
882 f_hits(zil_stats['zil_commit_writer_count']))
883 prt_i2('Transactions to SLOG storage pool:',
884 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
885 f_hits(zil_stats['zil_itx_metaslab_slog_count']))
886 prt_i2('Transactions to non-SLOG storage pool:',
887 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
888 f_hits(zil_stats['zil_itx_metaslab_normal_count']))
892 section_calls = {'arc': section_arc,
893 'archits': section_archits,
895 'l2arc': section_l2arc,
897 'tunables': section_tunables,
898 'vdev': section_vdev,
903 """Run program. The options to draw a graph and to print all data raw are
904 treated separately because they come with their own call.
907 kstats = get_kstats()
921 section_calls[ARGS.section](kstats)
923 print('Error: Section "{0}" unknown'.format(ARGS.section))
927 print('WARNING: Pages are deprecated, please use "--section"\n')
929 pages_to_calls = {1: 'arc',
937 call = pages_to_calls[ARGS.page]
939 print('Error: Page "{0}" not supported'.format(ARGS.page))
942 section_calls[call](kstats)
945 # If no parameters were given, we print all sections. We might want to
946 # change the sequence by hand
947 calls = sorted(section_calls.keys())
949 for section in calls:
950 section_calls[section](kstats)
955 if __name__ == '__main__':