#!/usr/bin/perl
#
# ccachestat - CPU cache statistics: I$, D$, E$. Solaris 8+.
#
# This monitors the three main CPU caches: the Instruction cache, the
# Data cache, and the External cache. The output is a summary for all the
# CPUs in the current processor set.
#
# 21-Aug-2005	ver 0.60
#
# USAGE: 	ccachestat [-h] | [interval [count]]
#
#		ccachestat		# print 1 x 5 second sample
#		ccachestat -h		# print help
#		ccachestat 1 8		# print 8 x 1 second samples
#
# NOTE: The output is an estimation - we can only measure these caches
# one by one, so we cycle between them and multiply the final result by 3.
#
# SEE ALSO: 	cpustat
#
# COPYRIGHT: Copyright (c) 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]
#
# 21-Aug-2005	Brendan Gregg	Created this.

#
#  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 = 5; $loop = 1; 
} elsif ($loop eq "") {
	$loop = 2**32;
}
$PAGESIZE = 19;                         # max lines per header
$lines = $PAGESIZE;                     # counter for lines printed
$ENV{PATH} = "/usr/bin:/usr/sbin";	# path to cpustat

#
# cpustat Sample Rate
#
# Three times this value must be > 1, to trigger output.
# This has been choosen to be slow - 334 ms, even though cpustat can measure
# much faster. The problem with fast measurements is that cpustat itself
# leaps onto the CPUs too often - polluting the caches and altering the
# very performance we are trying to measure.
#
$RATE = 0.334;

#
#  Fetch CPU type, count
#
$help = `cpustat -h`;
($CPU_TYPE) = $help =~ /.*CPU performance counter interface: (.*)\n/;
if ($CPU_TYPE =~ /Pentium Pro with MMX, Pentium II/) {
	@Lines = `cpustat -c pic0=ifu_ifetch,pic1=ifu_ifetch 0.01 1 2>&1`;
} else {
	@Lines = `cpustat -c pic0=Cycle_cnt,pic1=Cycle_cnt 0.01 1 2>&1`;
}
if (grep(/Usage:/, @Lines)) { die("ERROR1: Unknown CPU type: $CPU_TYPE\n"); }
if (grep(/Permission denied/, @Lines)) { 
	print @Lines;
	die("ERROR2: Run as root.\n"); 
}
$CPU_NUM = scalar @Lines - 2;

#
#  Run cpustat
#
if ($CPU_TYPE =~ /UltraSPARC I.II/) {
	&open_UltraSPARCII();
} elsif ($CPU_TYPE =~ /UltraSPARC III/) {
	&open_UltraSPARCIII();
} elsif ($CPU_TYPE =~ /Pentium (Pro|II)/) {
	&open_PentiumII();
} else {
	die("ERROR4: Unknown CPU type: $CPU_TYPE\n");
}
$SIG{INT} = \&cleanup;		# Ctrl-C
$SIG{QUIT} = \&cleanup;		# Ctrl-\
$SIG{TERM} = \&cleanup;		# TERM

