jradavenport / batlog

1 Year of MacBook Air Battery Logs
BSD 2-Clause "Simplified" License
189 stars 35 forks source link

More information, normalized data dumping, handy view commands #2

Open jakl opened 11 years ago

a1291762 commented 11 years ago

Ew! Thou shalt not invoke ioreg and perl 13 times per minute in the name of statistics (along with even more invocations of the slow, giant shell known as bash).

A single invocation of ioreg is enough (the output can be cached in memory easily enough, even in shell). All your bash scripts can be rewritten as shell functions inside battest.sh and the perl... well you should really avoid that if you're trying to do all this in shell, else you may as well rewrite everything in perl to avoid calling out to the various utility programs you're using now.

Here's what I got without actually rewriting everything in perl...

#!/bin/bash

BATDIR="$( cd "$( dirname "$0" )" && pwd )"

thevalue=
value()
{
    if [ -z "$thevalue" ]; then
        thevalue="$(/usr/sbin/ioreg -rk BatterySerialNumber)"
    fi
    eval "echo \"\$thevalue\" | perl -ne 'print \$1 if /\"$1\" = (.+)/'"
}

comma_value()
{
    echo -n ,
    value $1
}

batinfo()
{
    echo kernel,max_charge_new,manufacturer,device,serial,firmware

    uname -r | tr -d '\n'
    comma_value DesignCapacity
    comma_value Manufacturer
    comma_value DeviceName
    comma_value BatterySerialNumber
    comma_value FirmwareSerialNumber
    echo
}

batdata()
{
    # Date in UTC time in case you cross time zones
    TZ=utc date +%Y,%m,%d,%H,%M | tr -d '\n'
    comma_value CycleCount
    comma_value CurrentCapacity
    comma_value MaxCapacity

    # Calcuate battery decay as percentage
    MAX_CHARGE=$(value MaxCapacity)
    MAX_CHARGE_NEW=$(value DesignCapacity)
    echo -n ,
    echo "scale=2; $MAX_CHARGE*100/$MAX_CHARGE_NEW" | bc | tr -d '\n'

    comma_value ExternalConnected
    comma_value IsCharging
    comma_value FullyCharged
    comma_value AvgTimeToFull
    comma_value InstantTimeToEmpty
    comma_value Amperage
    comma_value Voltage
    comma_value Temperature
    echo
}

if [ ! -s "$BATDIR/batinfo.csv" ]
then
    batinfo >> "$BATDIR/batinfo.csv"
fi

HEADERS=year,month,day,hour,minute,cycle,charge,max_charge,decay_percent,plugged_in,charging,charged,charge_time,deplete_time,amps,volts,temperature
if [ ! -s "$BATDIR/batlog.csv" ]
then
    echo $HEADERS >> "$BATDIR/batlog.csv"
fi

batdata >> "$BATDIR/batlog.csv"

Is it faster? You bet.

[thirteen:~/git/batlog] link$ time ./battest.sh 

real    0m0.224s
user    0m0.060s
sys 0m0.169s
[thirteen:~/git/batlog] link$ time ./batfast.sh 

real    0m0.095s
user    0m0.040s
sys 0m0.059s

I'm gonna re-do it in perl now.

a1291762 commented 11 years ago

So is an all-perl script even faster. Oh yeah.

[thirteen:~/git/batlog] link$ time ./batlog

real    0m0.034s
user    0m0.016s
sys 0m0.017s

Here's the perl version.

#!/usr/bin/perl

use strict;
use warnings;

use File::Basename;
use POSIX qw(uname strftime);

my $BATDIR = dirname($0);

my $batinfo = "$BATDIR/batinfo.csv";
my $batdata = "$BATDIR/batlog.csv";

my @ioreg = split /\n/, `/usr/sbin/ioreg -rk BatterySerialNumber`;

batinfo() unless (-s $batinfo);

