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