]> granicus.if.org Git - zfs/blob - cmd/arcstat/arcstat.py
ba792358c68e38a3d74114b5db2b20f1303c1f56
[zfs] / 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, 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 Data hits per second"],
65     "dmis":       [4, 1000, "Demand Data misses per second"],
66     "dh%":        [3, 100, "Demand Data hit percentage"],
67     "dm%":        [3, 100, "Demand Data 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     "rmis":       [4, 1000, "recycle_miss per second"],
86     "dread":      [5, 1000, "Demand data accesses per second"],
87     "pread":      [5, 1000, "Prefetch accesses per second"],
88     "l2hits":     [6, 1000, "L2ARC hits per second"],
89     "l2miss":     [6, 1000, "L2ARC misses per second"],
90     "l2read":     [6, 1000, "Total L2ARC accesses per second"],
91     "l2hit%":     [6, 100, "L2ARC access hit percentage"],
92     "l2miss%":    [7, 100, "L2ARC access miss percentage"],
93     "l2asize":    [7, 1024, "Actual (compressed) size of the L2ARC"],
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.py [-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.py -o /tmp/a.log 2 10\n")
141     sys.stderr.write("\tarcstat.py -s \",\" -o /tmp/a.log 2 10\n")
142     sys.stderr.write("\tarcstat.py -v\n")
143     sys.stderr.write("\tarcstat.py -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     k = [line.strip() for line in open('/proc/spl/kstat/zfs/arcstats')]
153
154     if not k:
155         sys.exit(1)
156
157     del k[0:2]
158     kstat = {}
159
160     for s in k:
161         if not s:
162             continue
163
164         name, unused, value = s.split()
165         kstat[name] = Decimal(value)
166
167
168 def snap_stats():
169     global cur
170     global kstat
171
172     prev = copy.deepcopy(cur)
173     kstat_update()
174
175     cur = kstat
176     for key in cur:
177         if re.match(key, "class"):
178             continue
179         if key in prev:
180             d[key] = cur[key] - prev[key]
181         else:
182             d[key] = cur[key]
183
184
185 def prettynum(sz, scale, num=0):
186     suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
187     index = 0
188     save = 0
189
190     # Special case for date field
191     if scale == -1:
192         return "%s" % num
193
194     # Rounding error, return 0
195     elif 0 < num < 1:
196         num = 0
197
198     while num > scale and index < 5:
199         save = num
200         num = num / scale
201         index += 1
202
203     if index == 0:
204         return "%*d" % (sz, num)
205
206     if (save / scale) < 10:
207         return "%*.1f%s" % (sz - 1, num, suffix[index])
208     else:
209         return "%*d%s" % (sz - 1, num, suffix[index])
210
211
212 def print_values():
213     global hdr
214     global sep
215     global v
216
217     for col in hdr:
218         sys.stdout.write("%s%s" % (
219             prettynum(cols[col][0], cols[col][1], v[col]),
220             sep
221         ))
222     sys.stdout.write("\n")
223
224
225 def print_header():
226     global hdr
227     global sep
228
229     for col in hdr:
230         sys.stdout.write("%*s%s" % (cols[col][0], col, sep))
231     sys.stdout.write("\n")
232
233 def get_terminal_lines():
234     try:
235         import fcntl, termios, struct
236         data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '1234')
237         sz = struct.unpack('hh', data)
238         return sz[0]
239     except:
240         pass
241
242 def init():
243     global sint
244     global count
245     global hdr
246     global hdr_intr
247     global xhdr
248     global opfile
249     global sep
250     global out
251     global l2exist
252
253     desired_cols = None
254     xflag = False
255     hflag = False
256     vflag = False
257     i = 1
258
259     try:
260         opts, args = getopt.getopt(
261             sys.argv[1:],
262             "xo:hvs:f:",
263             [
264                 "extended",
265                 "outfile",
266                 "help",
267                 "verbose",
268                 "seperator",
269                 "columns"
270             ]
271         )
272     except getopt.error as msg:
273         sys.stderr.write(msg)
274         usage()
275         opts = None
276
277     for opt, arg in opts:
278         if opt in ('-x', '--extended'):
279             xflag = True
280         if opt in ('-o', '--outfile'):
281             opfile = arg
282             i += 1
283         if opt in ('-h', '--help'):
284             hflag = True
285         if opt in ('-v', '--verbose'):
286             vflag = True
287         if opt in ('-s', '--seperator'):
288             sep = arg
289             i += 1
290         if opt in ('-f', '--columns'):
291             desired_cols = arg
292             i += 1
293         i += 1
294
295     argv = sys.argv[i:]
296     sint = Decimal(argv[0]) if argv else sint
297     count = int(argv[1]) if len(argv) > 1 else count
298
299     if len(argv) > 1:
300         sint = Decimal(argv[0])
301         count = int(argv[1])
302
303     elif len(argv) > 0:
304         sint = Decimal(argv[0])
305         count = 0
306
307     if hflag or (xflag and desired_cols):
308         usage()
309
310     if vflag:
311         detailed_usage()
312
313     if xflag:
314         hdr = xhdr
315
316     lines = get_terminal_lines()
317     if lines:
318         hdr_intr = lines - 3
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             usage()
346
347     if opfile:
348         try:
349             out = open(opfile, "w")
350             sys.stdout = out
351
352         except IOError:
353             sys.stderr.write("Cannot open %s for writing\n" % opfile)
354             sys.exit(1)
355
356
357 def calculate():
358     global d
359     global v
360     global l2exist
361
362     v = dict()
363     v["time"] = time.strftime("%H:%M:%S", time.localtime())
364     v["hits"] = d["hits"] / sint
365     v["miss"] = d["misses"] / sint
366     v["read"] = v["hits"] + v["miss"]
367     v["hit%"] = 100 * v["hits"] / v["read"] if v["read"] > 0 else 0
368     v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0
369
370     v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) / sint
371     v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) / sint
372
373     v["dread"] = v["dhit"] + v["dmis"]
374     v["dh%"] = 100 * v["dhit"] / v["dread"] if v["dread"] > 0 else 0
375     v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0
376
377     v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) / sint
378     v["pmis"] = (d["prefetch_data_misses"] +
379                  d["prefetch_metadata_misses"]) / sint
380
381     v["pread"] = v["phit"] + v["pmis"]
382     v["ph%"] = 100 * v["phit"] / v["pread"] if v["pread"] > 0 else 0
383     v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0
384
385     v["mhit"] = (d["prefetch_metadata_hits"] +
386                  d["demand_metadata_hits"]) / sint
387     v["mmis"] = (d["prefetch_metadata_misses"] +
388                  d["demand_metadata_misses"]) / sint
389
390     v["mread"] = v["mhit"] + v["mmis"]
391     v["mh%"] = 100 * v["mhit"] / v["mread"] if v["mread"] > 0 else 0
392     v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0
393
394     v["arcsz"] = cur["size"]
395     v["c"] = cur["c"]
396     v["mfu"] = d["mfu_hits"] / sint
397     v["mru"] = d["mru_hits"] / sint
398     v["mrug"] = d["mru_ghost_hits"] / sint
399     v["mfug"] = d["mfu_ghost_hits"] / sint
400     v["eskip"] = d["evict_skip"] / sint
401     v["rmis"] = d["recycle_miss"] / sint
402     v["mtxmis"] = d["mutex_miss"] / sint
403
404     if l2exist:
405         v["l2hits"] = d["l2_hits"] / sint
406         v["l2miss"] = d["l2_misses"] / sint
407         v["l2read"] = v["l2hits"] + v["l2miss"]
408         v["l2hit%"] = 100 * v["l2hits"] / v["l2read"] if v["l2read"] > 0 else 0
409
410         v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0
411         v["l2asize"] = cur["l2_asize"]
412         v["l2size"] = cur["l2_size"]
413         v["l2bytes"] = d["l2_read_bytes"] / sint
414
415
416 def main():
417     global sint
418     global count
419     global hdr_intr
420
421     i = 0
422     count_flag = 0
423
424     init()
425     if count > 0:
426         count_flag = 1
427
428     signal(SIGINT, SIG_DFL)
429     while True:
430         if i == 0:
431             print_header()
432
433         snap_stats()
434         calculate()
435         print_values()
436
437         if count_flag == 1:
438             if count <= 1:
439                 break
440             count -= 1
441
442         i = 0 if i == hdr_intr else i + 1
443         time.sleep(sint)
444
445     if out:
446         out.close()
447
448
449 if __name__ == '__main__':
450     main()