#! /usr/bin/perl -w
# $Header: /global/src/usr.etc/RCS/cl-apmlog,v 1.14 2006/10/21 09:17:34 pb22 Exp $
# Use "pod2txt" for info

=head1 NAME

cl-apmlog -- log the apm info at intervals

=head1 SYNOPSIS

really /etc/rc.d/rc<n>.d/cl-apmlog [ start | stop ]

=head1 DESCRIPTION

Started by /etc/init.d/cl-apmlog

Tries not do sync writes if using the battery and a fair amount left,
so as to avoid spinning up the discs.

The data can be displayed by gnuplot, using commands such as
`plot "/var/log/apm.log" woth linespoints' for the capacity, and
`plot "/var/log/apm.log" using 1:3' for the discharge rate (if available, e.g. acpi)

=head1 FILES

=over 2

=item /var/run/cl-apmlog.pid [ PIDFILE ]

SYSV lock file

=item /var/log/apm.log [ LOG ]

File used for logging info.
Reopens as needed, so can be cycled.

=item /proc/apm [ PROCAPM ]

used to read info if in APM mode

=item /usr/bin/apm| [ PROCACPICMD ]

used to read info if in APM mode

=back

=cut

$defproc= "/proc/apm";
@apmcmds = ( "/usr/bin/cl-apm", "/usr/bin/apm" );
foreach $try (@apmcmds) { $apmcmd = $try, last if -e $try; };
$acpiproc= $ENV{'PROCACPICMD'}	|| "$apmcmd|";
$defproc= $acpiproc unless -r $defproc;
$pidfile= $ENV{'PIDFILE'}	|| "/var/run/cl-apmlog.pid";	# SYSV control
$log	= $ENV{'LOG'}		|| "/var/log/apm.log";		# Output file
$proc	= $ENV{'PROCAPM'}	|| $defproc;			# info source
$sleep	= $ENV{'SLEEP'}		|| 60;				# Sleep time
$trace	= $ENV{'APMLOGTRACE'} || $ENV{'TRACE'};					# Trace ...
$percent_sync = $ENV{'PERCENTSYNC'} || 5; # below this %age close file each time
$max_writes= $ENV{'MAXWRITES'}	|| 10;				# Max writes

$ENV{'SHOWRATE'} = 1;				# request apm rate if available

$SEC = 0; $MIN = 1; $HR  = 2; $DAY = 3; $MON = 4; $YR  = 5; # localtime offsets
$adj	= 0;			# kludge to try to synchronize
$percent= -1;			# Force value to be logged
$postscript = '';		# No postscript yet
$need_stamp = $when = 60*60;	# Large number -- trigger date stamp
$samepercent = 0;
$minsamepercent = 6;
$log_open	= 0;		# Is the log file open ?

open(LOG, ">>$log") || die("$0: unable to append to $log: $!\n"); close(LOG);
open(APM, "$proc")  || die("$0: unable to open $proc: $!\n");     close(APM);

# Looks OK, so return "OK" to shell, catch TERM to rm PIDfile, create PIDfile
unless ($trace) { fork() && exit(0); }
$pend	= "\n# Daemon $$ starting\n";	# pending output
$SIG{'TERM'} = "catch_term";
$SIG{'HUP'} = "catch_hup";
if (open(PIDFILE, ">$pidfile")) { print PIDFILE "$$\n"; close(PIDFILE); }

# Loop generating the info and sleeping.
while (1) { &stamp; sleep($sleep + $adj); }

# =============================================================================

# Generate a stamp entry.
sub stamp {
	my ($last)	= (-r $log) ? $when : $need_stamp;
	my ($lastpercent) = $percent;
	my ($lastcharge) = $charge;
	my ($lastrate) = $rate;
	$adj		= 0;
	($percent,$charge,$rate)= &percent;
	$when		= &when;
	$msg		= "$when $percent%$charge\n";
	$msg		=~ s/$/ ${rate}mW/ if  $rate;

	# Do we have lots of repeats of the same %age ?
	if (($lastpercent == $percent) && ($charge eq $lastcharge) &&
			($lastrate == $rate) &&
			($samepercent++ > $minsamepercent) && ! $postscript) {
		# Set up $pend in case this is the last instance ...
		$pend = "# Repeated $samepercent times\n$msg\n";
	}
	else {
		$fullstamp = 0;
		$samepercent = 0 unless $lastpercent == $percent && $charge eq $lastcharge;
		# If the log is already open, check if it needs re-opening
		if ($log_open) {
			@stat = stat $log;
			&close_log unless @stat == @log_stat;
		}
		# If significant change, check the battery
		if (defined($lastcharge) && defined($lastpercent) && (($charge ne $lastcharge) || (abs($lastpercent - $percent) > 5))) {
			print STDERR "Think again ...\n" if $trace;
			$prev = $last_bat_inf;
			$this = &bat_info;
			$fullstamp = 1 if $prev ne $this;
		}

		# If already open, or can be opened, log it.
		if ($log_open || &open_log) {
			$writes++;
			print LOG $pend if $pend;
			$pend = '';
			# If the time goes backwards, log the date.
			$fullstamp =1 if $when<$last;
			if ($fullstamp) {
				$bat_inf = &bat_info;
				printf LOG "# %04d/%02d/%02d$bat_inf\n",
		    		$when[$YR]+1900, $when[$MON]+1, $when[$DAY]
			}
			print LOG $msg;
	
			if ($postscript) {
				print LOG $postscript;
				$postscript = '';
			}
			# All done - close the file if nearly finished.
			if ($log_open && ($writes > $max_writes || $percent < $percent_sync)) { 
				&close_log;
			}
		}
	}
}

