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