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