Tag 2.2.0-rc4
[zfs.git] / cmd / zilstat.in
blobe8678e20cafa5a63745463e9e0ef495f814c01b3
1 #!/usr/bin/env @PYTHON_SHEBANG@
3 # Print out statistics for all zil stats. This information is
4 # available through the zil kstat.
6 # CDDL HEADER START
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.
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+.
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
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"],
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]")
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
96 def prettynum(sz, scale, num=0):
97         suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
98         index = 0
99         save = 0
101         if scale == -1:
102                 return "%*s" % (sz, num)
104         # Rounding error, return 0
105         elif 0 < num < 1:
106                 num = 0
108         while num > scale and index < 5:
109                 save = num
110                 num = num / scale
111                 index += 1
113         if index == 0:
114                 return "%*d" % (sz, num)
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])
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")
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")
142 def print_dict(d):
143         for pool in d:
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")
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)
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
164         curr = dict()
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')
180         parser.add_argument(
181                 "-v", "--verbose",
182                 action="store_true",
183                 help="List field headers and definitions"
184         )
186         pool_grp = parser.add_mutually_exclusive_group()
188         pool_grp.add_argument(
189                 "-a", "--all",
190                 action="store_true",
191                 dest="all",
192                 help="Print all dataset stats"
193         )
195         pool_grp.add_argument(
196                 "-p", "--pool",
197                 type=str,
198                 help="Print stats for all datasets of a speicfied pool"
199         )
201         pool_grp.add_argument(
202                 "-d", "--dataset",
203                 type=str,
204                 help="Print given dataset(s) (Comma separated)"
205         )
207         parser.add_argument(
208                 "-f", "--columns",
209                 type=str,
210                 help="Specify specific fields to print (see -v)"
211         )
213         parser.add_argument(
214                 "-s", "--separator",
215                 type=str,
216                 help="Override default field separator with custom "
217                          "character or string"
218         )
220         parser.add_argument(
221                 "-i", "--interval",
222                 type=int,
223                 dest="interval",
224                 help="Print stats between specified interval"
225                          " (in seconds)"
226         )
228         parsed_args = parser.parse_args()
230         if parsed_args.verbose:
231                 detailed_usage()
233         if parsed_args.all:
234                 gFlag = False
236         if parsed_args.interval:
237                 interval = parsed_args.interval
239         if parsed_args.pool:
240                 pool_name = parsed_args.pool
241                 gFlag = False
243         if parsed_args.dataset:
244                 dataset_name = parsed_args.dataset
245                 gFlag = False
247         if parsed_args.separator:
248                 sep = parsed_args.separator
250         if gFlag:
251                 hdr = ghdr
253         if parsed_args.columns:
254                 hdr = parsed_args.columns.split(",")
256                 invalid = []
257                 for ele in hdr:
258                         if ele not in cols:
259                                 invalid.append(ele)
261                 if len(invalid) > 0:
262                         sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
263                         sys.exit(1)
265         if pool_name and dataset_name:
266                 print ("Error: Can not filter both dataset and pool")
267                 sys.exit(1)
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)
276 if sys.platform.startswith('freebsd'):
277         # Requires py-sysctl on FreeBSD
278         import sysctl
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)
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)
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-", ""))
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)
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)
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)
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]
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
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
516 def sign_handler_epipe(sig, frame):
517         print("Caught EPIPE signal: " + str(frame))
518         print("Exitting...")
519         sys.exit(0)
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)
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)
549 if __name__ == '__main__':
550         main()