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 "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"],
57 }
58
59 hdr = ["time", "pool", "ds", "obj", "zcc", "zcwc", "ziic", "zic", "ziib", \
60 "zicc", "zicb", "zinc", "zinb", "zimnc", "zimnb", "zimsc", "zimsb"]
61
62 ghdr = ["time", "zcc", "zcwc", "ziic", "zic", "ziib", "zicc", "zicb",
63 "zinc", "zinb", "zimnc", "zimnb", "zimsc", "zimsb"]
64
65 cmd = ("Usage: zilstat [-hgdv] [-i interval] [-p pool_name]")
66
67 curr = {}
68 diff = {}
69 kstat = {}
70 ds_pairs = {}
71 pool_name = None
72 dataset_name = None
73 interval = 0
74 sep = " "
75 gFlag = True
76 dsFlag = False
77
78 def prettynum(sz, scale, num=0):
79 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
80 index = 0
81 save = 0
82
83 if scale == -1:
84 return "%*s" % (sz, num)
85
86 # Rounding error, return 0
87 elif 0 < num < 1:
88 num = 0
89
90 while num > scale and index < 5:
91 save = num
92 num = num / scale
93 index += 1
94
95 if index == 0:
96 return "%*d" % (sz, num)
97
98 if (save / scale) < 10:
99 return "%*.1f%s" % (sz - 1, num, suffix[index])
100 else:
101 return "%*d%s" % (sz - 1, num, suffix[index])
102
103 def print_header():
104 global hdr
105 global sep
106 for col in hdr:
107 new_col = col
108 if interval > 0 and col not in ['time', 'pool', 'ds', 'obj']:
109 new_col += "/s"
110 sys.stdout.write("%*s%s" % (cols[col][0], new_col, sep))
111 sys.stdout.write("\n")
112
113 def print_values(v):
114 global hdr
115 global sep
116 for col in hdr:
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")
123
124 def print_dict(d):
125 for pool in d:
126 for objset in d[pool]:
127 print_values(d[pool][objset])
128
129 def detailed_usage():
130 sys.stderr.write("%s\n" % cmd)
131 sys.stderr.write("Field definitions are as follows:\n")
132 for key in cols:
133 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
134 sys.stderr.write("\n")
135 sys.exit(0)
136
137 def init():
138 global pool_name
139 global dataset_name
140 global interval
141 global hdr
142 global curr
143 global gFlag
144 global sep
145
146 curr = dict()
147
148 parser = argparse.ArgumentParser(description='Program to print zilstats',
149 add_help=True,
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"\
154 "\tzilstat -a\n"\
155 '\tzilstat -v\n'\
156 '\tzilstat -p tank\n'\
157 '\tzilstat -d tank/d1,tank/d2,tank/zv1\n'\
158 '\tzilstat -i 1\n'\
159 '\tzilstat -s \"***\"\n'\
160 '\tzilstat -f zcwc,zimnb,zimsb\n')
161
162 parser.add_argument(
163 "-v", "--verbose",
164 action="store_true",
165 help="List field headers and definitions"
166 )
167
168 pool_grp = parser.add_mutually_exclusive_group()
169
170 pool_grp.add_argument(
171 "-a", "--all",
172 action="store_true",
173 dest="all",
174 help="Print all dataset stats"
175 )
176
177 pool_grp.add_argument(
178 "-p", "--pool",
179 type=str,
180 help="Print stats for all datasets of a speicfied pool"
181 )
182
183 pool_grp.add_argument(
184 "-d", "--dataset",
185 type=str,
186 help="Print given dataset(s) (Comma separated)"
187 )
188
189 parser.add_argument(
190 "-f", "--columns",
191 type=str,
192 help="Specify specific fields to print (see -v)"
193 )
194
195 parser.add_argument(
196 "-s", "--separator",
197 type=str,
198 help="Override default field separator with custom "
199 "character or string"
200 )
201
202 parser.add_argument(
203 "-i", "--interval",
204 type=int,
205 dest="interval",
206 help="Print stats between specified interval"
207 " (in seconds)"
208 )
209
210 parsed_args = parser.parse_args()
211
212 if parsed_args.verbose:
213 detailed_usage()
214
215 if parsed_args.all:
216 gFlag = False
217
218 if parsed_args.interval:
219 interval = parsed_args.interval
220
221 if parsed_args.pool:
222 pool_name = parsed_args.pool
223 gFlag = False
224
225 if parsed_args.dataset:
226 dataset_name = parsed_args.dataset
227 gFlag = False
228
229 if parsed_args.separator:
230 sep = parsed_args.separator
231
232 if gFlag:
233 hdr = ghdr
234
235 if parsed_args.columns:
236 hdr = parsed_args.columns.split(",")
237
238 invalid = []
239 for ele in hdr:
240 if gFlag and ele not in ghdr:
241 invalid.append(ele)
242 elif ele not in cols:
243 invalid.append(ele)
244
245 if len(invalid) > 0:
246 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
247 sys.exit(1)
248
249 if pool_name and dataset_name:
250 print ("Error: Can not filter both dataset and pool")
251 sys.exit(1)
252
253 def FileCheck(fname):
254 try:
255 return (open(fname))
256 except IOError:
257 print ("Unable to open zilstat proc file: " + fname)
258 sys.exit(1)
259
260 if sys.platform.startswith('freebsd'):
261 # Requires py-sysctl on FreeBSD
262 import sysctl
263
264 def kstat_update(pool = None, objid = None):
265 global kstat
266 kstat = {}
267 if not pool:
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 + "."))
272 elif objid:
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 + "."))
277 else:
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]
283 for s in k:
284 if not s or (s.name.find("zil") == -1 and \
285 s.name.find("dataset_name") == -1):
286 continue
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-" + \
293 objid + ".")
294 kstat[objid][name[zil_start:]] = value \
295 if (name.find("dataset_name")) \
296 else int(value)
297
298 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
299 global kstat
300 if not k:
301 print("Unable to process kstat for: " + file)
302 sys.exit(1)
303 kstat[objset] = dict()
304 for s in k:
305 if not s or (s.name.find("zil") == -1 and \
306 s.name.find("dataset_name") == -1):
307 continue
308 name, value = s.name, s.value
309 kstat[objset][name[zil_start:]] = value \
310 if (name.find("dataset_name")) else int(value)
311
312 elif sys.platform.startswith('linux'):
313 def kstat_update(pool = None, objid = None):
314 global kstat
315 kstat = {}
316 if not pool:
317 k = [line.strip() for line in \
318 FileCheck("/proc/spl/kstat/zfs/zil")]
319 kstat_process_str(k, "/proc/spl/kstat/zfs/zil")
320 elif objid:
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)
324 else:
325 if not os.path.exists(f"/proc/spl/kstat/zfs/{pool}"):
326 print("Pool \"" + pool + "\" does not exist, Exitting")
327 sys.exit(1)
328 objsets = os.listdir(f'/proc/spl/kstat/zfs/{pool}')
329 for objid in objsets:
330 if objid.find("objset-") == -1:
331 continue
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-", ""))
335
336 def kstat_process_str(k, file, objset = "GLOBAL", zil_start = 0):
337 global kstat
338 if not k:
339 print("Unable to process kstat for: " + file)
340 sys.exit(1)
341
342 kstat[objset] = dict()
343 for s in k:
344 if not s or (s.find("zil") == -1 and \
345 s.find("dataset_name") == -1):
346 continue
347 name, unused, value = s.split()
348 kstat[objset][name] = value \
349 if (name == "dataset_name") else int(value)
350
351 def zil_process_kstat():
352 global curr, pool_name, dataset_name, dsFlag, ds_pairs
353 curr.clear()
354 if gFlag == True:
355 kstat_update()
356 zil_build_dict()
357 else:
358 if pool_name:
359 kstat_update(pool_name)
360 zil_build_dict(pool_name)
361 elif dataset_name:
362 if dsFlag == False:
363 dsFlag = True
364 datasets = dataset_name.split(',')
365 ds_pairs = defaultdict(list)
366 for ds in datasets:
367 try:
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:"\
375 + str(e.returncode))
376 print("Please make sure that dataset \""\
377 + str(ds) + "\" exists")
378 sys.exit(1)
379 if not objid:
380 continue
381 ds_pairs[ds.split('/')[0]]. \
382 append(hex(int(objid)))
383 for pool, objids in ds_pairs.items():
384 for objid in objids:
385 kstat_update(pool, objid)
386 zil_build_dict(pool)
387 else:
388 try:
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))
394 sys.exit(1)
395 for pool in pools:
396 kstat_update(pool)
397 zil_build_dict(pool)
398
399 def calculate_diff():
400 global curr, diff
401 prev = copy.deepcopy(curr)
402 zil_process_kstat()
403 diff = copy.deepcopy(curr)
404 for pool in curr:
405 for objset in curr[pool]:
406 for col in hdr:
407 if col not in ['time', 'pool', 'ds', 'obj']:
408 key = cols[col][2]
409 # If prev is NULL, this is the
410 # first time we are here
411 if not prev:
412 diff[pool][objset][key] = 0
413 else:
414 diff[pool][objset][key] \
415 = curr[pool][objset][key] \
416 - prev[pool][objset][key]
417
418 def zil_build_dict(pool = "GLOBAL"):
419 global kstat
420 for objset in kstat:
421 for key in kstat[objset]:
422 val = kstat[objset][key]
423 if pool not in curr:
424 curr[pool] = dict()
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", \
431 time.localtime())
432
433 def sign_handler_epipe(sig, frame):
434 print("Caught EPIPE signal: " + str(frame))
435 print("Exitting...")
436 sys.exit(0)
437
438 def main():
439 global interval
440 global curr
441 hprint = False
442 init()
443 signal.signal(signal.SIGINT, signal.SIG_DFL)
444 signal.signal(signal.SIGPIPE, sign_handler_epipe)
445
446 if interval > 0:
447 while True:
448 calculate_diff()
449 if not diff:
450 print ("Error: No stats to show")
451 sys.exit(0)
452 if hprint == False:
453 print_header()
454 hprint = True
455 print_dict(diff)
456 time.sleep(interval)
457 else:
458 zil_process_kstat()
459 if not curr:
460 print ("Error: No stats to show")
461 sys.exit(0)
462 print_header()
463 print_dict(curr)
464
465 if __name__ == '__main__':
466 main()
467
Cache object: 171dc53d2234519ecd9d4caef7e6d34e
|