1#!/usr/bin/env @PYTHON_SHEBANG@ 2# 3# Print out ZFS ARC Statistics exported via kstat(1) 4# For a definition of fields, or usage, use arcstat -v 5# 6# This script was originally a fork of the original arcstat.pl (0.1) 7# by Neelakanth Nadgir, originally published on his Sun blog on 8# 09/18/2007 9# http://blogs.sun.com/realneel/entry/zfs_arc_statistics 10# 11# A new version aimed to improve upon the original by adding features 12# and fixing bugs as needed. This version was maintained by Mike 13# Harsch and was hosted in a public open source repository: 14# http://github.com/mharsch/arcstat 15# 16# but has since moved to the illumos-gate repository. 17# 18# This Python port was written by John Hixson for FreeNAS, introduced 19# in commit e2c29f: 20# https://github.com/freenas/freenas 21# 22# and has been improved by many people since. 23# 24# CDDL HEADER START 25# 26# The contents of this file are subject to the terms of the 27# Common Development and Distribution License, Version 1.0 only 28# (the "License"). You may not use this file except in compliance 29# with the License. 30# 31# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 32# or https://opensource.org/licenses/CDDL-1.0. 33# See the License for the specific language governing permissions 34# and limitations under the License. 35# 36# When distributing Covered Code, include this CDDL HEADER in each 37# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 38# If applicable, add the following below this CDDL HEADER, with the 39# fields enclosed by brackets "[]" replaced with your own identifying 40# information: Portions Copyright [yyyy] [name of copyright owner] 41# 42# CDDL HEADER END 43# 44# 45# Fields have a fixed width. Every interval, we fill the "v" 46# hash with its corresponding value (v[field]=value) using calculate(). 47# @hdr is the array of fields that needs to be printed, so we 48# just iterate over this array and print the values using our pretty printer. 49# 50# This script must remain compatible with Python 3.6+. 51# 52 53import sys 54import time 55import getopt 56import re 57import copy 58 59from signal import signal, SIGINT, SIGWINCH, SIG_DFL 60 61 62cols = { 63 # HDR: [Size, Scale, Description] 64 "time": [8, -1, "Time"], 65 "hits": [4, 1000, "ARC reads per second"], 66 "miss": [4, 1000, "ARC misses per second"], 67 "read": [4, 1000, "Total ARC accesses per second"], 68 "hit%": [4, 100, "ARC hit percentage"], 69 "miss%": [5, 100, "ARC miss percentage"], 70 "dhit": [4, 1000, "Demand hits per second"], 71 "dmis": [4, 1000, "Demand misses per second"], 72 "dh%": [3, 100, "Demand hit percentage"], 73 "dm%": [3, 100, "Demand miss percentage"], 74 "phit": [4, 1000, "Prefetch hits per second"], 75 "pmis": [4, 1000, "Prefetch misses per second"], 76 "ph%": [3, 100, "Prefetch hits percentage"], 77 "pm%": [3, 100, "Prefetch miss percentage"], 78 "mhit": [4, 1000, "Metadata hits per second"], 79 "mmis": [4, 1000, "Metadata misses per second"], 80 "mread": [5, 1000, "Metadata accesses per second"], 81 "mh%": [3, 100, "Metadata hit percentage"], 82 "mm%": [3, 100, "Metadata miss percentage"], 83 "arcsz": [5, 1024, "ARC size"], 84 "size": [4, 1024, "ARC size"], 85 "c": [4, 1024, "ARC target size"], 86 "mfu": [4, 1000, "MFU list hits per second"], 87 "mru": [4, 1000, "MRU list hits per second"], 88 "mfug": [4, 1000, "MFU ghost list hits per second"], 89 "mrug": [4, 1000, "MRU ghost list hits per second"], 90 "eskip": [5, 1000, "evict_skip per second"], 91 "el2skip": [7, 1000, "evict skip, due to l2 writes, per second"], 92 "el2cach": [7, 1024, "Size of L2 cached evictions per second"], 93 "el2el": [5, 1024, "Size of L2 eligible evictions per second"], 94 "el2mfu": [6, 1024, "Size of L2 eligible MFU evictions per second"], 95 "el2mru": [6, 1024, "Size of L2 eligible MRU evictions per second"], 96 "el2inel": [7, 1024, "Size of L2 ineligible evictions per second"], 97 "mtxmis": [6, 1000, "mutex_miss per second"], 98 "dread": [5, 1000, "Demand accesses per second"], 99 "pread": [5, 1000, "Prefetch accesses per second"], 100 "l2hits": [6, 1000, "L2ARC hits per second"], 101 "l2miss": [6, 1000, "L2ARC misses per second"], 102 "l2read": [6, 1000, "Total L2ARC accesses per second"], 103 "l2hit%": [6, 100, "L2ARC access hit percentage"], 104 "l2miss%": [7, 100, "L2ARC access miss percentage"], 105 "l2pref": [6, 1024, "L2ARC prefetch allocated size"], 106 "l2mfu": [5, 1024, "L2ARC MFU allocated size"], 107 "l2mru": [5, 1024, "L2ARC MRU allocated size"], 108 "l2data": [6, 1024, "L2ARC data allocated size"], 109 "l2meta": [6, 1024, "L2ARC metadata allocated size"], 110 "l2pref%": [7, 100, "L2ARC prefetch percentage"], 111 "l2mfu%": [6, 100, "L2ARC MFU percentage"], 112 "l2mru%": [6, 100, "L2ARC MRU percentage"], 113 "l2data%": [7, 100, "L2ARC data percentage"], 114 "l2meta%": [7, 100, "L2ARC metadata percentage"], 115 "l2asize": [7, 1024, "Actual (compressed) size of the L2ARC"], 116 "l2size": [6, 1024, "Size of the L2ARC"], 117 "l2bytes": [7, 1024, "Bytes read per second from the L2ARC"], 118 "grow": [4, 1000, "ARC grow disabled"], 119 "need": [4, 1024, "ARC reclaim need"], 120 "free": [4, 1024, "ARC free memory"], 121 "avail": [5, 1024, "ARC available memory"], 122 "waste": [5, 1024, "Wasted memory due to round up to pagesize"], 123} 124 125v = {} 126hdr = ["time", "read", "miss", "miss%", "dmis", "dm%", "pmis", "pm%", "mmis", 127 "mm%", "size", "c", "avail"] 128xhdr = ["time", "mfu", "mru", "mfug", "mrug", "eskip", "mtxmis", "dread", 129 "pread", "read"] 130sint = 1 # Default interval is 1 second 131count = 1 # Default count is 1 132hdr_intr = 20 # Print header every 20 lines of output 133opfile = None 134sep = " " # Default separator is 2 spaces 135version = "0.4" 136l2exist = False 137cmd = ("Usage: arcstat [-havxp] [-f fields] [-o file] [-s string] [interval " 138 "[count]]\n") 139cur = {} 140d = {} 141out = None 142kstat = None 143pretty_print = True 144 145 146if sys.platform.startswith('freebsd'): 147 # Requires py-sysctl on FreeBSD 148 import sysctl 149 150 def kstat_update(): 151 global kstat 152 153 k = [ctl for ctl in sysctl.filter('kstat.zfs.misc.arcstats') 154 if ctl.type != sysctl.CTLTYPE_NODE] 155 156 if not k: 157 sys.exit(1) 158 159 kstat = {} 160 161 for s in k: 162 if not s: 163 continue 164 165 name, value = s.name, s.value 166 # Trims 'kstat.zfs.misc.arcstats' from the name 167 kstat[name[24:]] = int(value) 168 169elif sys.platform.startswith('linux'): 170 def kstat_update(): 171 global kstat 172 173 k = [line.strip() for line in open('/proc/spl/kstat/zfs/arcstats')] 174 175 if not k: 176 sys.exit(1) 177 178 del k[0:2] 179 kstat = {} 180 181 for s in k: 182 if not s: 183 continue 184 185 name, unused, value = s.split() 186 kstat[name] = int(value) 187 188 189def detailed_usage(): 190 sys.stderr.write("%s\n" % cmd) 191 sys.stderr.write("Field definitions are as follows:\n") 192 for key in cols: 193 sys.stderr.write("%11s : %s\n" % (key, cols[key][2])) 194 sys.stderr.write("\n") 195 196 sys.exit(0) 197 198 199def usage(): 200 sys.stderr.write("%s\n" % cmd) 201 sys.stderr.write("\t -h : Print this help message\n") 202 sys.stderr.write("\t -a : Print all possible stats\n") 203 sys.stderr.write("\t -v : List all possible field headers and definitions" 204 "\n") 205 sys.stderr.write("\t -x : Print extended stats\n") 206 sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n") 207 sys.stderr.write("\t -o : Redirect output to the specified file\n") 208 sys.stderr.write("\t -s : Override default field separator with custom " 209 "character or string\n") 210 sys.stderr.write("\t -p : Disable auto-scaling of numerical fields\n") 211 sys.stderr.write("\nExamples:\n") 212 sys.stderr.write("\tarcstat -o /tmp/a.log 2 10\n") 213 sys.stderr.write("\tarcstat -s \",\" -o /tmp/a.log 2 10\n") 214 sys.stderr.write("\tarcstat -v\n") 215 sys.stderr.write("\tarcstat -f time,hit%,dh%,ph%,mh% 1\n") 216 sys.stderr.write("\n") 217 218 sys.exit(1) 219 220 221def snap_stats(): 222 global cur 223 global kstat 224 225 prev = copy.deepcopy(cur) 226 kstat_update() 227 228 cur = kstat 229 for key in cur: 230 if re.match(key, "class"): 231 continue 232 if key in prev: 233 d[key] = cur[key] - prev[key] 234 else: 235 d[key] = cur[key] 236 237 238def prettynum(sz, scale, num=0): 239 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'] 240 index = 0 241 save = 0 242 243 # Special case for date field 244 if scale == -1: 245 return "%s" % num 246 247 # Rounding error, return 0 248 elif 0 < num < 1: 249 num = 0 250 251 while abs(num) > scale and index < 5: 252 save = num 253 num = num / scale 254 index += 1 255 256 if index == 0: 257 return "%*d" % (sz, num) 258 259 if abs(save / scale) < 10: 260 return "%*.1f%s" % (sz - 1, num, suffix[index]) 261 else: 262 return "%*d%s" % (sz - 1, num, suffix[index]) 263 264 265def print_values(): 266 global hdr 267 global sep 268 global v 269 global pretty_print 270 271 if pretty_print: 272 fmt = lambda col: prettynum(cols[col][0], cols[col][1], v[col]) 273 else: 274 fmt = lambda col: v[col] 275 276 sys.stdout.write(sep.join(fmt(col) for col in hdr)) 277 sys.stdout.write("\n") 278 sys.stdout.flush() 279 280 281def print_header(): 282 global hdr 283 global sep 284 global pretty_print 285 286 if pretty_print: 287 fmt = lambda col: "%*s" % (cols[col][0], col) 288 else: 289 fmt = lambda col: col 290 291 sys.stdout.write(sep.join(fmt(col) for col in hdr)) 292 sys.stdout.write("\n") 293 294 295def get_terminal_lines(): 296 try: 297 import fcntl 298 import termios 299 import struct 300 data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '1234') 301 sz = struct.unpack('hh', data) 302 return sz[0] 303 except Exception: 304 pass 305 306 307def update_hdr_intr(): 308 global hdr_intr 309 310 lines = get_terminal_lines() 311 if lines and lines > 3: 312 hdr_intr = lines - 3 313 314 315def resize_handler(signum, frame): 316 update_hdr_intr() 317 318 319def init(): 320 global sint 321 global count 322 global hdr 323 global xhdr 324 global opfile 325 global sep 326 global out 327 global l2exist 328 global pretty_print 329 330 desired_cols = None 331 aflag = False 332 xflag = False 333 hflag = False 334 vflag = False 335 i = 1 336 337 try: 338 opts, args = getopt.getopt( 339 sys.argv[1:], 340 "axo:hvs:f:p", 341 [ 342 "all", 343 "extended", 344 "outfile", 345 "help", 346 "verbose", 347 "separator", 348 "columns", 349 "parsable" 350 ] 351 ) 352 except getopt.error as msg: 353 sys.stderr.write("Error: %s\n" % str(msg)) 354 usage() 355 opts = None 356 357 for opt, arg in opts: 358 if opt in ('-a', '--all'): 359 aflag = True 360 if opt in ('-x', '--extended'): 361 xflag = True 362 if opt in ('-o', '--outfile'): 363 opfile = arg 364 i += 1 365 if opt in ('-h', '--help'): 366 hflag = True 367 if opt in ('-v', '--verbose'): 368 vflag = True 369 if opt in ('-s', '--separator'): 370 sep = arg 371 i += 1 372 if opt in ('-f', '--columns'): 373 desired_cols = arg 374 i += 1 375 if opt in ('-p', '--parsable'): 376 pretty_print = False 377 i += 1 378 379 argv = sys.argv[i:] 380 sint = int(argv[0]) if argv else sint 381 count = int(argv[1]) if len(argv) > 1 else (0 if len(argv) > 0 else 1) 382 383 if hflag or (xflag and desired_cols): 384 usage() 385 386 if vflag: 387 detailed_usage() 388 389 if xflag: 390 hdr = xhdr 391 392 update_hdr_intr() 393 394 # check if L2ARC exists 395 snap_stats() 396 l2_size = cur.get("l2_size") 397 if l2_size: 398 l2exist = True 399 400 if desired_cols: 401 hdr = desired_cols.split(",") 402 403 invalid = [] 404 incompat = [] 405 for ele in hdr: 406 if ele not in cols: 407 invalid.append(ele) 408 elif not l2exist and ele.startswith("l2"): 409 sys.stdout.write("No L2ARC Here\n%s\n" % ele) 410 incompat.append(ele) 411 412 if len(invalid) > 0: 413 sys.stderr.write("Invalid column definition! -- %s\n" % invalid) 414 usage() 415 416 if len(incompat) > 0: 417 sys.stderr.write("Incompatible field specified! -- %s\n" % 418 incompat) 419 usage() 420 421 if aflag: 422 if l2exist: 423 hdr = cols.keys() 424 else: 425 hdr = [col for col in cols.keys() if not col.startswith("l2")] 426 427 if opfile: 428 try: 429 out = open(opfile, "w") 430 sys.stdout = out 431 432 except IOError: 433 sys.stderr.write("Cannot open %s for writing\n" % opfile) 434 sys.exit(1) 435 436 437def calculate(): 438 global d 439 global v 440 global l2exist 441 442 v = dict() 443 v["time"] = time.strftime("%H:%M:%S", time.localtime()) 444 v["hits"] = d["hits"] // sint 445 v["miss"] = d["misses"] // sint 446 v["read"] = v["hits"] + v["miss"] 447 v["hit%"] = 100 * v["hits"] // v["read"] if v["read"] > 0 else 0 448 v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0 449 450 v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) // sint 451 v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) // sint 452 453 v["dread"] = v["dhit"] + v["dmis"] 454 v["dh%"] = 100 * v["dhit"] // v["dread"] if v["dread"] > 0 else 0 455 v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0 456 457 v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) // sint 458 v["pmis"] = (d["prefetch_data_misses"] + 459 d["prefetch_metadata_misses"]) // sint 460 461 v["pread"] = v["phit"] + v["pmis"] 462 v["ph%"] = 100 * v["phit"] // v["pread"] if v["pread"] > 0 else 0 463 v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0 464 465 v["mhit"] = (d["prefetch_metadata_hits"] + 466 d["demand_metadata_hits"]) // sint 467 v["mmis"] = (d["prefetch_metadata_misses"] + 468 d["demand_metadata_misses"]) // sint 469 470 v["mread"] = v["mhit"] + v["mmis"] 471 v["mh%"] = 100 * v["mhit"] // v["mread"] if v["mread"] > 0 else 0 472 v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0 473 474 v["arcsz"] = cur["size"] 475 v["size"] = cur["size"] 476 v["c"] = cur["c"] 477 v["mfu"] = d["mfu_hits"] // sint 478 v["mru"] = d["mru_hits"] // sint 479 v["mrug"] = d["mru_ghost_hits"] // sint 480 v["mfug"] = d["mfu_ghost_hits"] // sint 481 v["eskip"] = d["evict_skip"] // sint 482 v["el2skip"] = d["evict_l2_skip"] // sint 483 v["el2cach"] = d["evict_l2_cached"] // sint 484 v["el2el"] = d["evict_l2_eligible"] // sint 485 v["el2mfu"] = d["evict_l2_eligible_mfu"] // sint 486 v["el2mru"] = d["evict_l2_eligible_mru"] // sint 487 v["el2inel"] = d["evict_l2_ineligible"] // sint 488 v["mtxmis"] = d["mutex_miss"] // sint 489 490 if l2exist: 491 v["l2hits"] = d["l2_hits"] // sint 492 v["l2miss"] = d["l2_misses"] // sint 493 v["l2read"] = v["l2hits"] + v["l2miss"] 494 v["l2hit%"] = 100 * v["l2hits"] // v["l2read"] if v["l2read"] > 0 else 0 495 496 v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0 497 v["l2asize"] = cur["l2_asize"] 498 v["l2size"] = cur["l2_size"] 499 v["l2bytes"] = d["l2_read_bytes"] // sint 500 501 v["l2pref"] = cur["l2_prefetch_asize"] 502 v["l2mfu"] = cur["l2_mfu_asize"] 503 v["l2mru"] = cur["l2_mru_asize"] 504 v["l2data"] = cur["l2_bufc_data_asize"] 505 v["l2meta"] = cur["l2_bufc_metadata_asize"] 506 v["l2pref%"] = 100 * v["l2pref"] // v["l2asize"] 507 v["l2mfu%"] = 100 * v["l2mfu"] // v["l2asize"] 508 v["l2mru%"] = 100 * v["l2mru"] // v["l2asize"] 509 v["l2data%"] = 100 * v["l2data"] // v["l2asize"] 510 v["l2meta%"] = 100 * v["l2meta"] // v["l2asize"] 511 512 v["grow"] = 0 if cur["arc_no_grow"] else 1 513 v["need"] = cur["arc_need_free"] 514 v["free"] = cur["memory_free_bytes"] 515 v["avail"] = cur["memory_available_bytes"] 516 v["waste"] = cur["abd_chunk_waste_size"] 517 518 519def main(): 520 global sint 521 global count 522 global hdr_intr 523 524 i = 0 525 count_flag = 0 526 527 init() 528 if count > 0: 529 count_flag = 1 530 531 signal(SIGINT, SIG_DFL) 532 signal(SIGWINCH, resize_handler) 533 while True: 534 if i == 0: 535 print_header() 536 537 snap_stats() 538 calculate() 539 print_values() 540 541 if count_flag == 1: 542 if count <= 1: 543 break 544 count -= 1 545 546 i = 0 if i >= hdr_intr else i + 1 547 time.sleep(sint) 548 549 if out: 550 out.close() 551 552 553if __name__ == '__main__': 554 main() 555