]>
git.proxmox.com Git - mirror_zfs.git/blob - cmd/arc_summary/arc_summary3
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>
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
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.
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
30 """Print statistics on the ZFS ARC Cache and other information
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
46 DESCRIPTION
= 'Print ARC and other statistics for ZFS on Linux'
49 DATE_FORMAT
= '%a %b %d %H:%M:%S %Y'
50 TITLE
= 'ZFS Subsystem Report'
52 SECTIONS
= 'arc archits dmu l2arc spl tunables vdev zil'.split()
53 SECTION_HELP
= 'print info from one section ('+' '.join(SECTIONS
)+')'
55 # Tunables and SPL are handled separately because they come from
57 SECTION_PATHS
= {'arc': 'arcstats',
59 'l2arc': 'arcstats', # L2ARC stuff lives in arcstats
60 'vdev': 'vdev_cache_stats',
62 'zfetch': 'zfetchstats',
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',
69 parser
.add_argument('-d', '--description', action
='store_true', default
=False,
70 help='print descriptions with tunables and SPL',
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',
79 parser
.add_argument('-s', '--section', dest
='section', help=SECTION_HELP
)
80 ARGS
= parser
.parse_args()
83 if sys
.platform
.startswith('freebsd'):
84 # Requires py36-sysctl on FreeBSD
87 VDEV_CACHE_SIZE
= 'vdev.cache_size'
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
):],
94 return [fmt(kstat
) for kstat
in sysctl
.filter(base
)]
97 cut
= 8 # = len('vfs.zfs.')
98 return {ctl
.name
[cut
:]: str(ctl
.value
) for ctl
in sysctl
.filter(base
)}
100 def get_tunable_params():
101 return get_params('vfs.zfs')
103 def get_vdev_params():
104 return get_params('vfs.zfs.vdev')
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
)
114 def get_descriptions(_request
):
115 # py-sysctl doesn't give descriptions, so we have to shell out.
116 command
= ['sysctl', '-d', 'vfs.zfs']
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')
126 info
= subprocess
.check_output(command
, universal_newlines
=True)
127 lines
= info
.split('\n')
130 name
, desc
= line
.split(':', 1)
131 return (name
.strip(), desc
.strip())
133 return dict([fmt(line
) for line
in lines
if len(line
) > 0])
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'
141 VDEV_CACHE_SIZE
= 'zfs_vdev_cache_size'
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
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
154 for name
in os
.listdir(basepath
):
155 path
= os
.path
.join(basepath
, name
)
156 with
open(path
) as f
:
158 result
[name
] = value
.strip()
161 def get_spl_params():
162 return get_params(SPL_PATH
)
164 def get_tunable_params():
165 return get_params(TUNABLES_PATH
)
167 def get_vdev_params():
168 return get_params(TUNABLES_PATH
)
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()
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()
185 info
= subprocess
.check_output(command
, universal_newlines
=True)
186 version
= info
.strip()
190 def get_descriptions(request
):
191 """Get the descriptions of the Solaris Porting Layer (SPL) or the
192 tunables, return with minimal formatting.
195 if request
not in ('spl', 'zfs'):
196 print('ERROR: description of "{0}" requested)'.format(request
))
200 target_prefix
= 'parm:'
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"]
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)
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')
219 info
= subprocess
.check_output(command
,
220 universal_newlines
=True)
221 raw_output
= info
.split('\0')
223 except subprocess
.CalledProcessError
:
224 print("Error: Descriptions not available",
225 "(can't access kernel module)")
228 for line
in raw_output
:
230 if not line
.startswith(target_prefix
):
233 line
= line
[len(target_prefix
):].strip()
234 name
, raw_desc
= line
.split(':', 1)
235 desc
= raw_desc
.rsplit('(', 1)[0]
238 desc
= '(No description found)'
240 descs
[name
.strip()] = desc
.strip()
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").
251 name
, _
, value
= single_line
.split()
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.
263 arc_stats
= isolate_section('arcstats', kstats_dict
)
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'])
276 info_form
= ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} ({5}) '
278 info_line
= info_form
.format(arc_size
, arc_perc
, mfu_size
, mru_size
,
279 meta_size
, meta_limit
, dnode_size
,
281 info_spc
= ' '*int((GRAPH_WIDTH
-len(info_line
))/2)
282 info_line
= GRAPH_INDENT
+info_spc
+info_line
284 graph_line
= GRAPH_INDENT
+'+'+('-'*(GRAPH_WIDTH
-2))+'+'
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
)
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
+'|'
298 for line
in ('', info_line
, graph_line
, core_line
, graph_line
, ''):
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.
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)
318 bites
= int(byte_string
)
321 for limit
, unit
in prefixes
:
324 value
= bites
/ limit
327 result
= '{0:.1f} {1}'.format(value
, unit
)
329 result
= '{0} Bytes'.format(bites
)
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
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)
352 hits
= int(hits_string
)
355 for limit
, symbol
in numbers
:
361 result
= "%0.1f%s" % (value
, symbol
)
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
381 except ZeroDivisionError:
384 result
= '{0:0.1f} %'.format(perc
)
386 if result
== '0.0 %' and v1
> 0:
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.
398 result
= '{0}{1}={2}'.format(INDENT
, name
, value
)
400 spc
= LINE_LENGTH
-(len(INDENT
)+len(value
))
401 result
= '{0}{1:<{spc}}{2}'.format(INDENT
, name
, value
, spc
=spc
)
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
415 for section
in SECTION_PATHS
.values():
416 if section
not in result
:
417 result
[section
] = load_kstats(section
)
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.
428 if request
not in ('spl', 'zfs'):
429 error_msg
= '(ERROR: "{0}" requested)'.format(request
)
432 return get_version_impl(request
)
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.
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()
447 sys_msg
= sys_version
.sysname
+' '+sys_version
.release
448 zfs
= get_version('zfs')
449 spc_zfs
= LINE_LENGTH
-len(zfs
)
451 machine_msg
= 'Machine: '+sys_version
.nodename
+' ('+sys_version
.machine
+')'
452 spl
= get_version('spl')
453 spc_spl
= LINE_LENGTH
-len(spl
)
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
))
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'.
466 sections
= sorted(kstats_dict
.keys())
468 for section
in sections
:
470 print('\n{0}:'.format(section
.upper()))
471 lines
= sorted(kstats_dict
[section
])
474 name
, value
= cleanup_line(line
)
475 print(format_raw_line(name
, value
))
477 # Tunables and SPL must be handled separately because they come from a
478 # different source and have descriptions the user might request
484 def isolate_section(section_name
, kstats_dict
):
485 """From the complete information on all sections, retrieve only those
490 section_data
= kstats_dict
[section_name
]
492 print('ERROR: Data on {0} not available'.format(section_data
))
495 section_dict
= dict(cleanup_line(l
) for l
in section_data
)
500 # Formatted output helper functions
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
))
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
))
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
))
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
))
529 # The section output concentrates on important parameters instead of
530 # being exhaustive (that is what the --raw parameter is for)
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.
538 arc_stats
= isolate_section('arcstats', kstats_dict
)
540 throttle
= arc_stats
['memory_throttle_count']
547 prt_1('ARC status:', health
)
548 prt_i1('Memory throttle count:', throttle
)
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
))
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
))
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']))
593 prt_i1('Chain max:', f_hits(arc_stats
['hash_chain_max']))
594 prt_i1('Chains:', f_hits(arc_stats
['hash_chains']))
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']))
604 def section_archits(kstats_dict
):
605 """Print information on how the caches are accessed ("arc hits").
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'])
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
))
617 for title
, value
in ta_todo
:
618 prt_i2(title
, f_perc(value
, all_accesses
), f_hits(value
))
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
),
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
),
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'])
637 anon_hits
= int(arc_stats
['hits'])-known_hits
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']))
648 for title
, value
in cl_todo
:
649 prt_i2(title
, f_perc(value
, arc_stats
['hits']), f_hits(value
))
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.
655 prt_i2('Anonymously used:',
656 f_perc(anon_hits
, arc_stats
['hits']), f_hits(anon_hits
))
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']))
666 for title
, value
in dt_todo
:
667 prt_i2(title
, f_perc(value
, arc_stats
['hits']), f_hits(value
))
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']))
678 for title
, value
in dm_todo
:
679 prt_i2(title
, f_perc(value
, arc_stats
['misses']), f_hits(value
))
684 def section_dmu(kstats_dict
):
685 """Collect information on the DMU"""
687 zfetch_stats
= isolate_section('zfetchstats', kstats_dict
)
689 zfetch_access_total
= int(zfetch_stats
['hits'])+int(zfetch_stats
['misses'])
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']))
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.
704 # The L2ARC statistics live in the same section as the normal ARC stuff
705 arc_stats
= isolate_section('arcstats', kstats_dict
)
707 if arc_stats
['l2_size'] == '0':
708 print('L2ARC not detected, skipping section\n')
711 l2_errors
= int(arc_stats
['l2_writes_error']) +\
712 int(arc_stats
['l2_cksum_bad']) +\
713 int(arc_stats
['l2_io_error'])
715 l2_access_total
= int(arc_stats
['l2_hits'])+int(arc_stats
['l2_misses'])
721 prt_1('L2ARC status:', health
)
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'))
729 for title
, value
in l2_todo
:
730 prt_i1(title
, f_hits(arc_stats
[value
]))
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']))
741 prt_1('L2ARC breakdown:', f_hits(l2_access_total
))
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']))
751 print('L2ARC writes:')
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']))
764 prt_i2('Writes sent:', '100 %', f_bytes(arc_stats
['l2_writes_sent']))
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']))
774 """Print the SPL parameters, if requested with alternative format
775 and/or descriptions. This does not use kstats.
778 if sys
.platform
.startswith('freebsd'):
779 # No SPL support in FreeBSD
782 spls
= get_spl_params()
783 keylist
= sorted(spls
.keys())
784 print('Solaris Porting Layer (SPL):')
787 descriptions
= get_descriptions('spl')
794 print(INDENT
+'#', descriptions
[key
])
796 print(INDENT
+'# (No description found)') # paranoid
798 print(format_raw_line(key
, value
))
803 def section_tunables(*_
):
804 """Print the tunables, if requested with alternative format and/or
805 descriptions. This does not use kstasts.
808 tunables
= get_tunable_params()
809 keylist
= sorted(tunables
.keys())
813 descriptions
= get_descriptions('zfs')
816 value
= tunables
[key
]
820 print(INDENT
+'#', descriptions
[key
])
822 print(INDENT
+'# (No description found)') # paranoid
824 print(format_raw_line(key
, value
))
829 def section_vdev(kstats_dict
):
830 """Collect information on VDEV caches"""
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
836 tunables
= get_vdev_params()
838 if tunables
[VDEV_CACHE_SIZE
] == '0':
839 print('VDEV cache disabled, skipping section\n')
842 vdev_stats
= isolate_section('vdev_cache_stats', kstats_dict
)
844 vdev_cache_total
= int(vdev_stats
['hits']) +\
845 int(vdev_stats
['misses']) +\
846 int(vdev_stats
['delegations'])
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']))
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
863 zil_stats
= isolate_section('zil', kstats_dict
)
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']))
879 section_calls
= {'arc': section_arc
,
880 'archits': section_archits
,
882 'l2arc': section_l2arc
,
884 'tunables': section_tunables
,
885 'vdev': section_vdev
,
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.
894 kstats
= get_kstats()
908 section_calls
[ARGS
.section
](kstats
)
910 print('Error: Section "{0}" unknown'.format(ARGS
.section
))
914 print('WARNING: Pages are deprecated, please use "--section"\n')
916 pages_to_calls
= {1: 'arc',
924 call
= pages_to_calls
[ARGS
.page
]
926 print('Error: Page "{0}" not supported'.format(ARGS
.page
))
929 section_calls
[call
](kstats
)
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())
936 for section
in calls
:
937 section_calls
[section
](kstats
)
942 if __name__
== '__main__':