Added arcstat.py from FreeNAS
[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()