#!/usr/bin/perl -w
#
# copyright Freek Reedeker 2009
#
# After a script "rrd_hddtemp.pl" by Martin Pot
# http://martybugs.net/linux/download/rrd_hddtemp.txt
#
# /usr/local/bin/lmsensor.pl

use RRDs;

# define location of rrdtool databases
my $rrd = '/var/lib/rrd';
# define location of images
my $img = '/var/www/html/rrdtool';
# Initialize variables
my ($sensor_name, $sensor_value, $sensor_units) = "";

# Check for number of commandline arguments
my $numberOfArguments= $#ARGV;
if (abs($numberOfArguments) != 1) 	# Exit if the wrong number of arguments are passed (0 or 2)
{ 
	print STDERR "Error: invalid number of arguments.\nOnly 0 or 2 arguments \nUsage: lmsensor.pl [-d|-debug 0|1|2 ] \n";
	exit(1);
}

# Debugmode = 0 is for normal operation and is the default
# Debugmode = 1 is to check the parsing process
# Debugmode = 2 is to generate the contents of the %monitor array.
# The debugmode can be set from the commandline with the option -d|-debug followed by a 0, 1 or 2
#
# I am not familiar with all the possible outputs of the <sensor> command
# The script might need some tweeking for different sensor setups
# Therefore the debugging options are included.

# default debugmode
my $debugmode = 0;
# If debugmode set from commandline -> lmsensor.pl -d (0|1|2)
# 2 commandline argument, first == "-d", second is 0, 1 or 2
if ($numberOfArguments == 1)	# no of arguments is two and debugmode needs to be changed
{ 		
	if (($ARGV[0] =~ m/^-(d|debug)$/) && ($ARGV[1]=~ m/^(0|1|2)$/)){ 
		$debugmode= $ARGV[1];
	}else {
		print STDERR "Error: invalid arguments.\nUsage: lmsensor.pl -d|-debug 0|1|2 \n";
		exit(1);
	}
}

# Select which sensors to graph. Uncomment the sensors you are interested in. 
# Array is of form -- %monitor ( 'sensor_name', 'Graphcolor' )
# If you need a different list for your sensors run the script with debugmode = 2
# Paste the output into the %monitor array assignment below, remove the last ","
my %monitor = (	
		#'+2.5V', 'Magenta',
		#'VCore', 'Default',
		#'+3.3V', 'Default',
		#'+5V', 'Default',
		#'+12V', 'Default',
		#'VCC', 'Default',
		#'+1.5V', 'Default',
		#'+1.8V', 'Default',
		'Chip Temp', 'Dark Faded Cyan',
		'CPU Temp', 'Green',
		'Sys Temp', 'Magenta',
		#'cpu0_vid', 'Default',
		'fan1', 'Default',
		'fan2', 'Dark Faded Red'
		);

# Which graphcolors to use for the different graphs. 
# Change the colour in the monitor array to suite your taste. Default = Blue
# Colourcodes taken from http://www.visibone.com/colorlab/ .
my %graphcolors = (	'Default', '0000FF', 
			'Blue', '0000FF', 
			'Green', '00FF00', 
			'Yellow','FFFF00' , 
			'Magenta','FF00FF' , 
			'Light Hard Orange' , 'FF9933' ,
			'Red' , 'FF0000',
			'Cyan', '00FFFF',
			'Dark Faded Blue', '000099',
			'Dark Faded Red', '990000',
			'Dark Faded Green', '009900',
			'Dark Faded Cyan', '009999',
			'Dark Faded Magenta', '990099'	);

my %scalarprintout = ( 	'V', 'volt',
			'C', 'degrees C',
			'F', 'degrees F',
			'RPM', 'rotations/minute'	);

# Get the output from sensors. You need to install lmsensors first.
my $lmsensor_string=`sensors`;

# motherboard data
my $mobo=`/usr/sbin/dmidecode -s baseboard-manufacturer`."-";
$mobo .= `/usr/sbin/dmidecode -s baseboard-product-name`;
# remove eol chars
$mobo =~ s/[\n]//g;
if ($debugmode == 1) {print "mobo = ".$mobo."\n";}

