#!/usr/bin/perl
#
# showsize	Print various cache sizes. Solaris 8+.
#
# Prints the maximum value of various caches. This ought to be run as root, 
# as we attempt to use "adb -k" to read some values. If any values cannot be
# read they are printed as "?".
#
# 06-May-2004	ver 0.90	(check for newer versions)
#
#
# USAGE: showsize [ -h ]
#        showsize 		# print stats
#	 showsize -h		# print help
#
# FIELDS:
#		RAM_Total	Total RAM installed
#		Level1		Level 1 cache size (I$, D$)
#		Level2		Level 2 cache size (E$)
#		icache		Level 1 cache - I$, Instruction cache
#		dcache		Level 1 cache - D$, Data cache
#		ecache		Level 2 cache - E$, External cache
#		DNLC		Directory Name Lookup Cache
#		inode		UFS inode cache
#		ufsbuf		UFS buffer cache (misc UFS metadata)
#		segvn_max	Max size of the filesystem page cache.
#
# The filesystem page cache mentioned above is implemented as a Cyclic Page
# Cache in Solaris 8+. I have called it segvn as files usually populate this
# cache by calls with with mmap(), which is managed by the seg_vn segment
# driver. The size of this cache is dynamic such that it uses available
# memory. Here we only list the maximum possible value, not the current.
#
# SEE ALSO: prtdiag
#
# NOTES:
# * This program does use Kstat, and call the prtconf and adb commands.
#   This would be more efficient rewritten in C, but since it's likely to be
#   used only very occasionally, such a move is not really necessary.
# * On a multi-CPU server, L1/L2 cache values are read from the first CPU.
#
# THANKS:
#	Gary Riseborough	# technical details on hardware caches
#
# 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)
#
# 14-Aug-2004	Brendan Gregg	Created this.
# 06-May-2005	   "      "	Added prtconf for hardware cache values.


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

### Fetch pagesize, bit
$ENV{PATH} = "/usr/bin:/usr/sbin";
chomp($PAGESIZE = `pagesize`);

### Variables
%Kstat = ();  # data from Kstat
%Adb = ();    # data from adb -k
%OBP = ();    # OBP data from prtconf -vp


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


#
# --- Kernel Parameters used ---
#
%Parameters = (	"level1" => "vac_size/D", 
		"level2" => "ecache_size/D",
	       	"dnlc" => "ncsize/D", 
		"inode" => "ufs_ninode/D" );

#
# --- Fetch Cache Sizes ---
#

### RAM total - Kstat
$ram_total = 0;
foreach $i (keys(%{$KSTAT->{lgrp}})) {				# instance
	foreach $c (keys(%{$KSTAT->{lgrp}->{$i}})) {		# class
		$Kstat{"ram_total"} += 
		 $KSTAT->{lgrp}->{$i}->{$c}->{"pages installed"};
	}
}
$Kstat{"ram_total"} *= $PAGESIZE;

### Cache sizes 1 - adb
$fetch_string = join("\n",values(%Parameters));
$sizes = `echo "$fetch_string" | adb -k`;
foreach $cache (keys(%Parameters)) {
	$param = $Parameters{$cache};
	$param =~ s:/.*::;
	($Adb{$cache}) = $sizes =~ /$param:\s+(\d+)/;
}

### Cache sizes 2 - Kstat
$Kstat{"ufsbuf"} = $KSTAT->{unix}->{0}->{var}->{v_bufhwm} * 1024;
$Kstat{"segvnmax"} = ($KSTAT->{unix}->{0}->{system_pages}->{availrmem} -
 $KSTAT->{unix}->{0}->{system_pages}->{lotsfree}) * $PAGESIZE;

### Cache sizes 3 - prtconf
$prtconf = `/usr/sbin/prtconf -vp`;
($OBP{icache}) = $prtconf =~ /\bicache-size:\s+(\d+)/;
($OBP{dcache}) = $prtconf =~ /\bdcache-size:\s+(\d+)/;
($type,$OBP{ecache}) = $prtconf =~ /\b(l2-|e)cache-size:\s+(\d+)/;
($OBP{icacheline}) = $prtconf =~ /\bicache-line-size:\s+(\d+)/;
($OBP{dcacheline}) = $prtconf =~ /\bdcache-line-size:\s+(\d+)/;
($type,$OBP{ecacheline}) = $prtconf =~ /\b(l2-|e)cache-line-size:\s+(\d+)/;
foreach $key qw(icache dcache ecache icacheline ecacheline dcacheline) {
	next if $OBP{$key} eq "";
	$OBP{$key} = hex($OBP{$key});
}


#
# --- Print Report ---
#
# Previous versions of the CacheKit (<0.92) used an elegant way to
# process the following, which turned out to be difficult to tweak.
# I've now unrolled the loops.
#
$def = "      ?";	# default outpet

### Print RAM
if ($Kstat{ram_total}) {
	$Kstat{ram_total} = sprintf("%7.1f",$Kstat{ram_total} / 1048576);
} else {
	$Kstat{ram_total} = $def;
}
print "RAM_Total  $Kstat{ram_total} Mb\n";

### Print I$
if ($OBP{icache}) {
	printf("icache     %7.1f Kb",$OBP{icache} / 1024);
	printf(" (linesize %d bytes)",$OBP{icacheline}) if $OBP{icacheline};
	printf("\n");
} else {
	if ($Adb{level1}) {
		$Adb{level1} = sprintf("%7.1f",$Adb{level1} / 1024);
	} else {
		$Adb{level1} = $def;
	}
	print "Level1     $Adb{level1} Kb\n";
}

### Print D$
if ($OBP{dcache}) {
	printf("dcache     %7.1f Kb",$OBP{dcache} / 1024);
	printf(" (linesize %d bytes)",$OBP{dcacheline}) if $OBP{dcacheline};
	printf("\n");
}

### Print E$
if ($OBP{ecache}) {
	printf("ecache     %7.1f Mb",$OBP{ecache} / 1048576);
	printf(" (linesize %d bytes)",$OBP{ecacheline}) if $OBP{ecacheline};
	printf("\n");
} else {
	if ($Adb{level2}) {
		$Adb{level2} = sprintf("%7.1f",$Adb{level2} / 1048576);
	} else {
		$Adb{level2} = $def;
	}
	print "Level2     $Adb{level2} Mb\n";
}

### Print DNLC
if ($Adb{dnlc}) {
	$Adb{dnlc} = sprintf("%7d",$Adb{dnlc});
} else {
	$Adb{dnlc} = $def;
}
print "DNLC       $Adb{dnlc} paths\n";

### Print inode
if ($Adb{inode}) {
        $Adb{inode} = sprintf("%7d",$Adb{inode});
} else {
        $Adb{inode} = $def;
}
print "inode      $Adb{inode} inode\n";

### Print ufsbuf
if ($Kstat{ufsbuf}) {
	$Kstat{ufsbuf} = sprintf("%7.1f",$Kstat{ufsbuf} / 1048576);
} else {
	$Kstat{ufsbuf} = $def;
}
print "ufsbuf     $Kstat{ufsbuf} Mb\n";

### Print segvn
if ($Kstat{segvnmax}) {
	$Kstat{segvnmax} = sprintf("%7.1f",$Kstat{segvnmax} / 1048576);
} else {
	$Kstat{segvnmax} = $def;
}
print "segvn_max  $Kstat{segvnmax} Mb\n";

