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