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 "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"],
80 hdr = ["time", "ds", "cc", "ic", "idc", "idb", "iic", "iib",
81 "imnc", "imnw", "imsc", "imsw"]
83 ghdr = ["time", "cc", "ic", "idc", "idb", "iic", "iib",
84 "imnc", "imnw", "imsc", "imsw"]
86 cmd = ("Usage: zilstat [-hgdv] [-i interval] [-p pool_name]")
99 def prettynum(sz, scale, num=0):
100 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
105 return "%*s" % (sz, num)
107 # Rounding error, return 0
111 while num > scale and index < 5:
117 return "%*d" % (sz, num)
119 if (save / scale) < 10:
120 return "%*.1f%s" % (sz - 1, num, suffix[index])
122 return "%*d%s" % (sz - 1, num, suffix[index])
129 if interval > 0 and cols[col][1] > 100:
131 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep))
132 sys.stdout.write("\n")
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")
147 for objset in d[pool]:
148 print_values(d[pool][objset])
150 def detailed_usage():
151 sys.stderr.write("%s\n" % cmd)
152 sys.stderr.write("Field definitions are as follows:\n")
154 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
155 sys.stderr.write("\n")
169 parser = argparse.ArgumentParser(description='Program to print zilstats',
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"\
177 '\tzilstat -p tank\n'\
178 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\
180 '\tzilstat -s \"***\"\n'\
181 '\tzilstat -f zcwc,zimnb,zimsb\n')
186 help="List field headers and definitions"
189 pool_grp = parser.add_mutually_exclusive_group()
191 pool_grp.add_argument(
195 help="Print all dataset stats"
198 pool_grp.add_argument(
201 help="Print stats for all datasets of a speicfied pool"
204 pool_grp.add_argument(
207 help="Print given dataset(s) (Comma separated)"
213 help="Specify specific fields to print (see -v)"
219 help="Override default field separator with custom "
220 "character or string"
227 help="Print stats between specified interval"
231 parsed_args = parser.parse_args()
233 if parsed_args.verbose:
239 if parsed_args.interval:
240 interval = parsed_args.interval
243 pool_name = parsed_args.pool
246 if parsed_args.dataset:
247 dataset_name = parsed_args.dataset
250 if parsed_args.separator:
251 sep = parsed_args.separator
256 if parsed_args.columns:
257 hdr = parsed_args.columns.split(",")
265 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
268 if pool_name and dataset_name:
269 print ("Error: Can not filter both dataset and pool")
272 def FileCheck(fname):
276 print ("Unable to open zilstat proc file: " + fname)
279 if sys.platform.startswith('freebsd'):
280 # Requires py-sysctl on FreeBSD
283 def kstat_update(pool = None, objid = None):
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 + "."))
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 + "."))
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]
303 if not s or (s.name.find("zil") == -1 and \
304 s.name.find("dataset_name") == -1):
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-" + \
313 kstat[objid][name[zil_start:]] = value \
314 if (name.find("dataset_name")) \
317 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
320 print("Unable to process kstat for: " + file)
322 kstat[objset] = dict()
324 if not s or (s.name.find("zil") == -1 and \
325 s.name.find("dataset_name") == -1):
327 name, value = s.name, s.value
328 kstat[objset][name[zil_start:]] = value \
329 if (name.find("dataset_name")) else int(value)
331 elif sys.platform.startswith('linux'):
332 def kstat_update(pool = None, objid = None):
336 k = [line.strip() for line in \
337 FileCheck("/proc/spl/kstat/zfs/zil")]
338 kstat_process_str(k, "/proc/spl/kstat/zfs/zil")
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)
344 if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"):
345 print("Pool \"" + pool + "\" does not exist, Exitting")
347 objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}')
348 for objid in objsets:
349 if objid.find("objset-") == -1:
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-", ""))
355 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
358 print("Unable to process kstat for: " + file)
361 kstat[objset] = dict()
363 if not s or (s.find("zil") == -1 and \
364 s.find("dataset_name") == -1):
366 name, unused, value = s.split()
367 kstat[objset][name] = value \
368 if (name == "dataset_name") else int(value)
370 def zil_process_kstat():
371 global curr, pool_name, dataset_name, dsFlag, ds_pairs
378 kstat_update(pool_name)
379 zil_build_dict(pool_name)
383 datasets = dataset_name.split(',')
384 ds_pairs = defaultdict(list)
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:"\
395 print("Please make sure that dataset \""\
396 + str(ds) + "\" exists")
400 ds_pairs[ds.split('/')[0]]. \
401 append(hex(int(objid)))
402 for pool, objids in ds_pairs.items():
404 kstat_update(pool, objid)
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))
418 def calculate_diff():
420 prev = copy.deepcopy(curr)
422 diff = copy.deepcopy(curr)
424 for objset in curr[pool]:
425 for key in curr[pool][objset]:
426 if not isinstance(diff[pool][objset][key], int):
428 # If prev is NULL, this is the
429 # first time we are here
431 diff[pool][objset][key] = 0
433 diff[pool][objset][key] \
434 = curr[pool][objset][key] \
435 - prev[pool][objset][key]
437 def zil_build_dict(pool = "GLOBAL"):
440 for key in kstat[objset]:
441 val = kstat[objset][key]
444 if objset not in curr[pool]:
445 curr[pool][objset] = dict()
446 curr[pool][objset][key] = val
448 def zil_extend_dict():
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", \
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"]
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"]
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"]
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"]
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"]
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"]
517 diff[pool][objset]["imsb/imsw"] = 100
519 def sign_handler_epipe(sig, frame):
520 print("Caught EPIPE signal: " + str(frame))
529 signal.signal(signal.SIGINT, signal.SIG_DFL)
530 signal.signal(signal.SIGPIPE, sign_handler_epipe)
534 print ("Error: No stats to show")
542 print ("Error: No stats to show")
552 if __name__ == '__main__':