]> git.proxmox.com Git - mirror_zfs.git/blob - cmd/arc_summary/arc_summary3
arc_summary: Make get_descriptions per platform
[mirror_zfs.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/zfsonlinux/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 ZFS on Linux'
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_bytes(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_bytes(arc_stats['l2_writes_error']))
763 else:
764 prt_i2('Writes sent:', '100 %', f_bytes(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/zfsonlinux/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/zfsonlinux/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()