]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - cmd/arc_summary
Use list_remove_head() where possible.
[FreeBSD/FreeBSD.git] / cmd / arc_summary
1 #!/usr/bin/env python3
2 #
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>
7 # All rights reserved.
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
11 # are met:
12 #
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.
18 #
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
29 # SUCH DAMAGE.
30 """Print statistics on the ZFS ARC Cache and other information
31
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
38 """
39
40 import argparse
41 import os
42 import subprocess
43 import sys
44 import time
45 import errno
46
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...
49 import io
50 if isinstance(sys.__stderr__.buffer, io.BufferedWriter):
51     os.execv(sys.executable, [sys.executable, "-u"] + sys.argv)
52
53 DESCRIPTION = 'Print ARC and other statistics for OpenZFS'
54 INDENT = ' '*8
55 LINE_LENGTH = 72
56 DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
57 TITLE = 'ZFS Subsystem Report'
58
59 SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
60 SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
61
62 # Tunables and SPL are handled separately because they come from
63 # different sources
64 SECTION_PATHS = {'arc': 'arcstats',
65                  'dmu': 'dmu_tx',
66                  'l2arc': 'arcstats',  # L2ARC stuff lives in arcstats
67                  'vdev': 'vdev_cache_stats',
68                  'zfetch': 'zfetchstats',
69                  'zil': 'zil'}
70
71 parser = argparse.ArgumentParser(description=DESCRIPTION)
72 parser.add_argument('-a', '--alternate', action='store_true', default=False,
73                     help='use alternate formatting for tunables and SPL',
74                     dest='alt')
75 parser.add_argument('-d', '--description', action='store_true', default=False,
76                     help='print descriptions with tunables and SPL',
77                     dest='desc')
78 parser.add_argument('-g', '--graph', action='store_true', default=False,
79                     help='print graph on ARC use and exit', dest='graph')
80 parser.add_argument('-p', '--page', type=int, dest='page',
81                     help='print page by number (DEPRECATED, use "-s")')
82 parser.add_argument('-r', '--raw', action='store_true', default=False,
83                     help='dump all available data with minimal formatting',
84                     dest='raw')
85 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
86 ARGS = parser.parse_args()
87
88
89 if sys.platform.startswith('freebsd'):
90     # Requires py36-sysctl on FreeBSD
91     import sysctl
92
93     VDEV_CACHE_SIZE = 'vdev.cache_size'
94
95     def is_value(ctl):
96         return ctl.type != sysctl.CTLTYPE_NODE
97
98     def namefmt(ctl, base='vfs.zfs.'):
99         # base is removed from the name
100         cut = len(base)
101         return ctl.name[cut:]
102
103     def load_kstats(section):
104         base = 'kstat.zfs.misc.{section}.'.format(section=section)
105         fmt = lambda kstat: '{name} : {value}'.format(name=namefmt(kstat, base),
106                                                       value=kstat.value)
107         kstats = sysctl.filter(base)
108         return [fmt(kstat) for kstat in kstats if is_value(kstat)]
109
110     def get_params(base):
111         ctls = sysctl.filter(base)
112         return {namefmt(ctl): str(ctl.value) for ctl in ctls if is_value(ctl)}
113
114     def get_tunable_params():
115         return get_params('vfs.zfs')
116
117     def get_vdev_params():
118         return get_params('vfs.zfs.vdev')
119
120     def get_version_impl(request):
121         # FreeBSD reports versions for zpl and spa instead of zfs and spl.
122         name = {'zfs': 'zpl',
123                 'spl': 'spa'}[request]
124         mib = 'vfs.zfs.version.{}'.format(name)
125         version = sysctl.filter(mib)[0].value
126         return '{} version {}'.format(name, version)
127
128     def get_descriptions(_request):
129         ctls = sysctl.filter('vfs.zfs')
130         return {namefmt(ctl): ctl.description for ctl in ctls if is_value(ctl)}
131
132
133 elif sys.platform.startswith('linux'):
134     KSTAT_PATH = '/proc/spl/kstat/zfs'
135     SPL_PATH = '/sys/module/spl/parameters'
136     TUNABLES_PATH = '/sys/module/zfs/parameters'
137
138     VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
139
140     def load_kstats(section):
141         path = os.path.join(KSTAT_PATH, section)
142         with open(path) as f:
143             return list(f)[2:] # Get rid of header
144
145     def get_params(basepath):
146         """Collect information on the Solaris Porting Layer (SPL) or the
147         tunables, depending on the PATH given. Does not check if PATH is
148         legal.
149         """
150         result = {}
151         for name in os.listdir(basepath):
152             path = os.path.join(basepath, name)
153             with open(path) as f:
154                 value = f.read()
155                 result[name] = value.strip()
156         return result
157
158     def get_spl_params():
159         return get_params(SPL_PATH)
160
161     def get_tunable_params():
162         return get_params(TUNABLES_PATH)
163
164     def get_vdev_params():
165         return get_params(TUNABLES_PATH)
166
167     def get_version_impl(request):
168         # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
169         # the version information. We switch to /sys/module/{spl,zfs}/version
170         # to make sure we get what is really loaded in the kernel
171         try:
172             with open("/sys/module/{}/version".format(request)) as f:
173                 return f.read().strip()
174         except:
175             return "(unknown)"
176
177     def get_descriptions(request):
178         """Get the descriptions of the Solaris Porting Layer (SPL) or the
179         tunables, return with minimal formatting.
180         """
181
182         if request not in ('spl', 'zfs'):
183             print('ERROR: description of "{0}" requested)'.format(request))
184             sys.exit(1)
185
186         descs = {}
187         target_prefix = 'parm:'
188
189         # We would prefer to do this with /sys/modules -- see the discussion at
190         # get_version() -- but there isn't a way to get the descriptions from
191         # there, so we fall back on modinfo
192         command = ["/sbin/modinfo", request, "-0"]
193
194         info = ''
195
196         try:
197
198             info = subprocess.run(command, stdout=subprocess.PIPE,
199                                   check=True, universal_newlines=True)
200             raw_output = info.stdout.split('\0')
201
202         except subprocess.CalledProcessError:
203             print("Error: Descriptions not available",
204                   "(can't access kernel module)")
205             sys.exit(1)
206
207         for line in raw_output:
208
209             if not line.startswith(target_prefix):
210                 continue
211
212             line = line[len(target_prefix):].strip()
213             name, raw_desc = line.split(':', 1)
214             desc = raw_desc.rsplit('(', 1)[0]
215
216             if desc == '':
217                 desc = '(No description found)'
218
219             descs[name.strip()] = desc.strip()
220
221         return descs
222
223 def handle_unraisableException(exc_type, exc_value=None, exc_traceback=None,
224                                err_msg=None, object=None):
225    handle_Exception(exc_type, object, exc_traceback)
226
227 def handle_Exception(ex_cls, ex, tb):
228     if ex_cls is KeyboardInterrupt:
229         sys.exit()
230
231     if ex_cls is BrokenPipeError:
232         # It turns out that while sys.exit() triggers an exception
233         # not handled message on Python 3.8+, os._exit() does not.
234         os._exit(0)
235
236     if ex_cls is OSError:
237       if ex.errno == errno.ENOTCONN:
238         sys.exit()
239
240     raise ex
241
242 if hasattr(sys,'unraisablehook'): # Python 3.8+
243     sys.unraisablehook = handle_unraisableException
244 sys.excepthook = handle_Exception
245
246
247 def cleanup_line(single_line):
248     """Format a raw line of data from /proc and isolate the name value
249     part, returning a tuple with each. Currently, this gets rid of the
250     middle '4'. For example "arc_no_grow    4    0" returns the tuple
251     ("arc_no_grow", "0").
252     """
253     name, _, value = single_line.split()
254
255     return name, value
256
257
258 def draw_graph(kstats_dict):
259     """Draw a primitive graph representing the basic information on the
260     ARC -- its size and the proportion used by MFU and MRU -- and quit.
261     We use max size of the ARC to calculate how full it is. This is a
262     very rough representation.
263     """
264
265     arc_stats = isolate_section('arcstats', kstats_dict)
266
267     GRAPH_INDENT = ' '*4
268     GRAPH_WIDTH = 60
269     arc_size = f_bytes(arc_stats['size'])
270     arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
271     mfu_size = f_bytes(arc_stats['mfu_size'])
272     mru_size = f_bytes(arc_stats['mru_size'])
273     meta_size = f_bytes(arc_stats['arc_meta_used'])
274     dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
275     dnode_size = f_bytes(arc_stats['dnode_size'])
276
277     info_form = ('ARC: {0} ({1})  MFU: {2}  MRU: {3}  META: {4} '
278                  'DNODE {5} ({6})')
279     info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
280                                  meta_size, dnode_size, dnode_limit)
281     info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
282     info_line = GRAPH_INDENT+info_spc+info_line
283
284     graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
285
286     mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
287     mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
288     arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
289     total_ticks = float(arc_perc)*GRAPH_WIDTH
290     mfu_ticks = mfu_perc*GRAPH_WIDTH
291     mru_ticks = mru_perc*GRAPH_WIDTH
292     other_ticks = total_ticks-(mfu_ticks+mru_ticks)
293
294     core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
295     core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
296     core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
297
298     for line in ('', info_line, graph_line, core_line, graph_line, ''):
299         print(line)
300
301
302 def f_bytes(byte_string):
303     """Return human-readable representation of a byte value in
304     powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
305     points. Values smaller than one KiB are returned without
306     decimal points. Note "bytes" is a reserved keyword.
307     """
308
309     prefixes = ([2**80, "YiB"],   # yobibytes (yotta)
310                 [2**70, "ZiB"],   # zebibytes (zetta)
311                 [2**60, "EiB"],   # exbibytes (exa)
312                 [2**50, "PiB"],   # pebibytes (peta)
313                 [2**40, "TiB"],   # tebibytes (tera)
314                 [2**30, "GiB"],   # gibibytes (giga)
315                 [2**20, "MiB"],   # mebibytes (mega)
316                 [2**10, "KiB"])   # kibibytes (kilo)
317
318     bites = int(byte_string)
319
320     if bites >= 2**10:
321         for limit, unit in prefixes:
322
323             if bites >= limit:
324                 value = bites / limit
325                 break
326
327         result = '{0:.1f} {1}'.format(value, unit)
328     else:
329         result = '{0} Bytes'.format(bites)
330
331     return result
332
333
334 def f_hits(hits_string):
335     """Create a human-readable representation of the number of hits.
336     The single-letter symbols used are SI to avoid the confusion caused
337     by the different "short scale" and "long scale" representations in
338     English, which use the same words for different values. See
339     https://en.wikipedia.org/wiki/Names_of_large_numbers and:
340     https://physics.nist.gov/cuu/Units/prefixes.html
341     """
342
343     numbers = ([10**24, 'Y'],  # yotta (septillion)
344                [10**21, 'Z'],  # zetta (sextillion)
345                [10**18, 'E'],  # exa   (quintrillion)
346                [10**15, 'P'],  # peta  (quadrillion)
347                [10**12, 'T'],  # tera  (trillion)
348                [10**9, 'G'],   # giga  (billion)
349                [10**6, 'M'],   # mega  (million)
350                [10**3, 'k'])   # kilo  (thousand)
351
352     hits = int(hits_string)
353
354     if hits >= 1000:
355         for limit, symbol in numbers:
356
357             if hits >= limit:
358                 value = hits/limit
359                 break
360
361         result = "%0.1f%s" % (value, symbol)
362     else:
363         result = "%d" % hits
364
365     return result
366
367
368 def f_perc(value1, value2):
369     """Calculate percentage and return in human-readable form. If
370     rounding produces the result '0.0' though the first number is
371     not zero, include a 'less-than' symbol to avoid confusion.
372     Division by zero is handled by returning 'n/a'; no error
373     is called.
374     """
375
376     v1 = float(value1)
377     v2 = float(value2)
378
379     try:
380         perc = 100 * v1/v2
381     except ZeroDivisionError:
382         result = 'n/a'
383     else:
384         result = '{0:0.1f} %'.format(perc)
385
386     if result == '0.0 %' and v1 > 0:
387         result = '< 0.1 %'
388
389     return result
390
391
392 def format_raw_line(name, value):
393     """For the --raw option for the tunable and SPL outputs, decide on the
394     correct formatting based on the --alternate flag.
395     """
396
397     if ARGS.alt:
398         result = '{0}{1}={2}'.format(INDENT, name, value)
399     else:
400         # Right-align the value within the line length if it fits,
401         # otherwise just separate it from the name by a single space.
402         fit = LINE_LENGTH - len(INDENT) - len(name)
403         overflow = len(value) + 1
404         w = max(fit, overflow)
405         result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w)
406
407     return result
408
409
410 def get_kstats():
411     """Collect information on the ZFS subsystem. The step does not perform any
412     further processing, giving us the option to only work on what is actually
413     needed. The name "kstat" is a holdover from the Solaris utility of the same
414     name.
415     """
416
417     result = {}
418
419     for section in SECTION_PATHS.values():
420         if section not in result:
421             result[section] = load_kstats(section)
422
423     return result
424
425
426 def get_version(request):
427     """Get the version number of ZFS or SPL on this machine for header.
428     Returns an error string, but does not raise an error, if we can't
429     get the ZFS/SPL version.
430     """
431
432     if request not in ('spl', 'zfs'):
433         error_msg = '(ERROR: "{0}" requested)'.format(request)
434         return error_msg
435
436     return get_version_impl(request)
437
438
439 def print_header():
440     """Print the initial heading with date and time as well as info on the
441     kernel and ZFS versions. This is not called for the graph.
442     """
443
444     # datetime is now recommended over time but we keep the exact formatting
445     # from the older version of arc_summary in case there are scripts
446     # that expect it in this way
447     daydate = time.strftime(DATE_FORMAT)
448     spc_date = LINE_LENGTH-len(daydate)
449     sys_version = os.uname()
450
451     sys_msg = sys_version.sysname+' '+sys_version.release
452     zfs = get_version('zfs')
453     spc_zfs = LINE_LENGTH-len(zfs)
454
455     machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
456     spl = get_version('spl')
457     spc_spl = LINE_LENGTH-len(spl)
458
459     print('\n'+('-'*LINE_LENGTH))
460     print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
461     print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
462     print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
463
464
465 def print_raw(kstats_dict):
466     """Print all available data from the system in a minimally sorted format.
467     This can be used as a source to be piped through 'grep'.
468     """
469
470     sections = sorted(kstats_dict.keys())
471
472     for section in sections:
473
474         print('\n{0}:'.format(section.upper()))
475         lines = sorted(kstats_dict[section])
476
477         for line in lines:
478             name, value = cleanup_line(line)
479             print(format_raw_line(name, value))
480
481     # Tunables and SPL must be handled separately because they come from a
482     # different source and have descriptions the user might request
483     print()
484     section_spl()
485     section_tunables()
486
487
488 def isolate_section(section_name, kstats_dict):
489     """From the complete information on all sections, retrieve only those
490     for one section.
491     """
492
493     try:
494         section_data = kstats_dict[section_name]
495     except KeyError:
496         print('ERROR: Data on {0} not available'.format(section_data))
497         sys.exit(1)
498
499     section_dict = dict(cleanup_line(l) for l in section_data)
500
501     return section_dict
502
503
504 # Formatted output helper functions
505
506
507 def prt_1(text, value):
508     """Print text and one value, no indent"""
509     spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
510     print('{0}{spc}{1}'.format(text, value, spc=spc))
511
512
513 def prt_i1(text, value):
514     """Print text and one value, with indent"""
515     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
516     print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
517
518
519 def prt_2(text, value1, value2):
520     """Print text and two values, no indent"""
521     values = '{0:>9}  {1:>9}'.format(value1, value2)
522     spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
523     print('{0}{spc}  {1}'.format(text, values, spc=spc))
524
525
526 def prt_i2(text, value1, value2):
527     """Print text and two values, with indent"""
528     values = '{0:>9}  {1:>9}'.format(value1, value2)
529     spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
530     print(INDENT+'{0}{spc}  {1}'.format(text, values, spc=spc))
531
532
533 # The section output concentrates on important parameters instead of
534 # being exhaustive (that is what the --raw parameter is for)
535
536
537 def section_arc(kstats_dict):
538     """Give basic information on the ARC, MRU and MFU. This is the first
539     and most used section.
540     """
541
542     arc_stats = isolate_section('arcstats', kstats_dict)
543
544     throttle = arc_stats['memory_throttle_count']
545
546     if throttle == '0':
547         health = 'HEALTHY'
548     else:
549         health = 'THROTTLED'
550
551     prt_1('ARC status:', health)
552     prt_i1('Memory throttle count:', throttle)
553     print()
554
555     arc_size = arc_stats['size']
556     arc_target_size = arc_stats['c']
557     arc_max = arc_stats['c_max']
558     arc_min = arc_stats['c_min']
559     meta = arc_stats['meta']
560     pd = arc_stats['pd']
561     pm = arc_stats['pm']
562     anon_data = arc_stats['anon_data']
563     anon_metadata = arc_stats['anon_metadata']
564     mfu_data = arc_stats['mfu_data']
565     mfu_metadata = arc_stats['mfu_metadata']
566     mru_data = arc_stats['mru_data']
567     mru_metadata = arc_stats['mru_metadata']
568     mfug_data = arc_stats['mfu_ghost_data']
569     mfug_metadata = arc_stats['mfu_ghost_metadata']
570     mrug_data = arc_stats['mru_ghost_data']
571     mrug_metadata = arc_stats['mru_ghost_metadata']
572     unc_data = arc_stats['uncached_data']
573     unc_metadata = arc_stats['uncached_metadata']
574     bonus_size = arc_stats['bonus_size']
575     dnode_limit = arc_stats['arc_dnode_limit']
576     dnode_size = arc_stats['dnode_size']
577     dbuf_size = arc_stats['dbuf_size']
578     hdr_size = arc_stats['hdr_size']
579     l2_hdr_size = arc_stats['l2_hdr_size']
580     abd_chunk_waste_size = arc_stats['abd_chunk_waste_size']
581     target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
582
583     prt_2('ARC size (current):',
584           f_perc(arc_size, arc_max), f_bytes(arc_size))
585     prt_i2('Target size (adaptive):',
586            f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
587     prt_i2('Min size (hard limit):',
588            f_perc(arc_min, arc_max), f_bytes(arc_min))
589     prt_i2('Max size (high water):',
590            target_size_ratio, f_bytes(arc_max))
591     caches_size = int(anon_data)+int(anon_metadata)+\
592         int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\
593         int(unc_data)+int(unc_metadata)
594     prt_i2('Anonymous data size:',
595            f_perc(anon_data, caches_size), f_bytes(anon_data))
596     prt_i2('Anonymous metadata size:',
597            f_perc(anon_metadata, caches_size), f_bytes(anon_metadata))
598     s = 4294967296
599     v = (s-int(pd))*(s-int(meta))/s
600     prt_i2('MFU data target:', f_perc(v, s),
601         f_bytes(v / 65536 * caches_size / 65536))
602     prt_i2('MFU data size:',
603            f_perc(mfu_data, caches_size), f_bytes(mfu_data))
604     prt_i1('MFU ghost data size:', f_bytes(mfug_data))
605     v = (s-int(pm))*int(meta)/s
606     prt_i2('MFU metadata target:', f_perc(v, s),
607         f_bytes(v / 65536 * caches_size / 65536))
608     prt_i2('MFU metadata size:',
609            f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata))
610     prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata))
611     v = int(pd)*(s-int(meta))/s
612     prt_i2('MRU data target:', f_perc(v, s),
613         f_bytes(v / 65536 * caches_size / 65536))
614     prt_i2('MRU data size:',
615            f_perc(mru_data, caches_size), f_bytes(mru_data))
616     prt_i1('MRU ghost data size:', f_bytes(mrug_data))
617     v = int(pm)*int(meta)/s
618     prt_i2('MRU metadata target:', f_perc(v, s),
619         f_bytes(v / 65536 * caches_size / 65536))
620     prt_i2('MRU metadata size:',
621            f_perc(mru_metadata, caches_size), f_bytes(mru_metadata))
622     prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata))
623     prt_i2('Uncached data size:',
624            f_perc(unc_data, caches_size), f_bytes(unc_data))
625     prt_i2('Uncached metadata size:',
626            f_perc(unc_metadata, caches_size), f_bytes(unc_metadata))
627     prt_i2('Bonus size:',
628            f_perc(bonus_size, arc_size), f_bytes(bonus_size))
629     prt_i2('Dnode cache target:',
630            f_perc(dnode_limit, arc_max), f_bytes(dnode_limit))
631     prt_i2('Dnode cache size:',
632            f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
633     prt_i2('Dbuf size:',
634            f_perc(dbuf_size, arc_size), f_bytes(dbuf_size))
635     prt_i2('Header size:',
636            f_perc(hdr_size, arc_size), f_bytes(hdr_size))
637     prt_i2('L2 header size:',
638            f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size))
639     prt_i2('ABD chunk waste size:',
640            f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size))
641     print()
642
643     print('ARC hash breakdown:')
644     prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
645     prt_i2('Elements current:',
646            f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
647            f_hits(arc_stats['hash_elements']))
648     prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
649
650     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
651     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
652     print()
653
654     print('ARC misc:')
655     prt_i1('Deleted:', f_hits(arc_stats['deleted']))
656     prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
657     prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
658     prt_i1('Eviction skips due to L2 writes:',
659            f_hits(arc_stats['evict_l2_skip']))
660     prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached']))
661     prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible']))
662     prt_i2('L2 eligible MFU evictions:',
663            f_perc(arc_stats['evict_l2_eligible_mfu'],
664            arc_stats['evict_l2_eligible']),
665            f_bytes(arc_stats['evict_l2_eligible_mfu']))
666     prt_i2('L2 eligible MRU evictions:',
667            f_perc(arc_stats['evict_l2_eligible_mru'],
668            arc_stats['evict_l2_eligible']),
669            f_bytes(arc_stats['evict_l2_eligible_mru']))
670     prt_i1('L2 ineligible evictions:',
671            f_bytes(arc_stats['evict_l2_ineligible']))
672     print()
673
674
675 def section_archits(kstats_dict):
676     """Print information on how the caches are accessed ("arc hits").
677     """
678
679     arc_stats = isolate_section('arcstats', kstats_dict)
680     all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\
681         int(arc_stats['misses'])
682
683     prt_1('ARC total accesses:', f_hits(all_accesses))
684     ta_todo = (('Total hits:', arc_stats['hits']),
685                ('Total I/O hits:', arc_stats['iohits']),
686                ('Total misses:', arc_stats['misses']))
687     for title, value in ta_todo:
688         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
689     print()
690
691     dd_total = int(arc_stats['demand_data_hits']) +\
692         int(arc_stats['demand_data_iohits']) +\
693         int(arc_stats['demand_data_misses'])
694     prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses),
695          f_hits(dd_total))
696     dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']),
697                ('Demand data I/O hits:', arc_stats['demand_data_iohits']),
698                ('Demand data misses:', arc_stats['demand_data_misses']))
699     for title, value in dd_todo:
700         prt_i2(title, f_perc(value, dd_total), f_hits(value))
701     print()
702
703     dm_total = int(arc_stats['demand_metadata_hits']) +\
704         int(arc_stats['demand_metadata_iohits']) +\
705         int(arc_stats['demand_metadata_misses'])
706     prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses),
707           f_hits(dm_total))
708     dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']),
709                ('Demand metadata I/O hits:',
710                 arc_stats['demand_metadata_iohits']),
711                ('Demand metadata misses:', arc_stats['demand_metadata_misses']))
712     for title, value in dm_todo:
713         prt_i2(title, f_perc(value, dm_total), f_hits(value))
714     print()
715
716     pd_total = int(arc_stats['prefetch_data_hits']) +\
717         int(arc_stats['prefetch_data_iohits']) +\
718         int(arc_stats['prefetch_data_misses'])
719     prt_2('ARC prefetch metadata accesses:', f_perc(pd_total, all_accesses),
720           f_hits(pd_total))
721     pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']),
722                ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']),
723                ('Prefetch data misses:', arc_stats['prefetch_data_misses']))
724     for title, value in pd_todo:
725         prt_i2(title, f_perc(value, pd_total), f_hits(value))
726     print()
727
728     pm_total = int(arc_stats['prefetch_metadata_hits']) +\
729         int(arc_stats['prefetch_metadata_iohits']) +\
730         int(arc_stats['prefetch_metadata_misses'])
731     prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses),
732           f_hits(pm_total))
733     pm_todo = (('Prefetch metadata hits:',
734                 arc_stats['prefetch_metadata_hits']),
735                ('Prefetch metadata I/O hits:',
736                 arc_stats['prefetch_metadata_iohits']),
737                ('Prefetch metadata misses:',
738                 arc_stats['prefetch_metadata_misses']))
739     for title, value in pm_todo:
740         prt_i2(title, f_perc(value, pm_total), f_hits(value))
741     print()
742
743     all_prefetches = int(arc_stats['predictive_prefetch'])+\
744         int(arc_stats['prescient_prefetch'])
745     prt_2('ARC predictive prefetches:',
746            f_perc(arc_stats['predictive_prefetch'], all_prefetches),
747            f_hits(arc_stats['predictive_prefetch']))
748     prt_i2('Demand hits after predictive:',
749            f_perc(arc_stats['demand_hit_predictive_prefetch'],
750                   arc_stats['predictive_prefetch']),
751            f_hits(arc_stats['demand_hit_predictive_prefetch']))
752     prt_i2('Demand I/O hits after predictive:',
753            f_perc(arc_stats['demand_iohit_predictive_prefetch'],
754                   arc_stats['predictive_prefetch']),
755            f_hits(arc_stats['demand_iohit_predictive_prefetch']))
756     never = int(arc_stats['predictive_prefetch']) -\
757         int(arc_stats['demand_hit_predictive_prefetch']) -\
758         int(arc_stats['demand_iohit_predictive_prefetch'])
759     prt_i2('Never demanded after predictive:',
760            f_perc(never, arc_stats['predictive_prefetch']),
761            f_hits(never))
762     print()
763
764     prt_2('ARC prescient prefetches:',
765            f_perc(arc_stats['prescient_prefetch'], all_prefetches),
766            f_hits(arc_stats['prescient_prefetch']))
767     prt_i2('Demand hits after prescient:',
768            f_perc(arc_stats['demand_hit_prescient_prefetch'],
769                   arc_stats['prescient_prefetch']),
770            f_hits(arc_stats['demand_hit_prescient_prefetch']))
771     prt_i2('Demand I/O hits after prescient:',
772            f_perc(arc_stats['demand_iohit_prescient_prefetch'],
773                   arc_stats['prescient_prefetch']),
774            f_hits(arc_stats['demand_iohit_prescient_prefetch']))
775     never = int(arc_stats['prescient_prefetch'])-\
776         int(arc_stats['demand_hit_prescient_prefetch'])-\
777         int(arc_stats['demand_iohit_prescient_prefetch'])
778     prt_i2('Never demanded after prescient:',
779            f_perc(never, arc_stats['prescient_prefetch']),
780            f_hits(never))
781     print()
782
783     print('ARC states hits of all accesses:')
784     cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
785                ('Most recently used (MRU):', arc_stats['mru_hits']),
786                ('Most frequently used (MFU) ghost:',
787                 arc_stats['mfu_ghost_hits']),
788                ('Most recently used (MRU) ghost:',
789                 arc_stats['mru_ghost_hits']),
790                ('Uncached:', arc_stats['uncached_hits']))
791     for title, value in cl_todo:
792         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
793     print()
794
795
796 def section_dmu(kstats_dict):
797     """Collect information on the DMU"""
798
799     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
800
801     zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
802
803     prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total))
804     prt_i2('Stream hits:',
805            f_perc(zfetch_stats['hits'], zfetch_access_total),
806            f_hits(zfetch_stats['hits']))
807     prt_i2('Stream misses:',
808            f_perc(zfetch_stats['misses'], zfetch_access_total),
809            f_hits(zfetch_stats['misses']))
810     prt_i2('Streams limit reached:',
811            f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']),
812            f_hits(zfetch_stats['max_streams']))
813     prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued']))
814     print()
815
816
817 def section_l2arc(kstats_dict):
818     """Collect information on L2ARC device if present. If not, tell user
819     that we're skipping the section.
820     """
821
822     # The L2ARC statistics live in the same section as the normal ARC stuff
823     arc_stats = isolate_section('arcstats', kstats_dict)
824
825     if arc_stats['l2_size'] == '0':
826         print('L2ARC not detected, skipping section\n')
827         return
828
829     l2_errors = int(arc_stats['l2_writes_error']) +\
830         int(arc_stats['l2_cksum_bad']) +\
831         int(arc_stats['l2_io_error'])
832
833     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
834     health = 'HEALTHY'
835
836     if l2_errors > 0:
837         health = 'DEGRADED'
838
839     prt_1('L2ARC status:', health)
840
841     l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
842                ('Free on write:', 'l2_free_on_write'),
843                ('R/W clashes:', 'l2_rw_clash'),
844                ('Bad checksums:', 'l2_cksum_bad'),
845                ('I/O errors:', 'l2_io_error'))
846
847     for title, value in l2_todo:
848         prt_i1(title, f_hits(arc_stats[value]))
849
850     print()
851     prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
852     prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
853            f_bytes(arc_stats['l2_asize']))
854     prt_i2('Header size:',
855            f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
856            f_bytes(arc_stats['l2_hdr_size']))
857     prt_i2('MFU allocated size:',
858            f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']),
859            f_bytes(arc_stats['l2_mfu_asize']))
860     prt_i2('MRU allocated size:',
861            f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']),
862            f_bytes(arc_stats['l2_mru_asize']))
863     prt_i2('Prefetch allocated size:',
864            f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']),
865            f_bytes(arc_stats['l2_prefetch_asize']))
866     prt_i2('Data (buffer content) allocated size:',
867            f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']),
868            f_bytes(arc_stats['l2_bufc_data_asize']))
869     prt_i2('Metadata (buffer content) allocated size:',
870            f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']),
871            f_bytes(arc_stats['l2_bufc_metadata_asize']))
872
873     print()
874     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
875     prt_i2('Hit ratio:',
876            f_perc(arc_stats['l2_hits'], l2_access_total),
877            f_hits(arc_stats['l2_hits']))
878     prt_i2('Miss ratio:',
879            f_perc(arc_stats['l2_misses'], l2_access_total),
880            f_hits(arc_stats['l2_misses']))
881     prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
882
883     print()
884     print('L2ARC writes:')
885
886     if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
887         prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
888         prt_i2('Done ratio:',
889                f_perc(arc_stats['l2_writes_done'],
890                       arc_stats['l2_writes_sent']),
891                f_hits(arc_stats['l2_writes_done']))
892         prt_i2('Error ratio:',
893                f_perc(arc_stats['l2_writes_error'],
894                       arc_stats['l2_writes_sent']),
895                f_hits(arc_stats['l2_writes_error']))
896     else:
897         prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
898
899     print()
900     print('L2ARC evicts:')
901     prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
902     prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
903     print()
904
905
906 def section_spl(*_):
907     """Print the SPL parameters, if requested with alternative format
908     and/or descriptions. This does not use kstats.
909     """
910
911     if sys.platform.startswith('freebsd'):
912         # No SPL support in FreeBSD
913         return
914
915     spls = get_spl_params()
916     keylist = sorted(spls.keys())
917     print('Solaris Porting Layer (SPL):')
918
919     if ARGS.desc:
920         descriptions = get_descriptions('spl')
921
922     for key in keylist:
923         value = spls[key]
924
925         if ARGS.desc:
926             try:
927                 print(INDENT+'#', descriptions[key])
928             except KeyError:
929                 print(INDENT+'# (No description found)')  # paranoid
930
931         print(format_raw_line(key, value))
932
933     print()
934
935
936 def section_tunables(*_):
937     """Print the tunables, if requested with alternative format and/or
938     descriptions. This does not use kstasts.
939     """
940
941     tunables = get_tunable_params()
942     keylist = sorted(tunables.keys())
943     print('Tunables:')
944
945     if ARGS.desc:
946         descriptions = get_descriptions('zfs')
947
948     for key in keylist:
949         value = tunables[key]
950
951         if ARGS.desc:
952             try:
953                 print(INDENT+'#', descriptions[key])
954             except KeyError:
955                 print(INDENT+'# (No description found)')  # paranoid
956
957         print(format_raw_line(key, value))
958
959     print()
960
961
962 def section_vdev(kstats_dict):
963     """Collect information on VDEV caches"""
964
965     # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
966     # harmful. When this is the case, we just skip the whole entry. See
967     # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
968     # for details
969     tunables = get_vdev_params()
970
971     if tunables[VDEV_CACHE_SIZE] == '0':
972         print('VDEV cache disabled, skipping section\n')
973         return
974
975     vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
976
977     vdev_cache_total = int(vdev_stats['hits']) +\
978         int(vdev_stats['misses']) +\
979         int(vdev_stats['delegations'])
980
981     prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
982     prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
983            f_hits(vdev_stats['hits']))
984     prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
985            f_hits(vdev_stats['misses']))
986     prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
987            f_hits(vdev_stats['delegations']))
988     print()
989
990
991 def section_zil(kstats_dict):
992     """Collect information on the ZFS Intent Log. Some of the information
993     taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
994     """
995
996     zil_stats = isolate_section('zil', kstats_dict)
997
998     prt_1('ZIL committed transactions:',
999           f_hits(zil_stats['zil_itx_count']))
1000     prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
1001     prt_i1('Flushes to stable storage:',
1002            f_hits(zil_stats['zil_commit_writer_count']))
1003     prt_i2('Transactions to SLOG storage pool:',
1004            f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
1005            f_hits(zil_stats['zil_itx_metaslab_slog_count']))
1006     prt_i2('Transactions to non-SLOG storage pool:',
1007            f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
1008            f_hits(zil_stats['zil_itx_metaslab_normal_count']))
1009     print()
1010
1011
1012 section_calls = {'arc': section_arc,
1013                  'archits': section_archits,
1014                  'dmu': section_dmu,
1015                  'l2arc': section_l2arc,
1016                  'spl': section_spl,
1017                  'tunables': section_tunables,
1018                  'vdev': section_vdev,
1019                  'zil': section_zil}
1020
1021
1022 def main():
1023     """Run program. The options to draw a graph and to print all data raw are
1024     treated separately because they come with their own call.
1025     """
1026
1027     kstats = get_kstats()
1028
1029     if ARGS.graph:
1030         draw_graph(kstats)
1031         sys.exit(0)
1032
1033     print_header()
1034
1035     if ARGS.raw:
1036         print_raw(kstats)
1037
1038     elif ARGS.section:
1039
1040         try:
1041             section_calls[ARGS.section](kstats)
1042         except KeyError:
1043             print('Error: Section "{0}" unknown'.format(ARGS.section))
1044             sys.exit(1)
1045
1046     elif ARGS.page:
1047         print('WARNING: Pages are deprecated, please use "--section"\n')
1048
1049         pages_to_calls = {1: 'arc',
1050                           2: 'archits',
1051                           3: 'l2arc',
1052                           4: 'dmu',
1053                           5: 'vdev',
1054                           6: 'tunables'}
1055
1056         try:
1057             call = pages_to_calls[ARGS.page]
1058         except KeyError:
1059             print('Error: Page "{0}" not supported'.format(ARGS.page))
1060             sys.exit(1)
1061         else:
1062             section_calls[call](kstats)
1063
1064     else:
1065         # If no parameters were given, we print all sections. We might want to
1066         # change the sequence by hand
1067         calls = sorted(section_calls.keys())
1068
1069         for section in calls:
1070             section_calls[section](kstats)
1071
1072     sys.exit(0)
1073
1074
1075 if __name__ == '__main__':
1076     main()