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 "zcc": [10, 1000, "zil_commit_count"],
45 "zcwc": [10, 1000, "zil_commit_writer_count"],
46 "ziic": [10, 1000, "zil_itx_indirect_count"],
47 "zic": [10, 1000, "zil_itx_count"],
48 "ziib": [10, 1024, "zil_itx_indirect_bytes"],
49 "zicc": [10, 1000, "zil_itx_copied_count"],
50 "zicb": [10, 1024, "zil_itx_copied_bytes"],
51 "zinc": [10, 1000, "zil_itx_needcopy_count"],
52 "zinb": [10, 1024, "zil_itx_needcopy_bytes"],
53 "zimnc": [10, 1000, "zil_itx_metaslab_normal_count"],
54 "zimnb": [10, 1024, "zil_itx_metaslab_normal_bytes"],
55 "zimsc": [10, 1000, "zil_itx_metaslab_slog_count"],
56 "zimsb": [10, 1024, "zil_itx_metaslab_slog_bytes"],
59 hdr = ["time", "pool", "ds", "obj", "zcc", "zcwc", "ziic", "zic", "ziib", \
60 "zicc", "zicb", "zinc", "zinb", "zimnc", "zimnb", "zimsc", "zimsb"]
62 ghdr = ["time", "zcc", "zcwc", "ziic", "zic", "ziib", "zicc", "zicb",
63 "zinc", "zinb", "zimnc", "zimnb", "zimsc", "zimsb"]
65 cmd = ("Usage: zilstat [-hgdv] [-i interval] [-p pool_name]")
78 def prettynum(sz, scale, num=0):
79 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
84 return "%*s" % (sz, num)
86 # Rounding error, return 0
90 while num > scale and index < 5:
96 return "%*d" % (sz, num)
98 if (save / scale) < 10:
99 return "%*.1f%s" % (sz - 1, num, suffix[index])
101 return "%*d%s" % (sz - 1, num, suffix[index])
108 if interval > 0 and col not in ['time', 'pool', 'ds', 'obj']:
110 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep))
111 sys.stdout.write("\n")
117 val = v[cols[col][2]]
118 if col not in ['time', 'pool', 'ds', 'obj'] and interval > 0:
119 val = v[cols[col][2]] // interval
120 sys.stdout.write("%s%s" % (
121 prettynum(cols[col][0], cols[col][1], val), sep))
122 sys.stdout.write("\n")
126 for objset in d[pool]:
127 print_values(d[pool][objset])
129 def detailed_usage():
130 sys.stderr.write("%s\n" % cmd)
131 sys.stderr.write("Field definitions are as follows:\n")
133 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
134 sys.stderr.write("\n")
148 parser = argparse.ArgumentParser(description='Program to print zilstats',
150 formatter_class=RawTextHelpFormatter,
151 epilog="\nUsage Examples\n"\
152 "Note: Global zilstats is shown by default,"\
153 " if none of a|p|d option is not provided\n"\
156 '\tzilstat -p tank\n'\
157 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\
159 '\tzilstat -s \"***\"\n'\
160 '\tzilstat -f zcwc,zimnb,zimsb\n')
165 help="List field headers and definitions"
168 pool_grp = parser.add_mutually_exclusive_group()
170 pool_grp.add_argument(
174 help="Print all dataset stats"
177 pool_grp.add_argument(
180 help="Print stats for all datasets of a speicfied pool"
183 pool_grp.add_argument(
186 help="Print given dataset(s) (Comma separated)"
192 help="Specify specific fields to print (see -v)"
198 help="Override default field separator with custom "
199 "character or string"
206 help="Print stats between specified interval"
210 parsed_args = parser.parse_args()
212 if parsed_args.verbose:
218 if parsed_args.interval:
219 interval = parsed_args.interval
222 pool_name = parsed_args.pool
225 if parsed_args.dataset:
226 dataset_name = parsed_args.dataset
229 if parsed_args.separator:
230 sep = parsed_args.separator
235 if parsed_args.columns:
236 hdr = parsed_args.columns.split(",")
240 if gFlag and ele not in ghdr:
242 elif ele not in cols:
246 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
249 if pool_name and dataset_name:
250 print ("Error: Can not filter both dataset and pool")
253 def FileCheck(fname):
257 print ("Unable to open zilstat proc file: " + fname)
260 if sys.platform.startswith('freebsd'):
261 # Requires py-sysctl on FreeBSD
264 def kstat_update(pool = None, objid = None):
268 file = "kstat.zfs.misc.zil"
269 k = [ctl for ctl in sysctl.filter(file) \
270 if ctl.type != sysctl.CTLTYPE_NODE]
271 kstat_process_str(k, file, "GLOBAL", len(file + "."))
273 file = "kstat.zfs." + pool + ".dataset.objset-" + objid
274 k = [ctl for ctl in sysctl.filter(file) if ctl.type \
275 != sysctl.CTLTYPE_NODE]
276 kstat_process_str(k, file, objid, len(file + "."))
278 file = "kstat.zfs." + pool + ".dataset"
279 zil_start = len(file + ".")
280 obj_start = len("kstat.zfs." + pool + ".")
281 k = [ctl for ctl in sysctl.filter(file)
282 if ctl.type != sysctl.CTLTYPE_NODE]
284 if not s or (s.name.find("zil") == -1 and \
285 s.name.find("dataset_name") == -1):
287 name, value = s.name, s.value
288 objid = re.findall(r'0x[0-9A-F]+', \
289 name[obj_start:], re.I)[0]
290 if objid not in kstat:
291 kstat[objid] = dict()
292 zil_start = len(file + ".objset-" + \
294 kstat[objid][name[zil_start:]] = value \
295 if (name.find("dataset_name")) \
298 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
301 print("Unable to process kstat for: " + file)
303 kstat[objset] = dict()
305 if not s or (s.name.find("zil") == -1 and \
306 s.name.find("dataset_name") == -1):
308 name, value = s.name, s.value
309 kstat[objset][name[zil_start:]] = value \
310 if (name.find("dataset_name")) else int(value)
312 elif sys.platform.startswith('linux'):
313 def kstat_update(pool = None, objid = None):
317 k = [line.strip() for line in \
318 FileCheck("/proc/spl/kstat/zfs/zil")]
319 kstat_process_str(k, "/proc/spl/kstat/zfs/zil")
321 file = "/proc/spl/kstat/zfs/" + pool + "/objset-" + objid
322 k = [line.strip() for line in FileCheck(file)]
323 kstat_process_str(k, file, objid)
325 if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"):
326 print("Pool \"" + pool + "\" does not exist, Exitting")
328 objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}')
329 for objid in objsets:
330 if objid.find("objset-") == -1:
332 file = "/proc/spl/kstat/zfs/" + pool + "/" + objid
333 k = [line.strip() for line in FileCheck(file)]
334 kstat_process_str(k, file, objid.replace("objset-", ""))
336 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
339 print("Unable to process kstat for: " + file)
342 kstat[objset] = dict()
344 if not s or (s.find("zil") == -1 and \
345 s.find("dataset_name") == -1):
347 name, unused, value = s.split()
348 kstat[objset][name] = value \
349 if (name == "dataset_name") else int(value)
351 def zil_process_kstat():
352 global curr, pool_name, dataset_name, dsFlag, ds_pairs
359 kstat_update(pool_name)
360 zil_build_dict(pool_name)
364 datasets = dataset_name.split(',')
365 ds_pairs = defaultdict(list)
368 objid = subprocess.check_output(['zfs',
369 'list', '-Hpo', 'objsetid', ds], \
370 stderr=subprocess.DEVNULL) \
371 .decode('utf-8').strip()
372 except subprocess.CalledProcessError as e:
373 print("Command: \"zfs list -Hpo objset "\
374 + str(ds) + "\" failed with error code:"\
376 print("Please make sure that dataset \""\
377 + str(ds) + "\" exists")
381 ds_pairs[ds.split('/')[0]]. \
382 append(hex(int(objid)))
383 for pool, objids in ds_pairs.items():
385 kstat_update(pool, objid)
389 pools = subprocess.check_output(['zpool', 'list', '-Hpo',\
390 'name']).decode('utf-8').split()
391 except subprocess.CalledProcessError as e:
392 print("Command: \"zpool list -Hpo name\" failed with error"\
393 "code: " + str(e.returncode))
399 def calculate_diff():
401 prev = copy.deepcopy(curr)
403 diff = copy.deepcopy(curr)
405 for objset in curr[pool]:
407 if col not in ['time', 'pool', 'ds', 'obj']:
409 # If prev is NULL, this is the
410 # first time we are here
412 diff[pool][objset][key] = 0
414 diff[pool][objset][key] \
415 = curr[pool][objset][key] \
416 - prev[pool][objset][key]
418 def zil_build_dict(pool = "GLOBAL"):
421 for key in kstat[objset]:
422 val = kstat[objset][key]
425 if objset not in curr[pool]:
426 curr[pool][objset] = dict()
427 curr[pool][objset][key] = val
428 curr[pool][objset]["pool"] = pool
429 curr[pool][objset]["objset"] = objset
430 curr[pool][objset]["time"] = time.strftime("%H:%M:%S", \
433 def sign_handler_epipe(sig, frame):
434 print("Caught EPIPE signal: " + str(frame))
443 signal.signal(signal.SIGINT, signal.SIG_DFL)
444 signal.signal(signal.SIGPIPE, sign_handler_epipe)
450 print ("Error: No stats to show")
460 print ("Error: No stats to show")
465 if __name__ == '__main__':