1#!/usr/bin/env @PYTHON_SHEBANG@ 2# 3# Print out statistics for all zil stats. This information is 4# available through the zil kstat. 5# 6# CDDL HEADER START 7# 8# The contents of this file are subject to the terms of the 9# Common Development and Distribution License, Version 1.0 only 10# (the "License"). You may not use this file except in compliance 11# with the License. 12# 13# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 14# or https://opensource.org/licenses/CDDL-1.0. 15# See the License for the specific language governing permissions 16# and limitations under the License. 17# 18# When distributing Covered Code, include this CDDL HEADER in each 19# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 20# If applicable, add the following below this CDDL HEADER, with the 21# fields enclosed by brackets "[]" replaced with your own identifying 22# information: Portions Copyright [yyyy] [name of copyright owner] 23# 24# This script must remain compatible with Python 3.6+. 25# 26 27import sys 28import subprocess 29import time 30import copy 31import os 32import re 33import signal 34from collections import defaultdict 35import argparse 36from argparse import RawTextHelpFormatter 37 38cols = { 39 # hdr: [size, scale, kstat name] 40 "time": [8, -1, "time"], 41 "pool": [12, -1, "pool"], 42 "ds": [12, -1, "dataset_name"], 43 "obj": [12, -1, "objset"], 44 "cc": [5, 1000, "zil_commit_count"], 45 "cwc": [5, 1000, "zil_commit_writer_count"], 46 "cec": [5, 1000, "zil_commit_error_count"], 47 "csc": [5, 1000, "zil_commit_stall_count"], 48 "cSc": [5, 1000, "zil_commit_suspend_count"], 49 "ic": [5, 1000, "zil_itx_count"], 50 "iic": [5, 1000, "zil_itx_indirect_count"], 51 "iib": [5, 1024, "zil_itx_indirect_bytes"], 52 "icc": [5, 1000, "zil_itx_copied_count"], 53 "icb": [5, 1024, "zil_itx_copied_bytes"], 54 "inc": [5, 1000, "zil_itx_needcopy_count"], 55 "inb": [5, 1024, "zil_itx_needcopy_bytes"], 56 "idc": [5, 1000, "icc+inc"], 57 "idb": [5, 1024, "icb+inb"], 58 "iwc": [5, 1000, "iic+idc"], 59 "iwb": [5, 1024, "iib+idb"], 60 "imnc": [6, 1000, "zil_itx_metaslab_normal_count"], 61 "imnb": [6, 1024, "zil_itx_metaslab_normal_bytes"], 62 "imnw": [6, 1024, "zil_itx_metaslab_normal_write"], 63 "imna": [6, 1024, "zil_itx_metaslab_normal_alloc"], 64 "imsc": [6, 1000, "zil_itx_metaslab_slog_count"], 65 "imsb": [6, 1024, "zil_itx_metaslab_slog_bytes"], 66 "imsw": [6, 1024, "zil_itx_metaslab_slog_write"], 67 "imsa": [6, 1024, "zil_itx_metaslab_slog_alloc"], 68 "imc": [5, 1000, "imnc+imsc"], 69 "imb": [5, 1024, "imnb+imsb"], 70 "imw": [5, 1024, "imnw+imsw"], 71 "ima": [5, 1024, "imna+imsa"], 72 "se%": [3, 100, "imb/ima"], 73 "sen%": [4, 100, "imnb/imna"], 74 "ses%": [4, 100, "imsb/imsa"], 75 "te%": [3, 100, "imb/imw"], 76 "ten%": [4, 100, "imnb/imnw"], 77 "tes%": [4, 100, "imsb/imsw"], 78} 79 80hdr = ["time", "ds", "cc", "ic", "idc", "idb", "iic", "iib", 81 "imnc", "imnw", "imsc", "imsw"] 82 83ghdr = ["time", "cc", "ic", "idc", "idb", "iic", "iib", 84 "imnc", "imnw", "imsc", "imsw"] 85 86cmd = ("Usage: zilstat [-hgdv] [-i interval] [-p pool_name]") 87 88curr = {} 89diff = {} 90kstat = {} 91ds_pairs = {} 92pool_name = None 93dataset_name = None 94interval = 0 95sep = " " 96gFlag = True 97dsFlag = False 98 99def prettynum(sz, scale, num=0): 100 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'] 101 index = 0 102 save = 0 103 104 if scale == -1: 105 return "%*s" % (sz, num) 106 107 # Rounding error, return 0 108 elif 0 < num < 1: 109 num = 0 110 111 while num > scale and index < 5: 112 save = num 113 num = num / scale 114 index += 1 115 116 if index == 0: 117 return "%*d" % (sz, num) 118 119 if (save / scale) < 10: 120 return "%*.1f%s" % (sz - 1, num, suffix[index]) 121 else: 122 return "%*d%s" % (sz - 1, num, suffix[index]) 123 124def print_header(): 125 global hdr 126 global sep 127 for col in hdr: 128 new_col = col 129 if interval > 0 and cols[col][1] > 100: 130 new_col += "/s" 131 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep)) 132 sys.stdout.write("\n") 133 134def print_values(v): 135 global hdr 136 global sep 137 for col in hdr: 138 val = v[cols[col][2]] 139 if interval > 0 and cols[col][1] > 100: 140 val = v[cols[col][2]] // interval 141 sys.stdout.write("%s%s" % ( 142 prettynum(cols[col][0], cols[col][1], val), sep)) 143 sys.stdout.write("\n") 144 145def print_dict(d): 146 for pool in d: 147 for objset in d[pool]: 148 print_values(d[pool][objset]) 149 150def detailed_usage(): 151 sys.stderr.write("%s\n" % cmd) 152 sys.stderr.write("Field definitions are as follows:\n") 153 for key in cols: 154 sys.stderr.write("%11s : %s\n" % (key, cols[key][2])) 155 sys.stderr.write("\n") 156 sys.exit(0) 157 158def init(): 159 global pool_name 160 global dataset_name 161 global interval 162 global hdr 163 global curr 164 global gFlag 165 global sep 166 167 curr = dict() 168 169 parser = argparse.ArgumentParser(description='Program to print zilstats', 170 add_help=True, 171 formatter_class=RawTextHelpFormatter, 172 epilog="\nUsage Examples\n"\ 173 "Note: Global zilstats is shown by default,"\ 174 " if none of a|p|d option is not provided\n"\ 175 "\tzilstat -a\n"\ 176 '\tzilstat -v\n'\ 177 '\tzilstat -p tank\n'\ 178 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\ 179 '\tzilstat -i 1\n'\ 180 '\tzilstat -s \"***\"\n'\ 181 '\tzilstat -f zcwc,zimnb,zimsb\n') 182 183 parser.add_argument( 184 "-v", "--verbose", 185 action="store_true", 186 help="List field headers and definitions" 187 ) 188 189 pool_grp = parser.add_mutually_exclusive_group() 190 191 pool_grp.add_argument( 192 "-a", "--all", 193 action="store_true", 194 dest="all", 195 help="Print all dataset stats" 196 ) 197 198 pool_grp.add_argument( 199 "-p", "--pool", 200 type=str, 201 help="Print stats for all datasets of a speicfied pool" 202 ) 203 204 pool_grp.add_argument( 205 "-d", "--dataset", 206 type=str, 207 help="Print given dataset(s) (Comma separated)" 208 ) 209 210 parser.add_argument( 211 "-f", "--columns", 212 type=str, 213 help="Specify specific fields to print (see -v)" 214 ) 215 216 parser.add_argument( 217 "-s", "--separator", 218 type=str, 219 help="Override default field separator with custom " 220 "character or string" 221 ) 222 223 parser.add_argument( 224 "-i", "--interval", 225 type=int, 226 dest="interval", 227 help="Print stats between specified interval" 228 " (in seconds)" 229 ) 230 231 parsed_args = parser.parse_args() 232 233 if parsed_args.verbose: 234 detailed_usage() 235 236 if parsed_args.all: 237 gFlag = False 238 239 if parsed_args.interval: 240 interval = parsed_args.interval 241 242 if parsed_args.pool: 243 pool_name = parsed_args.pool 244 gFlag = False 245 246 if parsed_args.dataset: 247 dataset_name = parsed_args.dataset 248 gFlag = False 249 250 if parsed_args.separator: 251 sep = parsed_args.separator 252 253 if gFlag: 254 hdr = ghdr 255 256 if parsed_args.columns: 257 hdr = parsed_args.columns.split(",") 258 259 invalid = [] 260 for ele in hdr: 261 if ele not in cols: 262 invalid.append(ele) 263 264 if len(invalid) > 0: 265 sys.stderr.write("Invalid column definition! -- %s\n" % invalid) 266 sys.exit(1) 267 268 if pool_name and dataset_name: 269 print ("Error: Can not filter both dataset and pool") 270 sys.exit(1) 271 272def FileCheck(fname): 273 try: 274 return (open(fname)) 275 except IOError: 276 print ("Unable to open zilstat proc file: " + fname) 277 sys.exit(1) 278 279if sys.platform.startswith('freebsd'): 280 # Requires py-sysctl on FreeBSD 281 import sysctl 282 283 def kstat_update(pool = None, objid = None): 284 global kstat 285 kstat = {} 286 if not pool: 287 file = "kstat.zfs.misc.zil" 288 k = [ctl for ctl in sysctl.filter(file) \ 289 if ctl.type != sysctl.CTLTYPE_NODE] 290 kstat_process_str(k, file, "GLOBAL", len(file + ".")) 291 elif objid: 292 file = "kstat.zfs." + pool + ".dataset.objset-" + objid 293 k = [ctl for ctl in sysctl.filter(file) if ctl.type \ 294 != sysctl.CTLTYPE_NODE] 295 kstat_process_str(k, file, objid, len(file + ".")) 296 else: 297 file = "kstat.zfs." + pool + ".dataset" 298 zil_start = len(file + ".") 299 obj_start = len("kstat.zfs." + pool + ".") 300 k = [ctl for ctl in sysctl.filter(file) 301 if ctl.type != sysctl.CTLTYPE_NODE] 302 for s in k: 303 if not s or (s.name.find("zil") == -1 and \ 304 s.name.find("dataset_name") == -1): 305 continue 306 name, value = s.name, s.value 307 objid = re.findall(r'0x[0-9A-F]+', \ 308 name[obj_start:], re.I)[0] 309 if objid not in kstat: 310 kstat[objid] = dict() 311 zil_start = len(file + ".objset-" + \ 312 objid + ".") 313 kstat[objid][name[zil_start:]] = value \ 314 if (name.find("dataset_name")) \ 315 else int(value) 316 317 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0): 318 global kstat 319 if not k: 320 print("Unable to process kstat for: " + file) 321 sys.exit(1) 322 kstat[objset] = dict() 323 for s in k: 324 if not s or (s.name.find("zil") == -1 and \ 325 s.name.find("dataset_name") == -1): 326 continue 327 name, value = s.name, s.value 328 kstat[objset][name[zil_start:]] = value \ 329 if (name.find("dataset_name")) else int(value) 330 331elif sys.platform.startswith('linux'): 332 def kstat_update(pool = None, objid = None): 333 global kstat 334 kstat = {} 335 if not pool: 336 k = [line.strip() for line in \ 337 FileCheck("/proc/spl/kstat/zfs/zil")] 338 kstat_process_str(k, "/proc/spl/kstat/zfs/zil") 339 elif objid: 340 file = "/proc/spl/kstat/zfs/" + pool + "/objset-" + objid 341 k = [line.strip() for line in FileCheck(file)] 342 kstat_process_str(k, file, objid) 343 else: 344 if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"): 345 print("Pool \"" + pool + "\" does not exist, Exitting") 346 sys.exit(1) 347 objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}') 348 for objid in objsets: 349 if objid.find("objset-") == -1: 350 continue 351 file = "/proc/spl/kstat/zfs/" + pool + "/" + objid 352 k = [line.strip() for line in FileCheck(file)] 353 kstat_process_str(k, file, objid.replace("objset-", "")) 354 355 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0): 356 global kstat 357 if not k: 358 print("Unable to process kstat for: " + file) 359 sys.exit(1) 360 361 kstat[objset] = dict() 362 for s in k: 363 if not s or (s.find("zil") == -1 and \ 364 s.find("dataset_name") == -1): 365 continue 366 name, unused, value = s.split() 367 kstat[objset][name] = value \ 368 if (name == "dataset_name") else int(value) 369 370def zil_process_kstat(): 371 global curr, pool_name, dataset_name, dsFlag, ds_pairs 372 curr.clear() 373 if gFlag == True: 374 kstat_update() 375 zil_build_dict() 376 else: 377 if pool_name: 378 kstat_update(pool_name) 379 zil_build_dict(pool_name) 380 elif dataset_name: 381 if dsFlag == False: 382 dsFlag = True 383 datasets = dataset_name.split(',') 384 ds_pairs = defaultdict(list) 385 for ds in datasets: 386 try: 387 objid = subprocess.check_output(['zfs', 388 'list', '-Hpo', 'objsetid', ds], \ 389 stderr=subprocess.DEVNULL) \ 390 .decode('utf-8').strip() 391 except subprocess.CalledProcessError as e: 392 print("Command: \"zfs list -Hpo objset "\ 393 + str(ds) + "\" failed with error code:"\ 394 + str(e.returncode)) 395 print("Please make sure that dataset \""\ 396 + str(ds) + "\" exists") 397 sys.exit(1) 398 if not objid: 399 continue 400 ds_pairs[ds.split('/')[0]]. \ 401 append(hex(int(objid))) 402 for pool, objids in ds_pairs.items(): 403 for objid in objids: 404 kstat_update(pool, objid) 405 zil_build_dict(pool) 406 else: 407 try: 408 pools = subprocess.check_output(['zpool', 'list', '-Hpo',\ 409 'name']).decode('utf-8').split() 410 except subprocess.CalledProcessError as e: 411 print("Command: \"zpool list -Hpo name\" failed with error"\ 412 "code: " + str(e.returncode)) 413 sys.exit(1) 414 for pool in pools: 415 kstat_update(pool) 416 zil_build_dict(pool) 417 418def calculate_diff(): 419 global curr, diff 420 prev = copy.deepcopy(curr) 421 zil_process_kstat() 422 diff = copy.deepcopy(curr) 423 for pool in curr: 424 for objset in curr[pool]: 425 for key in curr[pool][objset]: 426 if not isinstance(diff[pool][objset][key], int): 427 continue 428 # If prev is NULL, this is the 429 # first time we are here 430 if not prev: 431 diff[pool][objset][key] = 0 432 else: 433 diff[pool][objset][key] \ 434 = curr[pool][objset][key] \ 435 - prev[pool][objset][key] 436 437def zil_build_dict(pool = "GLOBAL"): 438 global kstat 439 for objset in kstat: 440 for key in kstat[objset]: 441 val = kstat[objset][key] 442 if pool not in curr: 443 curr[pool] = dict() 444 if objset not in curr[pool]: 445 curr[pool][objset] = dict() 446 curr[pool][objset][key] = val 447 448def zil_extend_dict(): 449 global diff 450 for pool in diff: 451 for objset in diff[pool]: 452 diff[pool][objset]["pool"] = pool 453 diff[pool][objset]["objset"] = objset 454 diff[pool][objset]["time"] = time.strftime("%H:%M:%S", \ 455 time.localtime()) 456 diff[pool][objset]["icc+inc"] = \ 457 diff[pool][objset]["zil_itx_copied_count"] + \ 458 diff[pool][objset]["zil_itx_needcopy_count"] 459 diff[pool][objset]["icb+inb"] = \ 460 diff[pool][objset]["zil_itx_copied_bytes"] + \ 461 diff[pool][objset]["zil_itx_needcopy_bytes"] 462 diff[pool][objset]["iic+idc"] = \ 463 diff[pool][objset]["zil_itx_indirect_count"] + \ 464 diff[pool][objset]["zil_itx_copied_count"] + \ 465 diff[pool][objset]["zil_itx_needcopy_count"] 466 diff[pool][objset]["iib+idb"] = \ 467 diff[pool][objset]["zil_itx_indirect_bytes"] + \ 468 diff[pool][objset]["zil_itx_copied_bytes"] + \ 469 diff[pool][objset]["zil_itx_needcopy_bytes"] 470 diff[pool][objset]["imnc+imsc"] = \ 471 diff[pool][objset]["zil_itx_metaslab_normal_count"] + \ 472 diff[pool][objset]["zil_itx_metaslab_slog_count"] 473 diff[pool][objset]["imnb+imsb"] = \ 474 diff[pool][objset]["zil_itx_metaslab_normal_bytes"] + \ 475 diff[pool][objset]["zil_itx_metaslab_slog_bytes"] 476 diff[pool][objset]["imnw+imsw"] = \ 477 diff[pool][objset]["zil_itx_metaslab_normal_write"] + \ 478 diff[pool][objset]["zil_itx_metaslab_slog_write"] 479 diff[pool][objset]["imna+imsa"] = \ 480 diff[pool][objset]["zil_itx_metaslab_normal_alloc"] + \ 481 diff[pool][objset]["zil_itx_metaslab_slog_alloc"] 482 if diff[pool][objset]["imna+imsa"] > 0: 483 diff[pool][objset]["imb/ima"] = 100 * \ 484 diff[pool][objset]["imnb+imsb"] // \ 485 diff[pool][objset]["imna+imsa"] 486 else: 487 diff[pool][objset]["imb/ima"] = 100 488 if diff[pool][objset]["zil_itx_metaslab_normal_alloc"] > 0: 489 diff[pool][objset]["imnb/imna"] = 100 * \ 490 diff[pool][objset]["zil_itx_metaslab_normal_bytes"] // \ 491 diff[pool][objset]["zil_itx_metaslab_normal_alloc"] 492 else: 493 diff[pool][objset]["imnb/imna"] = 100 494 if diff[pool][objset]["zil_itx_metaslab_slog_alloc"] > 0: 495 diff[pool][objset]["imsb/imsa"] = 100 * \ 496 diff[pool][objset]["zil_itx_metaslab_slog_bytes"] // \ 497 diff[pool][objset]["zil_itx_metaslab_slog_alloc"] 498 else: 499 diff[pool][objset]["imsb/imsa"] = 100 500 if diff[pool][objset]["imnw+imsw"] > 0: 501 diff[pool][objset]["imb/imw"] = 100 * \ 502 diff[pool][objset]["imnb+imsb"] // \ 503 diff[pool][objset]["imnw+imsw"] 504 else: 505 diff[pool][objset]["imb/imw"] = 100 506 if diff[pool][objset]["zil_itx_metaslab_normal_alloc"] > 0: 507 diff[pool][objset]["imnb/imnw"] = 100 * \ 508 diff[pool][objset]["zil_itx_metaslab_normal_bytes"] // \ 509 diff[pool][objset]["zil_itx_metaslab_normal_write"] 510 else: 511 diff[pool][objset]["imnb/imnw"] = 100 512 if diff[pool][objset]["zil_itx_metaslab_slog_alloc"] > 0: 513 diff[pool][objset]["imsb/imsw"] = 100 * \ 514 diff[pool][objset]["zil_itx_metaslab_slog_bytes"] // \ 515 diff[pool][objset]["zil_itx_metaslab_slog_write"] 516 else: 517 diff[pool][objset]["imsb/imsw"] = 100 518 519def sign_handler_epipe(sig, frame): 520 print("Caught EPIPE signal: " + str(frame)) 521 print("Exitting...") 522 sys.exit(0) 523 524def main(): 525 global interval 526 global curr, diff 527 hprint = False 528 init() 529 signal.signal(signal.SIGINT, signal.SIG_DFL) 530 signal.signal(signal.SIGPIPE, sign_handler_epipe) 531 532 zil_process_kstat() 533 if not curr: 534 print ("Error: No stats to show") 535 sys.exit(0) 536 print_header() 537 if interval > 0: 538 time.sleep(interval) 539 while True: 540 calculate_diff() 541 if not diff: 542 print ("Error: No stats to show") 543 sys.exit(0) 544 zil_extend_dict() 545 print_dict(diff) 546 time.sleep(interval) 547 else: 548 diff = curr 549 zil_extend_dict() 550 print_dict(diff) 551 552if __name__ == '__main__': 553 main() 554 555