#!/usr/bin/perl 
#-----------------------------------------------------------------
# By: john stile
#
# Purpouse: 
#     Keep fan as slow as possible for a given Target CPU Temp.
#
# Some Testing:
#     speed    temp
#     100      40-42
#     255-100  37-39
#   
# REFERENCE:   Find soruce for allowed range of operation
# CPU MODEL:   AMD Opteron(tm) Processor 244
# Min temp:    45
#-----------------------------------------------------------------
my $debug=          "1";                # debug 1=on, 0=off
my $graph=          "1";                # graph 1=on, 0=off
my $pause=          "5";                # Time between tests
my $sensors=        "/usr/bin/sensors"; # Binary for sensors
my $Sensor_string=  "k8temp-pci-00c3";  # Header in sensors output
my $time=time(  );
my $Speed=          "100" ;             # Setting default fan speed.
my $TargetTmp_upper="44";               # Target temp for CPU
my $TargetTmp_lower="42";               # Target temp for CPU
#------------------------------- 
# Files in sys for an opteron
#-------------------------------
# /sys/devices/pci0000:00/0000:00:19.3/temp1_input
# /sys/devices/pci0000:00/0000:00:18.3/temp1_input
# /sys/devices/platform/i2c-9191/9191-0480/temp4_input
# /sys/devices/platform/i2c-9191/9191-0480/temp3_input
# /sys/devices/platform/i2c-9191/9191-0480/temp2_input
# /sys/devices/platform/i2c-9191/9191-0480/temp1_input
# /sys/devices/platform/i2c-9191/9191-0480/fan4_input
# /sys/devices/platform/i2c-9191/9191-0480/fan3_input
# /sys/devices/platform/i2c-9191/9191-0480/fan2_input
# /sys/devices/platform/i2c-9191/9191-0480/fan1_input
my $ProcFile=       "/sys/devices/pci0000:00/0000:00:19.3/temp1_input";
my $rrd_file=       "/root/rrd/fan_temp.rrd";
my $graph_file_2hr= "/root/rrd/fan_temp_2hr.png";
my $graph_file_12hr="/root/rrd/fan_temp_12hr.png";
my $graph_file_1wk="/root/rrd/fan_temp_1wk.png";
#-----------------------------------------------------------------
# flush the buffer
$| = 1;
#-----------------------------------------------------------------
# daemonize the program
if ( $debug eq "0" ){  &daemonize; }
#-----------------------------------------------------------------
# Set initial fan speed to our base
#&update_fanspeed($Speed);
#-----------------------------------------------------------------
# Main Loop, 
# Runs forever
while ( 1 ){
    #
    # Get temp
    #
    chomp ( my $Temp=&get_temp );
    if ( $debug eq "1" ){  print "Temp: $Temp\n"; }
    #
    # Get speed
    #
#    chomp ( $Speed=&get_fanspeed );
    if ( $debug eq "1" ){  print "Speed:  $Speed\n"; }
    #
    # Calculate new speed
    #
    my $NewSpeed=&new_fanspeed($Temp,$Speed);
    if ( $debug eq "1" ){  print "NewSpeed:  $NewSpeed\n"; }
    #
    # If Speed and NewSpeed are not equal, update fan speed file.
    #
#    if ( $NewSpeed ne $Speed ){ 
#        &update_fanspeed($NewSpeed); 
#	if ( $debug eq "1" ){  print "Loading new speed\n"; }
#    }
    #
    # Create RRD file, if it does not exist
    #
    if ( ! -e "$rrd_file" ){
    	    &create_graph($rrd_file);  
    };
    #
    # Record Fan speed
    #
    my $NewSpeed=&record_fanspeed($Temp,$Speed,$rrd_file);
    #
    # Graph from rrd
    #
    &graph_fanspeed($rrd_file,$graph_file_2hr,$graph_file_12hr,$graph_file_1wk);
    #
    #
    # Sleep
    #
    system ( sleep $pause );
}
#-----------------------------------------------------------------
sub daemonize {
    chdir '/'                 or die "Can't chdir to /: $!";
    open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
    open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
    open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
    defined(my $pid = fork)   or die "Can't fork: $!";
    exit if $pid;
    setsid                    or die "Can't start a new session: $!";
    umask 0;
}
#-----------------------------------------------------------------
sub create_graph {
    chomp ( my $rrd_file=shift );
    # rrdtool create $rrd_directory/fan_temp.rrd \
    # --step 1 \		 # 1 sec steps for RRA's blow
    # DS:Temp:GAUGE:10:0:U \	 # DataSource: over max of 10sec, "0" minimum and unknown max value as valid data
    # DS:Speed:GAUGE:10:0:U \	 # DataSource: over max of 10sec, "0" minimum and unknown max value as valid data
    # RRA:AVERAGE:0.5:1:10 \	 # Archive: Average over 1   measurement  (10 sec),  10 values should be stroed
    # RRA:AVERAGE:0.5:6:60 \	 # Archive: Average over 6   measurements (1 min),   60 values should be stroed
    # RRA:AVERAGE:0.5:24:240 \   # Archive: Average over 24  measurements (4 min),  240 values should be stroed
    # RRA:AVERAGE:0.5:288:2880 \ # Archive: Average over 288 measurements (42 min),2880 values should be stroed
    # RRA:MAX:0.5:1:10 \	 # Archive: MAX     over 1   measurements (10 sec),  10 values should be stroed
    # RRA:MAX:0.5:6:60 \	 # Archive: MAX     over 6   measurements (1 min),   60 values should be stroed
    # RRA:MAX:0.5:24:240 \	 # Archive: MAX     over 24  measurements (4 min),  240 values should be stroed
    # RRA:MAX:0.5:288:2880 \	 # Archive: MAX     over 288 measurements (42 min),2880 values should be stroed
    # RRA:LAST:0.5:1:10 \	 # Archive: Last    over 1   measurement  (10 sec),  10 values should be stored
    # RRA:LAST:0.5:6:60 \	 # Archive: MAX     over 6   measurements (1 min),   60 values should be stroed
    # RRA:LAST:0.5:24:240 \	 # Archive: MAX     over 24  measurements (4 min),  240 values should be stroed
    # RRA:LAST:0.5:288:2880 \	 # Archive: MAX     over 288 measurements (42 min),2880 values should be stroed
    system( qq{rrdtool create $rrd_file \\
    	    --step 1  \\
    	    DS:Temp:GAUGE:10:0:U  \\
    	    DS:Speed:GAUGE:10:0:U  \\
    	    RRA:AVERAGE:0.5:1:10 \\
    	    RRA:AVERAGE:0.5:6:60 \\
    	    RRA:AVERAGE:0.5:24:240 \\
    	    RRA:AVERAGE:0.5:288:2880 \\
    	    RRA:MAX:0.5:1:10 \\
    	    RRA:MAX:0.5:6:60 \\
    	    RRA:MAX:0.5:24:240  \\
    	    RRA:MAX:0.5:288:2880 \\
    	    RRA:LAST:0.5:1:10	\\
    	    RRA:LAST:0.5:6:60	\\
    	    RRA:LAST:0.5:24:240   \\
    	    RRA:LAST:0.5:288:2880} );
}
#-----------------------------------------------------------------
sub record_fanspeed{
	my $temp=shift;
	my $speed=shift;
	my $rrd_file=shift ;
	system ( "rrdtool update $rrd_file --template Temp:Speed N:$temp:$speed" );	
}
#-----------------------------------------------------------------
sub graph_fanspeed {
        my $rrd_file=shift;
	my $graph_file_2hr=shift;
	my $graph_file_12hr=shift;
        my $graph_file_1wk=shift;
   #####################################
   # For the graph:
   #  Temp    a=ave   b=max    c=last  
   #  Speed   d=ave   e=max    f=last
   #####################################
   #
   # Draw 2 Hour png
   #
   system(  "rrdtool", "graph", "$graph_file_2hr",
   	    "--imgformat=PNG",
   	    "--start=-2880",
   	    "-c", "BACK#343435", "-c", "FONT#ffffff", "-c", "CANVAS#605f60",
   	    "--title=2 Hour Fanspeed Tmp",
   	    "--height=150", "--width=400", 
   	    "--vertical-label=Red=degreeC, Yellow=speed",
   	    "--step", "10",
   	    "DEF:a=$rrd_file:Temp:AVERAGE", 
   	    "DEF:b=$rrd_file:Temp:MAX",
   	    "DEF:c=$rrd_file:Temp:LAST",
   	    "DEF:d=$rrd_file:Speed:AVERAGE",
   	    "DEF:e=$rrd_file:Speed:MAX",
   	    "DEF:f=$rrd_file:Speed:LAST", 
   	    "LINE1:c#ea0e2b:Temp\\r:", 
   	      "GPRINT:a:AVERAGE:Average\\t%6.0lf%s\\r", 
   	      "GPRINT:b:MAX:Max\\t%6.0lf%s\\r",
   	      "GPRINT:c:LAST:Last\\t%6.0lf%s\\r",
   	      "COMMENT:\\n",  
   	    "LINE1:f#EACC00:Speed\\r:", 
   	      "GPRINT:d:AVERAGE:Average\\t%6.0lf%s\\r",
   	      "GPRINT:e:MAX:Max\\t%6.0lf%s\\r",
   	      "GPRINT:f:LAST:Last\\t%6.0lf%s\\r",
   	      "COMMENT:\\n"
   );
   #
   # Draw 12 Hour png
   #
   system(  "rrdtool", "graph", "$graph_file_12hr",
   	    "--imgformat=PNG",
   	    "--start=-86400",
   	    "-c", "BACK#343435", "-c", "FONT#ffffff", "-c", "CANVAS#605f60",
   	    "--title=12 Hour Fanspeed Tmp",
   	    "--height=150", "--width=400", 
   	    "--vertical-label=Red=degreeC, Yellow=speed",
   	    "--step", "10",
   	    "DEF:a=$rrd_file:Temp:AVERAGE", 
   	    "DEF:b=$rrd_file:Temp:MAX",
   	    "DEF:c=$rrd_file:Temp:LAST",
   	    "DEF:d=$rrd_file:Speed:AVERAGE",
   	    "DEF:e=$rrd_file:Speed:MAX",
   	    "DEF:f=$rrd_file:Speed:LAST", 
   	    "LINE1:c#ea0e2b:Temp\\r:", 
   	      "GPRINT:a:AVERAGE:Average\\t%6.0lf%s\\r", 
   	      "GPRINT:b:MAX:Max\\t%6.0lf%s\\r",
   	      "GPRINT:c:LAST:Last\\t%6.0lf%s\\r",
   	      "COMMENT:\\n",  
   	    "LINE1:f#EACC00:Speed\\r:", 
   	      "GPRINT:d:AVERAGE:Average\\t%6.0lf%s\\r",
   	      "GPRINT:e:MAX:Max\\t%6.0lf%s\\r",
   	      "GPRINT:f:LAST:Last\\t%6.0lf%s\\r",
   	      "COMMENT:\\n"
   );

   #
   # Draw 1week png
   #
   system(  "rrdtool", "graph", "$graph_file_1wk",
   	    "--imgformat=PNG",
   	    "--start=-604800",
   	    "-c", "BACK#343435", "-c", "FONT#ffffff", "-c", "CANVAS#605f60",
   	    "--title=1 Week Fanspeed Tmp",
   	    "--height=150", "--width=400", 
   	    "--vertical-label=Red=degreeC, Yellow=speed",
   	    "--step", "10",
   	    "DEF:a=$rrd_file:Temp:AVERAGE", 
   	    "DEF:b=$rrd_file:Temp:MAX",
   	    "DEF:c=$rrd_file:Temp:LAST",
   	    "DEF:d=$rrd_file:Speed:AVERAGE",
   	    "DEF:e=$rrd_file:Speed:MAX",
   	    "DEF:f=$rrd_file:Speed:LAST", 
   	    "LINE1:c#ea0e2b:Temp\\r:", 
   	      "GPRINT:a:AVERAGE:Average\\t%6.0lf%s\\r", 
   	      "GPRINT:b:MAX:Max\\t%6.0lf%s\\r",
   	      "GPRINT:c:LAST:Last\\t%6.0lf%s\\r",
   	      "COMMENT:\\n",  
   	    "LINE1:f#EACC00:Speed\\r:", 
   	      "GPRINT:d:AVERAGE:Average\\t%6.0lf%s\\r",
   	      "GPRINT:e:MAX:Max\\t%6.0lf%s\\r",
   	      "GPRINT:f:LAST:Last\\t%6.0lf%s\\r",
   	      "COMMENT:\\n"
   );

}
#-----------------------------------------------------------------
sub update_fanspeed {
    my $NewSpeed=shift;
    open ( FANSPEED, ">>$ProcFile") || die "Cannot read fanspeed file: $?\n";
    print FANSPEED "$NewSpeed";
    close ( FANSPEED ) || die "Cannot close fanspeed file: $?\n";
    return;
}    
#-----------------------------------------------------------------
sub new_fanspeed {
    my $Temp=shift;
    my $Speed=shift;    
    #
    # If temp is over 42, increment fan speed.
    #
    if (  "$Temp" > $TargetTmp_upper ){ unless( $Speed eq 255){ ++$Speed ; } }    
    #
    # If temp is under 42, decrement fan speed.
    #
    if (  $Temp < $TargetTmp_lower ){ unless( $Speed eq 0 ){ --$Speed ; } }
    #
    # Return the new speed
    #
    return ( $Speed );
}
#-----------------------------------------------------------------
#sub get_fanspeed {
#    open ( FANSPEED, "<$ProcFile") || die "Cannot read fanspeed file: $?\n";
#    chomp ( my $Speed=<FANSPEED> );
#    close ( FANSPEED ) || die "Cannot close fanspeed file: $?\n";
#    return ( $Speed );
#}
#-----------------------------------------------------------------
sub get_temp {
    # Process output of `sensors` one line at a time.
    open ( SENSORS, "$sensors |") || die "Cannot run sensors\n";
    while (<SENSORS>){

      ########################################
      #  Screen for relevent sensor data
      #   - If lm_sensors modules are loade incorrectly,
      #     double output of incorrect values result.
      #   - This allows one to specify which to collect
      ########################################
 
      # Start collecting data when this line is reached
      if ( $_ =~ m/$Sensor_string/ ){
    	  $Start="1";
      # Stop Collecting data when this line is reached
      } elsif ( $_ =~ m/^$/ ) {
    	  $Start="0";
    	  done;
      }
      # If Start is 1, we are working with good data
      if ( $Start eq "1" ){
        #
        # Example string:
        #




    	#
	# Example string: CPU0 Temp:   +42 C  (low  =    +0 C, high =   +70 C)
    	#
    	$bla=$_;
	# print "$bla";
    	if ( $bla =~ m{
    			  ^CPU0
			  \W*
			  Temp: 	# Starts with temp:
			  \W*           # NonWord characters
    			  \+
    			  (\d+)		# digits are the temp
			  .*            # Anything up to
    			  \n		# new line
    		      }xig		# allow comments, case insensetive, global
           ) { 
	       #
	       # Debug: Print the captured value
	       #
	       if ( $debug eq "0" ){  print "CPU Tmep:\t$1\n"; }
	       # END of SENSORS data
               close ( SENSORS );
	       return ($1);
	   }
      } 
    }
    # Should never get here.
    close ( SENSORS );
    return (0);
}
#-----------------------------------------------------------------
