FreeBSD: ignore some includes when not building kernel
[zfs.git] / cmd / zilstat.in
blob6be7f83936d7dd7a10137ccc0e04986d2b8823cf
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         "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]")
88 curr = {}
89 diff = {}
90 kstat = {}
91 ds_pairs = {}
92 pool_name = None
93 dataset_name = None
94 interval = 0
95 sep = "  "
96 gFlag = True
97 dsFlag = False
99 def prettynum(sz, scale, num=0):
100         suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
101         index = 0
102         save = 0
104         if scale == -1:
105                 return "%*s" % (sz, num)
107         # Rounding error, return 0
108         elif 0 < num < 1:
109                 num = 0
111         while num > scale and index < 5:
112                 save = num
113                 num = num / scale
114                 index += 1
116         if index == 0:
117                 return "%*d" % (sz, num)
119         if (save / scale) < 10:
120                 return "%*.1f%s" % (sz - 1, num, suffix[index])
121         else:
122                 return "%*d%s" % (sz - 1, num, suffix[index])
124 def print_header():
125         global hdr
126         global sep
127         for col in hdr:
128                 new_col = col
129                 if interval > 0 and cols[col][1] > 100:
130                         new_col += "/s"
131                 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep))
132         sys.stdout.write("\n")
134 def print_values(v):
135         global hdr
136         global sep
137         for col in hdr:
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")
145 def print_dict(d):
146         for pool in d:
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")
153         for key in cols:
154                 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
155         sys.stderr.write("\n")
156         sys.exit(0)
158 def init():
159         global pool_name
160         global dataset_name
161         global interval
162         global hdr
163         global curr
164         global gFlag
165         global sep
167         curr = dict()
169         parser = argparse.ArgumentParser(description='Program to print zilstats',
170                                          add_help=True,
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"\
175                                                 "\tzilstat -a\n"\
176                                                 '\tzilstat -v\n'\
177                                                 '\tzilstat -p tank\n'\
178                                                 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\
179                                                 '\tzilstat -i 1\n'\
180                                                 '\tzilstat -s \"***\"\n'\
181                                                 '\tzilstat -f zcwc,zimnb,zimsb\n')
183         parser.add_argument(
184                 "-v", "--verbose",
185                 action="store_true",
186                 help="List field headers and definitions"
187         )
189         pool_grp = parser.add_mutually_exclusive_group()
191         pool_grp.add_argument(
192                 "-a", "--all",
193                 action="store_true",
194                 dest="all",
195                 help="Print all dataset stats"
196         )
198         pool_grp.add_argument(
199                 "-p", "--pool",
200                 type=str,
201                 help="Print stats for all datasets of a speicfied pool"
202         )
204         pool_grp.add_argument(
205                 "-d", "--dataset",
206                 type=str,
207                 help="Print given dataset(s) (Comma separated)"
208         )
210         parser.add_argument(
211                 "-f", "--columns",
212                 type=str,
213                 help="Specify specific fields to print (see -v)"
214         )
216         parser.add_argument(
217                 "-s", "--separator",
218                 type=str,
219                 help="Override default field separator with custom "
220                          "character or string"
221         )
223         parser.add_argument(
224                 "-i", "--interval",
225                 type=int,
226                 dest="interval",
227                 help="Print stats between specified interval"
228                          " (in seconds)"
229         )
231         parsed_args = parser.parse_args()
233         if parsed_args.verbose:
234                 detailed_usage()
236         if parsed_args.all:
237                 gFlag = False
239         if parsed_args.interval:
240                 interval = parsed_args.interval
242         if parsed_args.pool:
243                 pool_name = parsed_args.pool
244                 gFlag = False
246         if parsed_args.dataset:
247                 dataset_name = parsed_args.dataset
248                 gFlag = False
250         if parsed_args.separator:
251                 sep = parsed_args.separator
253         if gFlag:
254                 hdr = ghdr
256         if parsed_args.columns:
257                 hdr = parsed_args.columns.split(",")
259                 invalid = []
260                 for ele in hdr:
261                         if ele not in cols:
262                                 invalid.append(ele)
264                 if len(invalid) > 0:
265                         sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
266                         sys.exit(1)
268         if pool_name and dataset_name:
269                 print ("Error: Can not filter both dataset and pool")
270                 sys.exit(1)
272 def FileCheck(fname):
273         try:
274                 return (open(fname))
275         except IOError:
276                 print ("Unable to open zilstat proc file: " + fname)
277                 sys.exit(1)
279 if sys.platform.startswith('freebsd'):
280         # Requires py-sysctl on FreeBSD
281         import sysctl
283         def kstat_update(pool = None, objid = None):
284                 global kstat
285                 kstat = {}
286                 if not pool:
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 + "."))
291                 elif objid:
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 + "."))
296                 else:
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]
302                         for s in k:
303                                 if not s or (s.name.find("zil") == -1 and \
304                                         s.name.find("dataset_name") == -1):
305                                         continue
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-" + \
312                                         objid + ".")
313                                 kstat[objid][name[zil_start:]] = value \
314                                         if (name.find("dataset_name")) \
315                                         else int(value)
317         def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
318                         global kstat
319                         if not k:
320                                 print("Unable to process kstat for: " + file)
321                                 sys.exit(1)
322                         kstat[objset] = dict()
323                         for s in k:
324                                 if not s or (s.name.find("zil") == -1 and \
325                                     s.name.find("dataset_name") == -1):
326                                         continue
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):
333                 global kstat
334                 kstat = {}
335                 if not pool:
336                         k = [line.strip() for line in \
337                                 FileCheck("/proc/spl/kstat/zfs/zil")]
338                         kstat_process_str(k, "/proc/spl/kstat/zfs/zil")
339                 elif objid:
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)
343                 else:
344                         if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"):
345                                 print("Pool \"" + pool + "\" does not exist, Exitting")
346                                 sys.exit(1)
347                         objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}')
348                         for objid in objsets:
349                                 if objid.find("objset-") == -1:
350                                         continue
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):
356                         global kstat
357                         if not k:
358                                 print("Unable to process kstat for: " + file)
359                                 sys.exit(1)
361                         kstat[objset] = dict()
362                         for s in k:
363                                 if not s or (s.find("zil") == -1 and \
364                                     s.find("dataset_name") == -1):
365                                         continue
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
372         curr.clear()
373         if gFlag == True:
374                 kstat_update()
375                 zil_build_dict()
376         else:
377                 if pool_name:
378                         kstat_update(pool_name)
379                         zil_build_dict(pool_name)
380                 elif dataset_name:
381                         if dsFlag == False:
382                                 dsFlag = True
383                                 datasets = dataset_name.split(',')
384                                 ds_pairs = defaultdict(list)
385                                 for ds in datasets:
386                                         try:
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:"\
394                                                 + str(e.returncode))
395                                                 print("Please make sure that dataset \""\
396                                                 + str(ds) + "\" exists")
397                                                 sys.exit(1)
398                                         if not objid:
399                                                 continue
400                                         ds_pairs[ds.split('/')[0]]. \
401                                                 append(hex(int(objid)))
402                         for pool, objids in ds_pairs.items():
403                                 for objid in objids:
404                                         kstat_update(pool, objid)
405                                         zil_build_dict(pool)
406                 else:
407                         try:
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))
413                                 sys.exit(1)
414                         for pool in pools:
415                                 kstat_update(pool)
416                                 zil_build_dict(pool)
418 def calculate_diff():
419         global curr, diff
420         prev = copy.deepcopy(curr)
421         zil_process_kstat()
422         diff = copy.deepcopy(curr)
423         for pool in curr:
424                 for objset in curr[pool]:
425                         for key in curr[pool][objset]:
426                                 if not isinstance(diff[pool][objset][key], int):
427                                         continue
428                                 # If prev is NULL, this is the
429                                 # first time we are here
430                                 if not prev:
431                                         diff[pool][objset][key] = 0
432                                 else:
433                                         diff[pool][objset][key] \
434                                                 = curr[pool][objset][key] \
435                                                 - prev[pool][objset][key]
437 def zil_build_dict(pool = "GLOBAL"):
438         global kstat
439         for objset in kstat:
440                 for key in kstat[objset]:
441                         val = kstat[objset][key]
442                         if pool not in curr:
443                                 curr[pool] = dict()
444                         if objset not in curr[pool]:
445                                 curr[pool][objset] = dict()
446                         curr[pool][objset][key] = val
448 def zil_extend_dict():
449         global diff
450         for pool in diff:
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", \
455                                 time.localtime())
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"]
486                         else:
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"]
492                         else:
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"]
498                         else:
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"]
504                         else:
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"]
510                         else:
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"]
516                         else:
517                                 diff[pool][objset]["imsb/imsw"] = 100
519 def sign_handler_epipe(sig, frame):
520         print("Caught EPIPE signal: " + str(frame))
521         print("Exitting...")
522         sys.exit(0)
524 def main():
525         global interval
526         global curr, diff
527         hprint = False
528         init()
529         signal.signal(signal.SIGINT, signal.SIG_DFL)
530         signal.signal(signal.SIGPIPE, sign_handler_epipe)
532         zil_process_kstat()
533         if not curr:
534                 print ("Error: No stats to show")
535                 sys.exit(0)
536         print_header()
537         if interval > 0:
538                 time.sleep(interval)
539                 while True:
540                         calculate_diff()
541                         if not diff:
542                                 print ("Error: No stats to show")
543                                 sys.exit(0)
544                         zil_extend_dict()
545                         print_dict(diff)
546                         time.sleep(interval)
547         else:
548                 diff = curr
549                 zil_extend_dict()
550                 print_dict(diff)
552 if __name__ == '__main__':
553         main()