]> git.proxmox.com Git - mirror_zfs.git/blob - cmd/arc_summary/arc_summary3.py
Add Python 3 rewrite of arc_summary.py
[mirror_zfs.git] / cmd / arc_summary / arc_summary3.py
1 #!/usr/bin/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 DECRIPTION = 'Print ARC and other statistics for ZFS on Linux'
47 INDENT = ' '*8
48 LINE_LENGTH = 72
49 PROC_PATH = '/proc/spl/kstat/zfs/'
50 SPL_PATH = '/sys/module/spl/parameters/'
51 TUNABLES_PATH = '/sys/module/zfs/parameters/'
52 DATE_FORMAT = '%a %b %d %H:%M:%S %Y'
53 TITLE = 'ZFS Subsystem Report'
54
55 SECTIONS = 'arc archits dmu l2arc spl tunables vdev zil'.split()
56 SECTION_HELP = 'print info from one section ('+' '.join(SECTIONS)+')'
57
58 # Tunables and SPL are handled separately because they come from
59 # different sources
60 SECTION_PATHS = {'arc': 'arcstats',
61 'dmu': 'dmu_tx',
62 'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
63 'vdev': 'vdev_cache_stats',
64 'xuio': 'xuio_stats',
65 'zfetch': 'zfetchstats',
66 'zil': 'zil'}
67
68 parser = argparse.ArgumentParser(description=DECRIPTION)
69 parser.add_argument('-a', '--alternate', action='store_true', default=False,
70 help='use alternate formatting for tunables and SPL',
71 dest='alt')
72 parser.add_argument('-d', '--description', action='store_true', default=False,
73 help='print descriptions with tunables and SPL',
74 dest='desc')
75 parser.add_argument('-g', '--graph', action='store_true', default=False,
76 help='print graph on ARC use and exit', dest='graph')
77 parser.add_argument('-p', '--page', type=int, dest='page',
78 help='print page by number (DEPRECATED, use "-s")')
79 parser.add_argument('-r', '--raw', action='store_true', default=False,
80 help='dump all available data with minimal formatting',
81 dest='raw')
82 parser.add_argument('-s', '--section', dest='section', help=SECTION_HELP)
83 ARGS = parser.parse_args()
84
85
86 def cleanup_line(single_line):
87 """Format a raw line of data from /proc and isolate the name value
88 part, returning a tuple with each. Currently, this gets rid of the
89 middle '4'. For example "arc_no_grow 4 0" returns the tuple
90 ("arc_no_grow", "0").
91 """
92 name, _, value = single_line.split()
93
94 return name, value
95
96
97 def draw_graph(kstats_dict):
98 """Draw a primitive graph representing the basic information on the
99 ARC -- its size and the proportion used by MFU and MRU -- and quit.
100 We use max size of the ARC to calculate how full it is. This is a
101 very rough representation.
102 """
103
104 arc_stats = isolate_section('arcstats', kstats_dict)
105
106 GRAPH_INDENT = ' '*4
107 GRAPH_WIDTH = 60
108 arc_size = f_bytes(arc_stats['size'])
109 arc_perc = f_perc(arc_stats['size'], arc_stats['c_max'])
110 mfu_size = f_bytes(arc_stats['mfu_size'])
111 mru_size = f_bytes(arc_stats['mru_size'])
112
113 info_form = 'ARC: {0} ({1}) MFU: {2} MRU: {3}'
114 info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size)
115 info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2)
116 info_line = GRAPH_INDENT+info_spc+info_line
117
118 graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+'
119
120 mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max']))
121 mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max']))
122 arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max']))
123 total_ticks = float(arc_perc)*GRAPH_WIDTH
124 mfu_ticks = mfu_perc*GRAPH_WIDTH
125 mru_ticks = mru_perc*GRAPH_WIDTH
126 other_ticks = total_ticks-(mfu_ticks+mru_ticks)
127
128 core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks)
129 core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form)))
130 core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|'
131
132 for line in ('', info_line, graph_line, core_line, graph_line, ''):
133 print(line)
134
135
136 def f_bytes(byte_string):
137 """Return human-readable representation of a byte value in
138 powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal
139 points. Values smaller than one KiB are returned without
140 decimal points. Note "bytes" is a reserved keyword.
141 """
142
143 prefixes = ([2**80, "YiB"], # yobibytes (yotta)
144 [2**70, "ZiB"], # zebibytes (zetta)
145 [2**60, "EiB"], # exbibytes (exa)
146 [2**50, "PiB"], # pebibytes (peta)
147 [2**40, "TiB"], # tebibytes (tera)
148 [2**30, "GiB"], # gibibytes (giga)
149 [2**20, "MiB"], # mebibytes (mega)
150 [2**10, "KiB"]) # kibibytes (kilo)
151
152 bites = int(byte_string)
153
154 if bites >= 2**10:
155 for limit, unit in prefixes:
156
157 if bites >= limit:
158 value = bites / limit
159 break
160
161 result = '{0:.1f} {1}'.format(value, unit)
162 else:
163 result = '{0} Bytes'.format(bites)
164
165 return result
166
167
168 def f_hits(hits_string):
169 """Create a human-readable representation of the number of hits.
170 The single-letter symbols used are SI to avoid the confusion caused
171 by the different "short scale" and "long scale" representations in
172 English, which use the same words for different values. See
173 https://en.wikipedia.org/wiki/Names_of_large_numbers and:
174 https://physics.nist.gov/cuu/Units/prefixes.html
175 """
176
177 numbers = ([10**24, 'Y'], # yotta (septillion)
178 [10**21, 'Z'], # zetta (sextillion)
179 [10**18, 'E'], # exa (quintrillion)
180 [10**15, 'P'], # peta (quadrillion)
181 [10**12, 'T'], # tera (trillion)
182 [10**9, 'G'], # giga (billion)
183 [10**6, 'M'], # mega (million)
184 [10**3, 'k']) # kilo (thousand)
185
186 hits = int(hits_string)
187
188 if hits >= 1000:
189 for limit, symbol in numbers:
190
191 if hits >= limit:
192 value = hits/limit
193 break
194
195 result = "%0.1f%s" % (value, symbol)
196 else:
197 result = "%d" % hits
198
199 return result
200
201
202 def f_perc(value1, value2):
203 """Calculate percentage and return in human-readable form. If
204 rounding produces the result '0.0' though the first number is
205 not zero, include a 'less-than' symbol to avoid confusion.
206 Division by zero is handled by returning 'n/a'; no error
207 is called.
208 """
209
210 v1 = float(value1)
211 v2 = float(value2)
212
213 try:
214 perc = 100 * v1/v2
215 except ZeroDivisionError:
216 result = 'n/a'
217 else:
218 result = '{0:0.1f} %'.format(perc)
219
220 if result == '0.0 %' and v1 > 0:
221 result = '< 0.1 %'
222
223 return result
224
225
226 def format_raw_line(name, value):
227 """For the --raw option for the tunable and SPL outputs, decide on the
228 correct formatting based on the --alternate flag.
229 """
230
231 if ARGS.alt:
232 result = '{0}{1}={2}'.format(INDENT, name, value)
233 else:
234 spc = LINE_LENGTH-(len(INDENT)+len(value))
235 result = '{0}{1:<{spc}}{2}'.format(INDENT, name, value, spc=spc)
236
237 return result
238
239
240 def get_kstats():
241 """Collect information on the ZFS subsystem from the /proc Linux virtual
242 file system. The step does not perform any further processing, giving us
243 the option to only work on what is actually needed. The name "kstat" is a
244 holdover from the Solaris utility of the same name.
245 """
246
247 result = {}
248 secs = SECTION_PATHS.values()
249
250 for section in secs:
251
252 with open(PROC_PATH+section, 'r') as proc_location:
253 lines = [line for line in proc_location]
254
255 del lines[0:2] # Get rid of header
256 result[section] = lines
257
258 return result
259
260
261 def get_spl_tunables(PATH):
262 """Collect information on the Solaris Porting Layer (SPL) or the
263 tunables, depending on the PATH given. Does not check if PATH is
264 legal.
265 """
266
267 result = {}
268 parameters = os.listdir(PATH)
269
270 for name in parameters:
271
272 with open(PATH+name, 'r') as para_file:
273 value = para_file.read()
274 result[name] = value.strip()
275
276 return result
277
278
279 def get_descriptions(request):
280 """Get the decriptions of the Solaris Porting Layer (SPL) or the
281 tunables, return with minimal formatting.
282 """
283
284 if request not in ('spl', 'zfs'):
285 print('ERROR: description of "{0}" requested)'.format(request))
286 sys.exit(1)
287
288 descs = {}
289 target_prefix = 'parm:'
290
291 # We would prefer to do this with /sys/modules -- see the discussion at
292 # get_version() -- but there isn't a way to get the descriptions from
293 # there, so we fall back on modinfo
294 command = ["/sbin/modinfo", request, "-0"]
295
296 # The recommended way to do this is with subprocess.run(). However,
297 # some installed versions of Python are < 3.5, so we offer them
298 # the option of doing it the old way (for now)
299 info = ''
300
301 try:
302
303 if 'run' in dir(subprocess):
304 info = subprocess.run(command, stdout=subprocess.PIPE,
305 universal_newlines=True)
306 raw_output = info.stdout.split('\0')
307 else:
308 info = subprocess.check_output(command, universal_newlines=True)
309 raw_output = info.split('\0')
310
311 except subprocess.CalledProcessError:
312 print("Error: Descriptions not available (can't access kernel module)")
313 sys.exit(1)
314
315 for line in raw_output:
316
317 if not line.startswith(target_prefix):
318 continue
319
320 line = line[len(target_prefix):].strip()
321 name, raw_desc = line.split(':', 1)
322 desc = raw_desc.rsplit('(', 1)[0]
323
324 if desc == '':
325 desc = '(No description found)'
326
327 descs[name.strip()] = desc.strip()
328
329 return descs
330
331
332 def get_version(request):
333 """Get the version number of ZFS or SPL on this machine for header.
334 Returns an error string, but does not raise an error, if we can't
335 get the ZFS/SPL version via modinfo.
336 """
337
338 if request not in ('spl', 'zfs'):
339 error_msg = '(ERROR: "{0}" requested)'.format(request)
340 return error_msg
341
342 # The original arc_summary.py called /sbin/modinfo/{spl,zfs} to get
343 # the version information. We switch to /sys/module/{spl,zfs}/version
344 # to make sure we get what is really loaded in the kernel
345 command = ["cat", "/sys/module/{0}/version".format(request)]
346 req = request.upper()
347 version = "(Can't get {0} version)".format(req)
348
349 # The recommended way to do this is with subprocess.run(). However,
350 # some installed versions of Python are < 3.5, so we offer them
351 # the option of doing it the old way (for now)
352 info = ''
353 if 'run' in dir(subprocess):
354 info = subprocess.run(command, stdout=subprocess.PIPE,
355 universal_newlines=True)
356 version = info.stdout.strip()
357 else:
358 info = subprocess.check_output(command, universal_newlines=True)
359 version = info.strip()
360
361 return version
362
363
364 def print_header():
365 """Print the initial heading with date and time as well as info on the
366 Linux and ZFS versions. This is not called for the graph.
367 """
368
369 # datetime is now recommended over time but we keep the exact formatting
370 # from the older version of arc_summary.py in case there are scripts
371 # that expect it in this way
372 daydate = time.strftime(DATE_FORMAT)
373 spc_date = LINE_LENGTH-len(daydate)
374 sys_version = os.uname()
375
376 sys_msg = sys_version.sysname+' '+sys_version.release
377 zfs = get_version('zfs')
378 spc_zfs = LINE_LENGTH-len(zfs)
379
380 machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')'
381 spl = get_version('spl')
382 spc_spl = LINE_LENGTH-len(spl)
383
384 print('\n'+('-'*LINE_LENGTH))
385 print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date))
386 print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs))
387 print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl))
388
389
390 def print_raw(kstats_dict):
391 """Print all available data from the system in a minimally sorted format.
392 This can be used as a source to be piped through 'grep'.
393 """
394
395 sections = sorted(kstats_dict.keys())
396
397 for section in sections:
398
399 print('\n{0}:'.format(section.upper()))
400 lines = sorted(kstats_dict[section])
401
402 for line in lines:
403 name, value = cleanup_line(line)
404 print(format_raw_line(name, value))
405
406 # Tunables and SPL must be handled separately because they come from a
407 # different source and have descriptions the user might request
408 print()
409 section_spl()
410 section_tunables()
411
412
413 def isolate_section(section_name, kstats_dict):
414 """From the complete information on all sections, retrieve only those
415 for one section.
416 """
417
418 try:
419 section_data = kstats_dict[section_name]
420 except KeyError:
421 print('ERROR: Data on {0} not available'.format(section_data))
422 sys.exit(1)
423
424 section_dict = dict(cleanup_line(l) for l in section_data)
425
426 return section_dict
427
428
429 # Formatted output helper functions
430
431
432 def prt_1(text, value):
433 """Print text and one value, no indent"""
434 spc = ' '*(LINE_LENGTH-(len(text)+len(value)))
435 print('{0}{spc}{1}'.format(text, value, spc=spc))
436
437
438 def prt_i1(text, value):
439 """Print text and one value, with indent"""
440 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value)))
441 print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc))
442
443
444 def prt_2(text, value1, value2):
445 """Print text and two values, no indent"""
446 values = '{0:>9} {1:>9}'.format(value1, value2)
447 spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2))
448 print('{0}{spc} {1}'.format(text, values, spc=spc))
449
450
451 def prt_i2(text, value1, value2):
452 """Print text and two values, with indent"""
453 values = '{0:>9} {1:>9}'.format(value1, value2)
454 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2))
455 print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc))
456
457
458 # The section output concentrates on important parameters instead of
459 # being exhaustive (that is what the --raw parameter is for)
460
461
462 def section_arc(kstats_dict):
463 """Give basic information on the ARC, MRU and MFU. This is the first
464 and most used section.
465 """
466
467 arc_stats = isolate_section('arcstats', kstats_dict)
468
469 throttle = arc_stats['memory_throttle_count']
470
471 if throttle == '0':
472 health = 'HEALTHY'
473 else:
474 health = 'THROTTLED'
475
476 prt_1('ARC status:', health)
477 prt_i1('Memory throttle count:', throttle)
478 print()
479
480 arc_size = arc_stats['size']
481 arc_target_size = arc_stats['c']
482 arc_max = arc_stats['c_max']
483 arc_min = arc_stats['c_min']
484 mfu_size = arc_stats['mfu_size']
485 mru_size = arc_stats['mru_size']
486 target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min))
487
488 prt_2('ARC size (current):',
489 f_perc(arc_size, arc_max), f_bytes(arc_size))
490 prt_i2('Target size (adaptive):',
491 f_perc(arc_target_size, arc_max), f_bytes(arc_target_size))
492 prt_i2('Min size (hard limit):',
493 f_perc(arc_min, arc_max), f_bytes(arc_min))
494 prt_i2('Max size (high water):',
495 target_size_ratio, f_bytes(arc_max))
496 caches_size = int(mfu_size)+int(mru_size)
497 prt_i2('Most Frequently Used (MFU) cache size:',
498 f_perc(mfu_size, caches_size), f_bytes(mfu_size))
499 prt_i2('Most Recently Used (MRU) cache size:',
500 f_perc(mru_size, caches_size), f_bytes(mru_size))
501 print()
502
503 print('ARC hash breakdown:')
504 prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max']))
505 prt_i2('Elements current:',
506 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']),
507 f_hits(arc_stats['hash_elements']))
508 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions']))
509
510 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max']))
511 prt_i1('Chains:', f_hits(arc_stats['hash_chains']))
512 print()
513
514 print('ARC misc:')
515 prt_i1('Deleted:', f_hits(arc_stats['deleted']))
516 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss']))
517 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip']))
518 print()
519
520
521 def section_archits(kstats_dict):
522 """Print information on how the caches are accessed ("arc hits").
523 """
524
525 arc_stats = isolate_section('arcstats', kstats_dict)
526 all_accesses = int(arc_stats['hits'])+int(arc_stats['misses'])
527 actual_hits = int(arc_stats['mfu_hits'])+int(arc_stats['mru_hits'])
528
529 prt_1('ARC total accesses (hits + misses):', f_hits(all_accesses))
530 ta_todo = (('Cache hit ratio:', arc_stats['hits']),
531 ('Cache miss ratio:', arc_stats['misses']),
532 ('Actual hit ratio (MFU + MRU hits):', actual_hits))
533
534 for title, value in ta_todo:
535 prt_i2(title, f_perc(value, all_accesses), f_hits(value))
536
537 dd_total = int(arc_stats['demand_data_hits']) +\
538 int(arc_stats['demand_data_misses'])
539 prt_i2('Data demand efficiency:',
540 f_perc(arc_stats['demand_data_hits'], dd_total),
541 f_hits(dd_total))
542
543 dp_total = int(arc_stats['prefetch_data_hits']) +\
544 int(arc_stats['prefetch_data_misses'])
545 prt_i2('Data prefetch efficiency:',
546 f_perc(arc_stats['prefetch_data_hits'], dp_total),
547 f_hits(dp_total))
548
549 known_hits = int(arc_stats['mfu_hits']) +\
550 int(arc_stats['mru_hits']) +\
551 int(arc_stats['mfu_ghost_hits']) +\
552 int(arc_stats['mru_ghost_hits'])
553
554 anon_hits = int(arc_stats['hits'])-known_hits
555
556 print()
557 print('Cache hits by cache type:')
558 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']),
559 ('Most recently used (MRU):', arc_stats['mru_hits']),
560 ('Most frequently used (MFU) ghost:',
561 arc_stats['mfu_ghost_hits']),
562 ('Most recently used (MRU) ghost:',
563 arc_stats['mru_ghost_hits']))
564
565 for title, value in cl_todo:
566 prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
567
568 # For some reason, anon_hits can turn negative, which is weird. Until we
569 # have figured out why this happens, we just hide the problem, following
570 # the behavior of the original arc_summary.py
571 if anon_hits >= 0:
572 prt_i2('Anonymously used:',
573 f_perc(anon_hits, arc_stats['hits']), f_hits(anon_hits))
574
575 print()
576 print('Cache hits by data type:')
577 dt_todo = (('Demand data:', arc_stats['demand_data_hits']),
578 ('Demand perfetch data:', arc_stats['prefetch_data_hits']),
579 ('Demand metadata:', arc_stats['demand_metadata_hits']),
580 ('Demand prefetch metadata:',
581 arc_stats['prefetch_metadata_hits']))
582
583 for title, value in dt_todo:
584 prt_i2(title, f_perc(value, arc_stats['hits']), f_hits(value))
585
586 print()
587 print('Cache misses by data type:')
588 dm_todo = (('Demand data:', arc_stats['demand_data_misses']),
589 ('Demand prefetch data:',
590 arc_stats['prefetch_data_misses']),
591 ('Demand metadata:', arc_stats['demand_metadata_misses']),
592 ('Demand prefetch metadata:',
593 arc_stats['prefetch_metadata_misses']))
594
595 for title, value in dm_todo:
596 prt_i2(title, f_perc(value, arc_stats['misses']), f_hits(value))
597
598 print()
599
600
601 def section_dmu(kstats_dict):
602 """Collect information on the DMU"""
603
604 zfetch_stats = isolate_section('zfetchstats', kstats_dict)
605
606 zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses'])
607
608 prt_1('DMU prefetch efficiency:', f_hits(zfetch_access_total))
609 prt_i2('Hit ratio:', f_perc(zfetch_stats['hits'], zfetch_access_total),
610 f_hits(zfetch_stats['hits']))
611 prt_i2('Miss ratio:', f_perc(zfetch_stats['misses'], zfetch_access_total),
612 f_hits(zfetch_stats['misses']))
613 print()
614
615
616 def section_l2arc(kstats_dict):
617 """Collect information on L2ARC device if present. If not, tell user
618 that we're skipping the section.
619 """
620
621 # The L2ARC statistics live in the same section as the normal ARC stuff
622 arc_stats = isolate_section('arcstats', kstats_dict)
623
624 if arc_stats['l2_size'] == '0':
625 print('L2ARC not detected, skipping section\n')
626 return
627
628 l2_errors = int(arc_stats['l2_writes_error']) +\
629 int(arc_stats['l2_cksum_bad']) +\
630 int(arc_stats['l2_io_error'])
631
632 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses'])
633 health = 'HEALTHY'
634
635 if l2_errors > 0:
636 health = 'DEGRADED'
637
638 prt_1('L2ARC status:', health)
639
640 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'),
641 ('Free on write:', 'l2_free_on_write'),
642 ('R/W clashes:', 'l2_rw_clash'),
643 ('Bad checksums:', 'l2_cksum_bad'),
644 ('I/O errors:', 'l2_io_error'))
645
646 for title, value in l2_todo:
647 prt_i1(title, f_hits(arc_stats[value]))
648
649 print()
650 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size']))
651 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']),
652 f_bytes(arc_stats['l2_asize']))
653 prt_i2('Header size:',
654 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']),
655 f_bytes(arc_stats['l2_hdr_size']))
656
657 print()
658 prt_1('L2ARC breakdown:', f_hits(l2_access_total))
659 prt_i2('Hit ratio:',
660 f_perc(arc_stats['l2_hits'], l2_access_total),
661 f_bytes(arc_stats['l2_hits']))
662 prt_i2('Miss ratio:',
663 f_perc(arc_stats['l2_misses'], l2_access_total),
664 f_bytes(arc_stats['l2_misses']))
665 prt_i1('Feeds:', f_hits(arc_stats['l2_feeds']))
666
667 print()
668 print('L2ARC writes:')
669
670 if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']:
671 prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent']))
672 prt_i2('Done ratio:',
673 f_perc(arc_stats['l2_writes_done'],
674 arc_stats['l2_writes_sent']),
675 f_bytes(arc_stats['l2_writes_done']))
676 prt_i2('Error ratio:',
677 f_perc(arc_stats['l2_writes_error'],
678 arc_stats['l2_writes_sent']),
679 f_bytes(arc_stats['l2_writes_error']))
680 else:
681 prt_i2('Writes sent:', '100 %', f_bytes(arc_stats['l2_writes_sent']))
682
683 print()
684 print('L2ARC evicts:')
685 prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry']))
686 prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading']))
687 print()
688
689
690 def section_spl(*_):
691 """Print the SPL parameters, if requested with alternative format
692 and/or decriptions. This does not use kstats.
693 """
694
695 spls = get_spl_tunables(SPL_PATH)
696 keylist = sorted(spls.keys())
697 print('Solaris Porting Layer (SPL):')
698
699 if ARGS.desc:
700 descriptions = get_descriptions('spl')
701
702 for key in keylist:
703 value = spls[key]
704
705 if ARGS.desc:
706 try:
707 print(INDENT+'#', descriptions[key])
708 except KeyError:
709 print(INDENT+'# (No decription found)') # paranoid
710
711 print(format_raw_line(key, value))
712
713 print()
714
715
716 def section_tunables(*_):
717 """Print the tunables, if requested with alternative format and/or
718 decriptions. This does not use kstasts.
719 """
720
721 tunables = get_spl_tunables(TUNABLES_PATH)
722 keylist = sorted(tunables.keys())
723 print('Tunables:')
724
725 if ARGS.desc:
726 descriptions = get_descriptions('zfs')
727
728 for key in keylist:
729 value = tunables[key]
730
731 if ARGS.desc:
732 try:
733 print(INDENT+'#', descriptions[key])
734 except KeyError:
735 print(INDENT+'# (No decription found)') # paranoid
736
737 print(format_raw_line(key, value))
738
739 print()
740
741
742 def section_vdev(kstats_dict):
743 """Collect information on VDEV caches"""
744
745 # Currently [Nov 2017] the VDEV cache is disabled, because it is actually
746 # harmful. When this is the case, we just skip the whole entry. See
747 # https://github.com/zfsonlinux/zfs/blob/master/module/zfs/vdev_cache.c
748 # for details
749 tunables = get_spl_tunables(TUNABLES_PATH)
750
751 if tunables['zfs_vdev_cache_size'] == '0':
752 print('VDEV cache disabled, skipping section\n')
753 return
754
755 vdev_stats = isolate_section('vdev_cache_stats', kstats_dict)
756
757 vdev_cache_total = int(vdev_stats['hits']) +\
758 int(vdev_stats['misses']) +\
759 int(vdev_stats['delegations'])
760
761 prt_1('VDEV cache summary:', f_hits(vdev_cache_total))
762 prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total),
763 f_hits(vdev_stats['hits']))
764 prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total),
765 f_hits(vdev_stats['misses']))
766 prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total),
767 f_hits(vdev_stats['delegations']))
768 print()
769
770
771 def section_zil(kstats_dict):
772 """Collect information on the ZFS Intent Log. Some of the information
773 taken from https://github.com/zfsonlinux/zfs/blob/master/include/sys/zil.h
774 """
775
776 zil_stats = isolate_section('zil', kstats_dict)
777
778 prt_1('ZIL committed transactions:',
779 f_hits(zil_stats['zil_itx_count']))
780 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count']))
781 prt_i1('Flushes to stable storage:',
782 f_hits(zil_stats['zil_commit_writer_count']))
783 prt_i2('Transactions to SLOG storage pool:',
784 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']),
785 f_hits(zil_stats['zil_itx_metaslab_slog_count']))
786 prt_i2('Transactions to non-SLOG storage pool:',
787 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']),
788 f_hits(zil_stats['zil_itx_metaslab_normal_count']))
789 print()
790
791
792 section_calls = {'arc': section_arc,
793 'archits': section_archits,
794 'dmu': section_dmu,
795 'l2arc': section_l2arc,
796 'spl': section_spl,
797 'tunables': section_tunables,
798 'vdev': section_vdev,
799 'zil': section_zil}
800
801
802 def main():
803 """Run program. The options to draw a graph and to print all data raw are
804 treated separately because they come with their own call.
805 """
806
807 kstats = get_kstats()
808
809 if ARGS.graph:
810 draw_graph(kstats)
811 sys.exit(0)
812
813 print_header()
814
815 if ARGS.raw:
816 print_raw(kstats)
817
818 elif ARGS.section:
819
820 try:
821 section_calls[ARGS.section](kstats)
822 except KeyError:
823 print('Error: Section "{0}" unknown'.format(ARGS.section))
824 sys.exit(1)
825
826 elif ARGS.page:
827 print('WARNING: Pages are deprecated, please use "--section"\n')
828
829 pages_to_calls = {1: 'arc',
830 2: 'archits',
831 3: 'l2arc',
832 4: 'dmu',
833 5: 'vdev',
834 6: 'tunables'}
835
836 try:
837 call = pages_to_calls[ARGS.page]
838 except KeyError:
839 print('Error: Page "{0}" not supported'.format(ARGS.page))
840 sys.exit(1)
841 else:
842 section_calls[call](kstats)
843
844 else:
845 # If no parameters were given, we print all sections. We might want to
846 # change the sequence by hand
847 calls = sorted(section_calls.keys())
848
849 for section in calls:
850 section_calls[section](kstats)
851
852 sys.exit(0)
853
854
855 if __name__ == '__main__':
856 main()