#!/usr/bin/perl
#
# fcachestat - filesystem cache statistics. Solaris 8+.
#
# 14-Aug-2005	ver 0.97
#
# USAGE: fcachestat [-h] | [interval [count]]
#        fcachestat		# print historic line only
#        fcachestat -h		# print help
#        fcachestat 1 5		# print 5 x 1 second samples
#
# NOTE: The segvn field is a crude approximation. check for updates.
#
# SEE ALSO: dnlcstat, inodestat, ufsbufstat, segmapstat, segvnstat
#
# COPYRIGHT: Copyright (c) 2004, 2005 Brendan Gregg.
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#  (http://www.gnu.org/copyleft/gpl.html)
#
# Author: Brendan Gregg  [Sydney, Australia]
#
# 10-Mar-2004	Brendan Gregg	Created this.
# 14-Aug-2004	   " 	  "	Added segvn.
# 21-Aug-2005	   "	  "	Renamed cachestat -> fcachestat.

use Sun::Solaris::Kstat;
my $Kstat = Sun::Solaris::Kstat->new();

#
#  Process command line args
#
if ($ARGV[0] eq "-h" || $ARGV[0] eq "--help" || $ARGV[0] eq "0") { &usage(); }

$sleep = $ARGV[0];
$loop = $ARGV[1];
if ($sleep eq "") {
	$sleep = 0; $loop = 1; 
} elsif ($loop eq "") {
	$loop = 2**32;
}
$PAGESIZE = 19;                         # max lines per header
$lines = $PAGESIZE;                     # counter for lines printed

#
#  Main
#
while (1) {
	### Print header
	if ($lines++ >= $PAGESIZE) {
		$lines = 0;
		print "  --- dnlc ---    -- inode --- " . 
		 "   -- ufsbuf --    -- segmap --    -- segvn ---\n";
		print "  %hit   total  " x 5, "\n";
	}

	foreach $j (0..4) {

		### Fetch values
		($oldhit[$j],$oldmiss[$j]) = ($hit[$j],$miss[$j]);
		if ($j == 0) { ($hit[0],$miss[0]) = &fetch_dnlc(); }
		if ($j == 1) { ($hit[1],$miss[1]) = &fetch_inode(); }
		if ($j == 2) { ($hit[2],$miss[2]) = &fetch_ufsbuf(); }
		if ($j == 3) { ($hit[3],$miss[3]) = &fetch_segmap(); }
		if ($j == 4) { ($hit[4],$miss[4]) = &fetch_segvn(); }

		### Calculations
		$hits[$j] = $hit[$j] - $oldhit[$j];
		$misses[$j] = $miss[$j] - $oldmiss[$j];
		if ($hits[$j] > 0) { 
			$ratio = $hits[$j] / ($misses[$j] + $hits[$j]);
		} else {
			$ratio = 0;
		}
		$total = $hits[$j] + $misses[$j];

		### Print data
		if ($i == 0 && $total > 1_000_000) {
			# print in Millions for the summary since boot line,
			printf "%6.2f %6.1fM  ",$ratio * 100,$total / 1000000;
		} else {
			printf "%6.2f %7s  ",$ratio * 100,$total;
		}
	}

	print "\n";
	last if ++$i == $loop;
	sleep ($sleep);
}

#
#  Subroutines
#

# fetch - fetch current values for hits and misses.
#
sub fetch_dnlc {
	$Kstat->update();
	return ($Kstat->{unix}->{0}->{dnlcstats}->{hits},
	 $Kstat->{unix}->{0}->{dnlcstats}->{misses});	
}

sub fetch_inode {
	return ($Kstat->{ufs}->{0}->{inode_cache}->{hits},
	 $Kstat->{ufs}->{0}->{inode_cache}->{misses});	
}

sub fetch_ufsbuf {
	my ($hits,$misses);
	$hits = $Kstat->{unix}->{0}->{biostats}->{buffer_cache_hits};
	$misses = $Kstat->{unix}->{0}->{biostats}->{buffer_cache_lookups} 
		 - $hits;
	return ($hits,$misses);
}

sub fetch_segmap {
	my ($hits,$misses,$reclaim,$use,$map);
	$reclaim = $Kstat->{unix}->{0}->{segmap}->{get_reclaim} +
	    $Kstat->{unix}->{0}->{segmap}->{get_use};
	$map = $Kstat->{unix}->{0}->{segmap}->{getmap};
	$hits = $reclaim;
	$misses = $map - $hits;
	return ($hits,$misses);
}

sub fetch_segvn {
	my ($hits,$misses,$cpu);
	$hits = 0;
	$misses = 0;

	foreach $cpu (keys %{$Kstat->{cpu_stat}}) {
		$Cpu_stat = $Kstat->{cpu_stat}->{$cpu}->{"cpu_stat$cpu"};
		# a hit is a reclaim from the page cache,
		$hits += $Cpu_stat->{pgfrec};
		# a miss is either an exec or fs page in,
		$misses += $Cpu_stat->{execpgin} + $Cpu_stat->{fspgin};
	}
	return ($hits,$misses);
}

# usage - print usage and exit.
#
sub usage {
        print STDERR <<END;
USAGE: $0 [-h] | [interval [count]]
   eg, $0		# print historic line only
       $0 1 5		# print 5 x 1 second samples
END
        exit 1;
}

