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