]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - cmd/arcstat.in
Replace dead opensolaris.org license link
[FreeBSD/FreeBSD.git] / cmd / 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 https://opensource.org/licenses/CDDL-1.0.
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 3.6+.
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     "el2skip":    [7, 1000, "evict skip, due to l2 writes, per second"],
92     "el2cach":    [7, 1024, "Size of L2 cached evictions per second"],
93     "el2el":      [5, 1024, "Size of L2 eligible evictions per second"],
94     "el2mfu":     [6, 1024, "Size of L2 eligible MFU evictions per second"],
95     "el2mru":     [6, 1024, "Size of L2 eligible MRU evictions per second"],
96     "el2inel":    [7, 1024, "Size of L2 ineligible evictions per second"],
97     "mtxmis":     [6, 1000, "mutex_miss per second"],
98     "dread":      [5, 1000, "Demand accesses per second"],
99     "pread":      [5, 1000, "Prefetch accesses per second"],
100     "l2hits":     [6, 1000, "L2ARC hits per second"],
101     "l2miss":     [6, 1000, "L2ARC misses per second"],
102     "l2read":     [6, 1000, "Total L2ARC accesses per second"],
103     "l2hit%":     [6, 100, "L2ARC access hit percentage"],
104     "l2miss%":    [7, 100, "L2ARC access miss percentage"],
105     "l2pref":     [6, 1024, "L2ARC prefetch allocated size"],
106     "l2mfu":      [5, 1024, "L2ARC MFU allocated size"],
107     "l2mru":      [5, 1024, "L2ARC MRU allocated size"],
108     "l2data":     [6, 1024, "L2ARC data allocated size"],
109     "l2meta":     [6, 1024, "L2ARC metadata allocated size"],
110     "l2pref%":    [7, 100, "L2ARC prefetch percentage"],
111     "l2mfu%":     [6, 100, "L2ARC MFU percentage"],
112     "l2mru%":     [6, 100, "L2ARC MRU percentage"],
113     "l2data%":    [7, 100, "L2ARC data percentage"],
114     "l2meta%":    [7, 100, "L2ARC metadata percentage"],
115     "l2asize":    [7, 1024, "Actual (compressed) size of the L2ARC"],
116     "l2size":     [6, 1024, "Size of the L2ARC"],
117     "l2bytes":    [7, 1024, "Bytes read per second from the L2ARC"],
118     "grow":       [4, 1000, "ARC grow disabled"],
119     "need":       [4, 1024, "ARC reclaim need"],
120     "free":       [4, 1024, "ARC free memory"],
121     "avail":      [5, 1024, "ARC available memory"],
122     "waste":      [5, 1024, "Wasted memory due to round up to pagesize"],
123 }
124
125 v = {}
126 hdr = ["time", "read", "miss", "miss%", "dmis", "dm%", "pmis", "pm%", "mmis",
127        "mm%", "size", "c", "avail"]
128 xhdr = ["time", "mfu", "mru", "mfug", "mrug", "eskip", "mtxmis", "dread",
129         "pread", "read"]
130 sint = 1               # Default interval is 1 second
131 count = 1              # Default count is 1
132 hdr_intr = 20          # Print header every 20 lines of output
133 opfile = None
134 sep = "  "              # Default separator is 2 spaces
135 version = "0.4"
136 l2exist = False
137 cmd = ("Usage: arcstat [-havxp] [-f fields] [-o file] [-s string] [interval "
138        "[count]]\n")
139 cur = {}
140 d = {}
141 out = None
142 kstat = None
143 pretty_print = True
144
145
146 if sys.platform.startswith('freebsd'):
147     # Requires py-sysctl on FreeBSD
148     import sysctl
149
150     def kstat_update():
151         global kstat
152
153         k = [ctl for ctl in sysctl.filter('kstat.zfs.misc.arcstats')
154              if ctl.type != sysctl.CTLTYPE_NODE]
155
156         if not k:
157             sys.exit(1)
158
159         kstat = {}
160
161         for s in k:
162             if not s:
163                 continue
164
165             name, value = s.name, s.value
166             # Trims 'kstat.zfs.misc.arcstats' from the name
167             kstat[name[24:]] = int(value)
168
169 elif sys.platform.startswith('linux'):
170     def kstat_update():
171         global kstat
172
173         k = [line.strip() for line in open('/proc/spl/kstat/zfs/arcstats')]
174
175         if not k:
176             sys.exit(1)
177
178         del k[0:2]
179         kstat = {}
180
181         for s in k:
182             if not s:
183                 continue
184
185             name, unused, value = s.split()
186             kstat[name] = int(value)
187
188
189 def detailed_usage():
190     sys.stderr.write("%s\n" % cmd)
191     sys.stderr.write("Field definitions are as follows:\n")
192     for key in cols:
193         sys.stderr.write("%11s : %s\n" % (key, cols[key][2]))
194     sys.stderr.write("\n")
195
196     sys.exit(0)
197
198
199 def usage():
200     sys.stderr.write("%s\n" % cmd)
201     sys.stderr.write("\t -h : Print this help message\n")
202     sys.stderr.write("\t -a : Print all possible stats\n")
203     sys.stderr.write("\t -v : List all possible field headers and definitions"
204                      "\n")
205     sys.stderr.write("\t -x : Print extended stats\n")
206     sys.stderr.write("\t -f : Specify specific fields to print (see -v)\n")
207     sys.stderr.write("\t -o : Redirect output to the specified file\n")
208     sys.stderr.write("\t -s : Override default field separator with custom "
209                      "character or string\n")
210     sys.stderr.write("\t -p : Disable auto-scaling of numerical fields\n")
211     sys.stderr.write("\nExamples:\n")
212     sys.stderr.write("\tarcstat -o /tmp/a.log 2 10\n")
213     sys.stderr.write("\tarcstat -s \",\" -o /tmp/a.log 2 10\n")
214     sys.stderr.write("\tarcstat -v\n")
215     sys.stderr.write("\tarcstat -f time,hit%,dh%,ph%,mh% 1\n")
216     sys.stderr.write("\n")
217
218     sys.exit(1)
219
220
221 def snap_stats():
222     global cur
223     global kstat
224
225     prev = copy.deepcopy(cur)
226     kstat_update()
227
228     cur = kstat
229     for key in cur:
230         if re.match(key, "class"):
231             continue
232         if key in prev:
233             d[key] = cur[key] - prev[key]
234         else:
235             d[key] = cur[key]
236
237
238 def prettynum(sz, scale, num=0):
239     suffix = [' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
240     index = 0
241     save = 0
242
243     # Special case for date field
244     if scale == -1:
245         return "%s" % num
246
247     # Rounding error, return 0
248     elif 0 < num < 1:
249         num = 0
250
251     while abs(num) > scale and index < 5:
252         save = num
253         num = num / scale
254         index += 1
255
256     if index == 0:
257         return "%*d" % (sz, num)
258
259     if abs(save / scale) < 10:
260         return "%*.1f%s" % (sz - 1, num, suffix[index])
261     else:
262         return "%*d%s" % (sz - 1, num, suffix[index])
263
264
265 def print_values():
266     global hdr
267     global sep
268     global v
269     global pretty_print
270
271     if pretty_print:
272         fmt = lambda col: prettynum(cols[col][0], cols[col][1], v[col])
273     else:
274         fmt = lambda col: v[col]
275
276     sys.stdout.write(sep.join(fmt(col) for col in hdr))
277     sys.stdout.write("\n")
278     sys.stdout.flush()
279
280
281 def print_header():
282     global hdr
283     global sep
284     global pretty_print
285
286     if pretty_print:
287         fmt = lambda col: "%*s" % (cols[col][0], col)
288     else:
289         fmt = lambda col: col
290
291     sys.stdout.write(sep.join(fmt(col) for col in hdr))
292     sys.stdout.write("\n")
293
294
295 def get_terminal_lines():
296     try:
297         import fcntl
298         import termios
299         import struct
300         data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '1234')
301         sz = struct.unpack('hh', data)
302         return sz[0]
303     except Exception:
304         pass
305
306
307 def update_hdr_intr():
308     global hdr_intr
309
310     lines = get_terminal_lines()
311     if lines and lines > 3:
312         hdr_intr = lines - 3
313
314
315 def resize_handler(signum, frame):
316     update_hdr_intr()
317
318
319 def init():
320     global sint
321     global count
322     global hdr
323     global xhdr
324     global opfile
325     global sep
326     global out
327     global l2exist
328     global pretty_print
329
330     desired_cols = None
331     aflag = False
332     xflag = False
333     hflag = False
334     vflag = False
335     i = 1
336
337     try:
338         opts, args = getopt.getopt(
339             sys.argv[1:],
340             "axo:hvs:f:p",
341             [
342                 "all",
343                 "extended",
344                 "outfile",
345                 "help",
346                 "verbose",
347                 "separator",
348                 "columns",
349                 "parsable"
350             ]
351         )
352     except getopt.error as msg:
353         sys.stderr.write("Error: %s\n" % str(msg))
354         usage()
355         opts = None
356
357     for opt, arg in opts:
358         if opt in ('-a', '--all'):
359             aflag = True
360         if opt in ('-x', '--extended'):
361             xflag = True
362         if opt in ('-o', '--outfile'):
363             opfile = arg
364             i += 1
365         if opt in ('-h', '--help'):
366             hflag = True
367         if opt in ('-v', '--verbose'):
368             vflag = True
369         if opt in ('-s', '--separator'):
370             sep = arg
371             i += 1
372         if opt in ('-f', '--columns'):
373             desired_cols = arg
374             i += 1
375         if opt in ('-p', '--parsable'):
376             pretty_print = False
377         i += 1
378
379     argv = sys.argv[i:]
380     sint = int(argv[0]) if argv else sint
381     count = int(argv[1]) if len(argv) > 1 else (0 if len(argv) > 0 else 1)
382
383     if hflag or (xflag and desired_cols):
384         usage()
385
386     if vflag:
387         detailed_usage()
388
389     if xflag:
390         hdr = xhdr
391
392     update_hdr_intr()
393
394     # check if L2ARC exists
395     snap_stats()
396     l2_size = cur.get("l2_size")
397     if l2_size:
398         l2exist = True
399
400     if desired_cols:
401         hdr = desired_cols.split(",")
402
403         invalid = []
404         incompat = []
405         for ele in hdr:
406             if ele not in cols:
407                 invalid.append(ele)
408             elif not l2exist and ele.startswith("l2"):
409                 sys.stdout.write("No L2ARC Here\n%s\n" % ele)
410                 incompat.append(ele)
411
412         if len(invalid) > 0:
413             sys.stderr.write("Invalid column definition! -- %s\n" % invalid)
414             usage()
415
416         if len(incompat) > 0:
417             sys.stderr.write("Incompatible field specified! -- %s\n" %
418                              incompat)
419             usage()
420
421     if aflag:
422         if l2exist:
423             hdr = cols.keys()
424         else:
425             hdr = [col for col in cols.keys() if not col.startswith("l2")]
426
427     if opfile:
428         try:
429             out = open(opfile, "w")
430             sys.stdout = out
431
432         except IOError:
433             sys.stderr.write("Cannot open %s for writing\n" % opfile)
434             sys.exit(1)
435
436
437 def calculate():
438     global d
439     global v
440     global l2exist
441
442     v = dict()
443     v["time"] = time.strftime("%H:%M:%S", time.localtime())
444     v["hits"] = d["hits"] // sint
445     v["miss"] = d["misses"] // sint
446     v["read"] = v["hits"] + v["miss"]
447     v["hit%"] = 100 * v["hits"] // v["read"] if v["read"] > 0 else 0
448     v["miss%"] = 100 - v["hit%"] if v["read"] > 0 else 0
449
450     v["dhit"] = (d["demand_data_hits"] + d["demand_metadata_hits"]) // sint
451     v["dmis"] = (d["demand_data_misses"] + d["demand_metadata_misses"]) // sint
452
453     v["dread"] = v["dhit"] + v["dmis"]
454     v["dh%"] = 100 * v["dhit"] // v["dread"] if v["dread"] > 0 else 0
455     v["dm%"] = 100 - v["dh%"] if v["dread"] > 0 else 0
456
457     v["phit"] = (d["prefetch_data_hits"] + d["prefetch_metadata_hits"]) // sint
458     v["pmis"] = (d["prefetch_data_misses"] +
459                  d["prefetch_metadata_misses"]) // sint
460
461     v["pread"] = v["phit"] + v["pmis"]
462     v["ph%"] = 100 * v["phit"] // v["pread"] if v["pread"] > 0 else 0
463     v["pm%"] = 100 - v["ph%"] if v["pread"] > 0 else 0
464
465     v["mhit"] = (d["prefetch_metadata_hits"] +
466                  d["demand_metadata_hits"]) // sint
467     v["mmis"] = (d["prefetch_metadata_misses"] +
468                  d["demand_metadata_misses"]) // sint
469
470     v["mread"] = v["mhit"] + v["mmis"]
471     v["mh%"] = 100 * v["mhit"] // v["mread"] if v["mread"] > 0 else 0
472     v["mm%"] = 100 - v["mh%"] if v["mread"] > 0 else 0
473
474     v["arcsz"] = cur["size"]
475     v["size"] = cur["size"]
476     v["c"] = cur["c"]
477     v["mfu"] = d["mfu_hits"] // sint
478     v["mru"] = d["mru_hits"] // sint
479     v["mrug"] = d["mru_ghost_hits"] // sint
480     v["mfug"] = d["mfu_ghost_hits"] // sint
481     v["eskip"] = d["evict_skip"] // sint
482     v["el2skip"] = d["evict_l2_skip"] // sint
483     v["el2cach"] = d["evict_l2_cached"] // sint
484     v["el2el"] = d["evict_l2_eligible"] // sint
485     v["el2mfu"] = d["evict_l2_eligible_mfu"] // sint
486     v["el2mru"] = d["evict_l2_eligible_mru"] // sint
487     v["el2inel"] = d["evict_l2_ineligible"] // sint
488     v["mtxmis"] = d["mutex_miss"] // sint
489
490     if l2exist:
491         v["l2hits"] = d["l2_hits"] // sint
492         v["l2miss"] = d["l2_misses"] // sint
493         v["l2read"] = v["l2hits"] + v["l2miss"]
494         v["l2hit%"] = 100 * v["l2hits"] // v["l2read"] if v["l2read"] > 0 else 0
495
496         v["l2miss%"] = 100 - v["l2hit%"] if v["l2read"] > 0 else 0
497         v["l2asize"] = cur["l2_asize"]
498         v["l2size"] = cur["l2_size"]
499         v["l2bytes"] = d["l2_read_bytes"] // sint
500
501         v["l2pref"] = cur["l2_prefetch_asize"]
502         v["l2mfu"] = cur["l2_mfu_asize"]
503         v["l2mru"] = cur["l2_mru_asize"]
504         v["l2data"] = cur["l2_bufc_data_asize"]
505         v["l2meta"] = cur["l2_bufc_metadata_asize"]
506         v["l2pref%"] = 100 * v["l2pref"] // v["l2asize"]
507         v["l2mfu%"] = 100 * v["l2mfu"] // v["l2asize"]
508         v["l2mru%"] = 100 * v["l2mru"] // v["l2asize"]
509         v["l2data%"] = 100 * v["l2data"] // v["l2asize"]
510         v["l2meta%"] = 100 * v["l2meta"] // v["l2asize"]
511
512     v["grow"] = 0 if cur["arc_no_grow"] else 1
513     v["need"] = cur["arc_need_free"]
514     v["free"] = cur["memory_free_bytes"]
515     v["avail"] = cur["memory_available_bytes"]
516     v["waste"] = cur["abd_chunk_waste_size"]
517
518
519 def main():
520     global sint
521     global count
522     global hdr_intr
523
524     i = 0
525     count_flag = 0
526
527     init()
528     if count > 0:
529         count_flag = 1
530
531     signal(SIGINT, SIG_DFL)
532     signal(SIGWINCH, resize_handler)
533     while True:
534         if i == 0:
535             print_header()
536
537         snap_stats()
538         calculate()
539         print_values()
540
541         if count_flag == 1:
542             if count <= 1:
543                 break
544             count -= 1
545
546         i = 0 if i >= hdr_intr else i + 1
547         time.sleep(sint)
548
549     if out:
550         out.close()
551
552
553 if __name__ == '__main__':
554     main()