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