]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/contrib/openzfs/cmd/arc_summary/arc_summary3
Update OpenZFS to 2.0.0-rc3-gfc5966
[FreeBSD/FreeBSD.git] / sys / contrib / openzfs / cmd / arc_summary / arc_summary3
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
46 DESCRIPTION = 'Print ARC and other statistics for OpenZFS'
47 INDENT = ' '*8
48 LINE_LENGTH = 72
49 DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
50 TITLE = 'ZFS Subsystem Report'
51
52 SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
53 SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
54
55 # Tunables and SPL are handled separately because they come from
56 # different sources
57 SECTION_PATHS = {'arc': 'arcstats',
58                  'dmu': 'dmu_tx',
59                  'l2arc': 'arcstats',  # L2ARC stuff lives in arcstats
60                  'vdev': 'vdev_cache_stats',
61                  'xuio': 'xuio_stats',
62                  'zfetch': 'zfetchstats',
63                  'zil': 'zil'}
64
65 parser = argparse.ArgumentParser(description=DESCRIPTION)
66 parser.add_argument('-a', '--alternate', action='store_true', default=False,
67                     help='use alternate formatting for tunables and SPL',
68                     dest='alt')
69 parser.add_argument('-d', '--description', action='store_true', default=False,
70                     help='print descriptions with tunables and SPL',
71                     dest='desc')
72 parser.add_argument('-g', '--graph', action='store_true', default=False,
73                     help='print graph on ARC use and exit', dest='graph')
74 parser.add_argument('-p', '--page', type=int, dest='page',
75                     help='print page by number (DEPRECATED, use "-s")')
76 parser.add_argument('-r', '--raw', action='store_true', default=False,
77                     help='dump all available data with minimal formatting',
78                     dest='raw')
79 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
80 ARGS = parser.parse_args()
81
82
83 if sys.platform.startswith('freebsd'):
84     # Requires py36-sysctl on FreeBSD
85     import sysctl
86
87     VDEV_CACHE_SIZE = 'vdev.cache_size'
88
89     def load_kstats(section):
90         base = 'kstat.zfs.misc.{section}.'.format(section=section)
91         # base is removed from the name
92         fmt = lambda kstat: '{name} : {value}'.format(name=kstat.name[len(base):],
93                                                       value=kstat.value)
94         return [fmt(kstat) for kstat in sysctl.filter(base)]
95
96     def get_params(base):
97         cut = 8 # = len('vfs.zfs.')
98         return {ctl.name[cut:]: str(ctl.value) for ctl in sysctl.filter(base)}
99
100     def get_tunable_params():
101         return get_params('vfs.zfs')
102
103     def get_vdev_params():
104         return get_params('vfs.zfs.vdev')
105
106     def get_version_impl(request):
107         # FreeBSD reports versions for zpl and spa instead of zfs and spl.
108         name = {'zfs': 'zpl',
109                 'spl': 'spa'}[request]
110         mib = 'vfs.zfs.version.{}'.format(name)
111         version = sysctl.filter(mib)[0].value
112         return '{} version {}'.format(name, version)
113
114     def get_descriptions(_request):
115         # py-sysctl doesn't give descriptions, so we have to shell out.
116         command = ['sysctl', '-d', 'vfs.zfs']
117
118         # The recommended way to do this is with subprocess.run(). However,
119         # some installed versions of Python are < 3.5, so we offer them
120         # the option of doing it the old way (for now)
121         if 'run' in dir(subprocess):
122             info = subprocess.run(command, stdout=subprocess.PIPE,
123                                   universal_newlines=True)
124             lines = info.stdout.split('\n')
125         else:
126             info = subprocess.check_output(command, universal_newlines=True)
127             lines = info.split('\n')
128
129         def fmt(line):
130             name, desc = line.split(':', 1)
131             return (name.strip(), desc.strip())
132
133         return dict([fmt(line) for line in lines if len(line) > 0])
134
135
136 elif sys.platform.startswith('linux'):
137     KSTAT_PATH = '/proc/spl/kstat/zfs'
138     SPL_PATH = '/sys/module/spl/parameters'
139     TUNABLES_PATH = '/sys/module/zfs/parameters'
140
141     VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
142
143     def load_kstats(section):
144         path = os.path.join(KSTAT_PATH, section)
145         with open(path) as f:
146             return list(f)[2:] # Get rid of header
147
148     def get_params(basepath):
149         """Collect information on the Solaris Porting Layer (SPL) or the
150         tunables, depending on the PATH given. Does not check if PATH is
151         legal.
152         """
153         result = {}
154         for name in os.listdir(basepath):
155             path = os.path.join(basepath, name)
156             with open(path) as f:
157                 value = f.read()
158                 result[name] = value.strip()
159         return result
160
161     def get_spl_params():
162         return get_params(SPL_PATH)
163
164     def get_tunable_params():
165         return get_params(TUNABLES_PATH)
166
167     def get_vdev_params():
168         return get_params(TUNABLES_PATH)
169
170     def get_version_impl(request):
171         # The original arc_summary called /sbin/modinfo/{spl,zfs} to get
172         # the version information. We switch to /sys/module/{spl,zfs}/version
173         # to make sure we get what is really loaded in the kernel
174         command = ["cat", "/sys/module/{0}/version".format(request)]
175         req = request.upper()
176
177         # The recommended way to do this is with subprocess.run(). However,
178         # some installed versions of Python are < 3.5, so we offer them
179         # the option of doing it the old way (for now)
180         if 'run' in dir(subprocess):
181             info = subprocess.run(command, stdout=subprocess.PIPE,
182                                   universal_newlines=True)
183             version = info.stdout.strip()
184         else:
185             info = subprocess.check_output(command, universal_newlines=True)
186             version = info.strip()
187
188         return version
189
190     def get_descriptions(request):
191         """Get the descriptions of the Solaris Porting Layer (SPL) or the
192         tunables, return with minimal formatting.
193         """
194
195         if request not in ('spl', 'zfs'):
196             print('ERROR: description of "{0}" requested)'.format(request))
197             sys.exit(1)
198
199         descs = {}
200         target_prefix = 'parm:'
201
202         # We would prefer to do this with /sys/modules -- see the discussion at
203         # get_version() -- but there isn't a way to get the descriptions from
204         # there, so we fall back on modinfo
205         command = ["/sbin/modinfo", request, "-0"]
206
207         # The recommended way to do this is with subprocess.run(). However,
208         # some installed versions of Python are < 3.5, so we offer them
209         # the option of doing it the old way (for now)
210         info = ''
211
212         try:
213
214             if 'run' in dir(subprocess):
215                 info = subprocess.run(command, stdout=subprocess.PIPE,
216                                       universal_newlines=True)
217                 raw_output = info.stdout.split('\0')
218             else:
219                 info = subprocess.check_output(command,
220                                                universal_newlines=True)
221                 raw_output = info.split('\0')
222
223         except subprocess.CalledProcessError:
224             print("Error: Descriptions not available",
225                   "(can't access kernel module)")
226             sys.exit(1)
227
228         for line in raw_output:
229
230             if not line.startswith(target_prefix):
231                 continue
232
233             line = line[len(target_prefix):].strip()
234             name, raw_desc = line.split(':', 1)
235             desc = raw_desc.rsplit('(', 1)[0]
236
237             if desc == '':
238                 desc = '(No description found)'
239
240             descs[name.strip()] = desc.strip()
241
242         return descs
243
244
245 def cleanup_line(single_line):
246     """Format a raw line of data from /proc and isolate the name value
247     part, returning a tuple with each. Currently, this gets rid of the
248     middle '4'. For example "arc_no_grow    4    0" returns the tuple
249     ("arc_no_grow", "0").
250     """
251     name, _, value = single_line.split()
252
253     return name, value
254
255
256 def draw_graph(kstats_dict):
257     """Draw a primitive graph representing the basic information on the
258     ARC -- its size and the proportion used by MFU and MRU -- and quit.
259     We use max size of the ARC to calculate how full it is. This is a
260     very rough representation.
261     """
262
263     arc_stats = isolate_section('arcstats', kstats_dict)
264
265     GRAPH_INDENT = ' '*4
266     GRAPH_WIDTH = 60
267     arc_size = f_bytes(arc_stats['size'])
268     arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
269     mfu_size = f_bytes(arc_stats['mfu_size'])
270     mru_size = f_bytes(arc_stats['mru_size'])
271     meta_limit = f_bytes(arc_stats['arc_meta_limit'])
272     meta_size = f_bytes(arc_stats['arc_meta_used'])
273     dnode_limit = f_bytes(arc_stats['arc_dnode_limit'])
274     dnode_size = f_bytes(arc_stats['dnode_size'])
275
276     info_form = ('ARC: {0} ({1})  MFU: {2}  MRU: {3}  META: {4} ({5}) '
277                  'DNODE {6} ({7})')
278     info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
279                                  meta_size, meta_limit, dnode_size,
280                                  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         spc = LINE_LENGTH-(len(INDENT)+len(value))
401         result = '{0}{1:<{spc}}{2}'.format(INDENT, name, value, spc=spc)
402
403     return result
404
405
406 def get_kstats():
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
410     name.
411     """
412
413     result = {}
414
415     for section in SECTION_PATHS.values():
416         if section not in result:
417             result[section] = load_kstats(section)
418
419     return result
420
421
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.
426     """
427
428     if request not in ('spl', 'zfs'):
429         error_msg = '(ERROR: "{0}" requested)'.format(request)
430         return error_msg
431
432     return get_version_impl(request)
433
434
435 def print_header():
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.
438     """
439
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()
446
447     sys_msg = sys_version.sysname+' '+sys_version.release
448     zfs = get_version('zfs')
449     spc_zfs = LINE_LENGTH-len(zfs)
450
451     machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
452     spl = get_version('spl')
453     spc_spl = LINE_LENGTH-len(spl)
454
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))
459
460
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'.
464     """
465
466     sections = sorted(kstats_dict.keys())
467
468     for section in sections:
469
470         print('\n{0}:'.format(section.upper()))
471         lines = sorted(kstats_dict[section])
472
473         for line in lines:
474             name, value = cleanup_line(line)
475             print(format_raw_line(name, value))
476
477     # Tunables and SPL must be handled separately because they come from a
478     # different source and have descriptions the user might request
479     print()
480     section_spl()
481     section_tunables()
482
483
484 def isolate_section(section_name, kstats_dict):
485     """From the complete information on all sections, retrieve only those
486     for one section.
487     """
488
489     try:
490         section_data = kstats_dict[section_name]
491     except KeyError:
492         print('ERROR: Data on {0} not available'.format(section_data))
493         sys.exit(1)
494
495     section_dict = dict(cleanup_line(l) for l in section_data)
496
497     return section_dict
498
499
500 # Formatted output helper functions
501
502
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))
507
508
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))
513
514
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))
520
521
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))
527
528
529 # The section output concentrates on important parameters instead of
530 # being exhaustive (that is what the --raw parameter is for)
531
532
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.
536     """
537
538     arc_stats = isolate_section('arcstats', kstats_dict)
539
540     throttle = arc_stats['memory_throttle_count']
541
542     if throttle == '0':
543         health = 'HEALTHY'
544     else:
545         health = 'THROTTLED'
546
547     prt_1('ARC status:', health)
548     prt_i1('Memory throttle count:', throttle)
549     print()
550
551     arc_size = arc_stats['size']
552     arc_target_size = arc_stats['c']
553     arc_max = arc_stats['c_max']
554     arc_min = arc_stats['c_min']
555     mfu_size = arc_stats['mfu_size']
556     mru_size = arc_stats['mru_size']
557     meta_limit = arc_stats['arc_meta_limit']
558     meta_size = arc_stats['arc_meta_used']
559     dnode_limit = arc_stats['arc_dnode_limit']
560     dnode_size = arc_stats['dnode_size']
561     target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
562
563     prt_2('ARC size (current):',
564           f_perc(arc_size, arc_max), f_bytes(arc_size))
565     prt_i2('Target size (adaptive):',
566            f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
567     prt_i2('Min size (hard limit):',
568            f_perc(arc_min, arc_max), f_bytes(arc_min))
569     prt_i2('Max size (high water):',
570            target_size_ratio, f_bytes(arc_max))
571     caches_size = int(mfu_size)+int(mru_size)
572     prt_i2('Most Frequently Used (MFU) cache size:',
573            f_perc(mfu_size, caches_size), f_bytes(mfu_size))
574     prt_i2('Most Recently Used (MRU) cache size:',
575            f_perc(mru_size, caches_size), f_bytes(mru_size))
576     prt_i2('Metadata cache size (hard limit):',
577            f_perc(meta_limit, arc_max), f_bytes(meta_limit))
578     prt_i2('Metadata cache size (current):',
579            f_perc(meta_size, meta_limit), f_bytes(meta_size))
580     prt_i2('Dnode cache size (hard limit):',
581            f_perc(dnode_limit, meta_limit), f_bytes(dnode_limit))
582     prt_i2('Dnode cache size (current):',
583            f_perc(dnode_size, dnode_limit), f_bytes(dnode_size))
584     print()
585
586     print('ARC hash breakdown:')
587     prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
588     prt_i2('Elements current:',
589            f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
590            f_hits(arc_stats['hash_elements']))
591     prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
592
593     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
594     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
595     print()
596
597     print('ARC misc:')
598     prt_i1('Deleted:', f_hits(arc_stats['deleted']))
599     prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
600     prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
601     print()
602
603
604 def section_archits(kstats_dict):
605     """Print information on how the caches are accessed ("arc hits").
606     """
607
608     arc_stats = isolate_section('arcstats', kstats_dict)
609     all_accesses = int(arc_stats['hits'])+int(arc_stats['misses'])
610     actual_hits = int(arc_stats['mfu_hits'])+int(arc_stats['mru_hits'])
611
612     prt_1('ARC total accesses (hits + misses):', f_hits(all_accesses))
613     ta_todo = (('Cache hit ratio:', arc_stats['hits']),
614                ('Cache miss ratio:', arc_stats['misses']),
615                ('Actual hit ratio (MFU + MRU hits):', actual_hits))
616
617     for title, value in ta_todo:
618         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
619
620     dd_total = int(arc_stats['demand_data_hits']) +\
621         int(arc_stats['demand_data_misses'])
622     prt_i2('Data demand efficiency:',
623            f_perc(arc_stats['demand_data_hits'], dd_total),
624            f_hits(dd_total))
625
626     dp_total = int(arc_stats['prefetch_data_hits']) +\
627         int(arc_stats['prefetch_data_misses'])
628     prt_i2('Data prefetch efficiency:',
629            f_perc(arc_stats['prefetch_data_hits'], dp_total),
630            f_hits(dp_total))
631
632     known_hits = int(arc_stats['mfu_hits']) +\
633         int(arc_stats['mru_hits']) +\
634         int(arc_stats['mfu_ghost_hits']) +\
635         int(arc_stats['mru_ghost_hits'])
636
637     anon_hits = int(arc_stats['hits'])-known_hits
638
639     print()
640     print('Cache hits by cache type:')
641     cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
642                ('Most recently used (MRU):', arc_stats['mru_hits']),
643                ('Most frequently used (MFU) ghost:',
644                 arc_stats['mfu_ghost_hits']),
645                ('Most recently used (MRU) ghost:',
646                 arc_stats['mru_ghost_hits']))
647
648     for title, value in cl_todo:
649         prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
650
651     # For some reason, anon_hits can turn negative, which is weird. Until we
652     # have figured out why this happens, we just hide the problem, following
653     # the behavior of the original arc_summary.
654     if anon_hits >= 0:
655         prt_i2('Anonymously used:',
656                f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
657
658     print()
659     print('Cache hits by data type:')
660     dt_todo = (('Demand data:', arc_stats['demand_data_hits']),
661                ('Demand prefetch data:', arc_stats['prefetch_data_hits']),
662                ('Demand metadata:', arc_stats['demand_metadata_hits']),
663                ('Demand prefetch metadata:',
664                 arc_stats['prefetch_metadata_hits']))
665
666     for title, value in dt_todo:
667         prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
668
669     print()
670     print('Cache misses by data type:')
671     dm_todo = (('Demand data:', arc_stats['demand_data_misses']),
672                ('Demand prefetch data:',
673                 arc_stats['prefetch_data_misses']),
674                ('Demand metadata:', arc_stats['demand_metadata_misses']),
675                ('Demand prefetch metadata:',
676                 arc_stats['prefetch_metadata_misses']))
677
678     for title, value in dm_todo:
679         prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
680
681     print()
682
683
684 def section_dmu(kstats_dict):
685     """Collect information on the DMU"""
686
687     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
688
689     zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
690
691     prt_1('DMU prefetch efficiency:', f_hits(zfetch_access_total))
692     prt_i2('Hit ratio:', f_perc(zfetch_stats['hits'], zfetch_access_total),
693            f_hits(zfetch_stats['hits']))
694     prt_i2('Miss ratio:', f_perc(zfetch_stats['misses'], zfetch_access_total),
695            f_hits(zfetch_stats['misses']))
696     print()
697
698
699 def section_l2arc(kstats_dict):
700     """Collect information on L2ARC device if present. If not, tell user
701     that we're skipping the section.
702     """
703
704     # The L2ARC statistics live in the same section as the normal ARC stuff
705     arc_stats = isolate_section('arcstats', kstats_dict)
706
707     if arc_stats['l2_size'] == '0':
708         print('L2ARC not detected, skipping section\n')
709         return
710
711     l2_errors = int(arc_stats['l2_writes_error']) +\
712         int(arc_stats['l2_cksum_bad']) +\
713         int(arc_stats['l2_io_error'])
714
715     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
716     health = 'HEALTHY'
717
718     if l2_errors > 0:
719         health = 'DEGRADED'
720
721     prt_1('L2ARC status:', health)
722
723     l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
724                ('Free on write:', 'l2_free_on_write'),
725                ('R/W clashes:', 'l2_rw_clash'),
726                ('Bad checksums:', 'l2_cksum_bad'),
727                ('I/O errors:', 'l2_io_error'))
728
729     for title, value in l2_todo:
730         prt_i1(title, f_hits(arc_stats[value]))
731
732     print()
733     prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
734     prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
735            f_bytes(arc_stats['l2_asize']))
736     prt_i2('Header size:',
737            f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
738            f_bytes(arc_stats['l2_hdr_size']))
739
740     print()
741     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
742     prt_i2('Hit ratio:',
743            f_perc(arc_stats['l2_hits'], l2_access_total),
744            f_hits(arc_stats['l2_hits']))
745     prt_i2('Miss ratio:',
746            f_perc(arc_stats['l2_misses'], l2_access_total),
747            f_hits(arc_stats['l2_misses']))
748     prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
749
750     print()
751     print('L2ARC writes:')
752
753     if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
754         prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
755         prt_i2('Done ratio:',
756                f_perc(arc_stats['l2_writes_done'],
757                       arc_stats['l2_writes_sent']),
758                f_hits(arc_stats['l2_writes_done']))
759         prt_i2('Error ratio:',
760                f_perc(arc_stats['l2_writes_error'],
761                       arc_stats['l2_writes_sent']),
762                f_hits(arc_stats['l2_writes_error']))
763     else:
764         prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
765
766     print()
767     print('L2ARC evicts:')
768     prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
769     prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
770     print()
771
772
773 def section_spl(*_):
774     """Print the SPL parameters, if requested with alternative format
775     and/or descriptions. This does not use kstats.
776     """
777
778     if sys.platform.startswith('freebsd'):
779         # No SPL support in FreeBSD
780         return
781
782     spls = get_spl_params()
783     keylist = sorted(spls.keys())
784     print('Solaris Porting Layer (SPL):')
785
786     if ARGS.desc:
787         descriptions = get_descriptions('spl')
788
789     for key in keylist:
790         value = spls[key]
791
792         if ARGS.desc:
793             try:
794                 print(INDENT+'#', descriptions[key])
795             except KeyError:
796                 print(INDENT+'# (No description found)')  # paranoid
797
798         print(format_raw_line(key, value))
799
800     print()
801
802
803 def section_tunables(*_):
804     """Print the tunables, if requested with alternative format and/or
805     descriptions. This does not use kstasts.
806     """
807
808     tunables = get_tunable_params()
809     keylist = sorted(tunables.keys())
810     print('Tunables:')
811
812     if ARGS.desc:
813         descriptions = get_descriptions('zfs')
814
815     for key in keylist:
816         value = tunables[key]
817
818         if ARGS.desc:
819             try:
820                 print(INDENT+'#', descriptions[key])
821             except KeyError:
822                 print(INDENT+'# (No description found)')  # paranoid
823
824         print(format_raw_line(key, value))
825
826     print()
827
828
829 def section_vdev(kstats_dict):
830     """Collect information on VDEV caches"""
831
832     # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
833     # harmful. When this is the case, we just skip the whole entry. See
834     # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c
835     # for details
836     tunables = get_vdev_params()
837
838     if tunables[VDEV_CACHE_SIZE] == '0':
839         print('VDEV cache disabled, skipping section\n')
840         return
841
842     vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
843
844     vdev_cache_total = int(vdev_stats['hits']) +\
845         int(vdev_stats['misses']) +\
846         int(vdev_stats['delegations'])
847
848     prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
849     prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
850            f_hits(vdev_stats['hits']))
851     prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
852            f_hits(vdev_stats['misses']))
853     prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
854            f_hits(vdev_stats['delegations']))
855     print()
856
857
858 def section_zil(kstats_dict):
859     """Collect information on the ZFS Intent Log. Some of the information
860     taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h
861     """
862
863     zil_stats = isolate_section('zil', kstats_dict)
864
865     prt_1('ZIL committed transactions:',
866           f_hits(zil_stats['zil_itx_count']))
867     prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
868     prt_i1('Flushes to stable storage:',
869            f_hits(zil_stats['zil_commit_writer_count']))
870     prt_i2('Transactions to SLOG storage pool:',
871            f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
872            f_hits(zil_stats['zil_itx_metaslab_slog_count']))
873     prt_i2('Transactions to non-SLOG storage pool:',
874            f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
875            f_hits(zil_stats['zil_itx_metaslab_normal_count']))
876     print()
877
878
879 section_calls = {'arc': section_arc,
880                  'archits': section_archits,
881                  'dmu': section_dmu,
882                  'l2arc': section_l2arc,
883                  'spl': section_spl,
884                  'tunables': section_tunables,
885                  'vdev': section_vdev,
886                  'zil': section_zil}
887
888
889 def main():
890     """Run program. The options to draw a graph and to print all data raw are
891     treated separately because they come with their own call.
892     """
893
894     kstats = get_kstats()
895
896     if ARGS.graph:
897         draw_graph(kstats)
898         sys.exit(0)
899
900     print_header()
901
902     if ARGS.raw:
903         print_raw(kstats)
904
905     elif ARGS.section:
906
907         try:
908             section_calls[ARGS.section](kstats)
909         except KeyError:
910             print('Error: Section "{0}" unknown'.format(ARGS.section))
911             sys.exit(1)
912
913     elif ARGS.page:
914         print('WARNING: Pages are deprecated, please use "--section"\n')
915
916         pages_to_calls = {1: 'arc',
917                           2: 'archits',
918                           3: 'l2arc',
919                           4: 'dmu',
920                           5: 'vdev',
921                           6: 'tunables'}
922
923         try:
924             call = pages_to_calls[ARGS.page]
925         except KeyError:
926             print('Error: Page "{0}" not supported'.format(ARGS.page))
927             sys.exit(1)
928         else:
929             section_calls[call](kstats)
930
931     else:
932         # If no parameters were given, we print all sections. We might want to
933         # change the sequence by hand
934         calls = sorted(section_calls.keys())
935
936         for section in calls:
937             section_calls[section](kstats)
938
939     sys.exit(0)
940
941
942 if __name__ == '__main__':
943     main()