Script for monitoring power consumption

From ThinkWiki
Revision as of 18:59, 15 August 2006 by Matt garman (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The intent of this script is to provide Thinkpad power consumption data. Hopefully the data will allow you to identify areas with higher-than-expected power draw. The ultimate goal of this script is to act as a tool that will ultimately allow you to maximize your battery time under Linux.

The script is written in Python.

ATTENTION!
You will probably need to modify this script to suit your needs. The original script was written using a Thinkpad T43 2668-89U. So, for example, if your particular does not have an ATI video card, then you will definately need to modify the script.
Help needed
The major deficiency with the script, in its current form, is that it does not consolidate the collected data. I would suggest that the "tpPowerMonitorDataSource" class be extended so that instances of that class know how to interpret their own data (from which nice reports can be generated).
NOTE!
Please feel very free to improve/fix this script. My intent for its posting is to make its ownership as public as possible. There's no need to try to E-mail me to validate your changes. If you feel they are in the best interest of the public, just make the changes. The script attempts to employ pre-conditions to intelligently apply functionality only to those laptops that appear to support it. Hopefully, its framework will allow for extension without heavy redesign.
#!/usr/bin/python

# COPYRIGHT (C) 2006 Matthew Garman
# matthew (dot) garman (at) gmail (dot) com
# License: MIT <http://www.opensource.org/licenses/mit-license.php>

import sys, time, getopt, os, re


class tpPowerMonitorDataSource:

    # The name of the file to parse for needed data (data_source_file) OR
    # the name of a command whose output will be read as a file
    # (os.popen(shell_command)).
    #
    # Typically, data_source_file will be something from /proc (or
    # possibly /sys), and shell_command will be a utility+arguments such
    # as "iwconfig eth1 power".
    #
    # Note that you should set EITHER data_source_file OR shell_command,
    # but not both.
    data_source_file    = None
    shell_command       = None

    # The line number of the data file containing our data OR the string
    # that marks the beginning of the line in which we're interested
    # (startswith_string) OR a regular expression to key the data for
    # which we're looking (regexp_pattern).
    line_number         = None
    startswith_string   = None
    regexp_pattern      = None

    # The string that delimits the line containing our data.  This will be
    # used as the parameter to split().  Note that you can leave this as
    # None to split on whitespace.
    split_string        = None

    # A mapping of data names to indices in the split string.
    name_index_map      = dict()

    def __init__(self, file, cmd, line, swstr, pat, splstr, map):
        self.data_source_file    = file
        self.shell_command       = cmd
        self.line_number         = line
        self.startswith_string   = swstr
        self.regexp_pattern      = pat
        self.split_string        = splstr
        self.name_index_map      = map

    def printMembers(self):
        print "\t" + 'self.data_source_file = ' + str(self.data_source_file)
        print "\t" + 'self.shell_command = ' + str(self.shell_command)
        print "\t" + 'self.line_number = ' + str(self.line_number)
        print "\t" + 'self.startswith_string = ' + str(self.startswith_string)
        print "\t" + 'self.regexp_pattern = ' + str(self.regexp_pattern)
        print "\t" + 'self.split_string = ' + str(self.split_string)
        print "\t" + 'self.name_index_map = ' + str(self.name_index_map)

    def readData(self, d):
        # open the data source file or run the command that will produce
        # the data
        file = None
        if self.data_source_file:
            file = open(self.data_source_file, 'r')
        elif self.shell_command:
            file = os.popen(self.shell_command)
        else:
            print 'error: no data source defined'
            self.printMembers()
        # read the contents of the file into a list and close the file
        lines = None
        if file:
            lines = file.readlines()
            file.close()
        else:
            print 'error: data file not opened'
            self.printMembers()
        # now get the line in the file with our data
        line = None
        if lines:
            if None != self.line_number:
                line = lines[self.line_number]
            elif self.startswith_string:
                for l in lines:
                    if l.startswith(self.startswith_string):
                        line = l
                        break
            elif self.regexp_pattern:
                for l in lines:
                    if re.compile(self.regexp_pattern).match(l):
                        line = l
                        break
        else:
            print 'error: no lines in data file'
            self.printMembers()
        # now get the data we want from the line itself
        if line:
            fields = line.split(self.split_string)
            for key in self.name_index_map.keys():
                d[key] = fields[self.name_index_map[key]].strip()
        else:
            print 'error: could not find line with data in file'
            self.printMembers()

# END --- class tpPowerMonitorDataSource


tp_data_sources = [

    tpPowerMonitorDataSource(
        '/proc/cpuinfo',             # data file
        None,                        # shell command
        None,                        # line index
        'cpu MHz',                   # startswith() string
        None,                        # regexp pattern
        ':',                         # split string
        {'proc_cpuinfo_cpu_mhz': 1}  # name-to-index map
    ),

    # http://thinkwiki.org/wiki/Ipw2200#Power_Management
    tpPowerMonitorDataSource(
        None,                          # data file
        '/sbin/iwpriv eth1 get_power', # shell command
        0,                             # line index
        None,                          # startswith() string
        None,                          # regexp pattern
        ':',                           # split string
        {'iwpriv_get_power': 2}        # name-to-index map
    ),

    # http://forums.gentoo.org/viewtopic-t-447841.html
    # see post from ruben on Wed Mar 29, 2006 4:09 am
    tpPowerMonitorDataSource(
        None,
        '/usr/bin/sudo /sbin/hdparm -C /dev/sda',
        None,
        ' drive state is',
        None,
        ':',
        {'hard_drive_power_state': 1}
    ),

    # http://thinkwiki.org/wiki/Thermal_sensors
    tpPowerMonitorDataSource(
        '/proc/acpi/ibm/thermal',
        None,
        0,
        None,
        None,
        None,
        {'ibm_thermal_cpu':     1,
         'ibm_thermal_hdaps':   2,
         'ibm_thermal_pmcia':   3,
         'ibm_thermal_gpu':     4,
         'ibm_thermal_batt_fl': 5,
         'ibm_thermal_batt_br': 7 }
    ),

    # http://mailman.linux-thinkpad.org/pipermail/linux-thinkpad/2006-July/034738.html
    tpPowerMonitorDataSource(
        '/proc/acpi/processor/CPU/power',
        None,
        None,
        'bus master activity',
        None,
        ':',
        {'bus_master_activity': 1}
    ),

    tpPowerMonitorDataSource(
        '/proc/acpi/battery/BAT0/state',
        None,
        None,
        'present rate:',
        None,
        None,
        {'battery0_state_present_rate': 2}
    ),

    tpPowerMonitorDataSource(
        None,
        '/usr/bin/sudo /usr/sbin/radeontool dac',
        0,
        None,
        None,
        None,
        {'radeontool_dac_ext_vga': -1}
    ),

    tpPowerMonitorDataSource(
        None,
        '/usr/bin/sudo /usr/sbin/radeontool light',
        0,
        None,
        None,
        None,
        {'radeontool_light_lcd': -1}
    ),

    # http://forums.gentoo.org/viewtopic-t-343029-highlight-rovclock.html
    tpPowerMonitorDataSource(
        None,
        '/usr/bin/sudo /usr/sbin/rovclock -i',
        None,
        'Core: ',
        None,
        None,
        {'rovclock_gpu_clock': 1,
         'rovclock_mem_clock': 4 }
    ),

    tpPowerMonitorDataSource(
        None,
        '/sbin/iwconfig eth1',
        None,
        None,
        '.*Power Management.*',
        ':',
        {'wireless_power_mgmt_state': 1}
    ),

    tpPowerMonitorDataSource(
        '/proc/loadavg',
        None,
        0,
        None,
        None,
        None,
        {'proc_loadavg_1min': 0,
         'proc_loadavg_5min': 1,
         'proc_loadavg_15min': 2 }
    ),
]
log_data = list()
poll_freq_hz = 1
run_time_sec = 60*60
logfile = None


def collectPowerData():
    global run_time_sec
    global poll_freq_hz
    global log_data
    global logfile
    global tp_data_sources

    log = None
    if logfile:
        log = open(logfile, 'w')
        log.write("time, data\n")
    end_t = time.time()+run_time_sec
    while time.time() < end_t:
        datum = dict()
        for dsrc in tp_data_sources:
            dsrc.readData(datum)
        log_data.append(datum)
        if log: log.write(str(time.time()) + ', ' + str(datum) + "\n")
        time.sleep(1.0/float(poll_freq_hz))
    if log: log.close()


def createStats():
    global log_data
    global logfile
    log = None
    if logfile:
        log = open(logfile, 'a')
    if log: log.close()


def usage():
    print 'Usage: ' + sys.argv[0] + \
        '[-h] [-f freq_hz] [-t time_min] [-l logfile]'
    print "\t-h            Display this help"
    print "\t-f freq_hz    The polling frequency in Hertz, default=" \
        + str(poll_freq_hz)
    print "\t-t time_min   Total poll time in minutes, default=" \
        + str(run_time_sec/60)
    print "\t-l logfile    Write poll data to indicated log file"


def main():
    global poll_freq_hz
    global run_time_sec
    global logfile

    # parse options
    # see: http://docs.python.org/lib/module-getopt.html
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hf:t:l:")
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    for o, a in opts:
        if '-h' == o:
            usage()
            sys.exit()
        if '-f' == o:
            poll_freq_hz = int(a)
        if '-t' == o:
            run_time_sec = int(a)*60
        if '-l' == o:
            logfile = a

    # do stuff
    print 'running for ' + str(run_time_sec/60) + ' minutes'
    print 'poll rate:  ' + str(poll_freq_hz) + ' Hz'
    print 'logfile:    ' + str(logfile)
    collectPowerData()
    print '...done!'
    print 'Data points collected: ' + str(len(log_data))
    sum = 0
    for d in log_data:
        sum += int(d['battery0_state_present_rate'])
    avg = float(sum) / float(len(log_data))
    print 'Average power draw:    ' + str(avg)
    if logfile:     log = open(logfile, 'a')
    if log:
        log.write('Average power draw:    ' + str(avg))
        log.close()


if __name__ == '__main__':
    main()