unless (-s $batdata) {
    open BATDATA, '>', $batdata or die "Can't write $batdata";
    print BATDATA "year,month,day,hour,minute,cycle,charge,max_charge,decay_percent,plugged_in,charging,charged,charge_time,deplete_time,amps,volts,temperature\n";
    close BATDATA;
}
batdata();

exit;

sub ioreg_value
{
    my ($arg) = @_;
    for (@ioreg) {
        if (/"$arg" = (.+)/) {
            return $1;
        }
    }
    die "Couldn't find value $arg";
}

sub batinfo
{
    open BATINFO, '>', $batinfo or die "Can't write $batinfo";
    print BATINFO "kernel,max_charge_new,manufacturer,device,serial,firmware\n";

    my @data;
    my ($os, $host, $kernel, $date, $arch) = uname();
    push(@data, $kernel);
    push(@data, ioreg_value('DesignCapacity'));
    push(@data, ioreg_value('Manufacturer'));
    push(@data, ioreg_value('DeviceName'));
    push(@data, ioreg_value('BatterySerialNumber'));
    push(@data, ioreg_value('FirmwareSerialNumber'));
    print BATINFO join(",", @data)."\n";
    close BATINFO;
}

sub batdata
{
    open BATDATA, '>>', $batdata or die "Can't append $batdata";

    my @data;
    # Date in UTC time in case you cross time zones
    push(@data, strftime "%Y,%m,%d,%H,%M", gmtime);
    push(@data, ioreg_value('CycleCount'));
    push(@data, ioreg_value('CurrentCapacity'));
    my $maxcap = ioreg_value('MaxCapacity');
    my $descap = ioreg_value('DesignCapacity');
    push(@data, $maxcap);
    # Calcuate battery decay as percentage
    my $decay = $maxcap * 100 / $descap;
    push(@data, sprintf('%0.2f', $decay));
    push(@data, ioreg_value('ExternalConnected'));
    push(@data, ioreg_value('IsCharging'));
    push(@data, ioreg_value('FullyCharged'));
    push(@data, ioreg_value('AvgTimeToFull'));
    push(@data, ioreg_value('InstantTimeToEmpty'));
    push(@data, ioreg_value('Amperage'));
    push(@data, ioreg_value('Voltage'));
    push(@data, ioreg_value('Temperature'));
    print BATDATA join(",", @data)."\n";
    close BATDATA;
}
jakl commented 11 years ago

Wow this is really awesome work, thanks! I hope this request is accepted.

samwyse commented 11 years ago

Or, just use egrep to grab as many stats as you need with one invocation. See my bug report. If you are concerned about bash being big and heavy, you could use csh instead, but in any event I prefer to use a really simple data collector and do the heavy parsing only when a report is needed.

jakl commented 11 years ago

That's a good approach for sure. Reflecting on my own off-hand assumptions, I predicted:

samwyse commented 11 years ago

I'm not sure what you mean by "installing dependencies". On my Mac (and every Unix system I've ever used), egrep is part of the standard installation, right alongside grep. And looking just now, I also see z- and bz- variants of all three, which I never knew existed! Cool!

As for viewing the data, the collection script will run maybe 1200 times a week (assuming you have your laptop turned on four hours a day, five days a week). I prefer to only start up Perl when I want to view the data as I believe it will save power in the long run. But everyone's situation is different, so feel free to disagree.

jakl commented 11 years ago

Oh I assumed csh was through brew - didn't know it's standard. Neat!

As far as energy use of this cron, I have faith it uses a negligible amount compared to everything else competing for my battery. It wouldn't hurt to run it every 10+ minutes though; every minute shows unnecessary jittery noise for me.

time ./battest.perl
real    0m0.036s
user    0m0.019s
sys     0m0.015s

Laptop Power Usage from Lifehacker:

power usage

Chart's similar to my droid where the screen is the vast majority, then games, then a long tail of everything else.