]> git.proxmox.com Git - mirror_zfs.git/blob - cmd/arcstat/arcstat.py
Added column definitions to arcstat.py
[mirror_zfs.git] / cmd / arcstat / arcstat.py
1 #!/usr/bin/python
2 #
3 # Print out ZFS ARC Statistics exported via kstat(1)
4 # For a definition of fields, or usage, use arctstat.pl -v
5 #
6 # This script is a fork of the original arcstat.pl (0.1) by
7 # Neelakanth Nadgir, originally published on his Sun blog on
8 # 09/18/2007
9 # http://blogs.sun.com/realneel/entry/zfs_arc_statistics
10 #
11 # This version aims to improve upon the original by adding features
12 # and fixing bugs as needed. This version is maintained by
13 # Mike Harsch and is hosted in a public open source repository:
14 # http://github.com/mharsch/arcstat
15 #
16 # Comments, Questions, or Suggestions are always welcome.
17 # Contact the maintainer at ( mike at harschsystems dot com )
18 #
19 # CDDL HEADER START
20 #
21 # The contents of this file are subject to the terms of the
22 # Common Development and Distribution License, Version 1.0 only
23 # (the "License"). You may not use this file except in compliance
24 # with the License.
25 #
26 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
27 # or http://www.opensolaris.org/os/licensing.
28 # See the License for the specific language governing permissions
29 # and limitations under the License.
30 #
31 # When distributing Covered Code, include this CDDL HEADER in each
32 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
33 # If applicable, add the following below this CDDL HEADER, with the
34 # fields enclosed by brackets "[]" replaced with your own identifying
35 # information: Portions Copyright [yyyy] [name of copyright owner]
36 #
37 # CDDL HEADER END
38 #
39 #
40 # Fields have a fixed width. Every interval, we fill the "v"
41 # hash with its corresponding value (v[field]=value) using calculate().
42 # @hdr is the array of fields that needs to be printed, so we
43 # just iterate over this array and print the values using our pretty printer.
44 #
45
46
47 import sys
48 import time
49 import getopt
50 import re
51 import copy
52
53 from decimal import Decimal
54 from signal import signal, SIGINT, SIGWINCH, SIG_DFL
55
56 cols = {
57 # HDR: [Size, Scale, Description]
58 "time": [8, -1, "Time"],
59 "hits": [4, 1000, "ARC reads per second"],
60 "miss": [4, 1000, "ARC misses per second"],
61 "read": [4, 1000, "Total ARC accesses per second"],
62 "hit%": [4, 100, "ARC Hit percentage"],
63 "miss%": [5, 100, "ARC miss percentage"],
64 "dhit": [4, 1000, "Demand hits per second"],
65 "dmis": [4, 1000, "Demand misses per second"],
66 "dh%": [3, 100, "Demand hit percentage"],
67 "dm%": [3, 100, "Demand miss percentage"],
68 "phit": [4, 1000, "Prefetch hits per second"],
69 "pmis": [4, 1000, "Prefetch misses per second"],
70 "ph%": [3, 100, "Prefetch hits percentage"],
71 "pm%": [3, 100, "Prefetch miss percentage"],
72 "mhit": [4, 1000, "Metadata hits per second"],
73 "mmis": [4, 1000, "Metadata misses per second"],
74 "mread": [5, 1000, "Metadata accesses per second"],
75 "mh%": [3, 100, "Metadata hit percentage"],
76 "mm%": [3, 100, "Metadata miss percentage"],
77 "arcsz": [5, 1024, "ARC Size"],
78 "c": [4, 1024, "ARC Target Size"],
79 "mfu": [4, 1000, "MFU List hits per second"],
80 "mru": [4, 1000, "MRU List hits per second"],
81 "mfug": [4, 1000, "MFU Ghost List hits per second"],
82 "mrug": [4, 1000, "MRU Ghost List hits per second"],
83 "eskip": [5, 1000, "evict_skip per second"],
84 "mtxmis": [6, 1000, "mutex_miss per second"],
85 "dread": [5, 1000, "Demand accesses per second"],
86 "pread": [5, 1000, "Prefetch accesses per second"],
87 "l2hits": [6, 1000, "L2ARC hits per second"],
88 "l2miss": [6, 1000, "L2ARC misses per second"],
89 "l2read": [6, 1000, "Total L2ARC accesses per second"],
90 "l2hit%": [6, 100, "L2ARC access hit percentage"],
91 "l2miss%": [7, 100, "L2ARC access miss percentage"],
92 "l2asize": [7, 1024, "Actual (compressed) size of the L2ARC"],
93 "l2size": [6, 1024, "Size of the L2ARC"],
94 "l2bytes": [7, 1024, "bytes read per second from the L2ARC"],
95 "grow": [4, 1000, "ARC Grow disabled"],
96 "need": [4, 1024, "ARC Reclaim need"],
97 "free": [4, 1024, "ARC Free memory"],
98 }
99
100 v = {}
101 hdr = ["time", "read", "miss", "miss%", "dmis", "dm%", "pmis", "pm%", "mmis",
102 "mm%", "arcsz", "c"]
103 xhdr = ["time", "mfu", "mru", "mfug", "mrug", "eskip", "mtxmis", "dread",
104 "pread", "read"]
105 sint = 1 # Default interval is 1 second
106 count = 1 # Default count is 1
107 hdr_intr = 20 # Print header every 20 lines of output
108 opfile = None
109 sep = " " # Default separator is 2 spaces
110 version = "0.4"
111 l2exist = False
112 cmd = ("Usage: arcstat.py [-hvx] [-f fields] [-o file] [-s string] [interval "
113 "[count]]\n")
114 cur = {}
115 d = {}
116 out = None
117 kstat = None
118
119
120 def detailed_usage():
121 sys.stderr.write("%s\n" % cmd)
122 sys.stderr.write("Field definitions are as follows:\n")
123 for key in cols:
124 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
125 sys.stderr.write("\n")
126
127 sys.exit(0)
128
129
130 def usage():
131 sys.stderr.write("%s\n" % cmd)
132 sys.stderr.write("\t -h : Print this help message\n")
133 sys.stderr.write("\t -v : List all possible field headers and definitions"
134 "\n")
135 sys.stderr.write("\t -x : Print extended stats\n")
136 sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n")
137 sys.stderr.write("\t -o : Redirect output to the specified file\n")
138 sys.stderr.write("\t -s : Override default field separator with custom "
139 "character or string\n")
140 sys.stderr.write("\nExamples:\n")
141 sys.stderr.write("\tarcstat.py -o /tmp/a.log 2 10\n")
142 sys.stderr.write("\tarcstat.py -s \",\" -o /tmp/a.log 2 10\n")
143 sys.stderr.write("\tarcstat.py -v\n")
144 sys.stderr.write("\tarcstat.py -f time,hit%,dh%,ph%,mh% 1\n")
145 sys.stderr.write("\n")
146
147 sys.exit(1)
148
149
150 def kstat_update():
151 global kstat
152
153 k = [line.strip() for line in open('/proc/spl/kstat/zfs/arcstats')]
154
155 if not k:
156 sys.exit(1)
157
158 del k[0:2]
159 kstat = {}
160
161 for s in k:
162 if not s:
163 continue
164
165 name, unused, value = s.split()
166 kstat[name] = Decimal(value)
167
168
169 def snap_stats():
170 global cur
171 global kstat
172
173 prev = copy.deepcopy(cur)
174 kstat_update()
175
176 cur = kstat
177 for key in cur:
178 if re.match(key, "class"):
179 continue
180 if key in prev:
181 d[key] = cur[key] - prev[key]
182 else:
183 d[key] = cur[key]
184
185
186 def prettynum(sz, scale, num=0):
187 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
188 index = 0
189 save = 0
190
191 # Special case for date field
192 if scale == -1:
193 return "%s" % num
194
195 # Rounding error, return 0
196 elif 0 < num < 1:
197 num = 0
198
199 while num > scale and index < 5:
200 save = num
201 num = num / scale
202 index += 1
203
204 if index == 0:
205 return "%*d" % (sz, num)
206
207 if (save / scale) < 10:
208 return "%*.1f%s" % (sz - 1, num, suffix[index])
209 else:
210 return "%*d%s" % (sz - 1, num, suffix[index])
211
212
213 def print_values():
214 global hdr
215 global sep
216 global v
217
218 for col in hdr:
219 sys.stdout.write("%s%s" % (
220 prettynum(cols[col][0], cols[col][1], v[col]),
221 sep
222 ))
223 sys.stdout.write("\n")
224 sys.stdout.flush()
225
226
227 def print_header():
228 global hdr
229 global sep
230
231 for col in hdr:
232 sys.stdout.write("%*s%s" % (cols[col][0], col, sep))
233 sys.stdout.write("\n")
234
235
236 def get_terminal_lines():
237 try:
238 import fcntl
239 import termios
240 import struct
241 data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '1234')
242 sz = struct.unpack('hh', data)
243 return sz[0]
244 except Exception:
245 pass
246
247
248 def update_hdr_intr():
249 global hdr_intr
250
251 lines = get_terminal_lines()
252 if lines and lines > 3:
253 hdr_intr = lines - 3
254
255
256 def resize_handler(signum, frame):
257 update_hdr_intr()
258
259
260 def init():
261 global sint
262 global count
263 global hdr
264 global xhdr
265 global opfile
266 global sep
267 global out
268 global l2exist
269
270 desired_cols = None
271 xflag = False
272 hflag = False
273 vflag = False
274 i = 1
275
276 try:
277 opts, args = getopt.getopt(
278 sys.argv[1:],
279 "xo:hvs:f:",
280 [
281 "extended",
282 "outfile",
283 "help",
284 "verbose",
285 "separator",
286 "columns"
287 ]
288 )
289 except getopt.error as msg:
290 sys.stderr.write("Error: %s\n" % str(msg))
291 usage()
292 opts = None
293
294 for opt, arg in opts:
295 if opt in ('-x', '--extended'):
296 xflag = True
297 if opt in ('-o', '--outfile'):
298 opfile = arg
299 i += 1
300 if opt in ('-h', '--help'):
301 hflag = True
302 if opt in ('-v', '--verbose'):
303 vflag = True
304 if opt in ('-s', '--separator'):
305 sep = arg
306 i += 1
307 if opt in ('-f', '--columns'):
308 desired_cols = arg
309 i += 1
310 i += 1
311
312 argv = sys.argv[i:]
313 sint = Decimal(argv[0]) if argv else sint
314 count = int(argv[1]) if len(argv) > 1 else count
315
316 if len(argv) > 1:
317 sint = Decimal(argv[0])
318 count = int(argv[1])
319
320 elif len(argv) > 0:
321 sint = Decimal(argv[0])
322 count = 0
323
324 if hflag or (xflag and desired_cols):
325 usage()
326
327 if vflag:
328 detailed_usage()
329
330 if xflag:
331 hdr = xhdr
332
333 update_hdr_intr()
334
335 # check if L2ARC exists
336 snap_stats()
337 l2_size = cur.get("l2_size")
338 if l2_size:
339 l2exist = True
340
341 if desired_cols:
342 hdr = desired_cols.split(",")
343
344 invalid = []
345 incompat = []
346 for ele in hdr:
347 if ele not in cols:
348 invalid.append(ele)
349 elif not l2exist and ele.startswith("l2"):
350 sys.stdout.write("No L2ARC Here\n%s\n" % ele)
351 incompat.append(ele)
352
353 if len(invalid) > 0:
354 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
355 usage()
356
357 if len(incompat) > 0:
358 sys.stderr.write("Incompatible field specified! -- %s\n" %
359 incompat)
360 usage()
361
362 if opfile:
363 try:
364 out = open(opfile, "w")
365 sys.stdout = out
366
367 except IOError:
368 sys.stderr.write("Cannot open %s for writing\n" % opfile)
369 sys.exit(1)
370
371
372 def calculate():
373 global d
374 global v
375 global l2exist
376
377 v = dict()
378 v["time"] = time.strftime("%H:%M:%S", time.localtime())
379 v["hits"] = d["hits"] / sint
380 v["miss"] = d["misses"] / sint
381 v["read"] = v["hits"] + v["miss"]
382 v["hit%"] = 100 * v["hits"] / v["read"] if v["read"] > 0 else 0
383 v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0
384
385 v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) / sint
386 v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) / sint
387
388 v["dread"] = v["dhit"] + v["dmis"]
389 v["dh%"] = 100 * v["dhit"] / v["dread"] if v["dread"] > 0 else 0
390 v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0
391
392 v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) / sint
393 v["pmis"] = (d["prefetch_data_misses"] +
394 d["prefetch_metadata_misses"]) / sint
395
396 v["pread"] = v["phit"] + v["pmis"]
397 v["ph%"] = 100 * v["phit"] / v["pread"] if v["pread"] > 0 else 0
398 v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0
399
400 v["mhit"] = (d["prefetch_metadata_hits"] +
401 d["demand_metadata_hits"]) / sint
402 v["mmis"] = (d["prefetch_metadata_misses"] +
403 d["demand_metadata_misses"]) / sint
404
405 v["mread"] = v["mhit"] + v["mmis"]
406 v["mh%"] = 100 * v["mhit"] / v["mread"] if v["mread"] > 0 else 0
407 v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0
408
409 v["arcsz"] = cur["size"]
410 v["c"] = cur["c"]
411 v["mfu"] = d["mfu_hits"] / sint
412 v["mru"] = d["mru_hits"] / sint
413 v["mrug"] = d["mru_ghost_hits"] / sint
414 v["mfug"] = d["mfu_ghost_hits"] / sint
415 v["eskip"] = d["evict_skip"] / sint
416 v["mtxmis"] = d["mutex_miss"] / sint
417
418 if l2exist:
419 v["l2hits"] = d["l2_hits"] / sint
420 v["l2miss"] = d["l2_misses"] / sint
421 v["l2read"] = v["l2hits"] + v["l2miss"]
422 v["l2hit%"] = 100 * v["l2hits"] / v["l2read"] if v["l2read"] > 0 else 0
423
424 v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0
425 v["l2asize"] = cur["l2_asize"]
426 v["l2size"] = cur["l2_size"]
427 v["l2bytes"] = d["l2_read_bytes"] / sint
428
429 v["grow"] = 0 if cur["arc_no_grow"] else 1
430 v["need"] = cur["arc_need_free"]
431 v["free"] = cur["arc_sys_free"]
432
433
434 def main():
435 global sint
436 global count
437 global hdr_intr
438
439 i = 0
440 count_flag = 0
441
442 init()
443 if count > 0:
444 count_flag = 1
445
446 signal(SIGINT, SIG_DFL)
447 signal(SIGWINCH, resize_handler)
448 while True:
449 if i == 0:
450 print_header()
451
452 snap_stats()
453 calculate()
454 print_values()
455
456 if count_flag == 1:
457 if count <= 1:
458 break
459 count -= 1
460
461 i = 0 if i >= hdr_intr else i + 1
462 time.sleep(sint)
463
464 if out:
465 out.close()
466
467
468 if __name__ == '__main__':
469 main()