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