]> git.proxmox.com Git - mirror_zfs.git/blob - cmd/arcstat/arcstat.py
Added arcstat.py from FreeNAS
[mirror_zfs.git] / cmd / arcstat / arcstat.py
1 #!/usr/local/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 subprocess import Popen, PIPE
55 from signal import signal, SIGINT
56
57 cols = {
58 # HDR: [Size, Scale, Description]
59 "time": [8, -1, "Time"],
60 "hits": [4, 1000, "ARC reads per second"],
61 "miss": [4, 1000, "ARC misses per second"],
62 "read": [4, 1000, "Total ARC accesses per second"],
63 "hit%": [4, 100, "ARC Hit percentage"],
64 "miss%": [5, 100, "ARC miss percentage"],
65 "dhit": [4, 1000, "Demand Data hits per second"],
66 "dmis": [4, 1000, "Demand Data misses per second"],
67 "dh%": [3, 100, "Demand Data hit percentage"],
68 "dm%": [3, 100, "Demand Data miss percentage"],
69 "phit": [4, 1000, "Prefetch hits per second"],
70 "pmis": [4, 1000, "Prefetch misses per second"],
71 "ph%": [3, 100, "Prefetch hits percentage"],
72 "pm%": [3, 100, "Prefetch miss percentage"],
73 "mhit": [4, 1000, "Metadata hits per second"],
74 "mmis": [4, 1000, "Metadata misses per second"],
75 "mread": [4, 1000, "Metadata accesses per second"],
76 "mh%": [3, 100, "Metadata hit percentage"],
77 "mm%": [3, 100, "Metadata miss percentage"],
78 "arcsz": [5, 1024, "ARC Size"],
79 "c": [4, 1024, "ARC Target Size"],
80 "mfu": [4, 1000, "MFU List hits per second"],
81 "mru": [4, 1000, "MRU List hits per second"],
82 "mfug": [4, 1000, "MFU Ghost List hits per second"],
83 "mrug": [4, 1000, "MRU Ghost List hits per second"],
84 "eskip": [5, 1000, "evict_skip per second"],
85 "mtxmis": [6, 1000, "mutex_miss per second"],
86 "rmis": [4, 1000, "recycle_miss per second"],
87 "dread": [5, 1000, "Demand data accesses per second"],
88 "pread": [5, 1000, "Prefetch accesses per second"],
89 "l2hits": [6, 1000, "L2ARC hits per second"],
90 "l2miss": [6, 1000, "L2ARC misses per second"],
91 "l2read": [6, 1000, "Total L2ARC accesses per second"],
92 "l2hit%": [6, 100, "L2ARC access hit percentage"],
93 "l2miss%": [7, 100, "L2ARC access miss percentage"],
94 "l2size": [6, 1024, "Size of the L2ARC"],
95 "l2bytes": [7, 1024, "bytes read per second from the L2ARC"],
96 }
97
98 v = {}
99 hdr = ["time", "read", "miss", "miss%", "dmis", "dm%", "pmis", "pm%", "mmis",
100 "mm%", "arcsz", "c"]
101 xhdr = ["time", "mfu", "mru", "mfug", "mrug", "eskip", "mtxmis", "rmis",
102 "dread", "pread", "read"]
103 sint = 1 # Default interval is 1 second
104 count = 1 # Default count is 1
105 hdr_intr = 20 # Print header every 20 lines of output
106 opfile = None
107 sep = " " # Default separator is 2 spaces
108 version = "0.4"
109 l2exist = False
110 cmd = ("Usage: arcstat [-hvx] [-f fields] [-o file] [-s string] [interval "
111 "[count]]\n")
112 cur = {}
113 d = {}
114 out = None
115 kstat = None
116 float_pobj = re.compile("^[0-9]+(\.[0-9]+)?$")
117
118
119 def detailed_usage():
120 sys.stderr.write("%s\n" % cmd)
121 sys.stderr.write("Field definitions are as follows:\n")
122 for key in cols:
123 sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
124 sys.stderr.write("\n")
125
126 sys.exit(1)
127
128
129 def usage():
130 sys.stderr.write("%s\n" % cmd)
131 sys.stderr.write("\t -h : Print this help message\n")
132 sys.stderr.write("\t -v : List all possible field headers and definitions"
133 "\n")
134 sys.stderr.write("\t -x : Print extended stats\n")
135 sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n")
136 sys.stderr.write("\t -o : Redirect output to the specified file\n")
137 sys.stderr.write("\t -s : Override default field separator with custom "
138 "character or string\n")
139 sys.stderr.write("\nExamples:\n")
140 sys.stderr.write("\tarcstat -o /tmp/a.log 2 10\n")
141 sys.stderr.write("\tarcstat -s \",\" -o /tmp/a.log 2 10\n")
142 sys.stderr.write("\tarcstat -v\n")
143 sys.stderr.write("\tarcstat -f time,hit%,dh%,ph%,mh% 1\n")
144 sys.stderr.write("\n")
145
146 sys.exit(1)
147
148
149 def kstat_update():
150 global kstat
151
152 p = Popen("/sbin/sysctl -q 'kstat.zfs.misc.arcstats'", stdin=PIPE,
153 stdout=PIPE, stderr=PIPE, shell=True, close_fds=True)
154 p.wait()
155
156 k = p.communicate()[0].split('\n')
157 if p.returncode != 0:
158 sys.exit(1)
159
160 if not k:
161 sys.exit(1)
162
163 kstat = {}
164
165 for s in k:
166 if not s:
167 continue
168
169 s = s.strip()
170
171 name, value = s.split(':')
172 name = name.strip()
173 value = value.strip()
174
175 parts = name.split('.')
176 n = parts.pop()
177
178 kstat[n] = Decimal(value)
179
180
181 def snap_stats():
182 global cur
183 global kstat
184
185 prev = copy.deepcopy(cur)
186 kstat_update()
187
188 cur = kstat
189 for key in cur:
190 if re.match(key, "class"):
191 continue
192 if key in prev:
193 d[key] = cur[key] - prev[key]
194 else:
195 d[key] = cur[key]
196
197
198 def prettynum(sz, scale, num=0):
199 suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
200 index = 0
201 save = 0
202
203 # Special case for date field
204 if scale == -1:
205 return "%s" % num
206
207 # Rounding error, return 0
208 elif num > 0 and num < 1:
209 num = 0
210
211 while num > scale and index < 5:
212 save = num
213 num = num / scale
214 index += 1
215
216 if index == 0:
217 return "%*d" % (sz, num)
218
219 if (save / scale) < 10:
220 return "%*.1f%s" % (sz - 1, num, suffix[index])
221 else:
222 return "%*d%s" % (sz - 1, num, suffix[index])
223
224
225 def print_values():
226 global hdr
227 global sep
228 global v
229
230 for col in hdr:
231 sys.stdout.write("%s%s" % (
232 prettynum(cols[col][0], cols[col][1], v[col]),
233 sep
234 ))
235 sys.stdout.write("\n")
236
237
238 def print_header():
239 global hdr
240 global sep
241
242 for col in hdr:
243 sys.stdout.write("%*s%s" % (cols[col][0], col, sep))
244 sys.stdout.write("\n")
245
246
247 def init():
248 global sint
249 global count
250 global hdr
251 global xhdr
252 global opfile
253 global sep
254 global out
255 global l2exist
256
257 desired_cols = None
258 xflag = False
259 hflag = False
260 vflag = False
261 i = 1
262
263 try:
264 opts, args = getopt.getopt(
265 sys.argv[1:],
266 "xo:hvs:f:",
267 [
268 "extended",
269 "outfile",
270 "help",
271 "verbose",
272 "seperator",
273 "columns"
274 ]
275 )
276
277 except getopt.error, msg:
278 sys.stderr.write(msg)
279 usage()
280
281 for opt, arg in opts:
282 if opt in ('-x', '--extended'):
283 xflag = True
284 if opt in ('-o', '--outfile'):
285 opfile = arg
286 i += 1
287 if opt in ('-h', '--help'):
288 hflag = True
289 if opt in ('-v', '--verbose'):
290 vflag = True
291 if opt in ('-s', '--seperator'):
292 sep = arg
293 i += 1
294 if opt in ('-f', '--columns'):
295 desired_cols = arg
296 i += 1
297 i += 1
298
299 argv = sys.argv[i:]
300 sint = Decimal(argv[0]) if argv else sint
301 count = int(argv[1]) if len(argv) > 1 else count
302
303 if len(argv) > 1:
304 sint = Decimal(argv[0])
305 count = int(argv[1])
306
307 elif len(argv) > 0:
308 sint = Decimal(argv[0])
309 count = 0
310
311 if hflag or (xflag and desired_cols):
312 usage()
313
314 if vflag:
315 detailed_usage()
316
317 if xflag:
318 hdr = xhdr
319
320 # check if L2ARC exists
321 snap_stats()
322 l2_size = cur.get("l2_size")
323 if l2_size:
324 l2exist = True
325
326 if desired_cols:
327 hdr = desired_cols.split(",")
328
329 invalid = []
330 incompat = []
331 for ele in hdr:
332 if ele not in cols:
333 invalid.append(ele)
334 elif not l2exist and ele.startswith("l2"):
335 sys.stdout.write("No L2ARC Here\n%s\n" % ele)
336 incompat.append(ele)
337
338 if len(invalid) > 0:
339 sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
340 usage()
341
342 if len(incompat) > 0:
343 sys.stderr.write("Incompatible field specified! -- %s\n" % (
344 incompat,
345 ))
346 usage()
347
348 if opfile:
349 try:
350 out = open(opfile, "w")
351 sys.stdout = out
352
353 except:
354 sys.stderr.write("Cannot open %s for writing\n" % opfile)
355 sys.exit(1)
356
357
358 def calculate():
359 global d
360 global v
361 global l2exist
362
363 v = {}
364 v["time"] = time.strftime("%H:%M:%S", time.localtime())
365 v["hits"] = d["hits"] / sint
366 v["miss"] = d["misses"] / sint
367 v["read"] = v["hits"] + v["miss"]
368 v["hit%"] = 100 * v["hits"] / v["read"] if v["read"] > 0 else 0
369 v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0
370
371 v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) / sint
372 v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) / sint
373
374 v["dread"] = v["dhit"] + v["dmis"]
375 v["dh%"] = 100 * v["dhit"] / v["dread"] if v["dread"] > 0 else 0
376 v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0
377
378 v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) / sint
379 v["pmis"] = (d["prefetch_data_misses"] +
380 d["prefetch_metadata_misses"]) / sint
381
382 v["pread"] = v["phit"] + v["pmis"]
383 v["ph%"] = 100 * v["phit"] / v["pread"] if v["pread"] > 0 else 0
384 v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0
385
386 v["mhit"] = (d["prefetch_metadata_hits"] +
387 d["demand_metadata_hits"]) / sint
388 v["mmis"] = (d["prefetch_metadata_misses"] +
389 d["demand_metadata_misses"]) / sint
390
391 v["mread"] = v["mhit"] + v["mmis"]
392 v["mh%"] = 100 * v["mhit"] / v["mread"] if v["mread"] > 0 else 0
393 v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0
394
395 v["arcsz"] = cur["size"]
396 v["c"] = cur["c"]
397 v["mfu"] = d["mfu_hits"] / sint
398 v["mru"] = d["mru_hits"] / sint
399 v["mrug"] = d["mru_ghost_hits"] / sint
400 v["mfug"] = d["mfu_ghost_hits"] / sint
401 v["eskip"] = d["evict_skip"] / sint
402 v["rmis"] = d["recycle_miss"] / sint
403 v["mtxmis"] = d["mutex_miss"] / sint
404
405 if l2exist:
406 v["l2hits"] = d["l2_hits"] / sint
407 v["l2miss"] = d["l2_misses"] / sint
408 v["l2read"] = v["l2hits"] + v["l2miss"]
409 v["l2hit%"] = 100 * v["l2hits"] / v["l2read"] if v["l2read"] > 0 else 0
410
411 v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0
412 v["l2size"] = cur["l2_size"]
413 v["l2bytes"] = d["l2_read_bytes"] / sint
414
415
416 def sighandler(*args):
417 sys.exit(0)
418
419
420 def main():
421 global sint
422 global count
423 global hdr_intr
424
425 i = 0
426 count_flag = 0
427
428 init()
429 if count > 0:
430 count_flag = 1
431
432 signal(SIGINT, sighandler)
433 while True:
434 if i == 0:
435 print_header()
436
437 snap_stats()
438 calculate()
439 print_values()
440
441 if count_flag == 1:
442 if count <= 1:
443 break
444 count -= 1
445
446 i = 0 if i == hdr_intr else i + 1
447 time.sleep(sint)
448
449 if out:
450 out.close()
451
452
453 if __name__ == '__main__':
454 main()