1 #!/usr/bin/env @PYTHON_SHEBANG@
3 # Print out statistics for all zil stats. This information is
4 # available through the zil kstat.
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
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.
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]
24 # This script must remain compatible with Python 3.6+.
34 from collections import defaultdict
36 from argparse import RawTextHelpFormatter
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 "ic": [5, 1000, "zil_itx_count"],
47 "iic": [5, 1000, "zil_itx_indirect_count"],
48 "iib": [5, 1024, "zil_itx_indirect_bytes"],
49 "icc": [5, 1000, "zil_itx_copied_count"],
50 "icb": [5, 1024, "zil_itx_copied_bytes"],
51 "inc": [5, 1000, "zil_itx_needcopy_count"],
52 "inb": [5, 1024, "zil_itx_needcopy_bytes"],
53 "idc": [5, 1000, "icc+inc"],
54 "idb": [5, 1024, "icb+inb"],
55 "iwc": [5, 1000, "iic+idc"],
56 "iwb": [5, 1024, "iib+idb"],
57 "imnc": [6, 1000, "zil_itx_metaslab_normal_count"],
58 "imnb": [6, 1024, "zil_itx_metaslab_normal_bytes"],
59 "imnw": [6, 1024, "zil_itx_metaslab_normal_write"],
60 "imna": [6, 1024, "zil_itx_metaslab_normal_alloc"],
61 "imsc": [6, 1000, "zil_itx_metaslab_slog_count"],
62 "imsb": [6, 1024, "zil_itx_metaslab_slog_bytes"],
63 "imsw": [6, 1024, "zil_itx_metaslab_slog_write"],
64 "imsa": [6, 1024, "zil_itx_metaslab_slog_alloc"],
65 "imc": [5, 1000, "imnc+imsc"],
66 "imb": [5, 1024, "imnb+imsb"],
67 "imw": [5, 1024, "imnw+imsw"],
68 "ima": [5, 1024, "imna+imsa"],
69 "se%": [3, 100, "imb/ima"],
70 "sen%": [4, 100, "imnb/imna"],
71 "ses%": [4, 100, "imsb/imsa"],
72 "te%": [3, 100, "imb/imw"],
73 "ten%": [4, 100, "imnb/imnw"],
74 "tes%": [4, 100, "imsb/imsw"],
77 hdr = ["time", "ds", "cc", "ic", "idc", "idb", "iic", "iib",
78 "imnc", "imnw", "imsc", "imsw"]
80 ghdr = ["time", "cc", "ic", "idc", "idb", "iic", "iib",
81 "imnc", "imnw", "imsc", "imsw"]
83 cmd = ("Usage: zilstat [-hgdv] [-i interval] [-p pool_name]")
96 def prettynum(sz, scale, num=0):
97 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
102 return "%*s" % (sz, num)
104 # Rounding error, return 0
108 while num > scale and index < 5:
114 return "%*d" % (sz, num)
116 if (save / scale) < 10:
117 return "%*.1f%s" % (sz - 1, num, suffix[index])
119 return "%*d%s" % (sz - 1, num, suffix[index])
126 if interval > 0 and cols[col][1] > 100:
128 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep))
129 sys.stdout.write("\n")
135 val = v[cols[col][2]]
136 if interval > 0 and cols[col][1] > 100:
137 val = v[cols[col][2]] // interval
138 sys.stdout.write("%s%s" % (
139 prettynum(cols[col][0], cols[col][1], val), sep))
140 sys.stdout.write("\n")
144 for objset in d[pool]:
145 print_values(d[pool][objset])
147 def detailed_usage():
148 sys.stderr.write("%s\n" % cmd)
149 sys.stderr.write("Field definitions are as follows:\n")
151 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
152 sys.stderr.write("\n")
166 parser = argparse.ArgumentParser(description='Program to print zilstats',
168 formatter_class=RawTextHelpFormatter,
169 epilog="\nUsage Examples\n"\
170 "Note: Global zilstats is shown by default,"\
171 " if none of a|p|d option is not provided\n"\
174 '\tzilstat -p tank\n'\
175 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\
177 '\tzilstat -s \"***\"\n'\
178 '\tzilstat -f zcwc,zimnb,zimsb\n')
183 help="List field headers and definitions"
186 pool_grp = parser.add_mutually_exclusive_group()
188 pool_grp.add_argument(
192 help="Print all dataset stats"
195 pool_grp.add_argument(
198 help="Print stats for all datasets of a speicfied pool"
201 pool_grp.add_argument(
204 help="Print given dataset(s) (Comma separated)"
210 help="Specify specific fields to print (see -v)"
216 help="Override default field separator with custom "
217 "character or string"
224 help="Print stats between specified interval"
228 parsed_args = parser.parse_args()
230 if parsed_args.verbose:
236 if parsed_args.interval:
237 interval = parsed_args.interval
240 pool_name = parsed_args.pool
243 if parsed_args.dataset:
244 dataset_name = parsed_args.dataset
247 if parsed_args.separator:
248 sep = parsed_args.separator
253 if parsed_args.columns:
254 hdr = parsed_args.columns.split(",")
262 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
265 if pool_name and dataset_name:
266 print ("Error: Can not filter both dataset and pool")
269 def FileCheck(fname):
273 print ("Unable to open zilstat proc file: " + fname)
276 if sys.platform.startswith('freebsd'):
277 # Requires py-sysctl on FreeBSD
280 def kstat_update(pool = None, objid = None):
284 file = "kstat.zfs.misc.zil"
285 k = [ctl for ctl in sysctl.filter(file) \
286 if ctl.type != sysctl.CTLTYPE_NODE]
287 kstat_process_str(k, file, "GLOBAL", len(file + "."))
289 file = "kstat.zfs." + pool + ".dataset.objset-" + objid
290 k = [ctl for ctl in sysctl.filter(file) if ctl.type \
291 != sysctl.CTLTYPE_NODE]
292 kstat_process_str(k, file, objid, len(file + "."))
294 file = "kstat.zfs." + pool + ".dataset"
295 zil_start = len(file + ".")
296 obj_start = len("kstat.zfs." + pool + ".")
297 k = [ctl for ctl in sysctl.filter(file)
298 if ctl.type != sysctl.CTLTYPE_NODE]
300 if not s or (s.name.find("zil") == -1 and \
301 s.name.find("dataset_name") == -1):
303 name, value = s.name, s.value
304 objid = re.findall(r'0x[0-9A-F]+', \
305 name[obj_start:], re.I)[0]
306 if objid not in kstat:
307 kstat[objid] = dict()
308 zil_start = len(file + ".objset-" + \
310 kstat[objid][name[zil_start:]] = value \
311 if (name.find("dataset_name")) \
314 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
317 print("Unable to process kstat for: " + file)
319 kstat[objset] = dict()
321 if not s or (s.name.find("zil") == -1 and \
322 s.name.find("dataset_name") == -1):
324 name, value = s.name, s.value
325 kstat[objset][name[zil_start:]] = value \
326 if (name.find("dataset_name")) else int(value)
328 elif sys.platform.startswith('linux'):
329 def kstat_update(pool = None, objid = None):
333 k = [line.strip() for line in \
334 FileCheck("/proc/spl/kstat/zfs/zil")]
335 kstat_process_str(k, "/proc/spl/kstat/zfs/zil")
337 file = "/proc/spl/kstat/zfs/" + pool + "/objset-" + objid
338 k = [line.strip() for line in FileCheck(file)]
339 kstat_process_str(k, file, objid)
341 if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"):
342 print("Pool \"" + pool + "\" does not exist, Exitting")
344 objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}')
345 for objid in objsets:
346 if objid.find("objset-") == -1:
348 file = "/proc/spl/kstat/zfs/" + pool + "/" + objid
349 k = [line.strip() for line in FileCheck(file)]
350 kstat_process_str(k, file, objid.replace("objset-", ""))
352 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
355 print("Unable to process kstat for: " + file)
358 kstat[objset] = dict()
360 if not s or (s.find("zil") == -1 and \
361 s.find("dataset_name") == -1):
363 name, unused, value = s.split()
364 kstat[objset][name] = value \
365 if (name == "dataset_name") else int(value)
367 def zil_process_kstat():
368 global curr, pool_name, dataset_name, dsFlag, ds_pairs
375 kstat_update(pool_name)
376 zil_build_dict(pool_name)
380 datasets = dataset_name.split(',')
381 ds_pairs = defaultdict(list)
384 objid = subprocess.check_output(['zfs',
385 'list', '-Hpo', 'objsetid', ds], \
386 stderr=subprocess.DEVNULL) \
387 .decode('utf-8').strip()
388 except subprocess.CalledProcessError as e:
389 print("Command: \"zfs list -Hpo objset "\
390 + str(ds) + "\" failed with error code:"\
392 print("Please make sure that dataset \""\
393 + str(ds) + "\" exists")
397 ds_pairs[ds.split('/')[0]]. \
398 append(hex(int(objid)))
399 for pool, objids in ds_pairs.items():
401 kstat_update(pool, objid)
405 pools = subprocess.check_output(['zpool', 'list', '-Hpo',\
406 'name']).decode('utf-8').split()
407 except subprocess.CalledProcessError as e:
408 print("Command: \"zpool list -Hpo name\" failed with error"\
409 "code: " + str(e.returncode))
415 def calculate_diff():
417 prev = copy.deepcopy(curr)
419 diff = copy.deepcopy(curr)
421 for objset in curr[pool]:
422 for key in curr[pool][objset]:
423 if not isinstance(diff[pool][objset][key], int):
425 # If prev is NULL, this is the
426 # first time we are here
428 diff[pool][objset][key] = 0
430 diff[pool][objset][key] \
431 = curr[pool][objset][key] \
432 - prev[pool][objset][key]
434 def zil_build_dict(pool = "GLOBAL"):
437 for key in kstat[objset]:
438 val = kstat[objset][key]
441 if objset not in curr[pool]:
442 curr[pool][objset] = dict()
443 curr[pool][objset][key] = val
445 def zil_extend_dict():
448 for objset in diff[pool]:
449 diff[pool][objset]["pool"] = pool
450 diff[pool][objset]["objset"] = objset
451 diff[pool][objset]["time"] = time.strftime("%H:%M:%S", \
453 diff[pool][objset]["icc+inc"] = \
454 diff[pool][objset]["zil_itx_copied_count"] + \
455 diff[pool][objset]["zil_itx_needcopy_count"]
456 diff[pool][objset]["icb+inb"] = \
457 diff[pool][objset]["zil_itx_copied_bytes"] + \
458 diff[pool][objset]["zil_itx_needcopy_bytes"]
459 diff[pool][objset]["iic+idc"] = \
460 diff[pool][objset]["zil_itx_indirect_count"] + \
461 diff[pool][objset]["zil_itx_copied_count"] + \
462 diff[pool][objset]["zil_itx_needcopy_count"]
463 diff[pool][objset]["iib+idb"] = \
464 diff[pool][objset]["zil_itx_indirect_bytes"] + \
465 diff[pool][objset]["zil_itx_copied_bytes"] + \
466 diff[pool][objset]["zil_itx_needcopy_bytes"]
467 diff[pool][objset]["imnc+imsc"] = \
468 diff[pool][objset]["zil_itx_metaslab_normal_count"] + \
469 diff[pool][objset]["zil_itx_metaslab_slog_count"]
470 diff[pool][objset]["imnb+imsb"] = \
471 diff[pool][objset]["zil_itx_metaslab_normal_bytes"] + \
472 diff[pool][objset]["zil_itx_metaslab_slog_bytes"]
473 diff[pool][objset]["imnw+imsw"] = \
474 diff[pool][objset]["zil_itx_metaslab_normal_write"] + \
475 diff[pool][objset]["zil_itx_metaslab_slog_write"]
476 diff[pool][objset]["imna+imsa"] = \
477 diff[pool][objset]["zil_itx_metaslab_normal_alloc"] + \
478 diff[pool][objset]["zil_itx_metaslab_slog_alloc"]
479 if diff[pool][objset]["imna+imsa"] > 0:
480 diff[pool][objset]["imb/ima"] = 100 * \
481 diff[pool][objset]["imnb+imsb"] // \
482 diff[pool][objset]["imna+imsa"]
484 diff[pool][objset]["imb/ima"] = 100
485 if diff[pool][objset]["zil_itx_metaslab_normal_alloc"] > 0:
486 diff[pool][objset]["imnb/imna"] = 100 * \
487 diff[pool][objset]["zil_itx_metaslab_normal_bytes"] // \
488 diff[pool][objset]["zil_itx_metaslab_normal_alloc"]
490 diff[pool][objset]["imnb/imna"] = 100
491 if diff[pool][objset]["zil_itx_metaslab_slog_alloc"] > 0:
492 diff[pool][objset]["imsb/imsa"] = 100 * \
493 diff[pool][objset]["zil_itx_metaslab_slog_bytes"] // \
494 diff[pool][objset]["zil_itx_metaslab_slog_alloc"]
496 diff[pool][objset]["imsb/imsa"] = 100
497 if diff[pool][objset]["imnw+imsw"] > 0:
498 diff[pool][objset]["imb/imw"] = 100 * \
499 diff[pool][objset]["imnb+imsb"] // \
500 diff[pool][objset]["imnw+imsw"]
502 diff[pool][objset]["imb/imw"] = 100
503 if diff[pool][objset]["zil_itx_metaslab_normal_alloc"] > 0:
504 diff[pool][objset]["imnb/imnw"] = 100 * \
505 diff[pool][objset]["zil_itx_metaslab_normal_bytes"] // \
506 diff[pool][objset]["zil_itx_metaslab_normal_write"]
508 diff[pool][objset]["imnb/imnw"] = 100
509 if diff[pool][objset]["zil_itx_metaslab_slog_alloc"] > 0:
510 diff[pool][objset]["imsb/imsw"] = 100 * \
511 diff[pool][objset]["zil_itx_metaslab_slog_bytes"] // \
512 diff[pool][objset]["zil_itx_metaslab_slog_write"]
514 diff[pool][objset]["imsb/imsw"] = 100
516 def sign_handler_epipe(sig, frame):
517 print("Caught EPIPE signal: " + str(frame))
526 signal.signal(signal.SIGINT, signal.SIG_DFL)
527 signal.signal(signal.SIGPIPE, sign_handler_epipe)
531 print ("Error: No stats to show")
539 print ("Error: No stats to show")
549 if __name__ == '__main__':