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