#
#  Process output
#
while (1) {
	### Print header
	if ($lines >= $PAGESIZE) {
		$lines = 0;
		print"       --- I-Cache ---           --- D-Cache ---   " .
		 "        --- E-Cache ---\n";
		printf("%9s %7s %7s %9s %7s %7s %9s %7s %7s\n",
		 "total", "miss", "%hit", "total", "miss", "%hit",
		 "total", "miss", "%hit");
	}

	### Read cpustat output
	for ($cpu = 0; $cpu < $CPU_NUM; $cpu++) {
		chomp($line = <CPUSTAT>);
		($time, $cpu, $event, $pic0, $pic1, $hash, $desc) =
		 split(' ', $line);

		if ($CPU_TYPE =~ /UltraSPARC I.II/) {
			&process_UltraSPARCII($line);
		} elsif ($CPU_TYPE =~ /UltraSPARC III/) {
			&process_UltraSPARCIII($line);
		} elsif ($CPU_TYPE =~ /Pentium (Pro|II)/) {
			&process_PentiumII();
		}
	}

	$elapsed += $RATE;
	next if $elapsed < $sleep;

	### Calculate %hit
	$CPC{all}{I}{hit} = $CPC{all}{I}{total} == 0 ? 0 : (100 * 
	 (($CPC{all}{I}{total} - $CPC{all}{I}{miss}) / $CPC{all}{I}{total}));
	$CPC{all}{D}{hit} = $CPC{all}{D}{total} == 0 ? 0 : (100 * 
	 (($CPC{all}{D}{total} - $CPC{all}{D}{miss}) / $CPC{all}{D}{total}));
	$CPC{all}{E}{hit} = $CPC{all}{E}{total} == 0 ? 0 : (100 * 
	 (($CPC{all}{E}{total} - $CPC{all}{E}{miss}) / $CPC{all}{E}{total}));

	### Print output line
	printf("%8dk %6dk %7.2f %8dk %6dk %7.2f %8dk %6dk %7.2f\n",
	 $CPC{all}{I}{total}/1000, $CPC{all}{I}{miss}/1000, $CPC{all}{I}{hit},
	 $CPC{all}{D}{total}/1000, $CPC{all}{D}{miss}/1000, $CPC{all}{D}{hit},
	 $CPC{all}{E}{total}/1000, $CPC{all}{E}{miss}/1000, $CPC{all}{E}{hit});

	### Reset counters
	undef %CPC;
	$elapsed = 0;
	$lines++;
	last if ++$i == $loop;
}

&cleanup();

#
#  Subroutines
#

# "UltraSPARC I&II"
#
sub open_UltraSPARCII {
	open(CPUSTAT, "cpustat -c pic0=IC_ref,pic1=IC_hit " . 
	 "-c pic0=DC_rd,pic1=DC_rd_hit -c pic0=EC_ref,pic1=EC_hit $RATE |") ||
	 die("ERROR5: Can't run cpustat. Not root / unknown CPU type? $!\n");
}
sub process_UltraSPARCII {
	my $line = shift;
	my $cache = "";
	$cache = "I" if $desc =~ /IC/;
	$cache = "D" if $desc =~ /DC/;
	$cache = "E" if $desc =~ /EC/;
	$CPC{all}{$cache}{total} += 3 * $pic0;
	$CPC{all}{$cache}{miss} += 3 * ($pic0 - $pic1);
}

# "UltraSPARC III", "UltraSPARC IIIi", "UltraSPARC III+ & IV"
#
sub open_UltraSPARCIII {
	open(CPUSTAT, "cpustat -c pic0=IC_ref,pic1=IC_miss " . 
	 "-c pic0=DC_rd,pic1=DC_rd_miss -c pic0=EC_ref,pic1=EC_misses $RATE |")
	 || die("ERROR6: Can't run cpustat. Not root / unknown CPU type? $!\n");
}
sub process_UltraSPARCIII {
	my $line = shift;
	my $cache = "";
	$cache = "I" if $desc =~ /IC/;
	$cache = "D" if $desc =~ /DC/;
	$cache = "E" if $desc =~ /EC/;
	$CPC{all}{$cache}{total} += 3 * $pic0;
	$CPC{all}{$cache}{miss} += 3 * $pic1;
}

# "Pentium Pro with MMX, Pentium II"
#
sub open_PentiumII {
	open(CPUSTAT, "cpustat -c pic0=ifu_ifetch,pic1=ifu_ifetch_miss " . 
	 "-c pic0=data_mem_refs,pic1=dcu_m_lines_out " .
	 "-c pic0=l2_ads,pic1=l2_lines_in $RATE |")
	 || die("ERROR6: Can't run cpustat. Not root / unknown CPU type? $!\n");
}
sub process_PentiumII {
	my $line = shift;
	my $cache = "";
	$cache = "I" if $desc =~ /=i/;
	$cache = "D" if $desc =~ /=d/;
	$cache = "E" if $desc =~ /=l/;
	$CPC{all}{$cache}{total} += 3 * $pic0;
	$CPC{all}{$cache}{miss} += 3 * $pic1;
}

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;
}

sub cleanup {
	close(CPUSTAT);
	exit(0);
}

