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 # we want to capture the stats here, so that we can use them to check 170 # if an L2ARC device exists; but more importantly, so that we print 171 # the stats since boot as the first line of output from main(). 172 snap_stats(); 173 174 if (defined $cur{"l2_size"}) { 175 $l2exist = 1; 176 } 177 178 if ($desired_cols) { 179 @hdr = split(/[ ,]+/, $desired_cols); 180 # Now check if they are valid fields 181 my @invalid = (); 182 my @incompat = (); 183 foreach my $ele (@hdr) { 184 if (not exists($cols{$ele})) { 185 push(@invalid, $ele); 186 } elsif (($l2exist == 0) && ($ele =~ /^l2/)) { 187 printf("No L2ARC here\n", $ele); 188 push(@incompat, $ele); 189 } 190 } 191 if (scalar @invalid > 0) { 192 print STDERR "Invalid column definition! -- " 193 . "@invalid\n\n"; 194 usage(); 195 } 196 197 if (scalar @incompat > 0) { 198 print STDERR "Incompatible field specified -- " 199 . "@incompat\n\n"; 200 usage(); 201 } 202 } 203 204 if ($opfile) { 205 open($out, ">$opfile") ||die "Cannot open $opfile for writing"; 206 $out->autoflush; 207 select $out; 208 } 209} 210 211# Capture kstat statistics. We maintain 3 hashes, prev, cur, and 212# d (delta). As their names imply they maintain the previous, current, 213# and delta (cur - prev) statistics. 214sub snap_stats { 215 my %prev = %cur; 216 if ($kstat->update()) { 217 printf("<State Changed>\n"); 218 } 219 my $hashref_cur = $kstat->{"zfs"}{0}{"arcstats"}; 220 %cur = %$hashref_cur; 221 foreach my $key (keys %cur) { 222 next if $key =~ /class/; 223 if (defined $prev{$key}) { 224 $d{$key} = $cur{$key} - $prev{$key}; 225 } else { 226 $d{$key} = $cur{$key}; 227 } 228 } 229} 230 231# Pretty print num. Arguments are width, scale, and num 232sub prettynum { 233 my @suffix = (' ', 'K', 'M', 'G', 'T'); 234 my $num = $_[2] || 0; 235 my $scale = $_[1]; 236 my $sz = $_[0]; 237 my $index = 0; 238 my $save = 0; 239 240 if ($scale == -1) { #special case for date field 241 return sprintf("%s", $num); 242 } elsif (($num > 0) && ($num < 1)) { #rounding error. return 0 243 $num = 0; 244 } 245 246 while ($num > $scale and $index < 5) { 247 $save = $num; 248 $num = $num/$scale; 249 $index++; 250 } 251 252 return sprintf("%*d", $sz, $num) if ($index == 0); 253 if (($save / $scale) < 10) { 254 return sprintf("%*.1f%s", $sz - 1, $num,$suffix[$index]); 255 } else { 256 return sprintf("%*d%s", $sz - 1, $num,$suffix[$index]); 257 } 258} 259 260sub print_values { 261 foreach my $col (@hdr) { 262 if (not $raw_output) { 263 printf("%s%s", prettynum($cols{$col}[0], $cols{$col}[1], 264 $v{$col}), $sep); 265 } else { 266 printf("%d%s", $v{$col} || 0, $sep); 267 } 268 } 269 printf("\n"); 270} 271 272sub print_header { 273 if (not $raw_output) { 274 foreach my $col (@hdr) { 275 printf("%*s%s", $cols{$col}[0], $col, $sep); 276 } 277 } else { 278 # Don't try to align headers in raw mode 279 foreach my $col (@hdr) { 280 printf("%s%s", $col, $sep); 281 } 282 } 283 printf("\n"); 284} 285 286sub calculate { 287 %v = (); 288 289 if ($raw_output) { 290 $v{"time"} = strftime("%s", localtime); 291 } else { 292 $v{"time"} = strftime("%H:%M:%S", localtime); 293 } 294 295 $v{"hits"} = $d{"hits"}/$int; 296 $v{"miss"} = $d{"misses"}/$int; 297 $v{"read"} = $v{"hits"} + $v{"miss"}; 298 $v{"hit%"} = 100 * ($v{"hits"} / $v{"read"}) if $v{"read"} > 0; 299 $v{"miss%"} = 100 - $v{"hit%"} if $v{"read"} > 0; 300 301 $v{"dhit"} = ($d{"demand_data_hits"} + 302 $d{"demand_metadata_hits"})/$int; 303 $v{"dmis"} = ($d{"demand_data_misses"} + 304 $d{"demand_metadata_misses"})/$int; 305 306 $v{"dread"} = $v{"dhit"} + $v{"dmis"}; 307 $v{"dh%"} = 100 * ($v{"dhit"} / $v{"dread"}) if $v{"dread"} > 0; 308 $v{"dm%"} = 100 - $v{"dh%"} if $v{"dread"} > 0; 309 310 $v{"phit"} = ($d{"prefetch_data_hits"} + 311 $d{"prefetch_metadata_hits"})/$int; 312 $v{"pmis"} = ($d{"prefetch_data_misses"} + 313 $d{"prefetch_metadata_misses"})/$int; 314 315 $v{"pread"} = $v{"phit"} + $v{"pmis"}; 316 $v{"ph%"} = 100 * ($v{"phit"} / $v{"pread"}) if $v{"pread"} > 0; 317 $v{"pm%"} = 100 - $v{"ph%"} if $v{"pread"} > 0; 318 319 $v{"mhit"} = ($d{"prefetch_metadata_hits"} + 320 $d{"demand_metadata_hits"})/$int; 321 $v{"mmis"} = ($d{"prefetch_metadata_misses"} + 322 $d{"demand_metadata_misses"})/$int; 323 324 $v{"mread"} = $v{"mhit"} + $v{"mmis"}; 325 $v{"mh%"} = 100 * ($v{"mhit"} / $v{"mread"}) if $v{"mread"} > 0; 326 $v{"mm%"} = 100 - $v{"mh%"} if $v{"mread"} > 0; 327 328 $v{"arcsz"} = $cur{"size"}; 329 $v{"c"} = $cur{"c"}; 330 $v{"mfu"} = $d{"mfu_hits"}/$int; 331 $v{"mru"} = $d{"mru_hits"}/$int; 332 $v{"mrug"} = $d{"mru_ghost_hits"}/$int; 333 $v{"mfug"} = $d{"mfu_ghost_hits"}/$int; 334 $v{"eskip"} = $d{"evict_skip"}/$int; 335 $v{"mtxmis"} = $d{"mutex_miss"}/$int; 336 337 if ($l2exist) { 338 $v{"l2hits"} = $d{"l2_hits"}/$int; 339 $v{"l2miss"} = $d{"l2_misses"}/$int; 340 $v{"l2read"} = $v{"l2hits"} + $v{"l2miss"}; 341 $v{"l2hit%"} = 100 * ($v{"l2hits"} / $v{"l2read"}) 342 if $v{"l2read"} > 0; 343 344 $v{"l2miss%"} = 100 - $v{"l2hit%"} if $v{"l2read"} > 0; 345 $v{"l2size"} = $cur{"l2_size"}; 346 $v{"l2asize"} = $cur{"l2_asize"}; 347 $v{"l2bytes"} = $d{"l2_read_bytes"}/$int; 348 } 349} 350 351sub main { 352 my $i = 0; 353 my $count_flag = 0; 354 355 init(); 356 if ($count > 0) { $count_flag = 1; } 357 while (1) { 358 print_header() if ($i == 0); 359 calculate(); 360 print_values(); 361 last if ($count_flag == 1 && $count-- <= 1); 362 $i = (($i == $hdr_intr) && (not $raw_output)) ? 0 : $i+1; 363 sleep($int); 364 snap_stats(); 365 } 366 close($out) if defined $out; 367} 368 369&main; 370