# Open the log file
sub open_log {
	open(LOG, ">>$log") || return 0;
	$writes = 0;
	$log_open = 1;
	@log_stat = stat LOG;
	return 1;
}

# Close the log file
sub close_log {
	return unless $log_open;
	close(LOG);
	$log_open = 0;
}

# Work out the "mins since midnight" and set up adjustments.
sub when {
	@when		= localtime;
	my($sec)	= $when[$SEC];
	my($when)	= $when[$HR] * 60 + $when[$MIN] + $sec/60;

	# test if not minute aligned ...
	if ($sec) {
		# try to converge on a multiple of the sleep interval
		$mod = ($when * 60) % $sleep;
		$adj = ($mod==0)  ? 0  : ($mod < ($sleep/2)) ?  -1 : 1;
		$when = sprintf("%.2f",  $when);
		print STDERR  "# $sleep,$when->$mod->$adj ($percent,$charge,$when,$samepercent)\n" if $trace;
	}
	return $when;
}


# Read the current info -- /proc/apm or apm|
sub percent {
	my($percent, $charge, $rate) = (-10, '', 0);
	if (open(PROC, $proc)) {
		# AC (on|off)-line, battery status high: 100% (3:20)
		# AC off-line, battery status high: 38.3% (1:27) 18827mW
		$_ = <PROC>; # 1.16 1.2 0x02 CHARGE=0x01 0x00 0x01 100% 98 min
		if (defined($_)) {
		if (/1\.\d+ \S+ 0x\d+ 0x(\d+) 0x\d+ 0x\d+ (\d+)%/) {
			$percent = $2; $charge = ($1 > 0) ? '+' : '-'; }
		elsif (/off-line.*[^\d](\d+\.\d+)%/) { $percent = $1; $charge = '-' }
		elsif (/on-line.*[^\d](\d+\.\d+)%/) { $percent = $1; $charge = '+' }
		elsif (/off-line.*[^\d](\d+)%/) { $percent = $1; $charge = '-' }
		elsif (/on-line.*[^\d](\d+)%/) { $percent = $1; $charge = '+' }
		elsif (/(\d+\.\d+)%/) { $percent = $1; $charge = '?' }
		elsif (/(\d+)%/) { $percent = $1; $charge = '?' }
		if (/ (\d+)mW/) { $rate = $1; }
		}
		close(PROC);
	}
	return ($percent, $charge, $rate);
}

# Catch the TERM signal from "/etc/init.d/cl-apmlog stop" - delete PIDfile.
sub catch_term { 
	$percent = -1;
	$postscript .= "# Daemon $$ exiting as catch_term sent <" . join(", ", @_) . ">\n\n";
	&stamp;
	&close_log;
	unlink $pidfile;
	exit(1);
}

# Catch a HUP signal - close the log
sub catch_hup { 
	$postscript .= "# Daemon $$ closed log as sent <" . join(", ", @_) . ">\n";
	&stamp;
	&close_log;
	$SIG{'HUP'} = "catch_hup";
}

# Try to find info about the battery
sub bat_info {
	# design capacity:         43200 mWh
	# last full capacity:      41288 mWh
	# battery technology:      rechargeable
	# design voltage:          10800 mV
	# design capacity warning: 831 mWh
	# design capacity low:     0 mWh
	# capacity granularity 1:  10 mWh
	# capacity granularity 2:  10 mWh
	# model number:            G71C0000J310
	# serial number:           0000002476
	# battery type:            Li-ION  
	# OEM info:                
	my (@trace) = `TRACE=3 $apmcmd 2>&1`;
	my($res) = '';
	print STDERR "'TRACE=3 $apmcmd' gave: <<@trace>>\n" if $trace;
	for $line (@trace) {
		$_ = $line;
		if (/design capacity:\s+(\d+)/) { $res .= " dc=$1"; }
		elsif (/last full capacity:\s+(\d+)/) { $res .= " lf=$1"; }
		elsif (/model number:\s+(\S+)/) { $res .= " mn=$1"; }
		elsif (/serial number:\s+(\S+)/) { $res .= " sn=$1"; }
		elsif (/battery type:\s+(\S+)/) { $res .= " bt=$1"; }
	}
	$last_bat_inf = $res;
	return $res;
}
