xref: /illumos-gate/usr/src/cmd/stat/arcstat/arcstat.pl (revision 52244c0958bdf281ca42932b449f644b4decfdc2)
1#!/usr/perl5/bin/perl -w
2# The above invocation line was changed in 0.5 to allow for
3# interoperability with linux.
4#
5# Print out ZFS ARC Statistics exported via kstat(1)
6# For a definition of fields, or usage, use arctstat.pl -v
7#
8# This script is a fork of the original arcstat.pl (0.1) by
9# Neelakanth Nadgir, originally published on his Sun blog on
10# 09/18/2007
11#     http://blogs.sun.com/realneel/entry/zfs_arc_statistics
12#
13# This version aims to improve upon the original by adding features
14# and fixing bugs as needed.  This version is maintained by
15# Mike Harsch and is hosted in a public open source repository:
16#    http://github.com/mharsch/arcstat
17#
18# Comments, Questions, or Suggestions are always welcome.
19# Contact the maintainer at ( mike at harschsystems dot com )
20#
21# CDDL HEADER START
22#
23# The contents of this file are subject to the terms of the
24# Common Development and Distribution License, Version 1.0 only
25# (the "License").  You may not use this file except in compliance
26# with the License.
27#
28# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
29# or http://www.opensolaris.org/os/licensing.
30# See the License for the specific language governing permissions
31# and limitations under the License.
32#
33# When distributing Covered Code, include this CDDL HEADER in each
34# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
35# If applicable, add the following below this CDDL HEADER, with the
36# fields enclosed by brackets "[]" replaced with your own identifying
37# information: Portions Copyright [yyyy] [name of copyright owner]
38#
39# CDDL HEADER END
40#
41#
42# Fields have a fixed width. Every interval, we fill the "v"
43# hash with its corresponding value (v[field]=value) using calculate().
44# @hdr is the array of fields that needs to be printed, so we
45# just iterate over this array and print the values using our pretty printer.
46
47#
48# Copyright (c) 2015 by Delphix. All rights reserved.
49#
50
51use strict;
52use warnings;
53use POSIX qw(strftime);
54use Sun::Solaris::Kstat;
55use Getopt::Long;
56use IO::Handle;
57
58my %cols = (# 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	"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);
97my %v=();
98my @hdr = qw(time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c);
99my @xhdr = qw(time mfu mru mfug mrug eskip mtxmis dread pread read);
100my $int = 1;		# Default interval is 1 second
101my $count = 1;		# Default count is 1
102my $hdr_intr = 20;	# Print header every 20 lines of output
103my $opfile = "";
104my $sep = "  ";		# Default separator is 2 spaces
105my $raw_output;
106my $version = "0.5";
107my $l2exist = 0;
108my $cmd = "Usage: arcstat [-hvxr] [-f fields] [-o file] [-s string] " .
109    "[interval [count]]\n";
110my %cur;
111my %d;
112my $out;
113my $kstat = Sun::Solaris::Kstat->new();
114STDOUT->autoflush;
115
116sub detailed_usage {
117	print STDERR "$cmd\n";
118	print STDERR "Field definitions are as follows:\n";
119	foreach my $hdr (keys %cols) {
120		print STDERR sprintf("%11s : %s\n", $hdr, $cols{$hdr}[2]);
121	}
122	exit(1);
123}
124
125sub usage {
126	print STDERR "$cmd\n";
127	print STDERR "\t -h : Print this help message\n";
128	print STDERR "\t -v : List all possible field headers " .
129	    "and definitions\n";
130	print STDERR "\t -x : Print extended stats\n";
131	print STDERR "\t -r : Raw output mode (values not scaled)\n";
132	print STDERR "\t -f : Specify specific fields to print (see -v)\n";
133	print STDERR "\t -o : Redirect output to the specified file\n";
134	print STDERR "\t -s : Override default field separator with custom " .
135	    "character or string\n";
136	print STDERR "\nExamples:\n";
137	print STDERR "\tarcstat -o /tmp/a.log 2 10\n";
138	print STDERR "\tarcstat -s \",\" -o /tmp/a.log 2 10\n";
139	print STDERR "\tarcstat -v\n";
140	print STDERR "\tarcstat -f time,hit%,dh%,ph%,mh% 1\n";
141	exit(1);
142}
143
144sub init {
145	my $desired_cols;
146	my $xflag = '';
147	my $hflag = '';
148	my $vflag;
149	my $res = GetOptions('x' => \$xflag,
150	    'o=s' => \$opfile,
151	    'help|h|?' => \$hflag,
152	    'v' => \$vflag,
153	    's=s' => \$sep,
154	    'f=s' => \$desired_cols,
155	    'r' => \$raw_output);
156
157	if (defined $ARGV[0] && defined $ARGV[1]) {
158		$int = $ARGV[0];
159		$count = $ARGV[1];
160	} elsif (defined $ARGV[0]) {
161		$int = $ARGV[0];
162		$count = 0;
163	}
164
165	usage() if !$res or $hflag or ($xflag and $desired_cols);
166	detailed_usage() if $vflag;
167	@hdr = @xhdr if $xflag;		#reset headers to xhdr
168
169	# check if L2ARC exists
170	snap_stats();
171	if (defined $cur{"l2_size"}) {
172		$l2exist = 1;
173	}
174
175	if ($desired_cols) {
176		@hdr = split(/[ ,]+/, $desired_cols);
177		# Now check if they are valid fields
178		my @invalid = ();
179		my @incompat = ();
180		foreach my $ele (@hdr) {
181			if (not exists($cols{$ele})) {
182				push(@invalid, $ele);
183			} elsif (($l2exist == 0) && ($ele =~ /^l2/)) {
184				printf("No L2ARC here\n", $ele);
185				push(@incompat, $ele);
186			}
187		}
188		if (scalar @invalid > 0) {
189			print STDERR "Invalid column definition! -- "
190			    . "@invalid\n\n";
191			usage();
192		}
193
194		if (scalar @incompat > 0) {
195			print STDERR "Incompatible field specified -- "
196			    . "@incompat\n\n";
197			usage();
198		}
199	}
200
201	if ($opfile) {
202		open($out, ">$opfile") ||die "Cannot open $opfile for writing";
203		$out->autoflush;
204		select $out;
205	}
206}
207
208# Capture kstat statistics. We maintain 3 hashes, prev, cur, and
209# d (delta). As their names imply they maintain the previous, current,
210# and delta (cur - prev) statistics.
211sub snap_stats {
212	my %prev = %cur;
213	if ($kstat->update()) {
214		printf("<State Changed>\n");
215	}
216	my $hashref_cur = $kstat->{"zfs"}{0}{"arcstats"};
217	%cur = %$hashref_cur;
218	foreach my $key (keys %cur) {
219		next if $key =~ /class/;
220		if (defined $prev{$key}) {
221			$d{$key} = $cur{$key} - $prev{$key};
222		} else {
223			$d{$key} = $cur{$key};
224		}
225	}
226}
227
228# Pretty print num. Arguments are width, scale, and num
229sub prettynum {
230	my @suffix = (' ', 'K', 'M', 'G', 'T');
231	my $num = $_[2] || 0;
232	my $scale = $_[1];
233	my $sz = $_[0];
234	my $index = 0;
235	my $save = 0;
236
237	if ($scale == -1) {			#special case for date field
238		return sprintf("%s", $num);
239	} elsif (($num > 0) && ($num < 1)) {	#rounding error.  return 0
240		$num = 0;
241	}
242
243	while ($num > $scale and $index < 5) {
244		$save = $num;
245		$num = $num/$scale;
246		$index++;
247	}
248
249	return sprintf("%*d", $sz, $num) if ($index == 0);
250	if (($save / $scale) < 10) {
251		return sprintf("%*.1f%s", $sz - 1, $num,$suffix[$index]);
252	} else {
253		return sprintf("%*d%s", $sz - 1, $num,$suffix[$index]);
254	}
255}
256
257sub print_values {
258	foreach my $col (@hdr) {
259		if (not $raw_output) {
260			printf("%s%s", prettynum($cols{$col}[0], $cols{$col}[1],
261			    $v{$col}), $sep);
262		} else {
263			printf("%d%s", $v{$col} || 0, $sep);
264		}
265	}
266	printf("\n");
267}
268
269sub print_header {
270	if (not $raw_output) {
271		foreach my $col (@hdr) {
272			printf("%*s%s", $cols{$col}[0], $col, $sep);
273		}
274	} else {
275		# Don't try to align headers in raw mode
276		foreach my $col (@hdr) {
277			printf("%s%s", $col, $sep);
278		}
279	}
280	printf("\n");
281}
282
283sub calculate {
284	%v = ();
285
286	if ($raw_output) {
287		$v{"time"} = strftime("%s", localtime);
288	} else {
289		$v{"time"} = strftime("%H:%M:%S", localtime);
290	}
291
292	$v{"hits"} = $d{"hits"}/$int;
293	$v{"miss"} = $d{"misses"}/$int;
294	$v{"read"} = $v{"hits"} + $v{"miss"};
295	$v{"hit%"} = 100 * ($v{"hits"} / $v{"read"}) if $v{"read"} > 0;
296	$v{"miss%"} = 100 - $v{"hit%"} if $v{"read"} > 0;
297
298	$v{"dhit"} = ($d{"demand_data_hits"} +
299	    $d{"demand_metadata_hits"})/$int;
300	$v{"dmis"} = ($d{"demand_data_misses"} +
301	    $d{"demand_metadata_misses"})/$int;
302
303	$v{"dread"} = $v{"dhit"} + $v{"dmis"};
304	$v{"dh%"} = 100 * ($v{"dhit"} / $v{"dread"}) if $v{"dread"} > 0;
305	$v{"dm%"} = 100 - $v{"dh%"} if $v{"dread"} > 0;
306
307	$v{"phit"} = ($d{"prefetch_data_hits"} +
308	    $d{"prefetch_metadata_hits"})/$int;
309	$v{"pmis"} = ($d{"prefetch_data_misses"} +
310	    $d{"prefetch_metadata_misses"})/$int;
311
312	$v{"pread"} = $v{"phit"} + $v{"pmis"};
313	$v{"ph%"} = 100 * ($v{"phit"} / $v{"pread"}) if $v{"pread"} > 0;
314	$v{"pm%"} = 100 - $v{"ph%"} if $v{"pread"} > 0;
315
316	$v{"mhit"} = ($d{"prefetch_metadata_hits"} +
317		$d{"demand_metadata_hits"})/$int;
318	$v{"mmis"} = ($d{"prefetch_metadata_misses"} +
319	    $d{"demand_metadata_misses"})/$int;
320
321	$v{"mread"} = $v{"mhit"} + $v{"mmis"};
322	$v{"mh%"} = 100 * ($v{"mhit"} / $v{"mread"}) if $v{"mread"} > 0;
323	$v{"mm%"} = 100 - $v{"mh%"} if $v{"mread"} > 0;
324
325	$v{"arcsz"} = $cur{"size"};
326	$v{"c"} = $cur{"c"};
327	$v{"mfu"} = $d{"mfu_hits"}/$int;
328	$v{"mru"} = $d{"mru_hits"}/$int;
329	$v{"mrug"} = $d{"mru_ghost_hits"}/$int;
330	$v{"mfug"} = $d{"mfu_ghost_hits"}/$int;
331	$v{"eskip"} = $d{"evict_skip"}/$int;
332	$v{"mtxmis"} = $d{"mutex_miss"}/$int;
333
334	if ($l2exist) {
335		$v{"l2hits"} = $d{"l2_hits"}/$int;
336		$v{"l2miss"} = $d{"l2_misses"}/$int;
337		$v{"l2read"} = $v{"l2hits"} + $v{"l2miss"};
338		$v{"l2hit%"} = 100 * ($v{"l2hits"} / $v{"l2read"})
339		    if $v{"l2read"} > 0;
340
341		$v{"l2miss%"} = 100 - $v{"l2hit%"} if $v{"l2read"} > 0;
342		$v{"l2size"} = $cur{"l2_size"};
343		$v{"l2asize"} = $cur{"l2_asize"};
344		$v{"l2bytes"} = $d{"l2_read_bytes"}/$int;
345	}
346}
347
348sub main {
349	my $i = 0;
350	my $count_flag = 0;
351
352	init();
353	if ($count > 0) { $count_flag = 1; }
354	while (1) {
355		print_header() if ($i == 0);
356		snap_stats();
357		calculate();
358		print_values();
359		last if ($count_flag == 1 && $count-- <= 1);
360		$i = (($i == $hdr_intr) && (not $raw_output)) ? 0 : $i+1;
361		sleep($int);
362	}
363	close($out) if defined $out;
364}
365
366&main;
367