Correct style in arcstat and arc_summary
[zfs.git] / cmd / arcstat / arcstat.py
blob4161207950d353139ade6817ecb7e27092fec428
1 #!/usr/bin/python
3 # Print out ZFS ARC Statistics exported via kstat(1)
4 # For a definition of fields, or usage, use arctstat.pl -v
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
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
16 # Comments, Questions, or Suggestions are always welcome.
17 # Contact the maintainer at ( mike at harschsystems dot com )
19 # CDDL HEADER START
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.
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.
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]
37 # CDDL HEADER END
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.
47 import sys
48 import time
49 import getopt
50 import re
51 import copy
53 from decimal import Decimal
54 from signal import signal, SIGINT, SIGWINCH, SIG_DFL
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": [4, 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"],
97 v = {}
98 hdr = ["time", "read", "miss", "miss%", "dmis", "dm%", "pmis", "pm%", "mmis",
99 "mm%", "arcsz", "c"]
100 xhdr = ["time", "mfu", "mru", "mfug", "mrug", "eskip", "mtxmis", "dread",
101 "pread", "read"]
102 sint = 1 # Default interval is 1 second
103 count = 1 # Default count is 1
104 hdr_intr = 20 # Print header every 20 lines of output
105 opfile = None
106 sep = " " # Default separator is 2 spaces
107 version = "0.4"
108 l2exist = False
109 cmd = ("Usage: arcstat.py [-hvx] [-f fields] [-o file] [-s string] [interval "
110 "[count]]\n")
111 cur = {}
112 d = {}
113 out = None
114 kstat = None
115 float_pobj = re.compile("^[0-9]+(\.[0-9]+)?$")
118 def detailed_usage():
119 sys.stderr.write("%s\n" % cmd)
120 sys.stderr.write("Field definitions are as follows:\n")
121 for key in cols:
122 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
123 sys.stderr.write("\n")
125 sys.exit(1)
128 def usage():
129 sys.stderr.write("%s\n" % cmd)
130 sys.stderr.write("\t -h : Print this help message\n")
131 sys.stderr.write("\t -v : List all possible field headers and definitions"
132 "\n")
133 sys.stderr.write("\t -x : Print extended stats\n")
134 sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n")
135 sys.stderr.write("\t -o : Redirect output to the specified file\n")
136 sys.stderr.write("\t -s : Override default field separator with custom "
137 "character or string\n")
138 sys.stderr.write("\nExamples:\n")
139 sys.stderr.write("\tarcstat.py -o /tmp/a.log 2 10\n")
140 sys.stderr.write("\tarcstat.py -s \",\" -o /tmp/a.log 2 10\n")
141 sys.stderr.write("\tarcstat.py -v\n")
142 sys.stderr.write("\tarcstat.py -f time,hit%,dh%,ph%,mh% 1\n")
143 sys.stderr.write("\n")
145 sys.exit(1)
148 def kstat_update():
149 global kstat
151 k = [line.strip() for line in open('/proc/spl/kstat/zfs/arcstats')]
153 if not k:
154 sys.exit(1)
156 del k[0:2]
157 kstat = {}
159 for s in k:
160 if not s:
161 continue
163 name, unused, value = s.split()
164 kstat[name] = Decimal(value)
167 def snap_stats():
168 global cur
169 global kstat
171 prev = copy.deepcopy(cur)
172 kstat_update()
174 cur = kstat
175 for key in cur:
176 if re.match(key, "class"):
177 continue
178 if key in prev:
179 d[key] = cur[key] - prev[key]
180 else:
181 d[key] = cur[key]
184 def prettynum(sz, scale, num=0):
185 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
186 index = 0
187 save = 0
189 # Special case for date field
190 if scale == -1:
191 return "%s" % num
193 # Rounding error, return 0
194 elif 0 < num < 1:
195 num = 0
197 while num > scale and index < 5:
198 save = num
199 num = num / scale
200 index += 1
202 if index == 0:
203 return "%*d" % (sz, num)
205 if (save / scale) < 10:
206 return "%*.1f%s" % (sz - 1, num, suffix[index])
207 else:
208 return "%*d%s" % (sz - 1, num, suffix[index])
211 def print_values():
212 global hdr
213 global sep
214 global v
216 for col in hdr:
217 sys.stdout.write("%s%s" % (
218 prettynum(cols[col][0], cols[col][1], v[col]),
221 sys.stdout.write("\n")
224 def print_header():
225 global hdr
226 global sep
228 for col in hdr:
229 sys.stdout.write("%*s%s" % (cols[col][0], col, sep))
230 sys.stdout.write("\n")
233 def get_terminal_lines():
234 try:
235 import fcntl
236 import termios
237 import struct
238 data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '1234')
239 sz = struct.unpack('hh', data)
240 return sz[0]
241 except:
242 pass
245 def update_hdr_intr():
246 global hdr_intr
248 lines = get_terminal_lines()
249 if lines and lines > 3:
250 hdr_intr = lines - 3
253 def resize_handler(signum, frame):
254 update_hdr_intr()
257 def init():
258 global sint
259 global count
260 global hdr
261 global xhdr
262 global opfile
263 global sep
264 global out
265 global l2exist
267 desired_cols = None
268 xflag = False
269 hflag = False
270 vflag = False
271 i = 1
273 try:
274 opts, args = getopt.getopt(
275 sys.argv[1:],
276 "xo:hvs:f:",
278 "extended",
279 "outfile",
280 "help",
281 "verbose",
282 "seperator",
283 "columns"
286 except getopt.error as msg:
287 sys.stderr.write(msg)
288 usage()
289 opts = None
291 for opt, arg in opts:
292 if opt in ('-x', '--extended'):
293 xflag = True
294 if opt in ('-o', '--outfile'):
295 opfile = arg
296 i += 1
297 if opt in ('-h', '--help'):
298 hflag = True
299 if opt in ('-v', '--verbose'):
300 vflag = True
301 if opt in ('-s', '--seperator'):
302 sep = arg
303 i += 1
304 if opt in ('-f', '--columns'):
305 desired_cols = arg
306 i += 1
307 i += 1
309 argv = sys.argv[i:]
310 sint = Decimal(argv[0]) if argv else sint
311 count = int(argv[1]) if len(argv) > 1 else count
313 if len(argv) > 1:
314 sint = Decimal(argv[0])
315 count = int(argv[1])
317 elif len(argv) > 0:
318 sint = Decimal(argv[0])
319 count = 0
321 if hflag or (xflag and desired_cols):
322 usage()
324 if vflag:
325 detailed_usage()
327 if xflag:
328 hdr = xhdr
330 update_hdr_intr()
332 # check if L2ARC exists
333 snap_stats()
334 l2_size = cur.get("l2_size")
335 if l2_size:
336 l2exist = True
338 if desired_cols:
339 hdr = desired_cols.split(",")
341 invalid = []
342 incompat = []
343 for ele in hdr:
344 if ele not in cols:
345 invalid.append(ele)
346 elif not l2exist and ele.startswith("l2"):
347 sys.stdout.write("No L2ARC Here\n%s\n" % ele)
348 incompat.append(ele)
350 if len(invalid) > 0:
351 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
352 usage()
354 if len(incompat) > 0:
355 sys.stderr.write("Incompatible field specified! -- %s\n" %
356 incompat)
357 usage()
359 if opfile:
360 try:
361 out = open(opfile, "w")
362 sys.stdout = out
364 except IOError:
365 sys.stderr.write("Cannot open %s for writing\n" % opfile)
366 sys.exit(1)
369 def calculate():
370 global d
371 global v
372 global l2exist
374 v = dict()
375 v["time"] = time.strftime("%H:%M:%S", time.localtime())
376 v["hits"] = d["hits"] / sint
377 v["miss"] = d["misses"] / sint
378 v["read"] = v["hits"] + v["miss"]
379 v["hit%"] = 100 * v["hits"] / v["read"] if v["read"] > 0 else 0
380 v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0
382 v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) / sint
383 v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) / sint
385 v["dread"] = v["dhit"] + v["dmis"]
386 v["dh%"] = 100 * v["dhit"] / v["dread"] if v["dread"] > 0 else 0
387 v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0
389 v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) / sint
390 v["pmis"] = (d["prefetch_data_misses"] +
391 d["prefetch_metadata_misses"]) / sint
393 v["pread"] = v["phit"] + v["pmis"]
394 v["ph%"] = 100 * v["phit"] / v["pread"] if v["pread"] > 0 else 0
395 v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0
397 v["mhit"] = (d["prefetch_metadata_hits"] +
398 d["demand_metadata_hits"]) / sint
399 v["mmis"] = (d["prefetch_metadata_misses"] +
400 d["demand_metadata_misses"]) / sint
402 v["mread"] = v["mhit"] + v["mmis"]
403 v["mh%"] = 100 * v["mhit"] / v["mread"] if v["mread"] > 0 else 0
404 v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0
406 v["arcsz"] = cur["size"]
407 v["c"] = cur["c"]
408 v["mfu"] = d["mfu_hits"] / sint
409 v["mru"] = d["mru_hits"] / sint
410 v["mrug"] = d["mru_ghost_hits"] / sint
411 v["mfug"] = d["mfu_ghost_hits"] / sint
412 v["eskip"] = d["evict_skip"] / sint
413 v["mtxmis"] = d["mutex_miss"] / sint
415 if l2exist:
416 v["l2hits"] = d["l2_hits"] / sint
417 v["l2miss"] = d["l2_misses"] / sint
418 v["l2read"] = v["l2hits"] + v["l2miss"]
419 v["l2hit%"] = 100 * v["l2hits"] / v["l2read"] if v["l2read"] > 0 else 0
421 v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0
422 v["l2asize"] = cur["l2_asize"]
423 v["l2size"] = cur["l2_size"]
424 v["l2bytes"] = d["l2_read_bytes"] / sint
427 def main():
428 global sint
429 global count
430 global hdr_intr
432 i = 0
433 count_flag = 0
435 init()
436 if count > 0:
437 count_flag = 1
439 signal(SIGINT, SIG_DFL)
440 signal(SIGWINCH, resize_handler)
441 while True:
442 if i == 0:
443 print_header()
445 snap_stats()
446 calculate()
447 print_values()
449 if count_flag == 1:
450 if count <= 1:
451 break
452 count -= 1
454 i = 0 if i >= hdr_intr else i + 1
455 time.sleep(sint)
457 if out:
458 out.close()
461 if __name__ == '__main__':
462 main()