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_size = f_bytes(arc_stats['arc_meta_used']) 274 dnode_limit = f_bytes(arc_stats['arc_dnode_limit']) 275 dnode_size = f_bytes(arc_stats['dnode_size']) 276 277 info_form = ('ARC: {0} ({1}) MFU: {2} MRU: {3} META: {4} ' 278 'DNODE {5} ({6})') 279 info_line = info_form.format(arc_size, arc_perc, mfu_size, mru_size, 280 meta_size, dnode_size, dnode_limit) 281 info_spc = ' '*int((GRAPH_WIDTH-len(info_line))/2) 282 info_line = GRAPH_INDENT+info_spc+info_line 283 284 graph_line = GRAPH_INDENT+'+'+('-'*(GRAPH_WIDTH-2))+'+' 285 286 mfu_perc = float(int(arc_stats['mfu_size'])/int(arc_stats['c_max'])) 287 mru_perc = float(int(arc_stats['mru_size'])/int(arc_stats['c_max'])) 288 arc_perc = float(int(arc_stats['size'])/int(arc_stats['c_max'])) 289 total_ticks = float(arc_perc)*GRAPH_WIDTH 290 mfu_ticks = mfu_perc*GRAPH_WIDTH 291 mru_ticks = mru_perc*GRAPH_WIDTH 292 other_ticks = total_ticks-(mfu_ticks+mru_ticks) 293 294 core_form = 'F'*int(mfu_ticks)+'R'*int(mru_ticks)+'O'*int(other_ticks) 295 core_spc = ' '*(GRAPH_WIDTH-(2+len(core_form))) 296 core_line = GRAPH_INDENT+'|'+core_form+core_spc+'|' 297 298 for line in ('', info_line, graph_line, core_line, graph_line, ''): 299 print(line) 300 301 302def f_bytes(byte_string): 303 """Return human-readable representation of a byte value in 304 powers of 2 (eg "KiB" for "kibibytes", etc) to two decimal 305 points. Values smaller than one KiB are returned without 306 decimal points. Note "bytes" is a reserved keyword. 307 """ 308 309 prefixes = ([2**80, "YiB"], # yobibytes (yotta) 310 [2**70, "ZiB"], # zebibytes (zetta) 311 [2**60, "EiB"], # exbibytes (exa) 312 [2**50, "PiB"], # pebibytes (peta) 313 [2**40, "TiB"], # tebibytes (tera) 314 [2**30, "GiB"], # gibibytes (giga) 315 [2**20, "MiB"], # mebibytes (mega) 316 [2**10, "KiB"]) # kibibytes (kilo) 317 318 bites = int(byte_string) 319 320 if bites >= 2**10: 321 for limit, unit in prefixes: 322 323 if bites >= limit: 324 value = bites / limit 325 break 326 327 result = '{0:.1f} {1}'.format(value, unit) 328 else: 329 result = '{0} Bytes'.format(bites) 330 331 return result 332 333 334def f_hits(hits_string): 335 """Create a human-readable representation of the number of hits. 336 The single-letter symbols used are SI to avoid the confusion caused 337 by the different "short scale" and "long scale" representations in 338 English, which use the same words for different values. See 339 https://en.wikipedia.org/wiki/Names_of_large_numbers and: 340 https://physics.nist.gov/cuu/Units/prefixes.html 341 """ 342 343 numbers = ([10**24, 'Y'], # yotta (septillion) 344 [10**21, 'Z'], # zetta (sextillion) 345 [10**18, 'E'], # exa (quintrillion) 346 [10**15, 'P'], # peta (quadrillion) 347 [10**12, 'T'], # tera (trillion) 348 [10**9, 'G'], # giga (billion) 349 [10**6, 'M'], # mega (million) 350 [10**3, 'k']) # kilo (thousand) 351 352 hits = int(hits_string) 353 354 if hits >= 1000: 355 for limit, symbol in numbers: 356 357 if hits >= limit: 358 value = hits/limit 359 break 360 361 result = "%0.1f%s" % (value, symbol) 362 else: 363 result = "%d" % hits 364 365 return result 366 367 368def f_perc(value1, value2): 369 """Calculate percentage and return in human-readable form. If 370 rounding produces the result '0.0' though the first number is 371 not zero, include a 'less-than' symbol to avoid confusion. 372 Division by zero is handled by returning 'n/a'; no error 373 is called. 374 """ 375 376 v1 = float(value1) 377 v2 = float(value2) 378 379 try: 380 perc = 100 * v1/v2 381 except ZeroDivisionError: 382 result = 'n/a' 383 else: 384 result = '{0:0.1f} %'.format(perc) 385 386 if result == '0.0 %' and v1 > 0: 387 result = '< 0.1 %' 388 389 return result 390 391 392def format_raw_line(name, value): 393 """For the --raw option for the tunable and SPL outputs, decide on the 394 correct formatting based on the --alternate flag. 395 """ 396 397 if ARGS.alt: 398 result = '{0}{1}={2}'.format(INDENT, name, value) 399 else: 400 # Right-align the value within the line length if it fits, 401 # otherwise just separate it from the name by a single space. 402 fit = LINE_LENGTH - len(INDENT) - len(name) 403 overflow = len(value) + 1 404 w = max(fit, overflow) 405 result = '{0}{1}{2:>{w}}'.format(INDENT, name, value, w=w) 406 407 return result 408 409 410def get_kstats(): 411 """Collect information on the ZFS subsystem. The step does not perform any 412 further processing, giving us the option to only work on what is actually 413 needed. The name "kstat" is a holdover from the Solaris utility of the same 414 name. 415 """ 416 417 result = {} 418 419 for section in SECTION_PATHS.values(): 420 if section not in result: 421 result[section] = load_kstats(section) 422 423 return result 424 425 426def get_version(request): 427 """Get the version number of ZFS or SPL on this machine for header. 428 Returns an error string, but does not raise an error, if we can't 429 get the ZFS/SPL version. 430 """ 431 432 if request not in ('spl', 'zfs'): 433 error_msg = '(ERROR: "{0}" requested)'.format(request) 434 return error_msg 435 436 return get_version_impl(request) 437 438 439def print_header(): 440 """Print the initial heading with date and time as well as info on the 441 kernel and ZFS versions. This is not called for the graph. 442 """ 443 444 # datetime is now recommended over time but we keep the exact formatting 445 # from the older version of arc_summary in case there are scripts 446 # that expect it in this way 447 daydate = time.strftime(DATE_FORMAT) 448 spc_date = LINE_LENGTH-len(daydate) 449 sys_version = os.uname() 450 451 sys_msg = sys_version.sysname+' '+sys_version.release 452 zfs = get_version('zfs') 453 spc_zfs = LINE_LENGTH-len(zfs) 454 455 machine_msg = 'Machine: '+sys_version.nodename+' ('+sys_version.machine+')' 456 spl = get_version('spl') 457 spc_spl = LINE_LENGTH-len(spl) 458 459 print('\n'+('-'*LINE_LENGTH)) 460 print('{0:<{spc}}{1}'.format(TITLE, daydate, spc=spc_date)) 461 print('{0:<{spc}}{1}'.format(sys_msg, zfs, spc=spc_zfs)) 462 print('{0:<{spc}}{1}\n'.format(machine_msg, spl, spc=spc_spl)) 463 464 465def print_raw(kstats_dict): 466 """Print all available data from the system in a minimally sorted format. 467 This can be used as a source to be piped through 'grep'. 468 """ 469 470 sections = sorted(kstats_dict.keys()) 471 472 for section in sections: 473 474 print('\n{0}:'.format(section.upper())) 475 lines = sorted(kstats_dict[section]) 476 477 for line in lines: 478 name, value = cleanup_line(line) 479 print(format_raw_line(name, value)) 480 481 # Tunables and SPL must be handled separately because they come from a 482 # different source and have descriptions the user might request 483 print() 484 section_spl() 485 section_tunables() 486 487 488def isolate_section(section_name, kstats_dict): 489 """From the complete information on all sections, retrieve only those 490 for one section. 491 """ 492 493 try: 494 section_data = kstats_dict[section_name] 495 except KeyError: 496 print('ERROR: Data on {0} not available'.format(section_data)) 497 sys.exit(1) 498 499 section_dict = dict(cleanup_line(l) for l in section_data) 500 501 return section_dict 502 503 504# Formatted output helper functions 505 506 507def prt_1(text, value): 508 """Print text and one value, no indent""" 509 spc = ' '*(LINE_LENGTH-(len(text)+len(value))) 510 print('{0}{spc}{1}'.format(text, value, spc=spc)) 511 512 513def prt_i1(text, value): 514 """Print text and one value, with indent""" 515 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(value))) 516 print(INDENT+'{0}{spc}{1}'.format(text, value, spc=spc)) 517 518 519def prt_2(text, value1, value2): 520 """Print text and two values, no indent""" 521 values = '{0:>9} {1:>9}'.format(value1, value2) 522 spc = ' '*(LINE_LENGTH-(len(text)+len(values)+2)) 523 print('{0}{spc} {1}'.format(text, values, spc=spc)) 524 525 526def prt_i2(text, value1, value2): 527 """Print text and two values, with indent""" 528 values = '{0:>9} {1:>9}'.format(value1, value2) 529 spc = ' '*(LINE_LENGTH-(len(INDENT)+len(text)+len(values)+2)) 530 print(INDENT+'{0}{spc} {1}'.format(text, values, spc=spc)) 531 532 533# The section output concentrates on important parameters instead of 534# being exhaustive (that is what the --raw parameter is for) 535 536 537def section_arc(kstats_dict): 538 """Give basic information on the ARC, MRU and MFU. This is the first 539 and most used section. 540 """ 541 542 arc_stats = isolate_section('arcstats', kstats_dict) 543 544 throttle = arc_stats['memory_throttle_count'] 545 546 if throttle == '0': 547 health = 'HEALTHY' 548 else: 549 health = 'THROTTLED' 550 551 prt_1('ARC status:', health) 552 prt_i1('Memory throttle count:', throttle) 553 print() 554 555 arc_size = arc_stats['size'] 556 arc_target_size = arc_stats['c'] 557 arc_max = arc_stats['c_max'] 558 arc_min = arc_stats['c_min'] 559 meta = arc_stats['meta'] 560 pd = arc_stats['pd'] 561 pm = arc_stats['pm'] 562 anon_data = arc_stats['anon_data'] 563 anon_metadata = arc_stats['anon_metadata'] 564 mfu_data = arc_stats['mfu_data'] 565 mfu_metadata = arc_stats['mfu_metadata'] 566 mru_data = arc_stats['mru_data'] 567 mru_metadata = arc_stats['mru_metadata'] 568 mfug_data = arc_stats['mfu_ghost_data'] 569 mfug_metadata = arc_stats['mfu_ghost_metadata'] 570 mrug_data = arc_stats['mru_ghost_data'] 571 mrug_metadata = arc_stats['mru_ghost_metadata'] 572 unc_data = arc_stats['uncached_data'] 573 unc_metadata = arc_stats['uncached_metadata'] 574 bonus_size = arc_stats['bonus_size'] 575 dnode_limit = arc_stats['arc_dnode_limit'] 576 dnode_size = arc_stats['dnode_size'] 577 dbuf_size = arc_stats['dbuf_size'] 578 hdr_size = arc_stats['hdr_size'] 579 l2_hdr_size = arc_stats['l2_hdr_size'] 580 abd_chunk_waste_size = arc_stats['abd_chunk_waste_size'] 581 target_size_ratio = '{0}:1'.format(int(arc_max) // int(arc_min)) 582 583 prt_2('ARC size (current):', 584 f_perc(arc_size, arc_max), f_bytes(arc_size)) 585 prt_i2('Target size (adaptive):', 586 f_perc(arc_target_size, arc_max), f_bytes(arc_target_size)) 587 prt_i2('Min size (hard limit):', 588 f_perc(arc_min, arc_max), f_bytes(arc_min)) 589 prt_i2('Max size (high water):', 590 target_size_ratio, f_bytes(arc_max)) 591 caches_size = int(anon_data)+int(anon_metadata)+\ 592 int(mfu_data)+int(mfu_metadata)+int(mru_data)+int(mru_metadata)+\ 593 int(unc_data)+int(unc_metadata) 594 prt_i2('Anonymous data size:', 595 f_perc(anon_data, caches_size), f_bytes(anon_data)) 596 prt_i2('Anonymous metadata size:', 597 f_perc(anon_metadata, caches_size), f_bytes(anon_metadata)) 598 s = 4294967296 599 v = (s-int(pd))*(s-int(meta))/s 600 prt_i2('MFU data target:', f_perc(v, s), 601 f_bytes(v / 65536 * caches_size / 65536)) 602 prt_i2('MFU data size:', 603 f_perc(mfu_data, caches_size), f_bytes(mfu_data)) 604 prt_i1('MFU ghost data size:', f_bytes(mfug_data)) 605 v = (s-int(pm))*int(meta)/s 606 prt_i2('MFU metadata target:', f_perc(v, s), 607 f_bytes(v / 65536 * caches_size / 65536)) 608 prt_i2('MFU metadata size:', 609 f_perc(mfu_metadata, caches_size), f_bytes(mfu_metadata)) 610 prt_i1('MFU ghost metadata size:', f_bytes(mfug_metadata)) 611 v = int(pd)*(s-int(meta))/s 612 prt_i2('MRU data target:', f_perc(v, s), 613 f_bytes(v / 65536 * caches_size / 65536)) 614 prt_i2('MRU data size:', 615 f_perc(mru_data, caches_size), f_bytes(mru_data)) 616 prt_i1('MRU ghost data size:', f_bytes(mrug_data)) 617 v = int(pm)*int(meta)/s 618 prt_i2('MRU metadata target:', f_perc(v, s), 619 f_bytes(v / 65536 * caches_size / 65536)) 620 prt_i2('MRU metadata size:', 621 f_perc(mru_metadata, caches_size), f_bytes(mru_metadata)) 622 prt_i1('MRU ghost metadata size:', f_bytes(mrug_metadata)) 623 prt_i2('Uncached data size:', 624 f_perc(unc_data, caches_size), f_bytes(unc_data)) 625 prt_i2('Uncached metadata size:', 626 f_perc(unc_metadata, caches_size), f_bytes(unc_metadata)) 627 prt_i2('Bonus size:', 628 f_perc(bonus_size, arc_size), f_bytes(bonus_size)) 629 prt_i2('Dnode cache target:', 630 f_perc(dnode_limit, arc_max), f_bytes(dnode_limit)) 631 prt_i2('Dnode cache size:', 632 f_perc(dnode_size, dnode_limit), f_bytes(dnode_size)) 633 prt_i2('Dbuf size:', 634 f_perc(dbuf_size, arc_size), f_bytes(dbuf_size)) 635 prt_i2('Header size:', 636 f_perc(hdr_size, arc_size), f_bytes(hdr_size)) 637 prt_i2('L2 header size:', 638 f_perc(l2_hdr_size, arc_size), f_bytes(l2_hdr_size)) 639 prt_i2('ABD chunk waste size:', 640 f_perc(abd_chunk_waste_size, arc_size), f_bytes(abd_chunk_waste_size)) 641 print() 642 643 print('ARC hash breakdown:') 644 prt_i1('Elements max:', f_hits(arc_stats['hash_elements_max'])) 645 prt_i2('Elements current:', 646 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']), 647 f_hits(arc_stats['hash_elements'])) 648 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions'])) 649 650 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max'])) 651 prt_i1('Chains:', f_hits(arc_stats['hash_chains'])) 652 print() 653 654 print('ARC misc:') 655 prt_i1('Deleted:', f_hits(arc_stats['deleted'])) 656 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss'])) 657 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip'])) 658 prt_i1('Eviction skips due to L2 writes:', 659 f_hits(arc_stats['evict_l2_skip'])) 660 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached'])) 661 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible'])) 662 prt_i2('L2 eligible MFU evictions:', 663 f_perc(arc_stats['evict_l2_eligible_mfu'], 664 arc_stats['evict_l2_eligible']), 665 f_bytes(arc_stats['evict_l2_eligible_mfu'])) 666 prt_i2('L2 eligible MRU evictions:', 667 f_perc(arc_stats['evict_l2_eligible_mru'], 668 arc_stats['evict_l2_eligible']), 669 f_bytes(arc_stats['evict_l2_eligible_mru'])) 670 prt_i1('L2 ineligible evictions:', 671 f_bytes(arc_stats['evict_l2_ineligible'])) 672 print() 673 674 675def section_archits(kstats_dict): 676 """Print information on how the caches are accessed ("arc hits"). 677 """ 678 679 arc_stats = isolate_section('arcstats', kstats_dict) 680 all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\ 681 int(arc_stats['misses']) 682 683 prt_1('ARC total accesses:', f_hits(all_accesses)) 684 ta_todo = (('Total hits:', arc_stats['hits']), 685 ('Total I/O hits:', arc_stats['iohits']), 686 ('Total misses:', arc_stats['misses'])) 687 for title, value in ta_todo: 688 prt_i2(title, f_perc(value, all_accesses), f_hits(value)) 689 print() 690 691 dd_total = int(arc_stats['demand_data_hits']) +\ 692 int(arc_stats['demand_data_iohits']) +\ 693 int(arc_stats['demand_data_misses']) 694 prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses), 695 f_hits(dd_total)) 696 dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']), 697 ('Demand data I/O hits:', arc_stats['demand_data_iohits']), 698 ('Demand data misses:', arc_stats['demand_data_misses'])) 699 for title, value in dd_todo: 700 prt_i2(title, f_perc(value, dd_total), f_hits(value)) 701 print() 702 703 dm_total = int(arc_stats['demand_metadata_hits']) +\ 704 int(arc_stats['demand_metadata_iohits']) +\ 705 int(arc_stats['demand_metadata_misses']) 706 prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses), 707 f_hits(dm_total)) 708 dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']), 709 ('Demand metadata I/O hits:', 710 arc_stats['demand_metadata_iohits']), 711 ('Demand metadata misses:', arc_stats['demand_metadata_misses'])) 712 for title, value in dm_todo: 713 prt_i2(title, f_perc(value, dm_total), f_hits(value)) 714 print() 715 716 pd_total = int(arc_stats['prefetch_data_hits']) +\ 717 int(arc_stats['prefetch_data_iohits']) +\ 718 int(arc_stats['prefetch_data_misses']) 719 prt_2('ARC prefetch metadata accesses:', f_perc(pd_total, all_accesses), 720 f_hits(pd_total)) 721 pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']), 722 ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']), 723 ('Prefetch data misses:', arc_stats['prefetch_data_misses'])) 724 for title, value in pd_todo: 725 prt_i2(title, f_perc(value, pd_total), f_hits(value)) 726 print() 727 728 pm_total = int(arc_stats['prefetch_metadata_hits']) +\ 729 int(arc_stats['prefetch_metadata_iohits']) +\ 730 int(arc_stats['prefetch_metadata_misses']) 731 prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses), 732 f_hits(pm_total)) 733 pm_todo = (('Prefetch metadata hits:', 734 arc_stats['prefetch_metadata_hits']), 735 ('Prefetch metadata I/O hits:', 736 arc_stats['prefetch_metadata_iohits']), 737 ('Prefetch metadata misses:', 738 arc_stats['prefetch_metadata_misses'])) 739 for title, value in pm_todo: 740 prt_i2(title, f_perc(value, pm_total), f_hits(value)) 741 print() 742 743 all_prefetches = int(arc_stats['predictive_prefetch'])+\ 744 int(arc_stats['prescient_prefetch']) 745 prt_2('ARC predictive prefetches:', 746 f_perc(arc_stats['predictive_prefetch'], all_prefetches), 747 f_hits(arc_stats['predictive_prefetch'])) 748 prt_i2('Demand hits after predictive:', 749 f_perc(arc_stats['demand_hit_predictive_prefetch'], 750 arc_stats['predictive_prefetch']), 751 f_hits(arc_stats['demand_hit_predictive_prefetch'])) 752 prt_i2('Demand I/O hits after predictive:', 753 f_perc(arc_stats['demand_iohit_predictive_prefetch'], 754 arc_stats['predictive_prefetch']), 755 f_hits(arc_stats['demand_iohit_predictive_prefetch'])) 756 never = int(arc_stats['predictive_prefetch']) -\ 757 int(arc_stats['demand_hit_predictive_prefetch']) -\ 758 int(arc_stats['demand_iohit_predictive_prefetch']) 759 prt_i2('Never demanded after predictive:', 760 f_perc(never, arc_stats['predictive_prefetch']), 761 f_hits(never)) 762 print() 763 764 prt_2('ARC prescient prefetches:', 765 f_perc(arc_stats['prescient_prefetch'], all_prefetches), 766 f_hits(arc_stats['prescient_prefetch'])) 767 prt_i2('Demand hits after prescient:', 768 f_perc(arc_stats['demand_hit_prescient_prefetch'], 769 arc_stats['prescient_prefetch']), 770 f_hits(arc_stats['demand_hit_prescient_prefetch'])) 771 prt_i2('Demand I/O hits after prescient:', 772 f_perc(arc_stats['demand_iohit_prescient_prefetch'], 773 arc_stats['prescient_prefetch']), 774 f_hits(arc_stats['demand_iohit_prescient_prefetch'])) 775 never = int(arc_stats['prescient_prefetch'])-\ 776 int(arc_stats['demand_hit_prescient_prefetch'])-\ 777 int(arc_stats['demand_iohit_prescient_prefetch']) 778 prt_i2('Never demanded after prescient:', 779 f_perc(never, arc_stats['prescient_prefetch']), 780 f_hits(never)) 781 print() 782 783 print('ARC states hits of all accesses:') 784 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']), 785 ('Most recently used (MRU):', arc_stats['mru_hits']), 786 ('Most frequently used (MFU) ghost:', 787 arc_stats['mfu_ghost_hits']), 788 ('Most recently used (MRU) ghost:', 789 arc_stats['mru_ghost_hits']), 790 ('Uncached:', arc_stats['uncached_hits'])) 791 for title, value in cl_todo: 792 prt_i2(title, f_perc(value, all_accesses), f_hits(value)) 793 print() 794 795 796def section_dmu(kstats_dict): 797 """Collect information on the DMU""" 798 799 zfetch_stats = isolate_section('zfetchstats', kstats_dict) 800 801 zfetch_access_total = int(zfetch_stats['hits'])+int(zfetch_stats['misses']) 802 803 prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total)) 804 prt_i2('Stream hits:', 805 f_perc(zfetch_stats['hits'], zfetch_access_total), 806 f_hits(zfetch_stats['hits'])) 807 prt_i2('Stream misses:', 808 f_perc(zfetch_stats['misses'], zfetch_access_total), 809 f_hits(zfetch_stats['misses'])) 810 prt_i2('Streams limit reached:', 811 f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']), 812 f_hits(zfetch_stats['max_streams'])) 813 prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued'])) 814 print() 815 816 817def section_l2arc(kstats_dict): 818 """Collect information on L2ARC device if present. If not, tell user 819 that we're skipping the section. 820 """ 821 822 # The L2ARC statistics live in the same section as the normal ARC stuff 823 arc_stats = isolate_section('arcstats', kstats_dict) 824 825 if arc_stats['l2_size'] == '0': 826 print('L2ARC not detected, skipping section\n') 827 return 828 829 l2_errors = int(arc_stats['l2_writes_error']) +\ 830 int(arc_stats['l2_cksum_bad']) +\ 831 int(arc_stats['l2_io_error']) 832 833 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses']) 834 health = 'HEALTHY' 835 836 if l2_errors > 0: 837 health = 'DEGRADED' 838 839 prt_1('L2ARC status:', health) 840 841 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'), 842 ('Free on write:', 'l2_free_on_write'), 843 ('R/W clashes:', 'l2_rw_clash'), 844 ('Bad checksums:', 'l2_cksum_bad'), 845 ('I/O errors:', 'l2_io_error')) 846 847 for title, value in l2_todo: 848 prt_i1(title, f_hits(arc_stats[value])) 849 850 print() 851 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size'])) 852 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']), 853 f_bytes(arc_stats['l2_asize'])) 854 prt_i2('Header size:', 855 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']), 856 f_bytes(arc_stats['l2_hdr_size'])) 857 prt_i2('MFU allocated size:', 858 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']), 859 f_bytes(arc_stats['l2_mfu_asize'])) 860 prt_i2('MRU allocated size:', 861 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']), 862 f_bytes(arc_stats['l2_mru_asize'])) 863 prt_i2('Prefetch allocated size:', 864 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']), 865 f_bytes(arc_stats['l2_prefetch_asize'])) 866 prt_i2('Data (buffer content) allocated size:', 867 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']), 868 f_bytes(arc_stats['l2_bufc_data_asize'])) 869 prt_i2('Metadata (buffer content) allocated size:', 870 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']), 871 f_bytes(arc_stats['l2_bufc_metadata_asize'])) 872 873 print() 874 prt_1('L2ARC breakdown:', f_hits(l2_access_total)) 875 prt_i2('Hit ratio:', 876 f_perc(arc_stats['l2_hits'], l2_access_total), 877 f_hits(arc_stats['l2_hits'])) 878 prt_i2('Miss ratio:', 879 f_perc(arc_stats['l2_misses'], l2_access_total), 880 f_hits(arc_stats['l2_misses'])) 881 prt_i1('Feeds:', f_hits(arc_stats['l2_feeds'])) 882 883 print() 884 print('L2ARC writes:') 885 886 if arc_stats['l2_writes_done'] != arc_stats['l2_writes_sent']: 887 prt_i2('Writes sent:', 'FAULTED', f_hits(arc_stats['l2_writes_sent'])) 888 prt_i2('Done ratio:', 889 f_perc(arc_stats['l2_writes_done'], 890 arc_stats['l2_writes_sent']), 891 f_hits(arc_stats['l2_writes_done'])) 892 prt_i2('Error ratio:', 893 f_perc(arc_stats['l2_writes_error'], 894 arc_stats['l2_writes_sent']), 895 f_hits(arc_stats['l2_writes_error'])) 896 else: 897 prt_i2('Writes sent:', '100 %', f_hits(arc_stats['l2_writes_sent'])) 898 899 print() 900 print('L2ARC evicts:') 901 prt_i1('Lock retries:', f_hits(arc_stats['l2_evict_lock_retry'])) 902 prt_i1('Upon reading:', f_hits(arc_stats['l2_evict_reading'])) 903 print() 904 905 906def section_spl(*_): 907 """Print the SPL parameters, if requested with alternative format 908 and/or descriptions. This does not use kstats. 909 """ 910 911 if sys.platform.startswith('freebsd'): 912 # No SPL support in FreeBSD 913 return 914 915 spls = get_spl_params() 916 keylist = sorted(spls.keys()) 917 print('Solaris Porting Layer (SPL):') 918 919 if ARGS.desc: 920 descriptions = get_descriptions('spl') 921 922 for key in keylist: 923 value = spls[key] 924 925 if ARGS.desc: 926 try: 927 print(INDENT+'#', descriptions[key]) 928 except KeyError: 929 print(INDENT+'# (No description found)') # paranoid 930 931 print(format_raw_line(key, value)) 932 933 print() 934 935 936def section_tunables(*_): 937 """Print the tunables, if requested with alternative format and/or 938 descriptions. This does not use kstasts. 939 """ 940 941 tunables = get_tunable_params() 942 keylist = sorted(tunables.keys()) 943 print('Tunables:') 944 945 if ARGS.desc: 946 descriptions = get_descriptions('zfs') 947 948 for key in keylist: 949 value = tunables[key] 950 951 if ARGS.desc: 952 try: 953 print(INDENT+'#', descriptions[key]) 954 except KeyError: 955 print(INDENT+'# (No description found)') # paranoid 956 957 print(format_raw_line(key, value)) 958 959 print() 960 961 962def section_vdev(kstats_dict): 963 """Collect information on VDEV caches""" 964 965 # Currently [Nov 2017] the VDEV cache is disabled, because it is actually 966 # harmful. When this is the case, we just skip the whole entry. See 967 # https://github.com/openzfs/zfs/blob/master/module/zfs/vdev_cache.c 968 # for details 969 tunables = get_vdev_params() 970 971 if tunables[VDEV_CACHE_SIZE] == '0': 972 print('VDEV cache disabled, skipping section\n') 973 return 974 975 vdev_stats = isolate_section('vdev_cache_stats', kstats_dict) 976 977 vdev_cache_total = int(vdev_stats['hits']) +\ 978 int(vdev_stats['misses']) +\ 979 int(vdev_stats['delegations']) 980 981 prt_1('VDEV cache summary:', f_hits(vdev_cache_total)) 982 prt_i2('Hit ratio:', f_perc(vdev_stats['hits'], vdev_cache_total), 983 f_hits(vdev_stats['hits'])) 984 prt_i2('Miss ratio:', f_perc(vdev_stats['misses'], vdev_cache_total), 985 f_hits(vdev_stats['misses'])) 986 prt_i2('Delegations:', f_perc(vdev_stats['delegations'], vdev_cache_total), 987 f_hits(vdev_stats['delegations'])) 988 print() 989 990 991def section_zil(kstats_dict): 992 """Collect information on the ZFS Intent Log. Some of the information 993 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h 994 """ 995 996 zil_stats = isolate_section('zil', kstats_dict) 997 998 prt_1('ZIL committed transactions:', 999 f_hits(zil_stats['zil_itx_count'])) 1000 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count'])) 1001 prt_i1('Flushes to stable storage:', 1002 f_hits(zil_stats['zil_commit_writer_count'])) 1003 prt_i2('Transactions to SLOG storage pool:', 1004 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']), 1005 f_hits(zil_stats['zil_itx_metaslab_slog_count'])) 1006 prt_i2('Transactions to non-SLOG storage pool:', 1007 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']), 1008 f_hits(zil_stats['zil_itx_metaslab_normal_count'])) 1009 print() 1010 1011 1012section_calls = {'arc': section_arc, 1013 'archits': section_archits, 1014 'dmu': section_dmu, 1015 'l2arc': section_l2arc, 1016 'spl': section_spl, 1017 'tunables': section_tunables, 1018 'vdev': section_vdev, 1019 'zil': section_zil} 1020 1021 1022def main(): 1023 """Run program. The options to draw a graph and to print all data raw are 1024 treated separately because they come with their own call. 1025 """ 1026 1027 kstats = get_kstats() 1028 1029 if ARGS.graph: 1030 draw_graph(kstats) 1031 sys.exit(0) 1032 1033 print_header() 1034 1035 if ARGS.raw: 1036 print_raw(kstats) 1037 1038 elif ARGS.section: 1039 1040 try: 1041 section_calls[ARGS.section](kstats) 1042 except KeyError: 1043 print('Error: Section "{0}" unknown'.format(ARGS.section)) 1044 sys.exit(1) 1045 1046 elif ARGS.page: 1047 print('WARNING: Pages are deprecated, please use "--section"\n') 1048 1049 pages_to_calls = {1: 'arc', 1050 2: 'archits', 1051 3: 'l2arc', 1052 4: 'dmu', 1053 5: 'vdev', 1054 6: 'tunables'} 1055 1056 try: 1057 call = pages_to_calls[ARGS.page] 1058 except KeyError: 1059 print('Error: Page "{0}" not supported'.format(ARGS.page)) 1060 sys.exit(1) 1061 else: 1062 section_calls[call](kstats) 1063 1064 else: 1065 # If no parameters were given, we print all sections. We might want to 1066 # change the sequence by hand 1067 calls = sorted(section_calls.keys()) 1068 1069 for section in calls: 1070 section_calls[section](kstats) 1071 1072 sys.exit(0) 1073 1074 1075if __name__ == '__main__': 1076 main() 1077