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