# for debugmode == 2
if ($debugmode == 2) 
{
	print "\n<----- Copy the following output and past it into the \%monitor array ----->\n\n "; # for %monitor array
}
# Parse output one line at a time
while($lmsensor_string =~ m/\G(.*)\n/g) 
{
	$lmsensor_outputline=$1;
	# All sensor output lines contain a ":". skip lines without colon.
	if ($lmsensor_outputline !~ /:/i) {
		if ($debugmode == 1) {print "Line does not contain <:> \n";}
		next;
	}
	# Some lines with a colon only specify the adapter. Skip these
	if ($lmsensor_outputline =~ /adapter/i) {
		if ($debugmode == 1) {print "Skipped Adapter line! \n";}
		next;
	}
	# Parse remaining lines.
	($sensor_name, $sensor_value, $sensor_units) = ($lmsensor_outputline =~ m/^(.*):\s*[+ -](\S*?)[\s°].*?(F|C|V|RPM).*/);
	# m/^(.*):\s*[+ -](\S*?)[\s°].*?(F|C|V|RPM).*/
	# First match is from beginning of line upto the semicolon 			-> $sensor_name
	# Skip the spaces and the +/- sign or <space> upto the second group of nonwhite characters
	# Second match is non-white characters upto a space or degree character   	-> $sensor_value
	# There is some nonprintable character associated with the degree character, skip this
	# Third match is either F, C, V or RPM  					-> $sensor_units
	# Skip rest of line.
	if ($debugmode == 2) # for %monitor array
	{	
		print "\t\t\#'"; 
		print $sensor_name;
		print "', \'Default\',";
		print "\n";
	} 
	if ($debugmode == 1) # To check parsing
	{
		print $sensor_name;
		print "\t";
		print $sensor_value."\t";
		print $sensor_units."\t";
		# This works but following is better	
	#	if (grep {$_ eq $sensor_name} @monitor) {print "<-- monitored" ;}
		# This is better with an Associative Array
		# This will show in the debug output which sensors are monitored.
		if ($monitor{$sensor_name}) { print "\t\t\t<-- monitored" ;}
		print "\n";
		print $lmsensor_outputline."\n"; # line by line to verify what is parsed
	}
	# This is the real stuff, after you've confirmed that the parsing works 		
	# as wanted and have selected some sensors to monitor and   		
	# switched off debug mode.	
	if (! $debugmode)	# Only do the following when not in debugmode
	{ 		
		if ($monitor{$sensor_name}) 	# Sensor is being monitored
		{	
			# if rrdtool database doesn't exist, create it
			if (! -e "$rrd/$sensor_name.rrd")
			{
				print "creating rrd database for $sensor_name...\n";
				RRDs::create "$rrd/$sensor_name.rrd",
					"-s 300",
					"DS:Values:GAUGE:600:0:U",
					"RRA:AVERAGE:0.5:1:576",
					"RRA:AVERAGE:0.5:6:672",
					"RRA:AVERAGE:0.5:24:732",
					"RRA:AVERAGE:0.5:144:1460";
			}
			# insert value into rrd
			RRDs::update "$rrd/$sensor_name.rrd",
			"-t", "Values",
			"N:$sensor_value";
			# create graphs
			&CreateGraph($sensor_name, "day", $mobo, $sensor_units);
			&CreateGraph($sensor_name, "week", $mobo, $sensor_units);
			&CreateGraph($sensor_name, "month", $mobo, $sensor_units);
			&CreateGraph($sensor_name, "year", $mobo, $sensor_units);
		
	#
	# Delete daily graph if it exists but sensor is no longer monitored
	# If the daily graph is deleted, the hddtemp.cgi and sensormc.cgi script will no longer load the graph
	# The rrd will remain. If you later reenable monitoring of the sensor only the daily
	# graph will be recreated.
	# If you want to get rid of the rrd's as well, uncomment the second exec() line
		}elsif(-e "$img/$sensor_name-day.png")  # Sensor is NOT being monitored but graph exists
		{	
			# Only for commandline debugging
			# *********************************
			# print $sensor_name." is not monitored but graph exists !!!\n";
			# print "Removing $sensor_name-day.png !!!\n";
			exec("rm $img/$sensor_name-day.png");
			# exec("rm $rrd/$sensor_name.rrd");	
		}
	}
}

# the whole output of the sensor command
if ($debugmode == 1) {print "\n\n".$lmsensor_string."\n";}

sub CreateGraph
{
# creates graph
# inputs: $_[0]: sensor name (ie +2.5V, Chip Temp, fan1)
#         $_[1]: interval (ie day, week, month, year)
#         $_[2]: MOBO description
#	  $_[3]: Sensor units ( C, F, V, RPM)

	RRDs::graph "$img/$_[0]-$_[1].png",
		"--lazy",
		"-s -1$_[1]",
		"-t Sensor :: $_[2] ($_[0])",
		"-h", "80", "-w", "600",
		"-a", "PNG",
		"-v $scalarprintout{$_[3]}",
		"DEF:temp=$rrd/$_[0].rrd:Values:AVERAGE",
		"LINE2:temp#$graphcolors{$monitor{$_[0]}}: $_[0]",
		"GPRINT:temp:MIN:  Min\\: %2.lf",
		"GPRINT:temp:MAX: Max\\: %2.lf",
		"GPRINT:temp:AVERAGE: Avg\\: %4.1lf",
		"GPRINT:temp:LAST: Current\\: %2.lf $scalarprintout{$_[3]}\\n";
	if ($ERROR = RRDs::error) { print "$0: unable to generate $_[0] graph: $ERROR\n"; }
}


