]> git.proxmox.com Git - mirror_zfs.git/blob - cmd/zilstat.in
Add newline to two zpool messages
[mirror_zfs.git] / cmd / zilstat.in
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
27 import sys
28 import subprocess
29 import time
30 import copy
31 import os
32 import re
33 import signal
34 from collections import defaultdict
35 import argparse
36 from argparse import RawTextHelpFormatter
37
38 cols = {
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"],
75 }
76
77 hdr = ["time", "ds", "cc", "ic", "idc", "idb", "iic", "iib",
78 "imnc", "imnw", "imsc", "imsw"]
79
80 ghdr = ["time", "cc", "ic", "idc", "idb", "iic", "iib",
81 "imnc", "imnw", "imsc", "imsw"]
82
83 cmd = ("Usage: zilstat [-hgdv] [-i interval] [-p pool_name]")
84
85 curr = {}
86 diff = {}
87 kstat = {}
88 ds_pairs = {}
89 pool_name = None
90 dataset_name = None
91 interval = 0
92 sep = " "
93 gFlag = True
94 dsFlag = False
95
96 def prettynum(sz, scale, num=0):
97 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
98 index = 0
99 save = 0
100
101 if scale == -1:
102 return "%*s" % (sz, num)
103
104 # Rounding error, return 0
105 elif 0 < num < 1:
106 num = 0
107
108 while num > scale and index < 5:
109 save = num
110 num = num / scale
111 index += 1
112
113 if index == 0:
114 return "%*d" % (sz, num)
115
116 if (save / scale) < 10:
117 return "%*.1f%s" % (sz - 1, num, suffix[index])
118 else:
119 return "%*d%s" % (sz - 1, num, suffix[index])
120
121 def print_header():
122 global hdr
123 global sep
124 for col in hdr:
125 new_col = col
126 if interval > 0 and cols[col][1] > 100:
127 new_col += "/s"
128 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep))
129 sys.stdout.write("\n")
130
131 def print_values(v):
132 global hdr
133 global sep
134 for col in hdr:
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")
141
142 def print_dict(d):
143 for pool in d:
144 for objset in d[pool]:
145 print_values(d[pool][objset])
146
147 def detailed_usage():
148 sys.stderr.write("%s\n" % cmd)
149 sys.stderr.write("Field definitions are as follows:\n")
150 for key in cols:
151 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
152 sys.stderr.write("\n")
153 sys.exit(0)
154
155 def init():
156 global pool_name
157 global dataset_name
158 global interval
159 global hdr
160 global curr
161 global gFlag
162 global sep
163
164 curr = dict()
165
166 parser = argparse.ArgumentParser(description='Program to print zilstats',
167 add_help=True,
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"\
172 "\tzilstat -a\n"\
173 '\tzilstat -v\n'\
174 '\tzilstat -p tank\n'\
175 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\
176 '\tzilstat -i 1\n'\
177 '\tzilstat -s \"***\"\n'\
178 '\tzilstat -f zcwc,zimnb,zimsb\n')
179
180 parser.add_argument(
181 "-v", "--verbose",
182 action="store_true",
183 help="List field headers and definitions"
184 )
185
186 pool_grp = parser.add_mutually_exclusive_group()
187
188 pool_grp.add_argument(
189 "-a", "--all",
190 action="store_true",
191 dest="all",
192 help="Print all dataset stats"
193 )
194
195 pool_grp.add_argument(
196 "-p", "--pool",
197 type=str,
198 help="Print stats for all datasets of a speicfied pool"
199 )
200
201 pool_grp.add_argument(
202 "-d", "--dataset",
203 type=str,
204 help="Print given dataset(s) (Comma separated)"
205 )
206
207 parser.add_argument(
208 "-f", "--columns",
209 type=str,
210 help="Specify specific fields to print (see -v)"
211 )
212
213 parser.add_argument(
214 "-s", "--separator",
215 type=str,
216 help="Override default field separator with custom "
217 "character or string"
218 )
219
220 parser.add_argument(
221 "-i", "--interval",
222 type=int,
223 dest="interval",
224 help="Print stats between specified interval"
225 " (in seconds)"
226 )
227
228 parsed_args = parser.parse_args()
229
230 if parsed_args.verbose:
231 detailed_usage()
232
233 if parsed_args.all:
234 gFlag = False
235
236 if parsed_args.interval:
237 interval = parsed_args.interval
238
239 if parsed_args.pool:
240 pool_name = parsed_args.pool
241 gFlag = False
242
243 if parsed_args.dataset:
244 dataset_name = parsed_args.dataset
245 gFlag = False
246
247 if parsed_args.separator:
248 sep = parsed_args.separator
249
250 if gFlag:
251 hdr = ghdr
252
253 if parsed_args.columns:
254 hdr = parsed_args.columns.split(",")
255
256 invalid = []
257 for ele in hdr:
258 if ele not in cols:
259 invalid.append(ele)
260
261 if len(invalid) > 0:
262 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
263 sys.exit(1)
264
265 if pool_name and dataset_name:
266 print ("Error: Can not filter both dataset and pool")
267 sys.exit(1)
268
269 def FileCheck(fname):
270 try:
271 return (open(fname))
272 except IOError:
273 print ("Unable to open zilstat proc file: " + fname)
274 sys.exit(1)
275
276 if sys.platform.startswith('freebsd'):
277 # Requires py-sysctl on FreeBSD
278 import sysctl
279
280 def kstat_update(pool = None, objid = None):
281 global kstat
282 kstat = {}
283 if not pool:
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 + "."))
288 elif objid:
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 + "."))
293 else:
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]
299 for s in k:
300 if not s or (s.name.find("zil") == -1 and \
301 s.name.find("dataset_name") == -1):
302 continue
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-" + \
309 objid + ".")
310 kstat[objid][name[zil_start:]] = value \
311 if (name.find("dataset_name")) \
312 else int(value)
313
314 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
315 global kstat
316 if not k:
317 print("Unable to process kstat for: " + file)
318 sys.exit(1)
319 kstat[objset] = dict()
320 for s in k:
321 if not s or (s.name.find("zil") == -1 and \
322 s.name.find("dataset_name") == -1):
323 continue
324 name, value = s.name, s.value
325 kstat[objset][name[zil_start:]] = value \
326 if (name.find("dataset_name")) else int(value)
327
328 elif sys.platform.startswith('linux'):
329 def kstat_update(pool = None, objid = None):
330 global kstat
331 kstat = {}
332 if not pool:
333 k = [line.strip() for line in \
334 FileCheck("/proc/spl/kstat/zfs/zil")]
335 kstat_process_str(k, "/proc/spl/kstat/zfs/zil")
336 elif objid:
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)
340 else:
341 if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"):
342 print("Pool \"" + pool + "\" does not exist, Exitting")
343 sys.exit(1)
344 objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}')
345 for objid in objsets:
346 if objid.find("objset-") == -1:
347 continue
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-", ""))
351
352 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
353 global kstat
354 if not k:
355 print("Unable to process kstat for: " + file)
356 sys.exit(1)
357
358 kstat[objset] = dict()
359 for s in k:
360 if not s or (s.find("zil") == -1 and \
361 s.find("dataset_name") == -1):
362 continue
363 name, unused, value = s.split()
364 kstat[objset][name] = value \
365 if (name == "dataset_name") else int(value)
366
367 def zil_process_kstat():
368 global curr, pool_name, dataset_name, dsFlag, ds_pairs
369 curr.clear()
370 if gFlag == True:
371 kstat_update()
372 zil_build_dict()
373 else:
374 if pool_name:
375 kstat_update(pool_name)
376 zil_build_dict(pool_name)
377 elif dataset_name:
378 if dsFlag == False:
379 dsFlag = True
380 datasets = dataset_name.split(',')
381 ds_pairs = defaultdict(list)
382 for ds in datasets:
383 try:
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:"\
391 + str(e.returncode))
392 print("Please make sure that dataset \""\
393 + str(ds) + "\" exists")
394 sys.exit(1)
395 if not objid:
396 continue
397 ds_pairs[ds.split('/')[0]]. \
398 append(hex(int(objid)))
399 for pool, objids in ds_pairs.items():
400 for objid in objids:
401 kstat_update(pool, objid)
402 zil_build_dict(pool)
403 else:
404 try:
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))
410 sys.exit(1)
411 for pool in pools:
412 kstat_update(pool)
413 zil_build_dict(pool)
414
415 def calculate_diff():
416 global curr, diff
417 prev = copy.deepcopy(curr)
418 zil_process_kstat()
419 diff = copy.deepcopy(curr)
420 for pool in curr:
421 for objset in curr[pool]:
422 for key in curr[pool][objset]:
423 if not isinstance(diff[pool][objset][key], int):
424 continue
425 # If prev is NULL, this is the
426 # first time we are here
427 if not prev:
428 diff[pool][objset][key] = 0
429 else:
430 diff[pool][objset][key] \
431 = curr[pool][objset][key] \
432 - prev[pool][objset][key]
433
434 def zil_build_dict(pool = "GLOBAL"):
435 global kstat
436 for objset in kstat:
437 for key in kstat[objset]:
438 val = kstat[objset][key]
439 if pool not in curr:
440 curr[pool] = dict()
441 if objset not in curr[pool]:
442 curr[pool][objset] = dict()
443 curr[pool][objset][key] = val
444
445 def zil_extend_dict():
446 global diff
447 for pool in diff:
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", \
452 time.localtime())
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"]
483 else:
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"]
489 else:
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"]
495 else:
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"]
501 else:
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"]
507 else:
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"]
513 else:
514 diff[pool][objset]["imsb/imsw"] = 100
515
516 def sign_handler_epipe(sig, frame):
517 print("Caught EPIPE signal: " + str(frame))
518 print("Exitting...")
519 sys.exit(0)
520
521 def main():
522 global interval
523 global curr, diff
524 hprint = False
525 init()
526 signal.signal(signal.SIGINT, signal.SIG_DFL)
527 signal.signal(signal.SIGPIPE, sign_handler_epipe)
528
529 zil_process_kstat()
530 if not curr:
531 print ("Error: No stats to show")
532 sys.exit(0)
533 print_header()
534 if interval > 0:
535 time.sleep(interval)
536 while True:
537 calculate_diff()
538 if not diff:
539 print ("Error: No stats to show")
540 sys.exit(0)
541 zil_extend_dict()
542 print_dict(diff)
543 time.sleep(interval)
544 else:
545 diff = curr
546 zil_extend_dict()
547 print_dict(diff)
548
549 if __name__ == '__main__':
550 main()
551