├── README ├── arcstat.pl └── zfs-arcstat.spec /README: -------------------------------------------------------------------------------- 1 | arcstat.pl is a Perl script that uses the Sun::Solaris::Kstat module to read 2 | ZFS ARC kstat values from the system and report on an interval basis. 3 | 4 | Some output examples can be found on the author's blog here: 5 | http://blogs.sun.com/realneel/entry/zfs_arc_statistics 6 | 7 | Examples showing L2ARC data can be found here: 8 | http://blog.harschsystems.com/tools/arcstat-pl-updated-for-l2arc-statistics/ 9 | 10 | Note: L2ARC statistics are only available on systems that have implemented 11 | the L2ARC (i.e. nevada-based releases). 12 | 13 | Usage for arcstat.pl can be obtained by providing the "--help" flag: 14 | 15 | ./arcstat.pl --help 16 | Usage: arcstat.pl [-hvxr] [-f fields] [-o file] [interval [count]] 17 | -x : Print extended stats 18 | -r : Raw Output mode (values not scaled) 19 | -f : Specify specific fields to print (see -v) 20 | -o : Print stats to file 21 | -s : Specify a seperator 22 | 23 | Examples: 24 | arcstat -o /tmp/a.log 2 10 25 | arcstat -s , -o /tmp/a.log 2 10 26 | arcstat -v 27 | arcstat -f time,hit%,dh%,ph%,mh% 28 | 29 | Available fields (statistics) can be retrieved with the "-v" flag: 30 | 31 | ./arcstat.pl -v 32 | Usage: arcstat.pl [-hvxr] [-f fields] [-o file] [interval [count]] 33 | Field definitions are as follows 34 | mtxmis : mutex_miss per second 35 | arcsz : Arc Size 36 | mrug : MRU Ghost List hits per second 37 | l2hit% : L2ARC access hit percentage 38 | mh% : Metadata hit percentage 39 | l2miss% : L2ARC access miss percentage 40 | read : Total Arc accesses per second 41 | c : Arc Target Size 42 | mfug : MFU Ghost List hits per second 43 | miss : Arc misses per second 44 | dm% : Demand Data miss percentage 45 | dhit : Demand Data hits per second 46 | pread : Prefetch accesses per second 47 | dread : Demand data accesses per second 48 | pmis : Prefetch misses per second 49 | l2miss : L2ARC misses per second 50 | time : Time 51 | l2bytes : bytes read per second from the L2ARC 52 | pm% : Prefetch miss percentage 53 | mm% : Metadata miss percentage 54 | hits : Arc reads per second 55 | mfu : MFU List hits per second 56 | l2read : Total L2ARC accesses per second 57 | mmis : Metadata misses per second 58 | rmis : recycle_miss per second 59 | mhit : Metadata hits per second 60 | dmis : Demand Data misses per second 61 | mru : MRU List hits per second 62 | ph% : Prefetch hits percentage 63 | eskip : evict_skip per second 64 | l2size : Size of the L2ARC 65 | l2hits : L2ARC hits per second 66 | hit% : Arc Hit percentage 67 | miss% : Arc miss percentage 68 | dh% : Demand Data hit percentage 69 | mread : Metadata accesses per second 70 | phit : Prefetch hits per second 71 | 72 | -------------------------------------------------------------------------------- /arcstat.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | ##!/usr/perl5/bin/perl -w 3 | # The above invocation line was changed in 0.5 to allow for 4 | # interoperability with linux. 5 | # 6 | # Print out ZFS ARC Statistics exported via kstat(1) 7 | # For a definition of fields, or usage, use arctstat.pl -v 8 | # 9 | # This script is a fork of the original arcstat.pl (0.1) by 10 | # Neelakanth Nadgir, originally published on his Sun blog on 11 | # 09/18/2007 12 | # http://blogs.sun.com/realneel/entry/zfs_arc_statistics 13 | # 14 | # This version aims to improve upon the original by adding features 15 | # and fixing bugs as needed. This version is maintained by 16 | # Mike Harsch and is hosted in a public open source repository: 17 | # http://github.com/mharsch/arcstat 18 | # 19 | # Comments, Questions, or Suggestions are always welcome. 20 | # Contact the maintainer at ( mike at harschsystems dot com ) 21 | # 22 | # CDDL HEADER START 23 | # 24 | # The contents of this file are subject to the terms of the 25 | # Common Development and Distribution License, Version 1.0 only 26 | # (the "License"). You may not use this file except in compliance 27 | # with the License. 28 | # 29 | # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 30 | # or http://www.opensolaris.org/os/licensing. 31 | # See the License for the specific language governing permissions 32 | # and limitations under the License. 33 | # 34 | # When distributing Covered Code, include this CDDL HEADER in each 35 | # file and include the License file at usr/src/OPENSOLARIS.LICENSE. 36 | # If applicable, add the following below this CDDL HEADER, with the 37 | # fields enclosed by brackets "[]" replaced with your own identifying 38 | # information: Portions Copyright [yyyy] [name of copyright owner] 39 | # 40 | # CDDL HEADER END 41 | # 42 | # 43 | # Fields have a fixed width. Every interval, we fill the "v" 44 | # hash with its corresponding value (v[field]=value) using calculate(). 45 | # @hdr is the array of fields that needs to be printed, so we 46 | # just iterate over this array and print the values using our pretty printer. 47 | 48 | use strict; 49 | use warnings; 50 | use POSIX qw(strftime); 51 | use Sun::Solaris::Kstat; 52 | use Getopt::Long; 53 | use IO::Handle; 54 | 55 | my %cols = (# HDR => [Size, Scale, Description] 56 | "time" =>[8, -1, "Time"], 57 | "hits" =>[4, 1000, "ARC reads per second"], 58 | "miss" =>[4, 1000, "ARC misses per second"], 59 | "read" =>[4, 1000, "Total ARC accesses per second"], 60 | "hit%" =>[4, 100, "ARC Hit percentage"], 61 | "miss%" =>[5, 100, "ARC miss percentage"], 62 | "dhit" =>[4, 1000, "Demand Data hits per second"], 63 | "dmis" =>[4, 1000, "Demand Data misses per second"], 64 | "dh%" =>[3, 100, "Demand Data hit percentage"], 65 | "dm%" =>[3, 100, "Demand Data miss percentage"], 66 | "phit" =>[4, 1000, "Prefetch hits per second"], 67 | "pmis" =>[4, 1000, "Prefetch misses per second"], 68 | "ph%" =>[3, 100, "Prefetch hits percentage"], 69 | "pm%" =>[3, 100, "Prefetch miss percentage"], 70 | "mhit" =>[4, 1000, "Metadata hits per second"], 71 | "mmis" =>[4, 1000, "Metadata misses per second"], 72 | "mread" =>[4, 1000, "Metadata accesses per second"], 73 | "mh%" =>[3, 100, "Metadata hit percentage"], 74 | "mm%" =>[3, 100, "Metadata miss percentage"], 75 | "arcsz" =>[5, 1024, "ARC Size"], 76 | "c" =>[4, 1024, "ARC Target Size"], 77 | "mfu" =>[4, 1000, "MFU List hits per second"], 78 | "mru" =>[4, 1000, "MRU List hits per second"], 79 | "mfug" =>[4, 1000, "MFU Ghost List hits per second"], 80 | "mrug" =>[4, 1000, "MRU Ghost List hits per second"], 81 | "eskip" =>[5, 1000, "evict_skip per second"], 82 | "mtxmis" =>[6, 1000, "mutex_miss per second"], 83 | "rmis" =>[4, 1000, "recycle_miss per second"], 84 | "dread" =>[5, 1000, "Demand data accesses per second"], 85 | "pread" =>[5, 1000, "Prefetch accesses per second"], 86 | "l2hits" =>[6, 1000, "L2ARC hits per second"], 87 | "l2miss" =>[6, 1000, "L2ARC misses per second"], 88 | "l2read" =>[6, 1000, "Total L2ARC accesses per second"], 89 | "l2hit%" =>[6, 100, "L2ARC access hit percentage"], 90 | "l2miss%" =>[7, 100, "L2ARC access miss percentage"], 91 | "l2size" =>[6, 1024, "Size of the L2ARC"], 92 | "l2bytes" =>[7, 1024, "bytes read per second from the L2ARC"], 93 | ); 94 | my %v=(); 95 | my @hdr = qw(time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c); 96 | my @xhdr = qw(time mfu mru mfug mrug eskip mtxmis rmis dread pread read); 97 | my $int = 1; # Default interval is 1 second 98 | my $count = 1; # Default count is 1 99 | my $hdr_intr = 20; # Print header every 20 lines of output 100 | my $opfile = ""; 101 | my $sep = " "; # Default separator is 2 spaces 102 | my $raw_output; 103 | my $version = "0.5"; 104 | my $l2exist = 0; 105 | my $cmd = "Usage: arcstat [-hvxr] [-f fields] [-o file] [-s string] " . 106 | "[interval [count]]\n"; 107 | my %cur; 108 | my %d; 109 | my $out; 110 | my $kstat = Sun::Solaris::Kstat->new(); 111 | STDOUT->autoflush; 112 | 113 | sub detailed_usage { 114 | print STDERR "$cmd\n"; 115 | print STDERR "Field definitions are as follows:\n"; 116 | foreach my $hdr (keys %cols) { 117 | print STDERR sprintf("%11s : %s\n", $hdr, $cols{$hdr}[2]); 118 | } 119 | exit(1); 120 | } 121 | 122 | sub usage { 123 | print STDERR "$cmd\n"; 124 | print STDERR "\t -h : Print this help message\n"; 125 | print STDERR "\t -v : List all possible field headers " . 126 | "and definitions\n"; 127 | print STDERR "\t -x : Print extended stats\n"; 128 | print STDERR "\t -r : Raw output mode (values not scaled)\n"; 129 | print STDERR "\t -f : Specify specific fields to print (see -v)\n"; 130 | print STDERR "\t -o : Redirect output to the specified file\n"; 131 | print STDERR "\t -s : Override default field separator with custom " . 132 | "character or string\n"; 133 | print STDERR "\nExamples:\n"; 134 | print STDERR "\tarcstat -o /tmp/a.log 2 10\n"; 135 | print STDERR "\tarcstat -s \",\" -o /tmp/a.log 2 10\n"; 136 | print STDERR "\tarcstat -v\n"; 137 | print STDERR "\tarcstat -f time,hit%,dh%,ph%,mh% 1\n"; 138 | exit(1); 139 | } 140 | 141 | sub init { 142 | my $desired_cols; 143 | my $xflag = ''; 144 | my $hflag = ''; 145 | my $vflag; 146 | my $res = GetOptions('x' => \$xflag, 147 | 'o=s' => \$opfile, 148 | 'help|h|?' => \$hflag, 149 | 'v' => \$vflag, 150 | 's=s' => \$sep, 151 | 'f=s' => \$desired_cols, 152 | 'r' => \$raw_output); 153 | 154 | $int = $ARGV[0] || $int; 155 | $count = $ARGV[1] || $count; 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. 211 | sub snap_stats { 212 | my %prev = %cur; 213 | if ($kstat->update()) { 214 | printf("\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 229 | sub 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 | 257 | sub 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 | 269 | sub 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 | 283 | sub 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{"rmiss"} = $d{"recycle_miss"}/$int; 333 | $v{"mtxmis"} = $d{"mutex_miss"}/$int; 334 | 335 | if ($l2exist) { 336 | $v{"l2hits"} = $d{"l2_hits"}/$int; 337 | $v{"l2miss"} = $d{"l2_misses"}/$int; 338 | $v{"l2read"} = $v{"l2hits"} + $v{"l2miss"}; 339 | $v{"l2hit%"} = 100 * ($v{"l2hits"} / $v{"l2read"}) 340 | if $v{"l2read"} > 0; 341 | 342 | $v{"l2miss%"} = 100 - $v{"l2hit%"} if $v{"l2read"} > 0; 343 | $v{"l2size"} = $cur{"l2_size"}; 344 | $v{"l2bytes"} = $d{"l2_read_bytes"}/$int; 345 | } 346 | } 347 | 348 | sub 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 | -------------------------------------------------------------------------------- /zfs-arcstat.spec: -------------------------------------------------------------------------------- 1 | %global commit 0b4546e89ded86d2f11727d32fb1eb2caaf91ceb 2 | %global shortcommit %(c=%{commit}; echo ${c:0:7}) 3 | %global repo_name arcstat 4 | 5 | Name: zfs-arcstat 6 | Version: 0.5 7 | Release: 1%{?dist} 8 | Summary: Perl script to read ZFS ARC kstat values 9 | License: GPLv2+ 10 | Group: Applications/System 11 | URL: https://github.com/zfsonlinux/%{repo_name} 12 | Source0: https://github.com/zfsonlinux/%{repo_name}/archive/%{commit}/%{name}-%{version}-%{shortcommit}.tar.gz 13 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 14 | BuildArch: noarch 15 | Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) 16 | Requires: perl(Sun::Solaris::Kstat) 17 | 18 | %description 19 | arcstat.pl is a Perl script that uses the Sun::Solaris::Kstat module to read 20 | ZFS ARC kstat values from the system and report on an interval basis. 21 | 22 | %prep 23 | %setup -qn %{repo_name}-%{commit} 24 | 25 | %build 26 | # Do nothing 27 | 28 | %install 29 | rm -rf $RPM_BUILD_ROOT 30 | 31 | mkdir -p %{buildroot}%{_bindir}/ 32 | cp -p arcstat.pl %{buildroot}%{_bindir}/arcstat.pl 33 | 34 | %clean 35 | rm -rf $RPM_BUILD_ROOT 36 | 37 | %files 38 | %defattr(-,root,root,-) 39 | %doc README 40 | %attr(0755, root, root) %{_bindir}/arcstat.pl 41 | 42 | %changelog 43 | * Tue Jul 23 2013 Trey Dockendorf 0.5-1 44 | - Initial spec file creation 45 | --------------------------------------------------------------------------------