]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - cmd/arc_summary/arc_summary3
FreeBSD: Prune some unneeded definitions
[FreeBSD/FreeBSD.git] / 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                  'zfetch': 'zfetchstats',
62                  'zil': 'zil'}
63
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',
67                     dest='alt')
68 parser.add_argument('-d', '--description', action='store_true', default=False,
69                     help='print descriptions with tunables and SPL',
70                     dest='desc')
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',
77                     dest='raw')
78 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
79 ARGS = parser.parse_args()
80
81
82 if sys.platform.startswith('freebsd'):
83     # Requires py36-sysctl on FreeBSD
84     import sysctl
85
86     VDEV_CACHE_SIZE = 'vdev.cache_size'
87
88     def is_value(ctl):
89         return ctl.type != sysctl.CTLTYPE_NODE
90
91     def namefmt(ctl, base='vfs.zfs.'):
92         # base is removed from the name
93         cut = len(base)
94         return ctl.name[cut:]
95
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),
99                                                       value=kstat.value)
100         kstats = sysctl.filter(base)
101         return [fmt(kstat) for kstat in kstats if is_value(kstat)]
102
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)}
106
107     def get_tunable_params():
108         return get_params('vfs.zfs')
109
110     def get_vdev_params():
111         return get_params('vfs.zfs.vdev')
112
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)
120
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)}
124
125
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'
130
131     VDEV_CACHE_SIZE = 'zfs_vdev_cache_size'
132
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
137
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
141         legal.
142         """
143         result = {}
144         for name in os.listdir(basepath):
145             path = os.path.join(basepath, name)
146             with open(path) as f:
147                 value = f.read()
148                 result[name] = value.strip()
149         return result
150
151     def get_spl_params():
152         return get_params(SPL_PATH)
153
154     def get_tunable_params():
155         return get_params(TUNABLES_PATH)
156
157     def get_vdev_params():
158         return get_params(TUNABLES_PATH)
159
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
164         try:
165             with open("/sys/module/{}/version".format(request)) as f:
166                 return f.read().strip()
167         except:
168             return "(unknown)"
169
170     def get_descriptions(request):
171         """Get the descriptions of the Solaris Porting Layer (SPL) or the
172         tunables, return with minimal formatting.
173         """
174
175         if request not in ('spl', 'zfs'):
176             print('ERROR: description of "{0}" requested)'.format(request))
177             sys.exit(1)
178
179         descs = {}
180         target_prefix = 'parm:'
181
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"]
186
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)
190         info = ''
191
192         try:
193
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')
198             else:
199                 info = subprocess.check_output(command,
200                                                universal_newlines=True)
201                 raw_output = info.split('\0')
202
203         except subprocess.CalledProcessError:
204             print("Error: Descriptions not available",
205                   "(can't access kernel module)")
206             sys.exit(1)
207
208         for line in raw_output:
209
210             if not line.startswith(target_prefix):
211                 continue
212
213             line = line[len(target_prefix):].strip()
214             name, raw_desc = line.split(':', 1)
215             desc = raw_desc.rsplit('(', 1)[0]
216
217             if desc == '':
218                 desc = '(No description found)'
219
220             descs[name.strip()] = desc.strip()
221
222         return descs
223
224
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").
230     """
231     name, _, value = single_line.split()
232
233     return name, value
234
235
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.
241     """
242
243     arc_stats = isolate_section('arcstats', kstats_dict)
244
245     GRAPH_INDENT = ' '*4
246     GRAPH_WIDTH = 60
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'])
255
256     info_form = ('ARC: {0} ({1})  MFU: {2}  MRU: {3}  META: {4} ({5}) '
257                  'DNODE {6} ({7})')
258     info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size,
259                                  meta_size, meta_limit, dnode_size,
260                                  dnode_limit)
261     info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
262     info_line = GRAPH_INDENT+info_spc+info_line
263
264     graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
265
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)
273
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+'|'
277
278     for line in ('', info_line, graph_line, core_line, graph_line, ''):
279         print(line)
280
281
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.
287     """
288
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)
297
298     bites = int(byte_string)
299
300     if bites >= 2**10:
301         for limit, unit in prefixes:
302
303             if bites >= limit:
304                 value = bites / limit
305                 break
306
307         result = '{0:.1f} {1}'.format(value, unit)
308     else:
309         result = '{0} Bytes'.format(bites)
310
311     return result
312
313
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
321     """
322
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)
331
332     hits = int(hits_string)
333
334     if hits >= 1000:
335         for limit, symbol in numbers:
336
337             if hits >= limit:
338                 value = hits/limit
339                 break
340
341         result = "%0.1f%s" % (value, symbol)
342     else:
343         result = "%d" % hits
344
345     return result
346
347
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
353     is called.
354     """
355
356     v1 = float(value1)
357     v2 = float(value2)
358
359     try:
360         perc = 100 * v1/v2
361     except ZeroDivisionError:
362         result = 'n/a'
363     else:
364         result = '{0:0.1f} %'.format(perc)
365
366     if result == '0.0 %' and v1 > 0:
367         result = '< 0.1 %'
368
369     return result
370
371
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.
375     """
376
377     if ARGS.alt:
378         result = '{0}{1}={2}'.format(INDENT, name, value)
379     else:
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)
386
387     return result
388
389
390 def get_kstats():
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
394     name.
395     """
396
397     result = {}
398
399     for section in SECTION_PATHS.values():
400         if section not in result:
401             result[section] = load_kstats(section)
402
403     return result
404
405
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.
410     """
411
412     if request not in ('spl', 'zfs'):
413         error_msg = '(ERROR: "{0}" requested)'.format(request)
414         return error_msg
415
416     return get_version_impl(request)
417
418
419 def print_header():
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.
422     """
423
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()
430
431     sys_msg = sys_version.sysname+' '+sys_version.release
432     zfs = get_version('zfs')
433     spc_zfs = LINE_LENGTH-len(zfs)
434
435     machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
436     spl = get_version('spl')
437     spc_spl = LINE_LENGTH-len(spl)
438
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))
443
444
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'.
448     """
449
450     sections = sorted(kstats_dict.keys())
451
452     for section in sections:
453
454         print('\n{0}:'.format(section.upper()))
455         lines = sorted(kstats_dict[section])
456
457         for line in lines:
458             name, value = cleanup_line(line)
459             print(format_raw_line(name, value))
460
461     # Tunables and SPL must be handled separately because they come from a
462     # different source and have descriptions the user might request
463     print()
464     section_spl()
465     section_tunables()
466
467
468 def isolate_section(section_name, kstats_dict):
469     """From the complete information on all sections, retrieve only those
470     for one section.
471     """
472
473     try:
474         section_data = kstats_dict[section_name]
475     except KeyError:
476         print('ERROR: Data on {0} not available'.format(section_data))
477         sys.exit(1)
478
479     section_dict = dict(cleanup_line(l) for l in section_data)
480
481     return section_dict
482
483
484 # Formatted output helper functions
485
486
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))
491
492
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))
497
498
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))
504
505
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))
511
512
513 # The section output concentrates on important parameters instead of
514 # being exhaustive (that is what the --raw parameter is for)
515
516
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.
520     """
521
522     arc_stats = isolate_section('arcstats', kstats_dict)
523
524     throttle = arc_stats['memory_throttle_count']
525
526     if throttle == '0':
527         health = 'HEALTHY'
528     else:
529         health = 'THROTTLED'
530
531     prt_1('ARC status:', health)
532     prt_i1('Memory throttle count:', throttle)
533     print()
534
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))
546
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))
568     print()
569
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']))
576
577     prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
578     prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
579     print()
580
581     print('ARC misc:')
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']))
599     print()
600
601
602 def section_archits(kstats_dict):
603     """Print information on how the caches are accessed ("arc hits").
604     """
605
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'])
609
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))
614
615     for title, value in ta_todo:
616         prt_i2(title, f_perc(value, all_accesses), f_hits(value))
617
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),
622            f_hits(dd_total))
623
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),
628            f_hits(dp_total))
629
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'])
634
635     anon_hits = int(arc_stats['hits'])-known_hits
636
637     print()
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']))
645
646     for title, value in cl_todo:
647         prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
648
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.
652     if anon_hits >= 0:
653         prt_i2('Anonymously used:',
654                f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
655
656     print()
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']))
663
664     for title, value in dt_todo:
665         prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
666
667     print()
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']))
675
676     for title, value in dm_todo:
677         prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
678
679     print()
680
681
682 def section_dmu(kstats_dict):
683     """Collect information on the DMU"""
684
685     zfetch_stats = isolate_section('zfetchstats', kstats_dict)
686
687     zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
688
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']))
694     print()
695
696
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.
700     """
701
702     # The L2ARC statistics live in the same section as the normal ARC stuff
703     arc_stats = isolate_section('arcstats', kstats_dict)
704
705     if arc_stats['l2_size'] == '0':
706         print('L2ARC not detected, skipping section\n')
707         return
708
709     l2_errors = int(arc_stats['l2_writes_error']) +\
710         int(arc_stats['l2_cksum_bad']) +\
711         int(arc_stats['l2_io_error'])
712
713     l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
714     health = 'HEALTHY'
715
716     if l2_errors > 0:
717         health = 'DEGRADED'
718
719     prt_1('L2ARC status:', health)
720
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'))
726
727     for title, value in l2_todo:
728         prt_i1(title, f_hits(arc_stats[value]))
729
730     print()
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']))
752
753     print()
754     prt_1('L2ARC breakdown:', f_hits(l2_access_total))
755     prt_i2('Hit ratio:',
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']))
762
763     print()
764     print('L2ARC writes:')
765
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']))
776     else:
777         prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent']))
778
779     print()
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']))
783     print()
784
785
786 def section_spl(*_):
787     """Print the SPL parameters, if requested with alternative format
788     and/or descriptions. This does not use kstats.
789     """
790
791     if sys.platform.startswith('freebsd'):
792         # No SPL support in FreeBSD
793         return
794
795     spls = get_spl_params()
796     keylist = sorted(spls.keys())
797     print('Solaris Porting Layer (SPL):')
798
799     if ARGS.desc:
800         descriptions = get_descriptions('spl')
801
802     for key in keylist:
803         value = spls[key]
804
805         if ARGS.desc:
806             try:
807                 print(INDENT+'#', descriptions[key])
808             except KeyError:
809                 print(INDENT+'# (No description found)')  # paranoid
810
811         print(format_raw_line(key, value))
812
813     print()
814
815
816 def section_tunables(*_):
817     """Print the tunables, if requested with alternative format and/or
818     descriptions. This does not use kstasts.
819     """
820
821     tunables = get_tunable_params()
822     keylist = sorted(tunables.keys())
823     print('Tunables:')
824
825     if ARGS.desc:
826         descriptions = get_descriptions('zfs')
827
828     for key in keylist:
829         value = tunables[key]
830
831         if ARGS.desc:
832             try:
833                 print(INDENT+'#', descriptions[key])
834             except KeyError:
835                 print(INDENT+'# (No description found)')  # paranoid
836
837         print(format_raw_line(key, value))
838
839     print()
840
841
842 def section_vdev(kstats_dict):
843     """Collect information on VDEV caches"""
844
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
848     # for details
849     tunables = get_vdev_params()
850
851     if tunables[VDEV_CACHE_SIZE] == '0':
852         print('VDEV cache disabled, skipping section\n')
853         return
854
855     vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
856
857     vdev_cache_total = int(vdev_stats['hits']) +\
858         int(vdev_stats['misses']) +\
859         int(vdev_stats['delegations'])
860
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']))
868     print()
869
870
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
874     """
875
876     zil_stats = isolate_section('zil', kstats_dict)
877
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']))
889     print()
890
891
892 section_calls = {'arc': section_arc,
893                  'archits': section_archits,
894                  'dmu': section_dmu,
895                  'l2arc': section_l2arc,
896                  'spl': section_spl,
897                  'tunables': section_tunables,
898                  'vdev': section_vdev,
899                  'zil': section_zil}
900
901
902 def main():
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.
905     """
906
907     kstats = get_kstats()
908
909     if ARGS.graph:
910         draw_graph(kstats)
911         sys.exit(0)
912
913     print_header()
914
915     if ARGS.raw:
916         print_raw(kstats)
917
918     elif ARGS.section:
919
920         try:
921             section_calls[ARGS.section](kstats)
922         except KeyError:
923             print('Error: Section "{0}" unknown'.format(ARGS.section))
924             sys.exit(1)
925
926     elif ARGS.page:
927         print('WARNING: Pages are deprecated, please use "--section"\n')
928
929         pages_to_calls = {1: 'arc',
930                           2: 'archits',
931                           3: 'l2arc',
932                           4: 'dmu',
933                           5: 'vdev',
934                           6: 'tunables'}
935
936         try:
937             call = pages_to_calls[ARGS.page]
938         except KeyError:
939             print('Error: Page "{0}" not supported'.format(ARGS.page))
940             sys.exit(1)
941         else:
942             section_calls[call](kstats)
943
944     else:
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())
948
949         for section in calls:
950             section_calls[section](kstats)
951
952     sys.exit(0)
953
954
955 if __name__ == '__main__':
956     main()