#!/usr/bin/perl -w
#
# xen_cpu_v2.pl 1.00
# Script to minitor the CPU usage of Xen domains
# Zoltan HERPAI (c) 2009, wigyori@uid0.hu
#
# Based loosely on Adam Crews' xen_cpu script
#
# This script tries to measure the CPU usage of the Xen guests
# accurately. 
# The problem with the current monitoring script is that these 
# scripts use the CPU output of xentop or xm list, which might be
# inaccurate due to the resources used up at the time of the query by
# the xm or xentop command.
#
# This script stores the previous value of the CPU sec value of the given
# guests in a tempfile, then does some simple calculations to get the real
# CPU usage percentage of the guests.
#

#%# family=auto
#%# capabilities=autoconf

use strict;
use POSIX;

# Define where to find xm tools
my $XM = '/usr/sbin/xm';
my $XMTOP = '/usr/sbin/xentop';
my $curtime = time();
my $basename = `/usr/bin/basename $0`; chop ($basename);
my $TEMPFILE = "/tmp/$basename";

my $debug = 0;

##############
# You should not need to edit anything below here
#

$ENV{PATH} = '/bin:/usr/bin:/usr/sbin';

my $arg;
if ( defined($ARGV[0]) )
{
	if ( $ARGV[0] eq 'config' )
	{
		$arg = 'config';
	}
	if ( $ARGV[0] eq 'autoconf' )
	{
		$arg = 'autoconf';
	}
	
	if ( $arg eq 'autoconf')
	{
		if ( -e $XM && -e $XMTOP )
		{
			print "yes\n";
			exit 0;
		}
		else
		{
			print "no ($XM and/or $XMTOP not found\n";
			exit 1;
		}
	}

	if ( $arg eq 'config' )
	{
		my %cnf;
		%cnf = (
			'graph_title' => 'Xen Domain CPU Usage v2',
			'graph_args' => '--base 1000 -l 0 --upper-limit 100 --rigid',
			'graph_vlabel' => 'Percent (%)',
			'graph_category' => 'Virtualization',
			'graph_info' => 'Display the % of CPU Usage for each domain',
		);

		my @domains = `$XM list`;
		my $cnt = 0;
		shift(@domains); # we dont need the header line
		foreach my $domain ( @domains )
		{
			my ($dom,undef) = split(/\s/, $domain, 2);
			$dom =~ s/[-.]/_/g;
			$cnf{ "$dom" . '.label' } = "$dom";
			$cnf{ "$dom" . '.draw' } = 'STACK';
			$cnf{ "$dom" . '.min' } = '0';
			$cnf{ "$dom" . '.max' } = '100';
			$cnf{ "$dom" . '.info' } = '% CPU used for ' . "$dom";
#			$cnf{ "$dom" . '.draw' } = 'AREA';
			if ( $cnt == 0 )
			{
				$cnf{ "$dom" . '.draw' } = 'AREA';
			}
			$cnt++;
		}
		foreach my $key (sort(keys(%cnf)))
		{
			print "$key $cnf{$key}\n";
		}
		exit 0;
	}
}
	
my @xmlist = `$XM list`;
shift (@xmlist);

my %dom; my $name; my $oldtime;
# Read old data
if ( -e $TEMPFILE )
{
	open(FH, "<", $TEMPFILE) or die $!;
	$oldtime = <FH>;

	if ( $debug )
	{
		print "Oldtime: $oldtime\n";
	}

	while (<FH>)
	{
		# Get the guest name and its CPU usage, and store it in $dom
		$_ =~ /(\S+)\s+\S+\s+\S+\s+\d+\s+\S+\s+(\S+)/;
		$dom{$1}->{'oldtime'} = $2;
	}
	
	close FH;
}

my $diff; my $cpusum = 0;
foreach my $domain ( @xmlist )
{
	# Get the domains' name and current CPU usage, store it in $dom
	$domain =~ /(\S+)\s+\S+\s+\S+\s+\d+\s+\S+\s+(\S+)/;
	$dom{$1}->{'newtime'} = $2;

	$diff = $dom{$1}->{'newtime'} - $dom{$1}->{'oldtime'};
	$diff = sprintf("%.2f", $diff);
	
	# Calc the diff between old and new cputime, or reset the counter
	if ( $diff < 0 )
	{
		$diff = $dom{$1}->{'newtime'};
	}
	$dom{$1}->{'diff'} = $diff;
	
	# Calc a sum CPU usage
	$cpusum = $cpusum + $diff;
}

my $numcpus = `$XM info |grep nr_cpus |cut -d \: -f 2`;
my $timediff = $curtime - $oldtime;
my $tcpuavail = $numcpus * $timediff;

if ( $debug )
{
	print "UsedCPUtime sum: $cpusum\n";
	print "CPUs: $numcpus\n";
	print "Timediff: $timediff\n";
	print "Total CPU time available: $tcpuavail\n";
}

my $key; my $value;
while (($key, $value) = each %dom)
{
	# Calc a percentage based on the used CPU time sum
	my $tmp = 0;
	$tmp = ( $dom{$key}->{'diff'} / $cpusum ) * 100;
	$dom{$key}->{'pc_time'} = sprintf("%.2f", $tmp);
	
	# Calc a percentage based on the _total_ available CPU time
	$tmp = 0;
	$tmp = ( $dom{$key}->{'diff'} / $tcpuavail ) * 100;
	$dom{$key}->{'pc_tcpu'} = sprintf("%.2f", $tmp);
	
	if ( $debug )
	{
		print "$key newtime: ".$dom{$key}->{'newtime'}.", oldtime: ".$dom{$key}->{'oldtime'}.", diff: ".$dom{$key}->{'diff'}.", pc_bytime ".$dom{$key}->{'pc_time'}.", pc_bytcpu ".$dom{$key}->{'pc_tcpu'}."\n";
	}
	print "$key.value ".$dom{$key}->{'pc_tcpu'}."\n";
}

# We'll need to log out the current "xm list" output, and the current time also.
open(FH, ">", $TEMPFILE) or die $!;
print FH $curtime."\n";
print FH @xmlist;
close FH;
	
