Difference between revisions of "Script for monitoring power consumption"

From ThinkWiki
Jump to: navigation, search
(MoveToCode)
m
 
(One intermediate revision by the same user not shown)
Line 9: Line 9:
 
{{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.}}
 
{{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.}}
  
{{MoveToCode}}
+
==The script==
<pre>
+
{{CodeRef|tp_power_monitor.py}}
#!/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()
 
</pre>
 
  
 
[[Category:Scripts]]
 
[[Category:Scripts]]

Latest revision as of 22:53, 19 August 2006

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.

The script

tp_power_monitor.py (download)