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 max:', f_hits(arc_stats['hash_elements_max'])) 666 prt_i2('Elements current:', 667 f_perc(arc_stats['hash_elements'], arc_stats['hash_elements_max']), 668 f_hits(arc_stats['hash_elements'])) 669 prt_i1('Collisions:', f_hits(arc_stats['hash_collisions'])) 670 671 prt_i1('Chain max:', f_hits(arc_stats['hash_chain_max'])) 672 prt_i1('Chains:', f_hits(arc_stats['hash_chains'])) 673 print() 674 675 print('ARC misc:') 676 prt_i1('Memory throttles:', arc_stats['memory_throttle_count']) 677 prt_i1('Memory direct reclaims:', arc_stats['memory_direct_count']) 678 prt_i1('Memory indirect reclaims:', arc_stats['memory_indirect_count']) 679 prt_i1('Deleted:', f_hits(arc_stats['deleted'])) 680 prt_i1('Mutex misses:', f_hits(arc_stats['mutex_miss'])) 681 prt_i1('Eviction skips:', f_hits(arc_stats['evict_skip'])) 682 prt_i1('Eviction skips due to L2 writes:', 683 f_hits(arc_stats['evict_l2_skip'])) 684 prt_i1('L2 cached evictions:', f_bytes(arc_stats['evict_l2_cached'])) 685 prt_i1('L2 eligible evictions:', f_bytes(arc_stats['evict_l2_eligible'])) 686 prt_i2('L2 eligible MFU evictions:', 687 f_perc(arc_stats['evict_l2_eligible_mfu'], 688 arc_stats['evict_l2_eligible']), 689 f_bytes(arc_stats['evict_l2_eligible_mfu'])) 690 prt_i2('L2 eligible MRU evictions:', 691 f_perc(arc_stats['evict_l2_eligible_mru'], 692 arc_stats['evict_l2_eligible']), 693 f_bytes(arc_stats['evict_l2_eligible_mru'])) 694 prt_i1('L2 ineligible evictions:', 695 f_bytes(arc_stats['evict_l2_ineligible'])) 696 print() 697 698 699def section_archits(kstats_dict): 700 """Print information on how the caches are accessed ("arc hits"). 701 """ 702 703 arc_stats = isolate_section('arcstats', kstats_dict) 704 all_accesses = int(arc_stats['hits'])+int(arc_stats['iohits'])+\ 705 int(arc_stats['misses']) 706 707 prt_1('ARC total accesses:', f_hits(all_accesses)) 708 ta_todo = (('Total hits:', arc_stats['hits']), 709 ('Total I/O hits:', arc_stats['iohits']), 710 ('Total misses:', arc_stats['misses'])) 711 for title, value in ta_todo: 712 prt_i2(title, f_perc(value, all_accesses), f_hits(value)) 713 print() 714 715 dd_total = int(arc_stats['demand_data_hits']) +\ 716 int(arc_stats['demand_data_iohits']) +\ 717 int(arc_stats['demand_data_misses']) 718 prt_2('ARC demand data accesses:', f_perc(dd_total, all_accesses), 719 f_hits(dd_total)) 720 dd_todo = (('Demand data hits:', arc_stats['demand_data_hits']), 721 ('Demand data I/O hits:', arc_stats['demand_data_iohits']), 722 ('Demand data misses:', arc_stats['demand_data_misses'])) 723 for title, value in dd_todo: 724 prt_i2(title, f_perc(value, dd_total), f_hits(value)) 725 print() 726 727 dm_total = int(arc_stats['demand_metadata_hits']) +\ 728 int(arc_stats['demand_metadata_iohits']) +\ 729 int(arc_stats['demand_metadata_misses']) 730 prt_2('ARC demand metadata accesses:', f_perc(dm_total, all_accesses), 731 f_hits(dm_total)) 732 dm_todo = (('Demand metadata hits:', arc_stats['demand_metadata_hits']), 733 ('Demand metadata I/O hits:', 734 arc_stats['demand_metadata_iohits']), 735 ('Demand metadata misses:', arc_stats['demand_metadata_misses'])) 736 for title, value in dm_todo: 737 prt_i2(title, f_perc(value, dm_total), f_hits(value)) 738 print() 739 740 pd_total = int(arc_stats['prefetch_data_hits']) +\ 741 int(arc_stats['prefetch_data_iohits']) +\ 742 int(arc_stats['prefetch_data_misses']) 743 prt_2('ARC prefetch data accesses:', f_perc(pd_total, all_accesses), 744 f_hits(pd_total)) 745 pd_todo = (('Prefetch data hits:', arc_stats['prefetch_data_hits']), 746 ('Prefetch data I/O hits:', arc_stats['prefetch_data_iohits']), 747 ('Prefetch data misses:', arc_stats['prefetch_data_misses'])) 748 for title, value in pd_todo: 749 prt_i2(title, f_perc(value, pd_total), f_hits(value)) 750 print() 751 752 pm_total = int(arc_stats['prefetch_metadata_hits']) +\ 753 int(arc_stats['prefetch_metadata_iohits']) +\ 754 int(arc_stats['prefetch_metadata_misses']) 755 prt_2('ARC prefetch metadata accesses:', f_perc(pm_total, all_accesses), 756 f_hits(pm_total)) 757 pm_todo = (('Prefetch metadata hits:', 758 arc_stats['prefetch_metadata_hits']), 759 ('Prefetch metadata I/O hits:', 760 arc_stats['prefetch_metadata_iohits']), 761 ('Prefetch metadata misses:', 762 arc_stats['prefetch_metadata_misses'])) 763 for title, value in pm_todo: 764 prt_i2(title, f_perc(value, pm_total), f_hits(value)) 765 print() 766 767 all_prefetches = int(arc_stats['predictive_prefetch'])+\ 768 int(arc_stats['prescient_prefetch']) 769 prt_2('ARC predictive prefetches:', 770 f_perc(arc_stats['predictive_prefetch'], all_prefetches), 771 f_hits(arc_stats['predictive_prefetch'])) 772 prt_i2('Demand hits after predictive:', 773 f_perc(arc_stats['demand_hit_predictive_prefetch'], 774 arc_stats['predictive_prefetch']), 775 f_hits(arc_stats['demand_hit_predictive_prefetch'])) 776 prt_i2('Demand I/O hits after predictive:', 777 f_perc(arc_stats['demand_iohit_predictive_prefetch'], 778 arc_stats['predictive_prefetch']), 779 f_hits(arc_stats['demand_iohit_predictive_prefetch'])) 780 never = int(arc_stats['predictive_prefetch']) -\ 781 int(arc_stats['demand_hit_predictive_prefetch']) -\ 782 int(arc_stats['demand_iohit_predictive_prefetch']) 783 prt_i2('Never demanded after predictive:', 784 f_perc(never, arc_stats['predictive_prefetch']), 785 f_hits(never)) 786 print() 787 788 prt_2('ARC prescient prefetches:', 789 f_perc(arc_stats['prescient_prefetch'], all_prefetches), 790 f_hits(arc_stats['prescient_prefetch'])) 791 prt_i2('Demand hits after prescient:', 792 f_perc(arc_stats['demand_hit_prescient_prefetch'], 793 arc_stats['prescient_prefetch']), 794 f_hits(arc_stats['demand_hit_prescient_prefetch'])) 795 prt_i2('Demand I/O hits after prescient:', 796 f_perc(arc_stats['demand_iohit_prescient_prefetch'], 797 arc_stats['prescient_prefetch']), 798 f_hits(arc_stats['demand_iohit_prescient_prefetch'])) 799 never = int(arc_stats['prescient_prefetch'])-\ 800 int(arc_stats['demand_hit_prescient_prefetch'])-\ 801 int(arc_stats['demand_iohit_prescient_prefetch']) 802 prt_i2('Never demanded after prescient:', 803 f_perc(never, arc_stats['prescient_prefetch']), 804 f_hits(never)) 805 print() 806 807 print('ARC states hits of all accesses:') 808 cl_todo = (('Most frequently used (MFU):', arc_stats['mfu_hits']), 809 ('Most recently used (MRU):', arc_stats['mru_hits']), 810 ('Most frequently used (MFU) ghost:', 811 arc_stats['mfu_ghost_hits']), 812 ('Most recently used (MRU) ghost:', 813 arc_stats['mru_ghost_hits']), 814 ('Uncached:', arc_stats['uncached_hits'])) 815 for title, value in cl_todo: 816 prt_i2(title, f_perc(value, all_accesses), f_hits(value)) 817 print() 818 819 820def section_dmu(kstats_dict): 821 """Collect information on the DMU""" 822 823 zfetch_stats = isolate_section('zfetchstats', kstats_dict) 824 825 zfetch_access_total = int(zfetch_stats['hits']) +\ 826 int(zfetch_stats['future']) + int(zfetch_stats['stride']) +\ 827 int(zfetch_stats['past']) + int(zfetch_stats['misses']) 828 829 prt_1('DMU predictive prefetcher calls:', f_hits(zfetch_access_total)) 830 prt_i2('Stream hits:', 831 f_perc(zfetch_stats['hits'], zfetch_access_total), 832 f_hits(zfetch_stats['hits'])) 833 future = int(zfetch_stats['future']) + int(zfetch_stats['stride']) 834 prt_i2('Hits ahead of stream:', f_perc(future, zfetch_access_total), 835 f_hits(future)) 836 prt_i2('Hits behind stream:', 837 f_perc(zfetch_stats['past'], zfetch_access_total), 838 f_hits(zfetch_stats['past'])) 839 prt_i2('Stream misses:', 840 f_perc(zfetch_stats['misses'], zfetch_access_total), 841 f_hits(zfetch_stats['misses'])) 842 prt_i2('Streams limit reached:', 843 f_perc(zfetch_stats['max_streams'], zfetch_stats['misses']), 844 f_hits(zfetch_stats['max_streams'])) 845 prt_i1('Stream strides:', f_hits(zfetch_stats['stride'])) 846 prt_i1('Prefetches issued', f_hits(zfetch_stats['io_issued'])) 847 print() 848 849 850def section_l2arc(kstats_dict): 851 """Collect information on L2ARC device if present. If not, tell user 852 that we're skipping the section. 853 """ 854 855 # The L2ARC statistics live in the same section as the normal ARC stuff 856 arc_stats = isolate_section('arcstats', kstats_dict) 857 858 if arc_stats['l2_size'] == '0': 859 print('L2ARC not detected, skipping section\n') 860 return 861 862 l2_errors = int(arc_stats['l2_writes_error']) +\ 863 int(arc_stats['l2_cksum_bad']) +\ 864 int(arc_stats['l2_io_error']) 865 866 l2_access_total = int(arc_stats['l2_hits'])+int(arc_stats['l2_misses']) 867 health = 'HEALTHY' 868 869 if l2_errors > 0: 870 health = 'DEGRADED' 871 872 prt_1('L2ARC status:', health) 873 874 l2_todo = (('Low memory aborts:', 'l2_abort_lowmem'), 875 ('Free on write:', 'l2_free_on_write'), 876 ('R/W clashes:', 'l2_rw_clash'), 877 ('Bad checksums:', 'l2_cksum_bad'), 878 ('Read errors:', 'l2_io_error'), 879 ('Write errors:', 'l2_writes_error')) 880 881 for title, value in l2_todo: 882 prt_i1(title, f_hits(arc_stats[value])) 883 884 print() 885 prt_1('L2ARC size (adaptive):', f_bytes(arc_stats['l2_size'])) 886 prt_i2('Compressed:', f_perc(arc_stats['l2_asize'], arc_stats['l2_size']), 887 f_bytes(arc_stats['l2_asize'])) 888 prt_i2('Header size:', 889 f_perc(arc_stats['l2_hdr_size'], arc_stats['l2_size']), 890 f_bytes(arc_stats['l2_hdr_size'])) 891 prt_i2('MFU allocated size:', 892 f_perc(arc_stats['l2_mfu_asize'], arc_stats['l2_asize']), 893 f_bytes(arc_stats['l2_mfu_asize'])) 894 prt_i2('MRU allocated size:', 895 f_perc(arc_stats['l2_mru_asize'], arc_stats['l2_asize']), 896 f_bytes(arc_stats['l2_mru_asize'])) 897 prt_i2('Prefetch allocated size:', 898 f_perc(arc_stats['l2_prefetch_asize'], arc_stats['l2_asize']), 899 f_bytes(arc_stats['l2_prefetch_asize'])) 900 prt_i2('Data (buffer content) allocated size:', 901 f_perc(arc_stats['l2_bufc_data_asize'], arc_stats['l2_asize']), 902 f_bytes(arc_stats['l2_bufc_data_asize'])) 903 prt_i2('Metadata (buffer content) allocated size:', 904 f_perc(arc_stats['l2_bufc_metadata_asize'], arc_stats['l2_asize']), 905 f_bytes(arc_stats['l2_bufc_metadata_asize'])) 906 907 print() 908 prt_1('L2ARC breakdown:', f_hits(l2_access_total)) 909 prt_i2('Hit ratio:', 910 f_perc(arc_stats['l2_hits'], l2_access_total), 911 f_hits(arc_stats['l2_hits'])) 912 prt_i2('Miss ratio:', 913 f_perc(arc_stats['l2_misses'], l2_access_total), 914 f_hits(arc_stats['l2_misses'])) 915 916 print() 917 print('L2ARC I/O:') 918 prt_i2('Reads:', 919 f_bytes(arc_stats['l2_read_bytes']), 920 f_hits(arc_stats['l2_hits'])) 921 prt_i2('Writes:', 922 f_bytes(arc_stats['l2_write_bytes']), 923 f_hits(arc_stats['l2_writes_sent'])) 924 925 print() 926 print('L2ARC evicts:') 927 prt_i1('L1 cached:', f_hits(arc_stats['l2_evict_l1cached'])) 928 prt_i1('While reading:', f_hits(arc_stats['l2_evict_reading'])) 929 print() 930 931 932def section_spl(*_): 933 """Print the SPL parameters, if requested with alternative format 934 and/or descriptions. This does not use kstats. 935 """ 936 937 if sys.platform.startswith('freebsd'): 938 # No SPL support in FreeBSD 939 return 940 941 spls = get_spl_params() 942 keylist = sorted(spls.keys()) 943 print('Solaris Porting Layer (SPL):') 944 945 if ARGS.desc: 946 descriptions = get_descriptions('spl') 947 948 for key in keylist: 949 value = spls[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_tunables(*_): 963 """Print the tunables, if requested with alternative format and/or 964 descriptions. This does not use kstasts. 965 """ 966 967 tunables = get_tunable_params() 968 keylist = sorted(tunables.keys()) 969 print('Tunables:') 970 971 if ARGS.desc: 972 descriptions = get_descriptions('zfs') 973 974 for key in keylist: 975 value = tunables[key] 976 977 if ARGS.desc: 978 try: 979 print(INDENT+'#', descriptions[key]) 980 except KeyError: 981 print(INDENT+'# (No description found)') # paranoid 982 983 print(format_raw_line(key, value)) 984 985 print() 986 987 988def section_zil(kstats_dict): 989 """Collect information on the ZFS Intent Log. Some of the information 990 taken from https://github.com/openzfs/zfs/blob/master/include/sys/zil.h 991 """ 992 993 zil_stats = isolate_section('zil', kstats_dict) 994 995 prt_1('ZIL committed transactions:', 996 f_hits(zil_stats['zil_itx_count'])) 997 prt_i1('Commit requests:', f_hits(zil_stats['zil_commit_count'])) 998 prt_i1('Flushes to stable storage:', 999 f_hits(zil_stats['zil_commit_writer_count'])) 1000 prt_i2('Transactions to SLOG storage pool:', 1001 f_bytes(zil_stats['zil_itx_metaslab_slog_bytes']), 1002 f_hits(zil_stats['zil_itx_metaslab_slog_count'])) 1003 prt_i2('Transactions to non-SLOG storage pool:', 1004 f_bytes(zil_stats['zil_itx_metaslab_normal_bytes']), 1005 f_hits(zil_stats['zil_itx_metaslab_normal_count'])) 1006 print() 1007 1008 1009section_calls = {'arc': section_arc, 1010 'archits': section_archits, 1011 'dmu': section_dmu, 1012 'l2arc': section_l2arc, 1013 'spl': section_spl, 1014 'tunables': section_tunables, 1015 'zil': section_zil} 1016 1017 1018def main(): 1019 """Run program. The options to draw a graph and to print all data raw are 1020 treated separately because they come with their own call. 1021 """ 1022 1023 kstats = get_kstats() 1024 1025 if ARGS.graph: 1026 draw_graph(kstats) 1027 sys.exit(0) 1028 1029 print_header() 1030 1031 if ARGS.raw: 1032 print_raw(kstats) 1033 1034 elif ARGS.section: 1035 1036 try: 1037 section_calls[ARGS.section](kstats) 1038 except KeyError: 1039 print('Error: Section "{0}" unknown'.format(ARGS.section)) 1040 sys.exit(1) 1041 1042 elif ARGS.page: 1043 print('WARNING: Pages are deprecated, please use "--section"\n') 1044 1045 pages_to_calls = {1: 'arc', 1046 2: 'archits', 1047 3: 'l2arc', 1048 4: 'dmu', 1049 5: 'vdev', 1050 6: 'tunables'} 1051 1052 try: 1053 call = pages_to_calls[ARGS.page] 1054 except KeyError: 1055 print('Error: Page "{0}" not supported'.format(ARGS.page)) 1056 sys.exit(1) 1057 else: 1058 section_calls[call](kstats) 1059 1060 else: 1061 # If no parameters were given, we print all sections. We might want to 1062 # change the sequence by hand 1063 calls = sorted(section_calls.keys()) 1064 1065 for section in calls: 1066 section_calls[section](kstats) 1067 1068 sys.exit(0) 1069 1070 1071if __name__ == '__main__': 1072 main() 1073