pwrkap-7.30/000077500000000000000000000000001114434640500127515ustar00rootroot00000000000000pwrkap-7.30/Makefile000066400000000000000000000015501114434640500144120ustar00rootroot00000000000000# Main build script # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. include Rules.mk SUBDIRS=data bin pwrkap docs man ALL_SUBDIRS=$(addsuffix _all,$(SUBDIRS)) CLEAN_SUBDIRS=$(addsuffix _clean,$(SUBDIRS)) INSTALL_SUBDIRS=$(addsuffix _install,$(SUBDIRS)) .PHONY: $(SUBDIRS) $(ALL_SUBDIRS) $(CLEAN_SUBDIRS) $(INSTALL_SUBDIRS) all: $(ALL_SUBDIRS) $(ALL_SUBDIRS): $(MAKE) all -C $(subst _all,,$@) clean: $(CLEAN_SUBDIRS) $(CLEAN_SUBDIRS): $(MAKE) clean -C $(subst _clean,,$@) install: $(INSTALL_SUBDIRS) $(INSTALL_SUBDIRS): $(MAKE) install -C $(subst _install,,$@) dist: @if test "`git describe`" != "$(PWRKAP_VERSION)" ; then \ echo 'Update PWRKAP_VERSION in the Rules.mk file before running "make dist".' ; \ exit 1 ; \ fi git archive --format=tar --prefix=pwrkap-$(PWRKAP_VERSION)/ HEAD^{tree} | gzip -9 > pwrkap-$(PWRKAP_VERSION).tar.gz pwrkap-7.30/Rules.mk000066400000000000000000000002611114434640500143730ustar00rootroot00000000000000# Build script variables # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. PWRKAP_VERSION=7.30 PWRKAP_DATE=2009-01-12 PREFIX?=/usr/local INSTALL=/usr/bin/install pwrkap-7.30/bin/000077500000000000000000000000001114434640500135215ustar00rootroot00000000000000pwrkap-7.30/bin/Makefile000066400000000000000000000014361114434640500151650ustar00rootroot00000000000000# Main build script # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. include ../Rules.mk BUILD_FILES=pwrkap_aggregate pwrkap_cli pwrkap_gtk pwrkap_main all: $(BUILD_FILES) clean:; rm -rf $(BUILD_FILES) pwrkap_aggregate: pwrkap.sh.template sed -e 's|%PREFIX%|$(PREFIX)|g' -e 's/%PROGRAM%/$@/g' < $^ > $@ chmod a+x $@ pwrkap_cli: pwrkap.sh.template sed -e 's|%PREFIX%|$(PREFIX)|g' -e 's/%PROGRAM%/$@/g' < $^ > $@ chmod a+x $@ pwrkap_gtk: pwrkap.sh.template sed -e 's|%PREFIX%|$(PREFIX)|g' -e 's/%PROGRAM%/$@/g' < $^ > $@ chmod a+x $@ pwrkap_main: pwrkap.sh.template sed -e 's|%PREFIX%|$(PREFIX)|g' -e 's/%PROGRAM%/$@/g' < $^ > $@ chmod a+x $@ install: all mkdir -p $(INST_PREFIX)$(PREFIX)/bin $(INSTALL) $(BUILD_FILES) -o root -g root -t $(INST_PREFIX)$(PREFIX)/bin pwrkap-7.30/bin/pwrkap.sh.template000066400000000000000000000002661114434640500171770ustar00rootroot00000000000000#!/bin/sh # Start a pwrkap program # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. export PYTHONPATH=%PREFIX%/lib/pwrkap exec python "$PYTHONPATH/%PROGRAM%.py" $* pwrkap-7.30/data/000077500000000000000000000000001114434640500136625ustar00rootroot00000000000000pwrkap-7.30/data/Makefile000066400000000000000000000012101114434640500153140ustar00rootroot00000000000000# Main build script # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. include ../Rules.mk BUILD_FILES=pwrkap.desktop OTHER_FILES=pwrkap_aggregate.conf ui.glade pwrkap.svg INSTALL_FILES=$(BUILD_FILES) $(OTHER_FILES) pwrkap.desktop: pwrkap.desktop.template sed -e 's|%PREFIX%|$(PREFIX)|g' < $^ > $@ all: $(INSTALL_FILES) clean: install: $(INSTALL_FILES) mkdir -p $(INST_PREFIX)$(PREFIX)/share/pwrkap $(INST_PREFIX)$(PREFIX)/share/applications $(INSTALL) $(OTHER_FILES) -o root -g root -m 644 -t $(INST_PREFIX)$(PREFIX)/share/pwrkap $(INSTALL) pwrkap.desktop -o root -g root -m 644 -t $(INST_PREFIX)$(PREFIX)/share/applications pwrkap-7.30/data/pwrkap.desktop.template000066400000000000000000000003711114434640500203740ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=pwrkap Comment=pwrkap GTK client Comment[fr]=Un client GTK pour pwrkap Comment[es]=Un cliente GTK para pwrkap Exec=pwrkap_gtk Terminal=false Icon=%PREFIX%/share/pwrkap/pwrkap.svg Categories=GNOME;GTK;Utility; pwrkap-7.30/data/pwrkap.svg000066400000000000000000001121141114434640500157070ustar00rootroot00000000000000 image/svg+xml System Monitor Rodney Dawes system monitor performance Lapo Calamandrei pwrkap-7.30/data/pwrkap_aggregate.conf000066400000000000000000000003061114434640500200420ustar00rootroot00000000000000domain 0.0.0.0 9410 agg0 consists of: system 9.47.66.254 9000 pwrdom0 system 9.47.66.254 9001 pwrdom0 system 9.47.66.254 9002 pwrdom0 system 9.47.66.254 9003 pwrdom0 system 9.47.66.254 9004 pwrdom0 pwrkap-7.30/data/ui.glade000066400000000000000000000472021114434640500153020ustar00rootroot00000000000000 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 700 500 pwrkap.svg 640 480 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 228 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 13 1 1 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 12 13 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 2 13 9 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 4 5 GTK_EXPAND GTK_EXPAND True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Energy Use: 4 5 GTK_EXPAND GTK_EXPAND True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False False 11 True False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True 1 1 10000000 1 10 10 True True GTK_UPDATE_IF_VALID False 1 90 True False True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK _Set Cap True 0 False False 2 1 2 2 3 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 1 2 GTK_EXPAND True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Utilization: GTK_JUSTIFY_RIGHT True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Power Use: 1 2 GTK_EXPAND True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Power _Cap: True GTK_JUSTIFY_RIGHT power_cap_field 2 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Discard samples _after: True GTK_JUSTIFY_RIGHT max_sample_age 3 4 True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 5 Minutes 15 Minutes 30 Minutes 45 Minutes 1 Hour 2 Hours 6 Hours 1 Day 1 2 3 4 False False 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Cap exceeded by %dW!</b> True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Show _Graph of Power Use True 0 True False 1 False False 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Close 0 False False 4 True True pwrkap-7.30/docs/000077500000000000000000000000001114434640500137015ustar00rootroot00000000000000pwrkap-7.30/docs/COPYING000066400000000000000000000431031114434640500147350ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. pwrkap-7.30/docs/DESIGN.template000066400000000000000000000310551114434640500164130ustar00rootroot00000000000000pwrkap -- Energy Use Monitor and Power Cap Enforcement Tools version %VERSION% (%DATE%) Written by Darrick J. Wong. (C) Copyright IBM Corp. 2008-2009 This software is covered under the GNU GPL v2; see COPYING for details. Overview -------- This document attempts to describe the structure of the the pwrkap software. There are two big parts to this program--device and power meter enumeration and the grouping of those devices into power domains; and the part that builds a table describing the effects of device power-state changes on the power consumption of that power domain. Power Meters ------------ All power meter objects must implement the methods described in the power_meter class in pwrkap_data.py. For a reference implementation, see syfs_meter.py. Obviously, power meter driver implementations will vary. Managed Devices --------------- All devices that are to be managed by pwrkap must implement the methods of the device class in pwrkap_data.py. For a reference implementation, see the file cpu_device.py. Obviously, device driver code will vary. Discovery --------- Code module, power meter, device and power domain discovery are all handled through discovery.py. Discovery for pwrkap is done in a much different way than it has been done in the past. All code modules to be loaded should be listed in PWRKAP_DRIVERS; every module listed in that array is imported when the program starts. Each code module should have a non-indented code snippet that adds discovery functions to the lists PWRKAP_DEVICE_DISCOVERY, PWRKAP_METER_DISCOVERY, or PWRKAP_POWER_DOMAIN_DISCOVERY. When discovery is done, all functions registered in those lists will be called in succession. First devices are discovered, then power meters, and finally power domains are created to map power meters to the devices that the meter measures. Device discovery has two parts. First, the devices should be enumerated and put into PWRKAP_DEVICES. Second, device control domain information must be discovered. This means that all devices with their own set of controls should be put into a device_domain, one domain per device. For sets of devices that share the same controls, all the devices should be put into one device_domain. Finally, all device_domain objects should be put into the PWRKAP_DOMAIN_DEVICES list. Power meters are discovered and should be put into PWRKAP_POWER_METERS. Energy meters are discovered and should be put into PWRKAP_ENERGY_METERS. Finally, meters and device control domains are united in the power domain discovery function. Each power domain is created with links to three data structures--the list of device_domain objects that are bound to the power meter, something called an identical device profile domain, and the power meter itself. The fourth argument is the initial power cap for the domain. All power domains should be put in PWRKAP_POWER_DOMAINS; all devices, device control domains, and meters claimed by the power domain should be removed from PWRKAP_DEVICES, PWRKAP_DEVICE_DOMAINS, PWRKAP_ENERGY_METERS, and PWRKAP_POWER_METERS, respectively. The identical device profile domain, or idomain, identifies devices that have identical power use profiles. This enables some extra flexibility in both the training program as well as the cap enforcement algorithm. Instead of having to iterate through all power states of all devices in a domain for training, the program uses the idomain data to test one device in the idomain and apply its observations to all other devices in the idomain. This drastically reduces training time as well as shrinking the size of the power use effects table. Please also note that even a device with a unique profile needs to be placed in an idomain by itself. The power domain discovery code are critical to correct operation of pwrkap! Currently, there are two discovery paths--one for certain IBM systems, and a simple one that lumps everything it finds into one power domain provided there is only one power meter. Systems with multiple domains will need to provide their own power domain discovery logic. Inventories and Snapshots ------------------------- Nearly all the data objects involved with pwrkap have two methods that have not yet been discussed--inventory() and snapshot(). These two functions are described below. However, a discussion of call flow may be useful. Generically, the inventory() function returns a description of the static characteristics of the system--a hardware identifier of the power meter, the list of supported CPU frequency states, etc. It is assumed that changes in inventory do not happen within the life of the daemon; thus, a change of this sort should result in the daemon discarding all training data and starting anew. Typically this data are used to estabish machine capabilities. The snapshot() function, then, returns a picture of dynamic system state at the time of invocation. These items should be fairly volatile and are used to compute the current state of the machine and where it should go next. pwrkap's controller object (discussed later) will call the inventory() or snapshot() methods of power domain objects; it is the duty of these objects to make the appropriate calls to the power meter and the control domain objects; the control domain objects will (eventually) call the methods in the device drivers. After that, the power domain object will compile the subordinate objects' inventory or snapshot data into a single report and return it. Relating Power Use to Device Utilization and Power States --------------------------------------------------------- The heart of pwrkap is a four-dimensional table that enables pwrkap to guess what kind of impact a change in a device's power state will have on the power domain's power use. This large-ish array is indexed like this: transition_table[idomain][dev_use][p_state][p_state] = power_change The idomain field describes an identical device profile domain as outlined above. The dev_use index cuts the rest of the table into utilization buckets, because changes between power states of certain devices (CPUs in particular) have different effects on power use depending on the device's utilization. The last two fields are used to index the current power state of the device and a proposed new power state. The value stored in the table is the average impact on power use given the four indexing factors. This table can contain empty cells. By convention, the two p-state indices are always ordered with the lower of the two coming first, as it is assumed that transitions are commutative, i.e. a->b => c and b->a => -c. Training -------- In the event that pwrkap finds itself lacking data relating power use to device utilization and power state, a training algorithm is needed to observe the necessary data to enforce the cap effectively. The code to do this can be found in transitions.py. The training algorithm is quite simple: 1. Load down the system so that the "100% Utilization" buckets are filled. This probably requires outside attention. 2. Set all devices to their lowest power state. This is done partly to avoid overloading the power mains and partly to work around cpufreq bugs in Linux. 3. In each power domain, identify one device from each idomain. 4. For all possible combinations of device and power state, take a snapshot of the power domain and process the snapshot. See Processing a Snapshot below for details. When the daemon is offline, the power domains and their transition tables are written to disk via python's cPickle mechanism. When the program loads, the inventory of the saved data is compared to what is discovered; if there is a mismatch, the saved data are discarded and the training algorithm is started. Processing a Snapshot --------------------- When snapshots are taken, either during training or during normal operation of the pwrkap daemon, it is useful to augment the transition database with the data that are being collected, thereby enabling the software to adapt to an environment that changes over time. Each power domain remembers the past few snapshots that were taken of that domain. When a new snapshot is taken, it is compared to every snapshot currently in the retention buffer. If it can be shown that the only difference between the old and new snapshots is a change in power state one device in an idomain, the differences in states and in power use are noted in the transition table. Enforcing Caps -------------- Each power domain gets its own thread to run a control loop. The operation of this control loop is as follows: 1. No more than once every MEASUREMENT_PERIOD seconds, take a snapshot of the power domain. 2. If less than ENFORCEMENT_INTERVAL seconds have passed since the last enforcement attempt, go to sleep and restart at step 1. 3. Compute the difference between the domain's power cap and power use. 4. If use is less than cap, find all transitions that increase performance. If use is more than cap, find all transitions that decrease use. if they are equal, go back to step 1. 5. Find the transition with the most positive performance increase for the most negative increase in power use. 6. Implement the power state change specified by the transition and return to step 3. Talking to Clients ------------------ Communication with the pwrkap client is achieved through three components-- controller, sockmux and lazy_log. The controller accepts incoming commands from sockmux and modifies the power domains as necessary. The lazy log receives new snapshots from the domains as they run through their control loops. While forwarding new log entries to the sockmux, the lazy log also retains the last few log entries, which are sent to new clients. Finally, the sockmux dispatches incoming commands to the controller, sends data from the lazy log to all clients, and deals with the underlying socket plumbing. Communications across sockets are done with Python objects in cPickle format. When a client first connects to a pwrkap server, it is sent a list of power domain inventories and the log entries retained in the lazy log. The pwrkap Client ------------------- The pwrkap client connects to the server and receives a list of power domain inventories and recent log entries. After that, the client should listen for incoming power domain snapshots; it may also send commands to the pwrkap daemon. The CLI client dumps all incoming data to the console in raw format. Anything typed into it is cPickle'd and send to the pwrkap server. The GTK client is a bit more sophisticated. In addition to being able to watch multiple pwrkap daemons simultaneously, it employs GTK controls to switch the view between power domains (or aggregates of power domains). It also interfaces with matplotlib to draw a graph of the past few minutes' of power cap, power use, and overall domain utilization. Most of the client code is fairly straightforward and not worth mentioning here, with the exception of the domain aggregator. Domain Aggregator ----------------- The domain aggregator (found in ui_data.py) is used by the GTK client to roll the power cap, use and domain utilization data of many domains into a single report. It can also be used to set the power cap of a large number of machines; in that case, the power cap of each machine is scaled up or down to maintain the same proportion between domain power cap and overall power cap. To aggregate data for graphing, the historical data of several power domains must be blended together. Individual samples are taken in timestamp order from each domain; the graphing interval is then split into small ranges of time. For each time range, the power use and cap of each domain are added and the utilizations are averaged to create the aggregate's profile for that time stamp. This much smaller set of data is then graphed. Wire Protocols -------------- The wire protocol in use between the server and client software is a simple one; both encode python objects in pickle format and send that across the wire. The handshake protocol looks approximately like this: 1. Management client connects to pwrkap daemon. 2. pwrkap daemon sends a list of tuples of the format (domain name, inventory). 3. pwrkap daemon replays the last few log entries one by one. Log entries are of the format (UTC timestamp, (domain name, snapshot)) 4. pwrkap daemon sends (UTC timestamp, "live") to indicate that all data after this point are live. The server's UTC timestamp can be used to synchronize with the client's clock, since all log entries have UTC timestamps. 5. pwrkap daemon sends live snapshots of the form (UTC timestamp, (domain name, snapshot)) 6. Client software can send commands as an array of strings at any time. pwrkap-7.30/docs/Makefile000066400000000000000000000011061114434640500153370ustar00rootroot00000000000000# Main build script # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. include ../Rules.mk BUILD_FILES=README DESIGN all: $(BUILD_FILES) clean:; rm -rf *.pyc *.pyo pwrkap*.bin $(BUILD_FILES) DESIGN: DESIGN.template sed -e 's/%VERSION%/$(PWRKAP_VERSION)/g' -e 's/%DATE%/$(PWRKAP_DATE)/g' < $^ > $@ README: README.template sed -e 's/%VERSION%/$(PWRKAP_VERSION)/g' -e 's/%DATE%/$(PWRKAP_DATE)/g' < $^ > $@ install: all mkdir -p $(INST_PREFIX)$(PREFIX)/share/pwrkap $(INSTALL) COPYING DESIGN README -o root -g root -m 644 -t $(INST_PREFIX)$(PREFIX)/share/pwrkap pwrkap-7.30/docs/README.template000066400000000000000000000124001114434640500163700ustar00rootroot00000000000000pwrkap -- Energy Use Monitor and Power Cap Enforcement Tools version %VERSION% (%DATE%) Written by Darrick J. Wong. (C) Copyright IBM Corp. 2008-2009 This software is covered under the GNU GPL v2; see COPYING for details. Overview -------- pwrkap is a set of utilities that attempt to enforce an upper limit on the amount of power consumed by a set of devices at any given time. The intent of the program is ensure that *power* use does not go above a given level; it is NOT to optimize the amount of *energy* consumed for a particular workload, though it may or may not end up doing that too. There are two components to this program--a self-training daemon that manages power domains and a GUI program (that can run from a separate monitoring station) that plots the power cap and use over time to show the effects of the program. Requirements ------------ Your computer needs to have at least one power meter and some devices with power management capabilities. Obviously, the power meter should measure the devices' power consumption. A few more specifications: The systems that you want to run the scripts on... ...must have Python 2.4 or newer. ...must have python bindings for GTK+ and matplotlib installed if you want to use the GUI program. The power meter... ...must be readable by a user-space app. Currently, pwrkap supports the IPMI sensor interface via ipmitool and the IBM PowerExecutive sensors via the ibmpex/ibmaem hwmon drivers. The software can also use discharging ACPI batteries as a makeshift power meter, though this has not been as well tested. Support for more devices should not be too difficult to add, but the author does not have any such devices. ...must not be too terribly slow to read. The software was written under the assumption that a power meter can be read in under 15 seconds. Power managed devices... ...must be controllable from user-space. At the moment, pwrkap only knows how to talk to CPUs that implement CPU frequency control. There are more devices (network cards, displays, disks, PCI devices, etc) that could be supported. ...must export utilization data. pwrkap's new power allocation algorithm takes device utilization into account when figuring out what to do to try to match the cap. Specific Requirements --------------------- - Red Hat Enterprise Linux 5, SUSE Linux Enterprise Server 10, Ubuntu 8.04. - Linux kernel 2.6 or newer. - CPU that supports CPU frequency changing via sysfs. Installation ------------ # tar -xzf pwrkap.tar.gz # cd pwrkap # make install You may also want to start pwrkap whenever the system is booted. The To run pwrkap when the system is started up, create an init script: * Debian/Ubuntu: 1. Copy debian/init-script from the source tarball to /etc/init.d/pwrkap 2. Run update-rc.d pwrkap defaults to set up the init script * Red Hat/SUSE: 1. Copy redhat/init-script from the source tarball to /etc/init.d/pwrkap 2. Run chkconfig pwrkap on to set up the init script. Usage ----- Upon starting up for the first time, pwrkap does not know what the power use profiles of the devices in the system, and will attempt to train itself. During operation, the pwrkap daemon runs in closed-loop mode, using system state snapshots to supplement the profile seed data. # # pwrkap_main [-d] [-w] -d: Debug mode, i.e. don't run as a daemon -w: Start the web server (runs on port 8036) -f: Simulate a power-managed system The database will be saved in /etc/pwrkap_$HOSTNAME.bin when the program exits. By default, the daemon listens for the pwrkap client on port 9410. To watch and control pwrkap, there are two clients. The first is a command line client that started its life as a debugging tool. As such, it dumps power snapshot data directly to standard output whenever the daemon sends data; its one command is " cap ". If you have a lot of machines, you may want to use the aggregator to reduce the load on the monitoring workstation. The aggregator reads a configuration file to determine how to assemble domains; the format of the config file is: domain $ip_to_listen_on $port_to_listen $name_of_aggregate_domain consists of: system $pwrkap_server $pwrkap_server_port $pwrkap_domain_name Then start the aggregator by running this: $ pwrkap_aggregate config_file You can then connect to the aggregator with a client to monitor the aggregated systems as if they were one system. $ pwrkap_cli machine_name[:port] Command format: $domain_name command args $domain_name is the name of the domain to alter. command = {dump, cap} dump: Dump transition tables to daemon console. cap $new_cap: Set a new power cap. The preferred method of interacting with pwrkap, however, is the gtk client. It can connect to a large number of systems and can plot power cap, power use, and device utilization data. $ pwrkap_gtk machine_name[:port] [machine_name[:port]...] Be forewarned that the graph gets a bit slow if there are a lot of machines connected (>200), so the display of it is disabled by default. If enabled, the graphs show you the last few minutes' worth of power cap, power use, and device utilization. Wrap-Up ------- I hope you enjoy pwrkap! Details about the design and innards are in the file DESIGN. pwrkap-7.30/man/000077500000000000000000000000001114434640500135245ustar00rootroot00000000000000pwrkap-7.30/man/Makefile000066400000000000000000000015711114434640500151700ustar00rootroot00000000000000# Main build script # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. include ../Rules.mk DAEMON_MANPAGES=pwrkap_main.8.gz pwrkap_aggregate.8.gz TOOL_MANPAGES=pwrkap_gtk.1.gz pwrkap_cli.1.gz CONFIGFILE_MANPAGES=pwrkap_aggregate.5.gz BUILD_FILES=$(DAEMON_MANPAGES) $(TOOL_MANPAGES) $(CONFIGFILE_MANPAGES) all: $(BUILD_FILES) clean:; rm -rf $(BUILD_FILES) %.8.gz: %.8 gzip -9 < $^ > $@ %.1.gz: %.1 gzip -9 < $^ > $@ %.5.gz: %.5 gzip -9 < $^ > $@ install: $(BUILD_FILES) mkdir -p $(INST_PREFIX)$(PREFIX)/share/man/man1 $(INST_PREFIX)$(PREFIX)/share/man/man8 $(INST_PREFIX)$(PREFIX)/share/man/man5 $(INSTALL) $(CONFIGFILE_MANPAGES) -o root -g root -t $(INST_PREFIX)$(PREFIX)/share/man/man5 $(INSTALL) $(DAEMON_MANPAGES) -o root -g root -t $(INST_PREFIX)$(PREFIX)/share/man/man8 $(INSTALL) $(TOOL_MANPAGES) -o root -g root -t $(INST_PREFIX)$(PREFIX)/share/man/man1 pwrkap-7.30/man/pwrkap_aggregate.5000066400000000000000000000020441114434640500171240ustar00rootroot00000000000000.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH pwrkap_aggregate 5 "January 2009" Linux "User Manuals" .SH NAME pwrkap_aggregate \- Configuration file for pwrkap_aggregate .SH DESCRIPTION Lines in the configuration file should have this format: .B domain .I listen_interface .I listen_port .I domain_name .B consists of: .B system .I pwrkap_server .I pwrkap_port .I pwrkap_server_domain [more .B domain or .B system lines...] .SH OPTIONS .IP listen_interface The IP address where the aggregate server should listen for clients. .IP listen_port The TCP port where the aggregate server should listen for clients. .IP domain_name The power control domain name that the server should present to clients. .IP pwrkap_server The IP address of a pwrkap server that should be aggregated. .IP pwrkap_port The TCP port of a pwrkap server that should be aggregated. .IP pwrkap_server_domain The domain name of a pwrkap server that should be aggregated. .SH AUTHOR Darrick J. Wong .SH "SEE ALSO" .BR pwrkap_aggregate (8), pwrkap-7.30/man/pwrkap_aggregate.8000066400000000000000000000020101114434640500171200ustar00rootroot00000000000000.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH pwrkap_aggregate 8 "January 2009" Linux "User Manuals" .SH NAME pwrkap_aggregate \- Present a large number of pwrkap servers as if they were one .SH SYNOPSIS .B pwrkap_aggregate .I config_file .SH DESCRIPTION .B pwrkap_aggregate is a daemon that connects to a multitude of pwrkap servers and power control domains, and presents them as if they were one server with a smaller number of domains. By aggregating pwrkap server instances, it is possible to monitor many thousands of servers without overworking the command console. It is also possible to give identical class designations to a group of pwrkap servers and manage them as an aggregate. .SH OPTIONS .IP config_file Text file mapping pwrkap servers to aggregated domains. See .BR pwrkap_aggregate (5) for information about the config file format. .SH AUTHOR Darrick J. Wong .SH "SEE ALSO" .BR pwrkap_gtk (1), .BR pwrkap_cli (1), .BR pwrkap_aggregate (5), .BR pwrkap_main (8) pwrkap-7.30/man/pwrkap_cli.1000066400000000000000000000023031114434640500157370ustar00rootroot00000000000000.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH pwrkap_cli 1 "January 2009" Linux "User Manuals" .SH NAME pwrkap_cli \- Command line monitor and control program for pwrkap .SH SYNOPSIS .B pwrkap_cli .I host:port .SH DESCRIPTION .B pwrkap_cli is a command-line program that connects to a pwrkap server and downloads the hardware configuration and power configuration domain data from the server. Power use, energy use, and system status are dumped to the screen when the server sends a status update, and raw commands can be sent to the server. .SH OPTIONS .IP "host:port" A hostname (or IP address) and a port to which this client should connect. .SH COMMAND FORMAT The command format looks like this: .I domain_name .B [ help | dump | cap .I new_cap .B ] .SH COMMANDS .IP domain_name The name of the power configuration domain that the user wants to control. .IP help Displays a short help screen. .IP dump Displays the current hardware configuration and state of the server. .IP "cap new_cap" Sets a new power cap and reconfigures the hardware to meet the cap. .SH AUTHOR Darrick J. Wong .SH "SEE ALSO" .BR pwrkap_gtk (1), .BR pwrkap_aggregate (8), .BR pwrkap_main (8) pwrkap-7.30/man/pwrkap_gtk.1000066400000000000000000000016421114434640500157620ustar00rootroot00000000000000.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH pwrkap_gtk 1 "January 2009" Linux "User Manuals" .SH NAME pwrkap_gtk \- Graphical monitor and control program for pwrkap .SH SYNOPSIS .B pwrkap_gtk .I host:port [ .I host:port ] .SH DESCRIPTION .B pwrkap_gtk is a GTK graphical program that connects to any number of pwrkap servers, and downloads the hardware configuration and power configuration domain data from each machine. It displays the list of power domains and provides the ability to drill down into each domain to see the current power and energy use of each domain, the historical power use of the domain, and provides a facility to set the domain's power cap. .SH OPTIONS .IP "host:port" A hostname (or IP address) and a port to which this client should connect. .SH AUTHOR Darrick J. Wong .SH "SEE ALSO" .BR pwrkap_cli (1), .BR pwrkap_aggregate (8), .BR pwrkap_main (8) pwrkap-7.30/man/pwrkap_main.8000066400000000000000000000016641114434640500161340ustar00rootroot00000000000000.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH pwrkap_main 8 "January 2009" Linux "User Manuals" .SH NAME pwrkap_main \- Main pwrkap system daemon .SH SYNOPSIS .B pwrkap_main [-d] [-w] [-f] [-t] .I listen_port [ .I log_age ] .SH DESCRIPTION .B pwrkap_main starts the main pwrkap control daemon. This piece of software observes power use for a given hardware configuration and system load, and tries to enforce a power cap by raising or lowering each device's performance capabilities. .SH OPTIONS .IP -d Run in the foreground, for debugging. .IP -w Start web service on port 8036. .IP -f Simulate a system full of power-managed system. .IP -t Do not reuse saved data. .IP listen_port Listen for pwrkap clients on this TCP port. .IP log_age Keep old status data around for .I log_age seconds. .SH AUTHOR Darrick J. Wong .SH "SEE ALSO" .BR pwrkap_gtk (1), .BR pwrkap_cli (1), .BR pwrkap_aggregate (8) pwrkap-7.30/old-debian-files/000077500000000000000000000000001114434640500160475ustar00rootroot00000000000000pwrkap-7.30/old-debian-files/init-script000077500000000000000000000017701114434640500202470ustar00rootroot00000000000000#!/bin/bash # Start and stop pwrkap service # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. ### BEGIN INIT INFO # Provides: pwrkap # Required-Start: # Required-Stop: # Default-Start: S # Default-Stop: # Short-Description: start pwrkap ### END INIT INFO PATH="/sbin:/bin:/usr/sbin:/usr/bin" [ -x /usr/bin/pwrkap_main ] || exit 0 . /lib/lsb/init-functions case "$1" in start) /usr/bin/pwrkap_main ;; stop) ps ax | grep -v grep | grep pwrkap_main.py | while read pid junk; do kill $pid; done ;; restart|force-reload) $0 stop $0 start ;; status) PWRKAPS=`ps ax | grep -v grep | grep pwrkap_main.py -c` if [ $PWRKAPS -lt 1 ]; then echo "pwrkap is not running." elif [ $PWRKAPS -gt 1 ]; then echo "Multiple copies of pwrkap are running. Turn some of them off." else echo "pwrkap is running." fi ;; *) echo "Usage: $0 {start|stop|restart|force-reload|status}" exit 1 ;; esac exit 0 pwrkap-7.30/old-debian-files/postinst000077500000000000000000000002041114434640500176540ustar00rootroot00000000000000#!/bin/bash # Enable pwrkap on bootup # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. update-rc.d pwrkap defaults pwrkap-7.30/old-debian-files/prerm000077500000000000000000000002051114434640500171170ustar00rootroot00000000000000#!/bin/bash # Disable pwrkap on boot. # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. update-rc.d -f pwrkap remove pwrkap-7.30/pwrkap/000077500000000000000000000000001114434640500142555ustar00rootroot00000000000000pwrkap-7.30/pwrkap/Makefile000066400000000000000000000006341114434640500157200ustar00rootroot00000000000000# Main build script # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. include ../Rules.mk BUILD_FILES=ui_controller.py all: $(BUILD_FILES) clean:; rm -rf $(BUILD_FILES) ui_controller.py: ui_controller.py.template sed -e 's|%PREFIX%|$(PREFIX)|g' < $^ > $@ install: all mkdir -p $(INST_PREFIX)$(PREFIX)/lib/pwrkap $(INSTALL) *.py -o root -g root -m 644 -t $(INST_PREFIX)$(PREFIX)/lib/pwrkap pwrkap-7.30/pwrkap/acpi_meter.py000066400000000000000000000037161114434640500167460ustar00rootroot00000000000000#!/usr/bin/python """Read power meters available via ACPI.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import pwrkap_data import datetime import os import dircache import util class acpi_power_meter(pwrkap_data.power_meter): """Driver for battery power meters available via ACPI.""" def __init__(self, sensor_filename): self.sensor_filename = sensor_filename self.latency = None def read(self): def is_discharging(acpi_data): """Return true if the battery is discharging.""" discharge_data = ["charging", "state:", "discharging"] for line in acpi_data: if line == discharge_data: return True return False def extract_rate(acpi_data): """Return discharge rate.""" for line in acpi_data: if line[0] == "present" and line[1] == "rate:": return int(line[2]) / 1000.0 return None try: before = datetime.datetime.utcnow() x = util.read_file_as_array(self.sensor_filename) if x == None: return None if not is_discharging(x): return None return extract_rate(x) finally: after = datetime.datetime.utcnow() if self.latency == None: self.latency = (after - before) else: self.latency = (8 * self.latency + 2 * (after - before)) / 10 def get_latency(self): return self.latency def inventory(self): return ("acpimeter", {"name": self.sensor_filename}) SYSFS_ACPI_DIR = "/proc/acpi/battery/" def acpi_meter_discover(): """Discover ACPI meters.""" global SYSFS_ACPI_DIR os.system("modprobe -q battery") # Now look for /proc/acpi/battery/BAT*/state for hwmon_dev in dircache.listdir(SYSFS_ACPI_DIR): if not hwmon_dev.startswith("BAT"): continue hwmon_dev_dir = SYSFS_ACPI_DIR + hwmon_dev + "/state" meter = acpi_power_meter(hwmon_dev_dir) discovery.PWRKAP_POWER_METERS.append(meter) def acpi_meter_init(): """Set up acpi meter discovery functions.""" discovery.PWRKAP_METER_DISCOVERY.append(acpi_meter_discover) return True acpi_meter_init() pwrkap-7.30/pwrkap/controller.py000066400000000000000000000101371114434640500170140ustar00rootroot00000000000000#!/usr/bin/python """Main controller for pwrkap.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import lazy_log import sockmux import thread import atexit import pwrkap_pickle import sys import traceback import time import cPickle as pickle import datetime import pwrkap_train import http_listener def save_and_exit(): """Tear down control threads and save data.""" print "Saving power use observations..." for dm in discovery.PWRKAP_POWER_DOMAINS: dm.exit_control_loop() pwrkap_pickle.save_domains(discovery.PWRKAP_POWER_DOMAINS) print "...done." class controller: """Main control loop.""" def __init__(self, port, log_age, log_size, start_httpd = False): """Create controller.""" discovery.load_pwrkap_drivers() discovery.discover_devices() discovery.discover_meters() discovery.discover_power_domains() if start_httpd: self.http_listener = http_listener.http_listener(self) self.sockmux = sockmux.sockmux(self, port) lazy_log.logger = lazy_log.lazy_log(self.sockmux, log_age, log_size) self.pwrkap_events = { "dump": self.dump_command, "cap": self.cap_command} def train(self): """Initialize the system with some data.""" # Speed things up if we're using fakedomain if "fake_cpudomain" in discovery.PWRKAP_DRIVERS: pwrkap_train.STABILIZE_TIME = 0 pwrkap_train.MAX_MEASUREMENTS = 10 print "Determining power use profile." pwrkap_train.train() def prepare(self, load_saved = True): """Prepare system for power capping.""" # Read in what we saved last time (if anything) saved_data = None if load_saved: pwrkap_pickle.load_domains(discovery.PWRKAP_POWER_DOMAINS) if saved_data == None: self.train() pwrkap_pickle.save_domains(discovery.PWRKAP_POWER_DOMAINS) else: discovery.PWRKAP_POWER_DOMAINS = saved_data atexit.register(save_and_exit) def run(self): """Run control methods.""" if len(discovery.PWRKAP_POWER_DOMAINS) < 1: print "No power domains found." return # Try to limit stack consumption. try: thread.stack_size(65536) except: pass # Start control methods for dm in discovery.PWRKAP_POWER_DOMAINS: thread.start_new_thread(dm.control_loop, ()) while True: time.sleep(1) def run_cli(self): # Start command line interface data = sys.stdin.readline() while len(data) > 0: components = data.strip().split() self.command(components) data = sys.stdin.readline() def connect(self, socket, queue): """Connect a new client.""" try: self.do_connect(socket, queue) except Exception, e: print e traceback.print_exc(file=sys.stdout) return False return True def do_connect(self, socket, queue): """Really connect a socket.""" # Dump the list of power domains. inventories = [] for dm in discovery.PWRKAP_POWER_DOMAINS: inventories.append(dm.inventory()) pickled = pickle.dumps(inventories, protocol = pickle.HIGHEST_PROTOCOL) queue.append(pickled) # Dump some cached snapshots pickles = lazy_log.logger.dump_log() for apickle in pickles: queue.append(apickle) # Display a "data now live" flag now = datetime.datetime.utcnow() tuple = (now, "live") pickled = pickle.dumps(tuple, protocol = pickle.HIGHEST_PROTOCOL) queue.append(pickled) def command(self, components): """Process commands.""" try: domain_name = components[0] command = components[1] domain = discovery.find_domain_by_name(domain_name) handler = self.pwrkap_events[command] handler(domain, components[2:]) except Exception, e: print e traceback.print_exc() print "Ignoring bogus input '%s'." % components def dump_command(self, domain, args): """Handle a dump command.""" print domain.trans_store.trans_table def cap_command(self, domain, args): """Handle a cap command.""" cap = int(args[0]) if cap < 1: return domain.set_cap(cap) def dump(self): """Dump current state.""" print ("devices", discovery.PWRKAP_DEVICES) print ("pmeters", discovery.PWRKAP_POWER_METERS) print ("emeters", discovery.PWRKAP_ENERGY_METERS) print ("devdomains", discovery.PWRKAP_DEVICE_DOMAINS) print ("pwrdomains", discovery.PWRKAP_POWER_DOMAINS) pwrkap-7.30/pwrkap/cpu_device.py000066400000000000000000000252751114434640500167500ustar00rootroot00000000000000#!/usr/bin/python """Device to represent CPU control.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import os import subprocess import pwrkap_data import util import datetime import dircache SYSFS_CPU_DIR = "/sys/devices/system/cpu/" SYSFS_CPUFREQ_DIR = "/cpufreq/" PROC_CPUINFO = "/proc/cpuinfo" SYSFS_CPU_PACKAGE_ID_FILE = "/topology/physical_package_id" SYSFS_CPU_CPUFREQ_DIR = "cpufreq" SYSFS_CPU_AFFINITY_FILE = "/" + SYSFS_CPU_CPUFREQ_DIR + "/affected_cpus" SYSFS_CPU_RELATED_FILE = "/" + SYSFS_CPU_CPUFREQ_DIR + "/related_cpus" SYSFS_CPU_HOTPLUG_FILE = "/online" SYSFS_CPU_GOVERNOR_FILE = "/" + SYSFS_CPU_CPUFREQ_DIR + "/scaling_governor" DEFAULT_CPU_GOVERNOR = "ondemand" class cpu_device(pwrkap_data.device): """Driver for a real CPU, as controlled by /proc and /sys.""" def __init__(self, id, stat_reader): """Create a CPU device.""" global SYSFS_CPU_DIR, SYSFS_CPUFREQ_DIR self.id = id self.cpufreq_dir = SYSFS_CPU_DIR + "cpu" + str(id) + SYSFS_CPUFREQ_DIR self.stat_reader = stat_reader self.last_util = None self.last_util_time = None self.load_process = None def get_power_states(self): """Return the power states supported by this CPU.""" res = [] states = util.read_line_as_array(self.cpufreq_dir + "scaling_available_frequencies") if states == None: return [] states.sort(cmp = util.cmp_string_as_number) max_state = int(states[len(states) - 1]) for state in states: res.append((int(state), float(state) / max_state)) return res def get_max_power_state(self): """Return the maximum power state.""" data = util.read_line_as_array(self.cpufreq_dir + "scaling_max_freq") return int(data[0]) def set_max_power_state(self, max_pstate): """Set the maximum power state.""" pstates = self.get_power_states() for (pstate, junk) in pstates: if pstate == max_pstate: util.write_file(self.cpufreq_dir + "scaling_max_freq", str(max_pstate)) return True return False def get_current_power_state(self): """Return the current power state.""" data = util.read_line_as_array(self.cpufreq_dir + "scaling_cur_freq") return int(data[0]) def get_power_state_performance_potential(self, state): """Return the performance potential of a given state.""" for (freq, potential) in self.get_power_states(): if freq == state: return potential return None def get_utilization_details(self): """Return details of the device's utilization.""" now = datetime.datetime.utcnow() if self.last_util_time == None or \ self.last_util == None or \ (now - self.last_util_time) > pwrkap_data.ONE_SECOND: (u, i) = self.stat_reader.read(self.id) if u + i == 0: i = 1 pot = self.get_power_state_performance_potential(self.get_current_power_state()) if pot == None: pot = 1.0 self.last_util = (float(u) / (i + u)) * pot self.last_util_time = now return {self.get_name(): self.last_util} def inventory(self): key = self.get_name() obj = {"states": self.get_power_states()} return (key, obj) def get_prefix(self): return "cpu" def get_name(self): return self.get_prefix() + str(self.id) def start_load(self): def try_run_process(args): """Try to run a process.""" try: self.load_process = subprocess.Popen(args) except: return False return True if self.load_process != None: return True if try_run_process("burnP6"): return True if try_run_process(["dd", "if=/dev/zero", "of=/dev/null", "bs=1"]): return True return False def stop_load(self): if self.load_process == None: return os.kill(self.load_process.pid, 9) self.load_process.wait() self.load_process = None PROC_CPUINFO = "/proc/cpuinfo" class fixed_cpu_device(cpu_device): """Driver for a fixed-frequency CPU, as controlled by /proc.""" def __init__(self, id, stat_reader): """Create a CPU device.""" def extract_cpu_speed(data): """Figure out the CPU's speed.""" # Skip down to the processor entry for line in data: if len(line) > 2 and line[0] == "procesor" and int(line[2]) == id: break; for line in data: if len(line) > 3 and line[0] == "cpu" and line[1] == "MHz": return int(float(line[3]) * 1000) return None global PROC_CPUINFO cpu_device.__init__(self, id, stat_reader) self.speed = extract_cpu_speed(util.read_file_as_array(PROC_CPUINFO)) def get_power_states(self): """Return the power states supported by this CPU.""" return [(self.speed, 1.0)] def get_max_power_state(self): """Return the maximum power state.""" return self.speed def set_max_power_state(self, max_pstate): """Set the maximum power state.""" if max_pstate == self.speed: return True return False def get_prefix(self): return "fixedcpu" def get_current_power_state(self): """Return the current power state.""" return self.speed PROC_STAT_UPDATE_INTERVAL = 500 class proc_stat_reader: """Common reader to cache accesses to /proc/stat.""" def __init__(self): self.old_lines = None self.lines = None self.next_update = None self.update() self.update() def update(self): """Update data.""" global PROC_STAT_UPDATE_INTERVAL self.old_lines = self.lines self.lines = util.read_file_as_array("/proc/stat") x = datetime.datetime.utcnow() self.next_update = x + datetime.timedelta(milliseconds = PROC_STAT_UPDATE_INTERVAL) def read(self, cpu): """Read usage data for a given CPU.""" def find_cpu_use(lines, cpukey): """Find the array corresponding to a CPU.""" for line in lines: if line[0] == cpukey: return line return None now = datetime.datetime.utcnow() if now > self.next_update: self.update() cpukey = "cpu" + str(cpu) old = find_cpu_use(self.old_lines, cpukey) new = find_cpu_use(self.lines, cpukey) assert len(old) == len(new) # Column 4 is idle time; the rest indicate use. use = 0 idle = 0 for i in range(1, len(old)): if i == 4: idle = int(new[i]) - int(old[i]) else: use = use + int(new[i]) - int(old[i]) return (use, idle) def cpus_reset(): """Bring all CPUs online and set them to default cpufreq control.""" global SYSFS_CPU_DIR, SYSFS_CPU_HOTPLUG_FILE, SYSFS_CPU_GOVERNOR_FILE, DEFAULT_CPU_GOVERNOR for cpu in dircache.listdir(SYSFS_CPU_DIR): if not cpu.startswith("cpu"): continue try: util.write_file(SYSFS_CPU_DIR + cpu + SYSFS_CPU_HOTPLUG_FILE, "1") util.write_file(SYSFS_CPU_DIR + cpu + SYSFS_CPU_GOVERNOR_FILE, DEFAULT_CPU_GOVERNOR) except: pass def get_proc_cpu_info(cpuid, attributes): """Search /proc/cpuinfo for data about a particular CPU.""" global PROC_CPUINFO proc_cpuinfo = util.read_file_as_array(PROC_CPUINFO, ":") # Find start of this cpu's section cpu_start = None for i in range(0, len(proc_cpuinfo)): if len(proc_cpuinfo[i]) < 2: continue if proc_cpuinfo[i][0].strip() == "processor" and \ int(proc_cpuinfo[i][1]) == cpuid: cpu_start = i break if cpu_start == None: return {} # Now loop through attributes res = {} for i in range(cpu_start + 1, len(proc_cpuinfo)): key = proc_cpuinfo[i][0].strip() if key == "processor": break if key in attributes: res[key] = proc_cpuinfo[i][1].strip() return res seen_cpu_affinity_bug = False def get_cpu_affinities(cpuid): """Determine CPU affinity data.""" global SYSFS_CPU_DIR, SYSFS_CPU_AFFINITY_FILE, SYSFS_CPU_PACKAGE_ID_FILE global seen_cpu_affinity_bug, SYSFS_CPU_RELATED_FILE set_all = False # Grab data from cpufreq sysfs attributes affinity = util.read_line_as_array(SYSFS_CPU_DIR + "cpu" + \ str(cpuid) + \ SYSFS_CPU_RELATED_FILE) if affinity == None: affinity = util.read_line_as_array(SYSFS_CPU_DIR + "cpu" + \ str(cpuid) + \ SYSFS_CPU_AFFINITY_FILE) # Errata report: If we have a Core2 (family 6, model 15+) system with # no coordination reported via sysfs, we have to calculate our own # affinity data because the kernel misreports hw coordinated cores as # if they were totally independent (there are only half as many power # planes as cores). For now we'll simply assume that all cores on a # package must run at the same speed--for quad-cores this isn't true; # they're dual dual-cores and if we could figure out which cores map # to which planes we could enable more fine-toothed control. cpuinfo = get_proc_cpu_info(cpuid, ["cpu family", "model"]) if len(affinity) == 1 and int(cpuinfo["cpu family"]) == 6 and \ int(cpuinfo["model"]) >= 15: if not seen_cpu_affinity_bug: print "Kernel coordination bug found, applying workaround." seen_cpu_affinity_bug = True set_all = True this_cpu_package = util.read_line_as_array(SYSFS_CPU_DIR + "cpu" + \ str(cpuid) + \ SYSFS_CPU_PACKAGE_ID_FILE) affinity = [cpuid] for dir in dircache.listdir(SYSFS_CPU_DIR): if not dir.startswith("cpu"): continue try: other_cpu_id = int(dir[3:]) except: continue if other_cpu_id == cpuid: continue cpu_package = util.read_line_as_array(SYSFS_CPU_DIR + dir + SYSFS_CPU_PACKAGE_ID_FILE) if cpu_package == this_cpu_package: affinity.append(other_cpu_id) # Sort data so that the lowest number CPU comes first affinity.sort(cmp = util.cmp_string_as_number) return (set_all, affinity) def cpu_discover(): """Discover system CPUs.""" global SYSFS_CPU_DIR, SYSFS_CPU_AFFINITY_FILE, SYSFS_CPU_CPUFREQ_DIR psr = proc_stat_reader() cpu_map = {} cpus = [] domains = [] fixed_cpus = [] # Bring all CPUs online cpus_reset() # Find CPUs for cpu in dircache.listdir(SYSFS_CPU_DIR): if not cpu.startswith("cpu"): continue # Check for the presence of "cpufreq" file cpufreq_found = False try: cpu_id = int(cpu[3:]) except: continue for handle in dircache.listdir(SYSFS_CPU_DIR + cpu + "/"): if handle.startswith(SYSFS_CPU_CPUFREQ_DIR): cpufreq_found = True break if not cpufreq_found: cpu_dev = fixed_cpu_device(cpu_id, psr) fixed_cpus.append(cpu_dev) discovery.PWRKAP_DEVICES.append(cpu_dev) continue # Proceed with cpu device construction cpu_dev = cpu_device(cpu_id, psr) cpu_map[cpu_id] = cpu_dev discovery.PWRKAP_DEVICES.append(cpu_dev) cpus.append(cpu_dev) # Map CPU affinities into domains for cpu_dev in cpus: (set_all, affinity) = get_cpu_affinities(cpu_dev.id) if int(affinity[0]) != cpu_dev.id: continue domain_list = [] for cpu_id in affinity: domain_list.append(cpu_map[int(cpu_id)]) domain = pwrkap_data.device_domain(domain_list) domain.must_set_all = set_all discovery.PWRKAP_DEVICE_DOMAINS.append(domain) # Put all the fixed CPUs into a separate domain if len(fixed_cpus) > 0: domain = pwrkap_data.device_domain(fixed_cpus) discovery.PWRKAP_DEVICE_DOMAINS.append(domain) def cpu_init(): """Set up CPU device discovery function.""" discovery.PWRKAP_DEVICE_DISCOVERY.append(cpu_discover) return True cpu_init() pwrkap-7.30/pwrkap/daemon.py000066400000000000000000000024541114434640500160770ustar00rootroot00000000000000#!/usr/bin/python """Helper function to make daemons out of normal processes.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import sys import os import time OUTPUT_FILE = "/dev/null" INPUT_FILE = "/dev/null" def daemonize(): """Fork and detach this process.""" global OUTPUT_FILE, INPUT_FILE #print "parent pid %d\n" % os.getpid() # fork try: pid = os.fork() if pid > 0: #print "parent %d exiting" % os.getpid() sys.exit(0) except OSError, e: print >>sys.stderr, "Fork failed %d (%s)" % (e.errno, e.strerror) sys.exit(1) #print "child1 pid %d" % os.getpid() # detach os.setsid() os.umask(0) # do second fork? try: pid = os.fork() if pid > 0: #print "child1 exiting %d" % os.getpid() sys.exit(0) except OSError, e: print >>sys.stderr, "Fork2 failed %d" % e.errno print "Backgrounding as process %d" % os.getpid() fp1 = file(INPUT_FILE, "rb") if fp1 == None: print "Can't open %s?" % OUTPUT_FILE sys.exit(1) fp2 = file(OUTPUT_FILE, "wb+", 1) if fp2 == None: print "Can't open %s?" % OUTPUT_FILE sys.exit(1) fp3 = file(OUTPUT_FILE, "wb+", 0) if fp3 == None: print "Can't open %s?" % OUTPUT_FILE sys.exit(1) os.dup2(fp1.fileno(), sys.stdin.fileno()) os.dup2(fp2.fileno(), sys.stdout.fileno()) os.dup2(fp3.fileno(), sys.stderr.fileno()) pwrkap-7.30/pwrkap/default_domain.py000066400000000000000000000031121114434640500175770ustar00rootroot00000000000000#!/usr/bin/python """Create power domains for one-meter systems.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import popen2 import discovery import pwrkap_data import sys # Note: This code does not autodetect systems with multiple power domains! def default_system_discover(): """Configure power domain on single-meter systems.""" if len(discovery.PWRKAP_POWER_METERS) > 1 or \ len(discovery.PWRKAP_ENERGY_METERS) > 1 or \ (len(discovery.PWRKAP_POWER_METERS) == 0 and \ len(discovery.PWRKAP_ENERGY_METERS) == 0): return # Take the sensor pmeter = discovery.PWRKAP_POWER_METERS[0] emeter = discovery.PWRKAP_POWER_METERS[0] # No devices? if len(discovery.PWRKAP_DEVICES) == 0: return # Assume all CPUs have identical power curves idomain = [] for device in discovery.PWRKAP_DEVICES: (name, data) = device.inventory() if not name.startswith("cpu"): continue idomain.append(device) if len(idomain) < 1: print "No power-manageable devices found." sys.exit(1) # Take all device domains for this power domain domains = discovery.PWRKAP_DEVICE_DOMAINS # Remove all devices, domains, and meters that we intend to use. discovery.PWRKAP_DEVICES = [] discovery.PWRKAP_DEVICE_DOMAINS = [] discovery.PWRKAP_POWER_METERS = [] discovery.PWRKAP_ENERGY_METERS = [] # Create power domain pd = pwrkap_data.power_domain(domains, [idomain], pmeter, emeter, 1000) discovery.PWRKAP_POWER_DOMAINS.append(pd) def default_init(): """Set up default system discovery functions.""" discovery.PWRKAP_POWER_DOMAIN_DISCOVERY.append(default_system_discover) pwrkap-7.30/pwrkap/discovery.py000066400000000000000000000035021114434640500166360ustar00rootroot00000000000000#!/usr/bin/python """Routines to manage pwrkap device/meter discovery.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import default_domain import traceback import sys # List of drivers and discovery functions PWRKAP_DRIVERS = ["acpi_meter", "sysfs_meter", "cpu_device", "ibm_domain", "ipmi_meter"] PWRKAP_DEVICE_DISCOVERY = [] PWRKAP_METER_DISCOVERY = [] PWRKAP_POWER_DOMAIN_DISCOVERY = [] # List of devices PWRKAP_DEVICES = [] PWRKAP_POWER_METERS = [] PWRKAP_ENERGY_METERS = [] PWRKAP_DEVICE_DOMAINS = [] PWRKAP_POWER_DOMAINS = [] def load_pwrkap_drivers(): """Load all known pwrkap drivers.""" global PWRKAP_DRIVERS for driver in PWRKAP_DRIVERS: __import__(driver) def discover_devices(): """Discover power-managed devices.""" global PWRKAP_DEVICE_DISCOVERY for func in PWRKAP_DEVICE_DISCOVERY: func() def discover_meters(): """Discover power meters.""" global PWRKAP_METER_DISCOVERY for func in PWRKAP_METER_DISCOVERY: try: func() except Exception, e: traceback.print_exc() pass def discover_power_domains(): """Discover power domains.""" global PWRKAP_POWER_DOMAIN_DISCOVERY, PWRKAP_POWER_DOMAINS for func in PWRKAP_POWER_DOMAIN_DISCOVERY: func() default_domain.default_system_discover() if len(PWRKAP_POWER_DOMAINS) < 1: print "No power domains found." sys.exit(1) # Now remove domains that aren't reporting power use dead_domains = [] for pdom in PWRKAP_POWER_DOMAINS: if pdom.get_energy_use() == None or \ pdom.get_power_use() == None: dead_domains.append(pdom) for pdom in dead_domains: PWRKAP_POWER_DOMAINS.remove(pdom) if len(PWRKAP_POWER_DOMAINS) < 1: print "No working power domains found." sys.exit(1) def find_domain_by_name(name): """Find a domain by name.""" for dom in PWRKAP_POWER_DOMAINS: if dom.name() == name: return dom return None pwrkap-7.30/pwrkap/fake_cpudomain.py000066400000000000000000000157021114434640500176010ustar00rootroot00000000000000#!/usr/bin/python """Fake pwrkap driver that simulates CPUs and a power meter for them.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import pwrkap_data import discovery import random import datetime import thread import threading import time import traceback cpu_domains = None cpus = None idomains = None pm = None em = None class fake_energy_meter(pwrkap_data.energy_meter): def __init__(self, pm_devices): self.pm_devices = pm_devices self.power_use = None self.energy_use = None self.last_update = None self.keep_running = True for pmdev in self.pm_devices: assert pmdev.get_prefix() == "fakecpu" # These must run last self.update_energy_use() thread.start_new_thread(self.run, ()) def __del__(self): self.keep_running = False def __setstate__(self, data): self.__dict__ = data self.energy_use = 0 thread.start_new_thread(self.run, ()) def run(self): """Periodically update energy use.""" self.keep_running = True while self.keep_running: try: self.update_energy_use() except Exception, e: print e traceback.print_exc() time.sleep(1) def update_energy_use(self): """Update the energy use counter.""" now = datetime.datetime.utcnow() if self.last_update == None: self.energy_use = 0 self.power_use = 0 self.last_update = now return td = now - self.last_update time_delta = td.microseconds + \ (td.seconds * 1000000) + \ (td.days * 86400000000) if time_delta == 0: time_delta = 1 time_delta = float(time_delta) / 1000000 # Baseline this system uses about 150W. delta_e = 0 #time_delta * (150 + random.randint(-5, 20)) # CPU energy costs are partially based on the current frequency # and partly on the number of clocks used. Scales are designed # such that a 3GHz CPU consumes 120W at full use. for cpu in self.pm_devices: nr_clocks = (0.4 + 0.6 * cpu.get_utilization()) * cpu.get_current_power_state() nr_clocks = nr_clocks * 0.00004 delta_e = delta_e + time_delta * nr_clocks self.energy_use = self.energy_use + delta_e self.power_use = (0.666 * self.power_use) + (0.333 * (delta_e / time_delta)) self.last_update = now def read(self): return self.energy_use def get_latency(self): return 0; def inventory(self): return ("fakemeter", {}) class fake_power_meter(pwrkap_data.power_meter): def __init__(self, energy_meter): self.energy_meter = energy_meter def read(self): return self.energy_meter.power_use def get_latency(self): return 0; def inventory(self): return ("fakemeter", {}) def build_pstate_table(min, max, step): """Build a fake power state table.""" states = [] for mhz in range(min, max + 1, step): states.append((mhz, (1.0 * mhz) / max)) return states MIN_FAKE_CPU_SPEED = 2000000 MAX_FAKE_CPU_SPEED = 4000000 FAKE_CPU_SPEED_STEP = 1000000 class fake_cpu(pwrkap_data.device): def __init__(self, id): global MAX_FAKE_CPU_SPEED self.max_pstate = MAX_FAKE_CPU_SPEED self.id = id self.cps_reads = 1 self.last_cps = MAX_FAKE_CPU_SPEED self.master_cpu = None self.last_util_read_time = datetime.datetime.utcnow() self.last_util_read = 0.5 self.pstates = build_pstate_table( MIN_FAKE_CPU_SPEED, MAX_FAKE_CPU_SPEED, FAKE_CPU_SPEED_STEP ) random.seed() def get_power_states(self): if self.master_cpu != None: return self.master_cpu.get_power_states() return self.pstates def get_max_power_state(self): if self.master_cpu != None: return self.master_cpu.get_max_power_state() return self.max_pstate def set_max_power_state(self, max_pstate): if self.master_cpu != None: return self.master_cpu.set_max_power_state(max_pstate) valid_state = False for (state, potential) in self.pstates: if max_pstate == state: valid_state = True assert valid_state self.max_pstate = max_pstate if self.last_cps > self.max_pstate: self.last_cps = self.max_pstate def find_index_of_pstate(self, pstate): for i in range(0, len(self.pstates)): if self.pstates[i][0] == pstate: return i return None def get_current_power_state(self): if self.master_cpu != None: return self.master_cpu.last_cps self.cps_reads = self.cps_reads + 1 if self.cps_reads > 8 or self.last_cps > self.max_pstate: self.cps_reads = 0 max_pstate = self.find_index_of_pstate(self.get_max_power_state()) pstate = random.randint(0, max_pstate) (speed, junk) = self.pstates[pstate] self.last_cps = speed assert self.last_cps <= self.max_pstate return self.last_cps def get_utilization(self): now = datetime.datetime.utcnow() if (now - self.last_util_read_time) < pwrkap_data.ONE_SECOND: return self.last_util self.last_util = (90.0 + random.uniform(-80, 10)) / 100 self.last_util_read_time = now return self.last_util def get_utilization_details(self): return {self.get_name(): self.get_utilization()} def get_id(self): return self.id def get_name(self): return self.get_prefix() + str(self.id) def inventory(self): key = self.get_prefix() + str(self.id) obj = {"states": self.get_power_states()} return (key, obj) def get_prefix(self): return "fakecpu" def start_load(self): return False def stop_load(self): # XXX Implement me pass def fake_cpu_discover_old(): """Discover fake system CPUs.""" global cpu_domains, cpus, idomains cpu_domains = [] cpus = [] idomains = [[], [], []] # Simulate 4 dual-core CPUs for cpuid in range(0, 4): core0 = fake_cpu(2 * cpuid) core1 = fake_cpu(2 * cpuid + 1) core1.master_cpu = core0 domain = pwrkap_data.device_domain([core0, core1]) cpu_domains.append(domain) cpus.append(core0) cpus.append(core1) idomains[cpuid / 2].append(core0) idomains[cpuid / 2].append(core1) # And a spurious 9th core for fun core0 = fake_cpu(8) domain = pwrkap_data.device_domain([core0]) cpu_domains.append(domain) cpus.append(core0) idomains[2].append(core0) def fake_cpu_discover(): """Discover fake system CPUs.""" global cpu_domains, cpus, idomains cpu_domains = [] cpus = [] idomains = [[]] # Simulate a single quad-core clovertown core0 = fake_cpu(0) core1 = fake_cpu(1) core2 = fake_cpu(2) core3 = fake_cpu(3) core1.master_cpu = core0 core3.master_cpu = core2 domain0 = pwrkap_data.device_domain([core0, core1]) domain1 = pwrkap_data.device_domain([core2, core3]) cpu_domains.append(domain0) cpu_domains.append(domain1) cpus = [core0, core1, core2, core3] idomains = [[core0, core1, core2, core3]] def fake_meter_discover(): """Discover fake power meters.""" global cpus, pm, em em = fake_energy_meter(cpus) pm = fake_power_meter(em) def fake_system_discover(): """Discover fake power domains.""" global cpu_domains, idomains, pm, em for i in range(0, 1): fake_cpu_discover() fake_meter_discover() dm = pwrkap_data.power_domain(cpu_domains, idomains, pm, em, 1000) dm.snapshot() discovery.PWRKAP_POWER_DOMAINS.append(dm) def fake_init(): """Set up discovery functions.""" discovery.PWRKAP_POWER_DOMAIN_DISCOVERY.append(fake_system_discover) return True fake_init() pwrkap-7.30/pwrkap/http_listener.py000066400000000000000000000104771114434640500175240ustar00rootroot00000000000000#!/usr/bin/python """Listen on a TCP/IP port for incoming HTTP requests.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import BaseHTTPServer import thread import discovery from httplib import OK, NOT_FOUND from urlparse import urlparse from cgi import parse_qs from urllib import unquote HOST_NAME = '0.0.0.0' PORT_NUMBER = 8036 XML_ROOT = 'ReturnValues' PWRKAP_CMD = 'setpowercap' PWRKAP_PARAM_OLD= 'pcap' PWRKAP_PARAM = 'pwrkap' PWRDOM_PARAM = 'pwrdom' # The controller variable should be set before starting a new HTTP listener. # The controller contains the method that actually sets a pwrkap. pwrkap_controller = None class http_server(BaseHTTPServer.HTTPServer): """Extends the base HTTP server with the ability to invoke termination.""" def serve_terminable(self): self.terminating = False while not self.terminating: self.handle_request() def server_close(self): BaseHTTPServer.HTTPServer.server_close(self) self.terminating = True class http_handler(BaseHTTPServer.BaseHTTPRequestHandler): """Defines methods to handle HTTP requests.""" def do_HEAD(self): """Respond to an HTTP HEAD request.""" parsedURL = urlparse(self.path) if (unquote(parsedURL.path) != "/" + PWRKAP_CMD): self.send_error(NOT_FOUND, "The specified path or command does not exist.") return self.send_response(OK) self.send_header("Content-type", "text/html") self.end_headers() def do_GET(self): """Respond to an HTTP GET request.""" parsedURL = urlparse(self.path) if (unquote(parsedURL.path) != "/" + PWRKAP_CMD): self.send_error(NOT_FOUND, "The specified path or command does not exist.") return self.send_response(OK) self.send_header("Content-type", "text/xml") self.end_headers() print "Received request from", self.client_address params = parse_qs(parsedURL.query) pwrkap = -1 powerDomainName = "" # Get the specified pwrkap value if it can be obtained from the parameters. # Use old parameter if specified, to avoid breaking older clients if (params.has_key(PWRKAP_PARAM_OLD) and len(params[PWRKAP_PARAM_OLD]) > 0): try: pwrkap = int(params[PWRKAP_PARAM_OLD][0]) except ValueError: pwrkap = -1 # Get the specified pwrkap value if it can be obtained from the parameters. if (params.has_key(PWRKAP_PARAM) and len(params[PWRKAP_PARAM]) > 0): try: pwrkap = int(params[PWRKAP_PARAM][0]) except ValueError: pwrkap = -1 if (pwrkap >= 0): # Get the specified power domain name if it can be obtained from # the parameters. Otherwise, attempt to obtain the first power # domain name that can be found in the discovery module. if (params.has_key(PWRDOM_PARAM) and len(params[PWRDOM_PARAM]) > 0): powerDomainName = params[PWRDOM_PARAM][0] elif (len(discovery.PWRKAP_POWER_DOMAINS) > 0): print "Found at least one entry in PWRKAP_POWER_DOMAINS" powerDomainName = discovery.PWRKAP_POWER_DOMAINS[0].name() print "Power domain name:", powerDomainName if (discovery.find_domain_by_name(powerDomainName) != None): print "Attempting to set pwrkap to", pwrkap, "on power domain", \ powerDomainName, "..." if (pwrkap_controller != None): pwrkap_controller.command([powerDomainName, "cap", pwrkap]) else: print "The pwrkap Controller was not defined. An HTTP", \ "server should only be started via the", \ "http_listener class." pwrkap = -1 else: print "The specified power domain name could not be", \ "identified. Power cap will not be set." pwrkap = -1 self.wfile.write("") self.wfile.write("<" + XML_ROOT + ">") self.wfile.write("<" + PWRKAP_CMD + ">") self.wfile.write("<" + PWRKAP_PARAM + ">" + str(pwrkap) + "") self.wfile.write("<" + PWRDOM_PARAM + ">" + powerDomainName + "") self.wfile.write("") self.wfile.write("\n") class http_listener: """ Defines the HTTP listener object that sets the global pwrkap controller to handle incoming capping requests and starts the HTTP server. """ def __init__(self, controller): global pwrkap_controller pwrkap_controller = controller self.httpd = http_server((HOST_NAME, PORT_NUMBER), http_handler) thread.start_new_thread(self.httpd.serve_terminable, ()) def stop(self): self.httpd.server_close() pwrkap-7.30/pwrkap/ibm_domain.py000066400000000000000000000210021114434640500167200ustar00rootroot00000000000000#!/usr/bin/python """Create power domains for IBM System X machines with PowerExecutive.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import popen2 import time import discovery import pwrkap_data import util import dircache SYSFS_NODE_DIR = "/sys/devices/system/node/" def cmp_meter(x, y): """Compare two meters by sysfs name.""" xi = x.inventory() yi = y.inventory() (xt, xd) = xi (yt, yd) = yi assert xt == "sysfsmeter" and yt == "sysfsmeter" xn = xd["name"][22:] yn = yd["name"][22:] xs = xn.find("/") ys = yn.find("/") assert xs >= 0 and ys >= 0 return util.cmp_string_as_number(xn[:xs], yn[:ys]) def find_device_in_list(list, devname): for listdev in list: x = listdev.inventory() if devname == x[0]: return listdev return None def find_domain_with_dev(domains, dev): for dom in domains: if dev in dom.devices: return dom return None def ibm_aem_discover(): """Configure power domains on AEM systems.""" global SYSFS_NODE_DIR def find_aem_energy_meters(): """Find AEM energy meters.""" meters = [] # energy2 meter is usually more accurate, but if it's # zero then it's not connected for meter in discovery.PWRKAP_ENERGY_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "aem2": continue if data["name"].endswith("energy2_input"): if meter.read() != 0: meters.append(meter) if len(meters) > 0: return meters # Try for energy1 if there aren't any energy2s. for meter in discovery.PWRKAP_ENERGY_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "aem2": continue if data["name"].endswith("energy1_input"): meters.append(meter) return meters def find_aem_power_meters(): """Find AEM power meters.""" meters = [] # power2 meter is usually more accurate, but if it's # zero then it's not connected for meter in discovery.PWRKAP_POWER_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "aem2": continue if data["name"].endswith("power2_average"): if meter.read() != 0: meters.append(meter) if len(meters) > 0: return meters # Try for power1 if there aren't any power1s. for meter in discovery.PWRKAP_POWER_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "aem2": continue if data["name"].endswith("power1_average"): meters.append(meter) return meters # Go look for meters emeters = find_aem_energy_meters() pmeters = find_aem_power_meters() if len(emeters) == 0 or len(pmeters) == 0: return emeters.sort(cmp = cmp_meter) pmeters.sort(cmp = cmp_meter) # Some AEM systems are actually NUMA systems. For this to work, # we must grab node info from /sys/devices/system/node/nodeX/. # Each node tells us which CPUs are in that node; to find the # power/energy meter we'll simply sort them by /sys/class/hwmon/hwmonX # order and assign them to the nodes. # (This system should work for the non-NUMA systems too.) i = 0 for node in dircache.listdir(SYSFS_NODE_DIR): if not node.startswith("node"): continue cpus = [] emeter = emeters[i] pmeter = pmeters[i] dev_domains = [] devs = [] for cpu in dircache.listdir(SYSFS_NODE_DIR + node): if not cpu.startswith("cpu"): continue cpu_device = find_device_in_list(discovery.PWRKAP_DEVICES, cpu) if cpu_device == None: continue cpu_domain = find_domain_with_dev(discovery.PWRKAP_DEVICE_DOMAINS, cpu_device) assert cpu_domain != None for dev in cpu_domain.devices: discovery.PWRKAP_DEVICES.remove(dev) devs.append(dev) discovery.PWRKAP_DEVICE_DOMAINS.remove(cpu_domain) dev_domains.append(cpu_domain) discovery.PWRKAP_POWER_METERS.remove(pmeter) discovery.PWRKAP_ENERGY_METERS.remove(emeter) idomains = pwrkap_data.detect_idomains_for_devices(devs) pd = pwrkap_data.power_domain(dev_domains, idomains, pmeter, emeter, 1000) discovery.PWRKAP_POWER_DOMAINS.append(pd) i = i + 1 def ibm_simple_pex_discover(): """Configure power domain on PEx systems.""" def find_ibmaem_energy_sensor(): """Return an appropriate ibmaem energy meter.""" p2 = p1 = None for meter in discovery.PWRKAP_ENERGY_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "aem2": continue if data["name"].endswith("energy2_input"): p2 = meter elif data["name"].endswith("energy1_input"): p1 = meter if p2 != None: return p2 return p1 def find_ibmaem_power_sensor(): """Return an appropriate ibmaem power meter.""" p2 = p1 = None for meter in discovery.PWRKAP_POWER_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "aem2": continue if data["name"].endswith("power2_average"): p2 = meter elif data["name"].endswith("power1_average"): p1 = meter if p2 != None: return p2 return p1 def find_ibmpex_sensor(): """Return an appropriate ibmpex power meter.""" for meter in discovery.PWRKAP_POWER_METERS: (name, data) = meter.inventory() if not name.startswith("sysfsmeter"): continue if not data["chip"] == "ibmpex": continue if not data["name"].endswith("power11_average"): continue return meter return None def find_ipmi_sensor(): """Return an appropriate ipmi power meter.""" for meter in discovery.PWRKAP_POWER_METERS: (name, data) = meter.inventory() if not name.startswith("ipmimeter"): continue if data["name"] == "AVG Power": return meter elif data["name"] == "AvgPwrIns1": return meter return None # First see if we can find an ibmaem meter pmeter = find_ibmaem_power_sensor() emeter = find_ibmaem_energy_sensor() # Then try to find an ibmpex power meter if pmeter == None: pmeter = find_ibmpex_sensor() # If none found, go for IPMI sensor if pmeter == None: pmeter = find_ipmi_sensor() # No meter? if pmeter == None: return form_domain_from_all_devices(pmeter, emeter) def ibm_x3_discover(): """Configure power domain on simple X3 systems.""" def find_ipmi_sensor(): """Return an appropriate ipmi power meter.""" for meter in discovery.PWRKAP_POWER_METERS: (name, data) = meter.inventory() if not name.startswith("ipmimeter"): continue if not data["name"] == "RFG 1PS 220": continue return meter return None # If none found, go for IPMI sensor meter = find_ipmi_sensor() # No meter? if meter == None: return form_domain_from_all_devices(meter, None) def form_domain_from_all_devices(pmeter, emeter): """Assume system has one meter for all devices and create domain.""" # No devices? if len(discovery.PWRKAP_DEVICE_DOMAINS) == 0: return # Assume all CPUs have identical power curves idomains = pwrkap_data.detect_idomains_for_devices(discovery.PWRKAP_DEVICES) # Take all device domains for this power domain domains = discovery.PWRKAP_DEVICE_DOMAINS # Remove all devices, domains, and meters that we intend to use. discovery.PWRKAP_DEVICES = [] discovery.PWRKAP_DEVICE_DOMAINS = [] discovery.PWRKAP_POWER_METERS.remove(pmeter) if emeter != None: discovery.PWRKAP_ENERGY_METERS.remove(emeter) # Create power domain pd = pwrkap_data.power_domain(domains, idomains, pmeter, emeter, 1000) discovery.PWRKAP_POWER_DOMAINS.append(pd) known_systems = [ (["IBM System x3650", "IBM System x3655", "IBM System x3755", "IBM System x3350", "BladeCenter LS21", "IBM eServer BladeCenter HS21", "IBM System x3550"], ibm_simple_pex_discover), (["eserver xSeries 366", "IBM x3850", "IBM x3800"], ibm_x3_discover), (["IBM 3850 M2 / x3950 M2"], ibm_aem_discover), ] def ibm_system_discover(): """Configure power domains on IBM systems.""" def get_dmi_system_name(): """Retrieve system name from DMI.""" proc = popen2.Popen4("dmidecode -s system-product-name") input = proc.fromchild while proc.poll() == -1: time.sleep(0.1) return input.readline().strip() global known_systems # First determine if this is one of the known systems. sys_name = get_dmi_system_name() if sys_name == None: return sys_func = None for (sys_list, func) in known_systems: for sys in sys_list: if sys_name.startswith(sys): sys_func = func break if sys_func != None: break; if sys_func == None: return return func() def ibm_init(): """Set up IBM system discovery functions.""" discovery.PWRKAP_POWER_DOMAIN_DISCOVERY.append(ibm_system_discover) ibm_init() pwrkap-7.30/pwrkap/ipmi_meter.py000066400000000000000000000035741114434640500167720ustar00rootroot00000000000000#!/usr/bin/python """Read power meters available via IPMI.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import pwrkap_data import datetime import os import popen2 class ipmi_power_meter(pwrkap_data.power_meter): """Driver for power meters available via IPMI.""" def __init__(self, sensor_name, ipmitool_args = ""): self.sensor_name = sensor_name self.latency = None self.ipmitool_args = ipmitool_args self.meter_name = "ipmitool" def read(self): try: before = datetime.datetime.utcnow() proc = popen2.Popen4("ipmitool %s sensor get '%s'" % \ (self.ipmitool_args, self.sensor_name), 512) res = proc.wait() if res != 0: return None input = proc.fromchild for line in input: if line.strip().startswith("Sensor Reading"): foo = line.split() if foo[3] == "na": return None else: return float(foo[3]) return None finally: after = datetime.datetime.utcnow() if self.latency == None: self.latency = (after - before) else: self.latency = (8 * self.latency + 2 * (after - before)) / 10 def get_latency(self): return self.latency def inventory(self): return (self.meter_name, {"name": self.sensor_name}) def ipmi_meter_discover(): """Discover IPMI meters.""" os.system("modprobe -q ipmi-si") os.system("modprobe -q ipmi-devintf") proc = popen2.Popen4("ipmitool sensor", 512) # Don't wait for output; apparently RHEL5 drops all pipe data # after the process terminates. #res = proc.wait() #if res != 0: # return input = proc.fromchild for line in input: foo = line.split("|") if len(foo) > 2 and foo[2].strip() == "Watts": meter = ipmi_power_meter(foo[0].strip()) discovery.PWRKAP_POWER_METERS.append(meter) def ipmi_init(): """Set up IPMI discovery functions.""" discovery.PWRKAP_METER_DISCOVERY.append(ipmi_meter_discover) return True ipmi_init() pwrkap-7.30/pwrkap/lazy_log.py000066400000000000000000000024511114434640500164510ustar00rootroot00000000000000#!/usr/bin/python """A log that retains events for a certain amount of time.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import datetime import cPickle as pickle class lazy_log: """A log of pickled objects that retains log data for a given amount of time.""" def __init__(self, writer, max_age, max_size): """Create a log object.""" self.writer = writer self.messages = [] self.max_age = max_age self.max_size = max_size def log(self, object): """Pickle an object, save it to the log, and write it out.""" time = datetime.datetime.utcnow() tuple = (time, object) pickled = pickle.dumps(tuple, protocol = pickle.HIGHEST_PROTOCOL) self.messages.append((time, pickled)) self.writer.write(pickled) print tuple self.scrape_log() def scrape_log(self): """Remove expired messages from the log.""" now = datetime.datetime.utcnow() for i in range(0, len(self.messages)): if (now - self.messages[i][0]).seconds <= self.max_age: del self.messages[0:i] break if len(self.messages) > self.max_size: self.messages = self.messages[len(self.messages) - self.max_size:] def dump_log(self): """Retrieve log contents.""" pickles = [] self.scrape_log() for (time, pickle) in self.messages: pickles.append(pickle) return pickles logger = None pwrkap-7.30/pwrkap/power_energy_meter.py000066400000000000000000000066551114434640500205440ustar00rootroot00000000000000#!/usr/bin/python """Fake energy meter that calculates energy use by integrating power use.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import pwrkap_data import thread import datetime import time import traceback class power_energy_meter(pwrkap_data.energy_meter): """Fake energy meter that uses power meter to estimate energy use.""" def __init__(self, pmeter, threaded): self.power_meter = pmeter self.energy_use = None self.last_update = None self.keep_running = True self.threaded = threaded # This must run last if self.threaded: thread.start_new_thread(self.run, ()) def __del__(self): self.keep_running = False def __setstate__(self, data): self.__dict__ = data self.last_update = None if self.threaded: thread.start_new_thread(self.run, ()) def run(self): """Periodically update energy use.""" self.keep_running = True while self.keep_running: try: self.update_energy_use() except Exception, e: print e traceback.print_exc() time.sleep(1) def update_energy_use(self): """Update the energy use counter.""" now = datetime.datetime.utcnow() if self.last_update == None: self.energy_use = 0 self.last_update = now return td = now - self.last_update time_delta = td.microseconds + \ (td.seconds * 1000000) + \ (td.days * 86400000000) if time_delta == 0: time_delta = 1 time_delta = float(time_delta) / 1000000 rate = self.power_meter.read() self.energy_use = self.energy_use + (rate * time_delta) self.last_update = now def read(self): if not self.threaded: self.update_energy_use() time.sleep(2) self.update_energy_use() return self.energy_use def get_latency(self): return 0; def inventory(self): return ("power_energy_meter", {}) class energy_power_meter(pwrkap_data.power_meter): """Fake power meter that uses energy meter to estimate energy use.""" def __init__(self, emeter, threaded): self.energy_meter = emeter self.last_energy_use = None self.last_update = None self.power_use = None self.keep_running = True self.threaded = threaded # This must run last if self.threaded: thread.start_new_thread(self.run, ()) def __del__(self): self.keep_running = False def __setstate__(self, data): self.__dict__ = data self.last_update = None if self.threaded: thread.start_new_thread(self.run, ()) def run(self): """Periodically update energy use.""" self.keep_running = True while self.keep_running: try: self.update_energy_use() except Exception, e: print e traceback.print_exc() time.sleep(1) def update_power_use(self): """Update the power use counter.""" now = datetime.datetime.utcnow() if self.last_update == None: self.last_energy_use = self.energy_meter.read() self.power_use = 0 self.last_update = now return td = now - self.last_update time_delta = td.microseconds + \ (td.seconds * 1000000) + \ (td.days * 86400000000) if time_delta == 0: time_delta = 1 time_delta = float(time_delta) / 1000000 energy_now = self.energy_meter.read() self.power_use = float(energy_now - self.last_energy_use) / time_delta self.last_energy_use = energy_now self.last_update = now def read(self): if not self.threaded: self.update_power_use() time.sleep(2) self.update_power_use() return self.power_use def get_latency(self): return 0; def inventory(self): return ("energy_power_meter", {}) pwrkap-7.30/pwrkap/pwrkap_aggregate.py000066400000000000000000000256031114434640500201470ustar00rootroot00000000000000#!/usr/bin/python """Aggregate pwrkap servers into one virtual pwrkap server.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import asyncore import socket import cPickle as pickle import StringIO import lazy_log import traceback import datetime import thread import time import sys import pwrkap_data AGGREGATE_REFRESH = 15 def read_objs_from_string(str): """Unpickle commands from a string.""" sio = StringIO.StringIO(str) objs = [] pos = 0 while True: try: obj = pickle.load(sio) pos = sio.tell() objs.append(obj) except EOFError: break return (pos, objs) class listen_dispatcher(asyncore.dispatcher): """Dispatch incoming connections.""" def __init__(self, socket, controller): """Set up server socket.""" asyncore.dispatcher.__init__(self, socket) self.controller = controller self.set_reuse_addr() self.listen(5) def handle_accept(self): """Dispatch incoming connection attempt.""" conn, addr = self.accept() client_dispatcher(conn, self.controller) class base_dispatcher(asyncore.dispatcher): """Writable dispatcher.""" def __init__(self, socket = None, map = None): """Create a writable dispatcher.""" asyncore.dispatcher.__init__(self, socket, map) self.out_buf = "" self.in_buf = "" def handle_write(self): """Push data to client.""" sent = self.send(self.out_buf[0]) self.out_buf = self.out_buf[sent:] def writable(self): """Determine if there are data to write to the client.""" return (len(self.out_buf) > 0) def write(self, buffer): """Send some data to be written.""" self.out_buf = self.out_buf + buffer def read_objs_from_socket(self): """Read objects from socket.""" try: buf = self.recv(4096) except: self.handle_close() return [] self.in_buf = self.in_buf + buf while len(buf) > 0: try: buf = self.recv(4096) self.in_buf = self.in_buf + buf except: break pos, objs = read_objs_from_string(self.in_buf) self.in_buf = self.in_buf[pos:] return objs class client_dispatcher(base_dispatcher): """Talk to a pwrkap client.""" def __init__(self, socket, controller): """Create a client dispatcher.""" base_dispatcher.__init__(self, socket) self.controller = controller self.controller.add_client(self) def handle_read(self): """Read and dispatch commands.""" for obj in self.read_objs_from_socket(): self.controller.command(obj) def handle_close(self): """Remove ourself.""" self.controller.remove_client(self) asyncore.dispatcher.close(self) self.connected = False class server_dispatcher(base_dispatcher): """Talk to a pwrkap server.""" def __init__(self, controller, sock_func, connect_opts): """Create a server dispatcher.""" base_dispatcher.__init__(self) self.controller = controller self.connect_opts = connect_opts self.sock_func = sock_func self.usock = None self.ignore_next = True def handle_read(self): """Read and dispatch status.""" for obj in self.read_objs_from_socket(): if self.ignore_next: self.ignore_next = False continue try: self.controller.status(self, obj) except: traceback.print_exc() def handle_close(self): """Handle connection closing.""" self.controller.remove_server(self) self.close() self.usock = None self.connected = False self.peer = None def try_connect(self): """Try to connect.""" assert not self.connected if self.usock == None: self.usock = self.sock_func() self.usock.setblocking(0) self.set_socket(self.usock) try: print "Connecting to %s:%d..." % self.connect_opts x = self.socket.connect_ex(self.connect_opts) except: return False return True def handle_connect(self): """Handle a successful connection.""" self.ignore_next = True try: self.peer = self.socket.getpeername() except: self.peer = None pass self.controller.add_server(self) class aggregate_controller: """Aggregate a bunch of pwrkap servers to pwrkap clients.""" def __init__(self, name, server_socket_info, domains): """Create a pwrkap aggregator with given name and a list of (host, port, domain) tuples.""" assert len(domains) > 0 self.clients = [] self.servers = [] self.name = name self.logger = lazy_log.lazy_log(self, 3600, 2) self.command_table = { "cap": self.cap_command} self.ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ssock.bind(server_socket_info) self.listen_dispatcher = listen_dispatcher(self.ssock, self) # Contains domain info self.domains = {} for hpd in domains: self.domains[hpd] = {"cap": None, "energy": None, "power": None, "utilization": None} self.dispatchers = {} self.hostportdispatcher = {} hostport_seen = [] self.inactive_dispatchers = [] self.active_dispatchers = [] for host, port, dom in domains: if (host, port) in hostport_seen: continue dispatcher = server_dispatcher(self, lambda: socket.socket(socket.AF_INET, socket.SOCK_STREAM), (host, port)) self.dispatchers[dispatcher] = (host, port) self.hostportdispatcher[(host, port)] = dispatcher self.inactive_dispatchers.append(dispatcher) hostport_seen.append((host, port)) print ("dispatchers ready" , self.dispatchers) self.try_to_activate_dispatchers() def try_to_activate_dispatchers(self): """Try to connect inactive dispatchers.""" connected = [] for d in self.inactive_dispatchers: if not d.try_connect(): continue connected.append(d) for c in connected: self.inactive_dispatchers.remove(c) self.active_dispatchers.extend(connected) def add_client(self, client_dispatcher): """Add a client.""" self.clients.append(client_dispatcher) # Write fake inventory data = [(self.name, {"domains": [], "meter": {"aggregate": {}}})] datastr = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) client_dispatcher.write(datastr) # Write old snapshots pickles = self.logger.dump_log() for apickle in pickles: client_dispatcher.write(apickle) # Write live stamp data = (datetime.datetime.utcnow(), "live") datastr = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) client_dispatcher.write(datastr) def remove_client(self, client_dispatcher): """Remove a client.""" self.clients.remove(client_dispatcher) def add_server(self, server_dispatcher): """Add a server.""" self.servers.append(server_dispatcher) def remove_server(self, server_dispatcher): """Remove a server.""" self.servers.remove(server_dispatcher) self.active_dispatchers.remove(server_dispatcher) self.inactive_dispatchers.append(server_dispatcher) def write(self, buffer): """Write data to all clients.""" for client in self.clients: client.write(buffer) def cap_command(self, command): """Handle the cap command.""" new_cap = float(command[2]) total_cap = 0.0 for key in self.domains.keys(): dom = self.domains[key] if dom["cap"] != None: total_cap = total_cap + dom["cap"] for key in self.domains.keys(): (host, port, domname) = key dom = self.domains[key] command = [domname, "cap", (dom["cap"] / total_cap) * new_cap] cmdstr = pickle.dumps(command, pickle.HIGHEST_PROTOCOL) dispatcher = self.hostportdispatcher[(host, port)] dispatcher.write(cmdstr) def command(self, command): """Handle commands.""" if command[0] != self.name: return self.command_table[command[1]](command) def status(self, dispatcher, status): """Handle status reports.""" (timestamp, some_data) = status if some_data == "live": return (domname, domstatus) = some_data peer = dispatcher.peer if peer == None: peer = self.dispatchers[dispatcher] (host, port) = peer # Are we watching this domain? domkey = (host, port, domname) if not self.domains.has_key(domkey): return # Collect status data dom = self.domains[domkey] dom["cap"] = domstatus["cap"] dom["power"] = domstatus["power"] if domstatus.has_key("energy"): energy = domstatus["energy"] else: energy = None dom["energy"] = energy dom["utilization"] = domstatus["utilization"] if domstatus.has_key("util_details"): ud = {} for detail in domstatus["util_details"].keys(): ud["%s:%d:%s:%s" % (host, port, domname, detail)] = domstatus["util_details"][detail] else: ud = {domname: dom["utilization"]} dom["util_details"] = ud def run(self): """Start this controller.""" thread.start_new_thread(self.do_periodic_updates, ()) def do_periodic_updates(self): """Periodically aggregate data and send to clients.""" while True: print "Refresh" self.try_to_activate_dispatchers() self.update_clients() time.sleep(AGGREGATE_REFRESH) def update_clients(self): """Send status update to clients.""" total_cap = total_energy = total_power = total_utilization = 0.0 util_details = {} doms_found = len(self.domains) for key in self.domains.keys(): dom = self.domains[key] try: total_cap = total_cap + dom["cap"] total_power = total_power + dom["power"] if "energy" in dom.keys(): total_energy = total_energy + dom["energy"] total_utilization = total_utilization + dom["utilization"] util_details.update(dom["util_details"]) except: doms_found = doms_found - 1 if doms_found == 0: return avg_utilization = pwrkap_data.average_utilization(util_details) old_avg_util = total_utilization / doms_found data = (self.name, {"domains": [], "cap": total_cap, "power": total_power, "energy": total_energy, "utilization": avg_utilization, "old_avg_util": old_avg_util, "util_details": util_details}) self.logger.log(data) #ac = aggregate_controller("agg0", ('0.0.0.0', 9410), [ # ('9.47.66.63', 9410, 'pwrdom0'), # ('9.47.66.254', 9410, 'pwrdom0') #]) #ac.run() def read_config_file(file): """Read config file and set up aggregates. config file format: domain $listen_addr $port $aggregate_name consists of: system $hostname $port $domain """ listen_addr = None port = None domain = None systems = [] controllers = [] for line in file: components = line.split() if len(components) < 1 or components[0][0] == "#": continue if port == None and components[0] != "domain": continue if components[0] == "domain": if port != None: print ("C", domain, listen_addr, port) ac = aggregate_controller(domain, (listen_addr, port), systems) controllers.append(ac) port = None systems = [] listen_addr = components[1] port = int(components[2]) domain = components[3] elif components[0] == "system": system_hostname = components[1] system_port = int(components[2]) system_domain = components[3] systems.append((system_hostname, system_port, system_domain)) if port != None: print ("D", domain, listen_addr, port) ac = aggregate_controller(domain, (listen_addr, port), systems) controllers.append(ac) return controllers fname = "./pwrkap_aggregate.conf" if len(sys.argv) > 1: fname = sys.argv[1] ctrls = read_config_file(file(fname)) for ac in ctrls: ac.run() asyncore.loop() pwrkap-7.30/pwrkap/pwrkap_cli.py000066400000000000000000000025701114434640500167660ustar00rootroot00000000000000#!/usr/bin/python """Command-line client for pwrkap.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import socket import sys import cPickle as pickle import traceback import thread host = "localhost" port = 9410 if len(sys.argv) > 1: host = sys.argv[1] if len(sys.argv) > 2: port = int(sys.argv[2]) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) fp = sock.makefile() def dump_input(file): """Read objects from a socket and dump them.""" try: obj = pickle.load(file) while True: print ("recv'd", obj) obj = pickle.load(file) except Exception, e: print e traceback.print_exc() sys.exit(1) def print_help(): """Print command help.""" print "Command format: domain_name command args" print "domain_name is the name of the domain to alter." print "command = {dump, cap}" print " dump: Dump transition tables to daemon console." print " cap new_cap: Set a new power cap." def send_console(file): """Read console and send to server.""" data = sys.stdin.readline() while data != "": commands = data.split() if commands[0] == "help": print_help() data = sys.stdin.readline() continue pickled = pickle.dumps(commands, protocol = pickle.HIGHEST_PROTOCOL) file.write(pickled) file.flush() data = sys.stdin.readline() thread.start_new_thread(dump_input, (fp,)) send_console(fp) pwrkap-7.30/pwrkap/pwrkap_data.py000066400000000000000000000360711114434640500171330ustar00rootroot00000000000000#!/usr/bin/python """Common data types for pwrkap drivers.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import math import transitions import datetime import threading import traceback import lazy_log ONE_SECOND = datetime.timedelta(0, 0, 1) def average_utilization(util_map): """Calculate the average utilization from a map of device -> utilization.""" assert len(util_map) > 0 sum = 0.0 for key in util_map.keys(): sum = sum + util_map[key] return float(sum) / len(util_map) class meter: """Abstract base class for meter implementations.""" def read(self): """Read the meter.""" pass def inventory(self): """Return an inventory of the meter capabilities.""" pass def get_latency(self): """Return the average latency of the meter in seconds.""" pass class power_meter(meter): """Abstract base class for power meters. read() returns Watts.""" pass class energy_meter(meter): """Abstract base class for energy meters. read() returns Joules.""" pass import power_energy_meter class device: """Abstract base class for power-managed devices.""" def get_prefix(self): """Return the type of this device.""" pass def get_power_states(self): """Return a mapping of all possible power states to the device's ability to perform while in that state (in percent).""" pass def get_max_power_state(self): """Return the maximum power state.""" pass def set_max_power_state(self, max_pstate): """Set the maximum power state.""" pass def get_current_power_state(self): """Return the current power state.""" pass def snapshot(self): """Return a snapshot of the current state of the device.""" pass def inventory(self): """Return an inventory of the device capabilities.""" pass def get_utilization_details(self): """Return a dictionary containing {subdevice: utilization} pairs. If there are no subdevices, return a single {device: utilization} pair.""" pass def get_name(self): """Return the name of this device.""" pass def snapshot(self): """Take a snapshot of this device.""" key = self.get_name() obj = { "state": self.get_current_power_state(), \ "max_state": self.get_max_power_state(), \ "util_details": self.get_utilization_details()} return (key, obj) def start_load(self): """Start a load for training purposes.""" pass def stop_load(self): """Stop the training load.""" pass class device_domain(device): """A collection of devices that must be power-managed together.""" def __init__(self, devices): """Create a domain of power-managed devices.""" assert len(devices) > 0 self.devices = devices self.must_set_all = False def get_prefix(self): """Return the prefix of the domain.""" return "domain" def get_current_power_state(self): """Return the current power state.""" highest_seen = None for dev in self.devices: cps = dev.get_current_power_state() if highest_seen == None or highest_seen < cps: highest_seen = cps return highest_seen def get_power_states(self): """Return a list of all possible power states.""" return self.devices[0].get_power_states() def get_max_power_state(self): """Return the maximum power state.""" return self.devices[0].get_max_power_state() def set_max_power_state(self, max_pstate): """Set the maximum power state.""" if not self.must_set_all: return self.devices[0].set_max_power_state(max_pstate) res = True for dev in self.devices: res = res and dev.set_max_power_state(max_pstate) return res def get_utilization_details(self): """Merge and return maps of device utilization.""" dom_util_map = {} for dev in self.devices: dev_util_map = dev.get_utilization_details() assert dev_util_map != None dom_util_map.update(dev_util_map) return dom_util_map def snapshot(self): """Take a snapshot of this device domain.""" snap_list = {} for dev in self.devices: (k, v) = dev.snapshot() snap_list[k] = v return snap_list def inventory(self): """Take an inventory of this device domain.""" inv_list = {} for dev in self.devices: (k, v) = dev.inventory() inv_list[k] = v return inv_list def get_device(self): """Return a device that represents this domain.""" return self.devices[0] def start_load(self): loaded = [] for dev in self.devices: if dev.start_load(): loaded.append(dev) else: self.stop_load_for(loaded) return False return True def stop_load_for(self, devices): for dev in devices: dev.stop_load() def stop_load(self): self.stop_load_for(self.devices) class IllegalDomain(Exception): pass class power_domain_volatile_data: """Dummy class to isolate power domain data that can't be preserved.""" def __init__(self): self.signal = threading.Condition() def __getstate__(self): pass def __setstate__(self, state): self.signal = threading.Condition() NUM_UTIL_BUCKETS = 4 ENFORCEMENT_INTERVAL = 30 MEASUREMENT_PERIOD = 15 ENFORCEMENT_SNAPSHOTS = 2 DEFAULT_SNAPSHOTS_TO_KEEP = 1000 class power_domain: """A collection of power-managed device domains, a power meter, and various routines to manage them.""" def __init__(self, domains, idomains, power_meter, energy_meter, cap): """Create a power domain.""" global NUM_UTIL_BUCKETS, DEFAULT_SNAPSHOTS_TO_KEEP assert len(domains) > 0 self.power_meter = power_meter self.energy_meter = energy_meter if self.energy_meter == None: self.energy_meter = power_energy_meter.power_energy_meter(self.power_meter) self.domains = domains self.id = next_power_domain_id() self.cap = cap self.inter_domains = idomains self.snap_store = transitions.snapshot_store(DEFAULT_SNAPSHOTS_TO_KEEP) self.trans_store = transitions.transition_store(self.snap_store, self.inter_domains, NUM_UTIL_BUCKETS) self.check_idomain() self.control_loop_active = False self.need_enforcement = False self.volatile = power_domain_volatile_data() self.control_loop_should_exit = False self.last_enforcement = datetime.datetime.utcnow() def choose_domains_for_training(self): """Return the smallest set of domains that are needed to collect training data.""" training = [] for dom in self.domains: dev = dom.get_device() already_covered = False for idom in self.inter_domains: if idom[0] == dev: training.append(dom) already_covered = True break elif dev in idom: already_covered = True if not already_covered: training.append(dom) return training def check_idomain(self): """Check interchangeable domains for problems.""" # No empty idoms for idom in self.inter_domains: assert len(idom) > 0 # All devices must be part of an idom. devs = set() for dom in self.domains: for dev in dom.devices: devs.add(dev) for idom in self.inter_domains: for dev in idom: assert dev in devs devs.remove(dev) assert len(devs) == 0 def get_utilization_details(self): """Merge and return maps of domain utilization.""" pdom_util_map = {} for dom in self.domains: dom_util_map = dom.get_utilization_details() assert dom_util_map != None pdom_util_map.update(dom_util_map) return pdom_util_map def get_energy_use(self): """Return the power domain's energy use, in Joules.""" return self.energy_meter.read() def get_power_use(self): """Return the power domain's power use, in Watts.""" return self.power_meter.read() def get_cap(self): """Return the power domain's maximum usage.""" return self.cap def set_cap(self, cap): """Set a new cap for this domain.""" self.cap = cap if self.control_loop_active: self.need_enforcement = True # Signal the control loop self.volatile.signal.acquire() self.volatile.signal.notify() self.volatile.signal.release() else: self.enforce_cap() return True def exit_control_loop(self): """Terminate the control loop.""" if not self.control_loop_active: return self.control_loop_should_exit = True self.volatile.signal.acquire() self.volatile.signal.notify() self.volatile.signal.release() def control_loop(self): try: self.do_control_loop() except Exception, ex: traceback.print_exc() def do_control_loop(self): """Control loop for power use regulation.""" global ENFORCEMENT_INTERVAL, MEASUREMENT_PERIOD, ENFORCEMENT_SNAPSHOTS snapshots_since_enforcement = 0 self.control_loop_should_exit = False self.control_loop_active = True while True: # Are we being told to exit? if self.control_loop_should_exit: self.control_loop_active = False return before = datetime.datetime.utcnow() # Take snapshot (name, props) = self.process_snapshot() usage = props["power"] lazy_log.logger.log((name, props)) snapshots_since_enforcement = snapshots_since_enforcement + 1 # Figure out if we need to run the enforcement loop nowtime = datetime.datetime.utcnow() if self.need_enforcement or \ (nowtime - self.last_enforcement).seconds > ENFORCEMENT_INTERVAL or \ snapshots_since_enforcement >= ENFORCEMENT_SNAPSHOTS: snapshots_since_enforcement = 0 self.need_enforcement = False self.do_enforce_cap(usage) self.last_enforcement = datetime.datetime.utcnow() # Now sleep for a bit? after = datetime.datetime.utcnow() if (after - before).seconds < MEASUREMENT_PERIOD: self.volatile.signal.acquire() self.volatile.signal.wait(MEASUREMENT_PERIOD - (after - before).seconds) self.volatile.signal.release() def snapshot(self): """Return a (key, obj) representation of the power domain.""" def snapshot_domains(list): """Snapshot a list of domains.""" snap_list = [] for domain in list: snap_list.append(domain.snapshot()) return snap_list ud = self.get_utilization_details() aud = average_utilization(ud) key = self.name() obj = { "utilization": aud, \ "power": self.get_power_use(), \ "energy": self.get_energy_use(), \ "domains": snapshot_domains(self.domains), \ "cap": self.get_cap(), \ "util_details": ud} return (key, obj) def process_snapshot(self): """Capture and record a snapshot.""" snap = self.snapshot() self.trans_store.consider_snapshot(snap[1]) return snap def inventory(self): """Return a (key, obj) representation of the power domain's \ capabilities.""" def inventory_domains(list): """Inventory a list of domains.""" inv_list = [] for domain in list: inv_list.append(domain.inventory()) return inv_list (pmeter_name, pmeter_data) = self.power_meter.inventory() (emeter_name, emeter_data) = self.energy_meter.inventory() key = self.name() obj = { "domains": inventory_domains(self.domains), \ "pmeter": {pmeter_name: pmeter_data}, "emeter": {emeter_name: emeter_data}} return (key, obj) def name(self): """Return the domain's name.""" return "pwrdom" + str(self.id) def enforce_cap(self): """Try to enforce the power cap on a one-off basis.""" return self.do_enforce_cap(self.get_power_use()) def do_enforce_cap(self, power_use): """Try to enforce the power cap given a power usage reading.""" recent_transitions = set() recent_devs = set() # Make it so that we don't go back to where we started from for domain in self.domains: state = domain.get_current_power_state() recent_transitions.add((domain, state)) # XXX: Fudge things a bit here--give ourselves 10W of headroom remaining_delta = self.get_cap() - power_use - 10 original_delta = remaining_delta print "***********" print "delta=%(delta)d cap=%(cap)d usage=%(use)d" % {"delta": remaining_delta, "cap": self.get_cap(), "use": power_use} delta = self.change_power_target(remaining_delta, recent_transitions, recent_devs) while delta != None: remaining_delta = remaining_delta - delta if remaining_delta * original_delta < 0: break delta = self.change_power_target(remaining_delta, recent_transitions, recent_devs) print "Remaining delta=%(rem)dW" % {"rem": remaining_delta} if remaining_delta < 0: return remaining_delta return 0 def change_power_target(self, delta, recent, recent_devs): """Take one step towards changing the power use target.""" proposals = [] if delta == 0: return None # For each device domain, construct a set of transitions # from the (current state, utilization) to another state # for which we know the power cost. XXX: If there is no # data for (c0, u) -> (c1), can we use (c0, u') -> (c1) # instead? for domain in self.domains: dev_proposals = self.trans_store.propose_transitions(domain) for prop in dev_proposals: # Don't monkey around with CPUs we've already modified if prop.device in recent_devs: continue # Ignore proposals that don't change power use. if prop.power_impact == 0: continue # If we have to decrease power use, eliminate # options that consume more energy. if delta < 0 and prop.power_impact > 0: continue # If delta positive, do not pick any option that results # in a performance decrease. This greedy algorithm only # cares about now; it does not look ahead. if delta > 0 and prop.performance_impact < 0: continue # Eliminate transitions that exceed the desired delta # if the delta is positive. if delta > 0 and prop.power_impact > delta: continue # Else, add to proposal list. Note that it is # quite valid to have options that increase # peformance and decrease energy use! proposals.append(prop) # If there are no transitions left, we're stuck; exit if len(proposals) == 0: return None # Sort transitions in order of goodness. proposals.sort(cmp = transitions.compare_proposals) if True: print "------------------" for x in proposals: print (x.device.get_device().inventory()[0], x.new_state, 100*x.performance_impact, \ x.power_impact, x.performance_impact / x.power_impact) # Pick the best one proposal = None for prop in proposals: if (prop.device, prop.new_state) in recent: continue proposal = prop break if proposal == None: return None print ("I CHOOSE", proposal) # Implement it. proposal.device.set_max_power_state(proposal.new_state) # Remember that fact. recent.add((proposal.device, proposal.new_state)) recent_devs.add(proposal.device) return proposal.power_impact def start_load(self): loaded = [] for dom in self.domains: if dom.start_load(): loaded.append(dom) else: self.stop_load_for(loaded) return False return True def stop_load_for(self, domains): for dom in domains: dom.stop_load() def stop_load(self): self.stop_load_for(self.domains) def detect_idomains_for_devices(devices): """Compute identical-domains for a list of devices.""" idomains = [] for device in devices: (name, data) = device.inventory() prefix = device.get_prefix() dev_idomain = [] for idomain in idomains: (idom_name, idom_data) = idomain[0].inventory() idom_prefix = idomain[0].get_prefix() if idom_prefix == prefix and idom_data == data: dev_idomain = idomain break if len(dev_idomain) == 0: idomains.append(dev_idomain) dev_idomain.append(device) return idomains next_dom_id = 0 def next_power_domain_id(): """Return a unique power domain identifier.""" global next_dom_id next_dom_id = next_dom_id + 1 return next_dom_id - 1 pwrkap-7.30/pwrkap/pwrkap_gtk.py000066400000000000000000000013161114434640500170010ustar00rootroot00000000000000#!/usr/bin/python """GTK user interface for pwrkap.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import ui_controller import gtk import sys import ui_data import thread import gobject hosts = [] if len(sys.argv) == 1: host = ui_data.pwrkap_host("localhost", 9410) hosts.append(host) else: for arg in sys.argv[1:]: stuff = arg.split(":") hostname = stuff[0] if len(stuff) == 1: port = 9410 else: port = int(stuff[1]) host = ui_data.pwrkap_host(hostname, port) hosts.append(host) sys.setcheckinterval(10) gtk.gdk.threads_init() uc = ui_controller.ui_controller(hosts) gtk.gdk.threads_enter() thread.start_new_thread(uc.connect_hosts, ()) gtk.main() gtk.gdk.threads_leave() pwrkap-7.30/pwrkap/pwrkap_main.py000066400000000000000000000030771114434640500171460ustar00rootroot00000000000000#!/usr/bin/python """Main pwrkap daemon.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import controller import pwrkap_train import sys import daemon def print_help(): """Print command line usage.""" print "Usage: %s [-d] [-w] [-f] [-t] [listen_port] [log_age]" % sys.argv[0] print "-d: Run in debug mode, i.e. don't run as a daemon." print "-w: Start HTTP web server." print "-f: Use fake devices." print "-t: Retrain system." print "listen_port: Listen for clients on this port." print "log_age: Keep old snapshots for this many seconds." def main(): """Run main program.""" optargs = [] # Interpret arguments daemonize = True http = False load_saved = True for i in range(1, len(sys.argv)): arg = sys.argv[i] if arg == "-d": daemonize = False elif arg == "-w": http = True elif arg == "-f": print "Using fake devices." discovery.PWRKAP_DRIVERS = ["fake_cpudomain"] elif arg == "-h" or arg == "--help": print_help() return elif arg == "-t": load_saved = False else: optargs.append(sys.argv[i]) if daemonize: daemon.daemonize() # Now gather arguments port = 9410 log_age = 3600 log_size = 2 if len(optargs) > 0: port = int(optargs[0]) if len(optargs) > 1: log_age = int(optargs[1]) ctrl = controller.controller(port, log_age, log_size, http) # Speed things up if we're using fakedomain if "fake_cpudomain" in discovery.PWRKAP_DRIVERS: pwrkap_train.STABILIZE_TIME = 0 pwrkap_train.MAX_MEASUREMENTS = 10 ctrl.prepare(load_saved) ctrl.dump() ctrl.run() # We never return main() pwrkap-7.30/pwrkap/pwrkap_pickle.py000066400000000000000000000034011114434640500174600ustar00rootroot00000000000000#!/usr/bin/python """Helper to save and restore power domains.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import cPickle as pickle import socket import os # Search for power cap data files in these locations: # ./pwrkap_$HOSTNAME.bin # /etc/pwrkap_$HOSTNAME.bin # /etc/pwrkap.bin def find_data_file_name(): """Construct the name of the saved data file.""" hostname = socket.gethostname() files = [] files.append("./pwrkap_%s.bin" % hostname) files.append("/etc/pwrkap_%s.bin" % hostname) files.append("/etc/pwrkap.bin") for file in files: if os.path.exists(file): return file def default_data_file_name(): """Default location for saved data.""" hostname = socket.gethostname() files = [] files.append("/etc/pwrkap_%s.bin" % hostname) files.append("./pwrkap_%s.bin" % hostname) for file in files: try: fp = open(file, "wb") fp.close() return file except: continue return None def save_domains(domains): """Save domain data to a file.""" file = open(default_data_file_name(), "wb") pickle.dump(domains, file, pickle.HIGHEST_PROTOCOL) file.close() def load_domains(domains): """Load domain data from a file and return it if the inventory of the restored domains match the inventory of the given domains.""" inventories = [] fname = find_data_file_name() if fname == None: return None file = open(fname, "rb") try: loaded = pickle.load(file) except: file.close() return None file.close() for pd in loaded: inventories.append(pd.inventory()) for pd in domains: inventory = pd.inventory() if inventory not in inventories: print "Configuration change, discarding old data." return None inventories.remove(pd.inventory()) if len(inventories) > 0: print "err2" return None return loaded pwrkap-7.30/pwrkap/pwrkap_test.py000066400000000000000000000063601114434640500171770ustar00rootroot00000000000000#!/usr/bin/python """pwrkap test routines.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import time import pwrkap_pickle import sys import pwrkap_train def discover_world(): """Discovery and assemble power domains.""" print "Probing hardware..." discovery.load_pwrkap_drivers() discovery.discover_devices() discovery.discover_meters() discovery.discover_power_domains() discover_world() print ("devices", discovery.PWRKAP_DEVICES) print ("pmeters", discovery.PWRKAP_POWER_METERS) print ("emeters", discovery.PWRKAP_ENERGY_METERS) print ("devdomains", discovery.PWRKAP_DEVICE_DOMAINS) print ("pwrdomains", discovery.PWRKAP_POWER_DOMAINS) # Speed things up if we're using fakedomain def train(): if "fake_cpudomain" in discovery.PWRKAP_DRIVERS: pwrkap_train.STABILIZE_TIME = 0 pwrkap_train.MAX_MEASUREMENTS = 10 pwrkap_train.train() def quick_simulate(): if len(discovery.PWRKAP_POWER_DOMAINS) == 0: return for pd in discovery.PWRKAP_POWER_DOMAINS: print pd.inventory() while True: for pd in discovery.PWRKAP_POWER_DOMAINS: print pd.snapshot() time.sleep(1) def pickle_test(): for i in range(0, 500): for dm in discovery.PWRKAP_POWER_DOMAINS: dm.process_snapshot() count = 0 for dm in discovery.PWRKAP_POWER_DOMAINS: count = count + 1 fname = "/tmp/pdom%d" % count print "Saving to '%s'..." % fname fp = open(fname, "w") dm.save_snapshots(fp) fp.close() def pickle2(): for i in range(0, 500): for dm in discovery.PWRKAP_POWER_DOMAINS: dm.process_snapshot() pwrkap_pickle.save_domains(discovery.PWRKAP_POWER_DOMAINS) fubar = pwrkap_pickle.load_domains(discovery.PWRKAP_POWER_DOMAINS) if fubar == None: print "Screw up?!" return False print "INVENTORY" for dm in discovery.PWRKAP_POWER_DOMAINS: print dm.inventory() print "INVENTORY PICKLED" for dm in fubar: print dm.inventory() print "SNAPSHOT" for dm in fubar: print dm.snapshot() def pickle3(): pwrkap_pickle.save_domains(discovery.PWRKAP_POWER_DOMAINS) fubar = pwrkap_pickle.load_domains(discovery.PWRKAP_POWER_DOMAINS) if fubar == None: print "Screw up?!" return False print "INVENTORY" for dm in discovery.PWRKAP_POWER_DOMAINS: print dm.inventory() print "INVENTORY PICKLED" for dm in fubar: print dm.inventory() print "SNAPSHOT" for dm in fubar: print dm.snapshot() def pickle4(): fubar = pwrkap_pickle.load_domains(discovery.PWRKAP_POWER_DOMAINS) if fubar == None: print "Screw up?!" return False print "INVENTORY" for dm in discovery.PWRKAP_POWER_DOMAINS: print dm.inventory() print "INVENTORY PICKLED" for dm in fubar: print dm.inventory() print "SNAPSHOT" for dm in fubar: print dm.snapshot() def simulate(): for i in range(0, 300): for dm in discovery.PWRKAP_POWER_DOMAINS: dm.process_snapshot() for dm in discovery.PWRKAP_POWER_DOMAINS: print dm.trans_store.trans_table x = dm.get_cap() x = x * 0.75 while x > 10: y = dm.set_cap(x) if y < 0: print "Missed cap %(cap)dW by %(miss)dW usage %(use)dW" \ % {"miss": y, "cap": x, "use": dm.get_power_use()} x = dm.get_cap() x = x * 0.75 tests = [] if len(sys.argv) < 2: tests = ["train", "pickle3"] else: tests = sys.argv[1:] for test in tests: print "Executing %s()" % test exec "%s()" % test pwrkap-7.30/pwrkap/pwrkap_train.py000066400000000000000000000042741114434640500173370ustar00rootroot00000000000000#!/usr/bin/python """Train the system by cycling devices through all known power states.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import time import datetime STABILIZE_TIME = 5 MAX_MEASUREMENTS = 10 MAX_MEASUREMENT_TIME = 60 def set_and_test(power_domain, domains): """Given a list of domains, loop through the power states of the first domain in the list and recursively call ourself with the rest of the list. If the domain list is empty, collect data.""" global STABILIZE_TIME, MAX_MEASUREMENTS, MAX_MEASUREMENT_TIME if len(domains) == 0: # Sleep a bit to let the system stabilize time.sleep(STABILIZE_TIME) before = datetime.datetime.utcnow() after = datetime.datetime.utcnow() count = 0 while count < MAX_MEASUREMENTS and (after - before).seconds < MAX_MEASUREMENT_TIME: power_domain.process_snapshot() count = count + 1 after = datetime.datetime.utcnow() return for state in domains[0].get_power_states(): print ("Set device", domains[0].inventory().keys()[0], state[0]) domains[0].set_max_power_state(state[0]) set_and_test(power_domain, domains[1:]) def train(): """Train the system.""" def stop_load_for(devices): """Stop load for a selection of devices.""" for dev in devices: dev.stop_load() def start_load(devices): loaded = [] for pd in devices: if pd.start_load(): loaded.append(pd) else: stop_load_for(loaded) return False return True if len(discovery.PWRKAP_POWER_DOMAINS) == 0: return print "Training power cap database..." # Set all devices to the lowest power state for pd in discovery.PWRKAP_POWER_DOMAINS: for dd in pd.domains: min_state = dd.get_power_states()[0] dd.set_max_power_state(min_state[0]) # Start load here. if not start_load(discovery.PWRKAP_POWER_DOMAINS): print "Could not start load for testing, transition table will be less effective." return # Record load for pd in discovery.PWRKAP_POWER_DOMAINS: doms = pd.choose_domains_for_training() if len(doms) > 0: set_and_test(pd, doms) # End load here. stop_load_for(discovery.PWRKAP_POWER_DOMAINS) print "...done." for pd in discovery.PWRKAP_POWER_DOMAINS: print pd.trans_store.trans_table pwrkap-7.30/pwrkap/sockmux.py000066400000000000000000000110371114434640500163220ustar00rootroot00000000000000#!/usr/bin/python """Multiplex various sockets to a single command channel.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import select import socket import sys import thread import struct import traceback import os import cPickle as pickle def res_unavailable_error(err): """Determine if the error is due to lack of resource availability.""" if isinstance(err, socket.error) and err[0] == 11: return True return False READ_BUFFER_SIZE = 1 class sockmux: """Multiplexer that sends objects from an input channel to sockets and from sockets to the controller.""" def __init__(self, controller, listen_port): """Create a socket mux with a controller and a given listening port.""" self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) print "Listening on port %d" % listen_port self.server_socket.bind(('0.0.0.0', listen_port)) self.server_socket.listen(5) self.controller = controller self.should_close = False self.queues = {} self.in_buffers = {} self.pipe = os.pipe() self.pipe_read = os.fdopen(self.pipe[0], "r") self.pipe_write = os.fdopen(self.pipe[1], "w") # This MUST come last! thread.start_new_thread(self.run, ()) def run(self): """Run the mux.""" self.should_close = False while not self.should_close: try: self.run_once() except Exception, e: print e self.server_socket.shutdown(socket.SHUT_RDWR) self.server_socket.close() for cs in self.queues.keys(): cs.shutdown(socket.SHUT_RDWR) cs.close() self.pipe_read.close() self.pipe_write.close() def shut_down(self): """Deactivate the mux.""" self.should_close = True def write(self, object): """Write an object to all outputs.""" for cs in self.queues.keys(): queue = self.queues[cs] queue.append(object) # Wake up select() self.pipe_write.write("0") self.pipe_write.flush() def kill_socket(self, cs): """Terminate a socket.""" del self.queues[cs] del self.in_buffers[cs] cs.shutdown(socket.SHUT_RDWR) cs.close() def run_once(self): """Shovel objects between controller and sockets.""" def write_socket(cs, queue): """Write something to a socket.""" try: assert len(queue) > 0 obj = queue[0] objlen = len(obj) sent = cs.send(obj) while sent != 0: if objlen == sent: del queue[0] else: queue[0] = obj[sent:] if len(queue) == 0: return obj = queue[0] objlen = len(obj) sent = cs.send(obj) except Exception, e: # Don't fault on -EAGAIN if res_unavailable_error(e): return print e traceback.print_exc() self.kill_socket(cs) def read_socket(cs): """Read something from a socket.""" global READ_BUFFER_SIZE try: buffer = self.in_buffers[cs] str = cs.recv(1) # Readable but no data == EOF if len(str) == 0: raise EOFError() while len(str) > 0: buffer = buffer + str try: obj = pickle.loads(buffer) self.in_buffers[cs] = "" self.controller.command(obj) except: self.in_buffers[cs] = buffer pass str = cs.recv(1) except Exception, e: # Don't kill socket if -EAGAIN if res_unavailable_error(e): return print e traceback.print_exc(file=sys.stdout) self.kill_socket(cs) readers = [self.pipe_read, self.server_socket] writers = [] exceptions = [] # Nominate all sockets with pending writes for select, and # all sockets for reads for cs in self.queues.keys(): queue = self.queues[cs] readers.append(cs) exceptions.append(cs) if len(queue) > 0: writers.append(cs) # Find sockets that aren't blocked #print ("before: ", readers, writers, exceptions) (r, w, x) = select.select(readers, writers, exceptions) #print ("after: ", r, w, x) assert len(x) == 0 # If someone connects, tell the controller and add the # socket to our list. if self.server_socket in r: try: (cs, addr) = self.server_socket.accept() print (cs, addr) cs.setblocking(0) queue = [] res = self.controller.connect(cs, queue) if not res: cs.shutdown(socket.SHUT_RDWR) cs.close() else: self.queues[cs] = queue self.in_buffers[cs] = "" except socket.error: pass # For all writers that can be written, write queued data for cs in w: queue = self.queues[cs] write_socket(cs, queue) # For all reader sockets... for cs in r: if cs == self.pipe_read: x = self.pipe_read.read(1) continue if cs == self.server_socket: continue read_socket(cs) pwrkap-7.30/pwrkap/sysfs_meter.py000066400000000000000000000075531114434640500172040ustar00rootroot00000000000000#!/usr/bin/python """Read power meters available via sysfs.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import discovery import pwrkap_data import datetime import os import dircache import util class sysfs_energy_meter(pwrkap_data.energy_meter): """Driver for energy meters available via sysfs.""" def __init__(self, sensor_filename): self.sensor_filename = sensor_filename self.latency = None self.initial_value = self.__read() def __getstate__(self): state = {} for key in self.__dict__.keys(): value = self.__dict__[key] state[key] = value del state["initial_value"] return state def __setstate__(self, state): self.__dict__ = state self.initial_value = self.__read() def get_chip_name(self): """Determine the name of the chip that controls this sensor.""" last_slash = self.sensor_filename.rfind("/") if last_slash == -1: return None base_dir = self.sensor_filename[:last_slash + 1] base_dir = base_dir + "/name" x = util.read_line_as_array(base_dir) if x == None: return None return x[0] def __read(self): """Read the energy meter.""" x = util.read_line_as_array(self.sensor_filename) if x == None: return None return float(x[0]) / 1000000 def read(self): try: before = datetime.datetime.utcnow() return self.__read() - self.initial_value finally: after = datetime.datetime.utcnow() if self.latency == None: self.latency = (after - before) else: self.latency = (8 * self.latency + 2 * (after - before)) / 10 def get_latency(self): return self.latency def inventory(self): return ("sysfsmeter", { "name": self.sensor_filename, \ "chip": self.get_chip_name(), \ }) class sysfs_power_meter(pwrkap_data.power_meter): """Driver for power meters available via sysfs.""" def __init__(self, sensor_filename): self.sensor_filename = sensor_filename self.latency = None def get_chip_name(self): """Determine the name of the chip that controls this sensor.""" last_slash = self.sensor_filename.rfind("/") if last_slash == -1: return None base_dir = self.sensor_filename[:last_slash + 1] base_dir = base_dir + "/name" x = util.read_line_as_array(base_dir) if x == None: return None return x[0] def read(self): try: before = datetime.datetime.utcnow() x = util.read_line_as_array(self.sensor_filename) if x == None: return None return float(x[0]) / 1000000 finally: after = datetime.datetime.utcnow() if self.latency == None: self.latency = (after - before) else: self.latency = (8 * self.latency + 2 * (after - before)) / 10 def get_latency(self): return self.latency def inventory(self): return ("sysfsmeter", { "name": self.sensor_filename, \ "chip": self.get_chip_name(), \ }) SYSFS_HWMON_DIR = "/sys/class/hwmon/" def sysfs_meter_discover(): """Discover sysfs meters.""" global SYSFS_HWMON_DIR # This will load ibmpex/ibmaem drivers os.system("modprobe -q hwmon") os.system("modprobe -q ipmi-si") os.system("modprobe -q ibmpex") os.system("modprobe -q ibmaem") # Now look for /sys/class/hwmon/hwmon*/device/power*_input for hwmon_dev in dircache.listdir(SYSFS_HWMON_DIR): if not hwmon_dev.startswith("hwmon"): continue hwmon_dev_dir = SYSFS_HWMON_DIR + hwmon_dev + "/device/" for sensor_dev in dircache.listdir(hwmon_dev_dir): if sensor_dev.startswith("power") \ and (sensor_dev.endswith("_input") or sensor_dev.endswith("_average")): meter = sysfs_power_meter(hwmon_dev_dir + sensor_dev) discovery.PWRKAP_POWER_METERS.append(meter) if sensor_dev.startswith("energy") \ and sensor_dev.endswith("_input"): meter = sysfs_energy_meter(hwmon_dev_dir + sensor_dev) discovery.PWRKAP_ENERGY_METERS.append(meter) def sysfs_meter_init(): """Set up sysfs meter discovery functions.""" discovery.PWRKAP_METER_DISCOVERY.append(sysfs_meter_discover) return True sysfs_meter_init() pwrkap-7.30/pwrkap/transitions.py000066400000000000000000000235031114434640500172070ustar00rootroot00000000000000#!/usr/bin/python """Remember power domain snapshots and calculate expected transition costs.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import math import pwrkap_data NEW_TRANSITION_WEIGHT = 0.2 class transition_store: """Compute and store power-managed device transitions.""" def __init__(self, snapshot_store, inter_domains, num_util_buckets): """Create a transition store with a snapshot store.""" self.snapshot_store = snapshot_store self.trans_table = {} self.num_util_buckets = num_util_buckets self.inter_domains = inter_domains for i in range(0, len(inter_domains)): util_buckets = [] for j in range(0, num_util_buckets): p_states = {} for (p_state, potential) in inter_domains[i][0].get_power_states(): p_state_dests = {} for (p_state_dest, potential_dest) in inter_domains[i][0].get_power_states(): if p_state >= p_state_dest: continue p_state_dests[p_state_dest] = (None, potential_dest - potential) if len(p_state_dests) != 0: p_states[p_state] = p_state_dests util_buckets.append(p_states) #[(j + 1.00) / num_util_buckets] = p_states self.trans_table["idom" + str(i)] = util_buckets self.stat_cache = [] def consider_snapshot(self, snap): """Add a snapshot and try to determine some transitions.""" global NEW_TRANSITION_WEIGHT self.snapshot_store.append(snap) a = self.genericize_snapshot(snap) a_hist = self.calc_snapshot_stats(a) for (old_hist, old_power) in self.stat_cache: # Find an interchangable-domain with just two changes key = self.find_idom_with_two_changes(a_hist, old_hist) if key == None: continue delta_power = old_power - a["power"] # Store this result in our transition table idom = key[0] bucket = key[1][1] p0 = key[1][0] p1 = key[2][0] if p0 > p1: pt = p0 p0 = p1 p1 = pt delta_power = -delta_power (power, perf) = self.trans_table[idom][bucket][p0][p1] if power == None: power = delta_power self.trans_table[idom][bucket][p0][p1] = \ (NEW_TRANSITION_WEIGHT * delta_power + \ (1 - NEW_TRANSITION_WEIGHT) * power, \ perf) if len(self.stat_cache) > self.snapshot_store.max_size: self.stat_cache.pop() self.stat_cache.append( (a_hist, a["power"]) ) def find_idom_with_two_changes(self, a_hist, b_hist): """See if we can find only one idom where one power state \ loses a core and one power state gains a core.""" res = None # Same idoms? assert a_hist.keys() == b_hist.keys() for idom in a_hist.keys(): a_idom_hist = a_hist[idom] b_idom_hist = b_hist[idom] a_set = set(a_idom_hist.keys()) b_set = set(b_idom_hist.keys()) ab_set = a_set.union(b_set) # No transitions? Ignore this idom. if a_idom_hist == b_idom_hist: continue # A and B differ. If we've already found a solution, # it's now invalid as A and B have two different idoms. if res != None: return None # Now scan A and B for changes a_state = None b_state = None for key in ab_set: if not b_idom_hist.has_key(key): b_val = 0 else: b_val = b_idom_hist[key] if not a_idom_hist.has_key(key): a_val = 0 else: a_val = a_idom_hist[key] diff = b_val - a_val # b_idom_hist[key] - a_idom_hist[key] # More than 1 device entered/exited this state; # this sample cannot be used. if diff > 1 or diff < -1: return None # No change; ignore if diff == 0: continue # This is a -1/+1 transition. if diff == 1: if b_state != None: return None b_state = key elif diff == -1: if a_state != None: return None a_state = key # Ignore transitions that don't involve speed changes if a_state != None and b_state != None and a_state[0] == b_state[0]: return None assert (a_state == None and b_state == None) or (a_state != None and b_state != None) if a_state != None and b_state != None: res = (idom, a_state, b_state) return res def calc_snapshot_stats(self, pseudo_snap): """Construct a histogram of the number of idoms in a given \ (pwr_state, util_bucket).""" histogram = {} domains = pseudo_snap["domains"] for (domain, state) in domains: if histogram.has_key(domain) == False: histogram[domain] = {} hist_key = (state["state"], state["util_bucket"]) if histogram[domain].has_key(hist_key) == False: histogram[domain][hist_key] = 1 else: histogram[domain][hist_key] = histogram[domain][hist_key] + 1 return histogram def find_idomain_for_dev(self, dev_name): """Find an identical-domain for a device.""" for i in range(0, len(self.inter_domains)): domain = self.inter_domains[i] for domain_dev in domain: if domain_dev.inventory()[0] == dev_name: return "idom" + str(i) return dev_name def find_util_bucket(self, util): """Change utilization to utilization bucket number.""" return min(self.num_util_buckets - 1, int(util * self.num_util_buckets)) def genericize_snapshot(self, snap): """Construct a pseudo-snapshot from a real snapshot with device \ name changed to identical-domain ID, domain hierarchy flattened, devices \ within a domain collapsed into one, and utilization changed to utilization \ bucket number.""" # Copy non-domain properties to new snapshot new_snap = {} for key in snap.keys(): if key != "domains": new_snap[key] = snap[key] # Now copy domains but with a few changes. # XXX: We'll be in trouble if a domain isn't a strict subset # of an idomain! new_domains = [] for domain in snap["domains"]: new_state = {} dev0 = domain.keys()[0] new_name = self.find_idomain_for_dev(dev0) #new_state["old_name"] = dev0 dev0_state = domain[dev0] for key in dev0_state.keys(): if key != "utilization": new_state[key] = dev0_state[key] sum = 0.0 for device in domain.keys(): assert new_name == self.find_idomain_for_dev(device) device_state = domain[device] sum = sum + pwrkap_data.average_utilization(device_state["util_details"]) new_state["util_bucket"] = self.find_util_bucket(sum / len(domain.keys())) new_domains.append((new_name, new_state)) new_snap["domains"] = new_domains return new_snap def propose_transitions(self, domain): """Propose power state transitions that can be executed for a domain.""" def try_to_find_transition(idom_table, bucket, a, b): """Try to find a transition for the current utilization. If none found, try adjoining buckets.""" (x, y) = idom_table[bucket][a][b] if not x == None: return (x, y) print ("Guessing!", bucket, a, b) for delta in range(1, max(len(idom_table) - bucket, bucket)): if bucket + delta < len(idom_table): (x, y) = idom_table[bucket + delta][a][b] if not x == None: return (x, y) if bucket - delta >= 0: (x, y) = idom_table[bucket - delta][a][b] if not x == None: return (x, y) return None curr_state = domain.get_current_power_state() curr_util = pwrkap_data.average_utilization(domain.get_utilization_details()) possible_states = domain.get_power_states() device = domain.get_device() idom = self.find_idomain_for_dev(device.inventory()[0]) bucket = self.find_util_bucket(curr_util) props = [] for (new_state, junk) in possible_states: if curr_state == new_state: continue if curr_state > new_state: a = new_state b = curr_state else: a = curr_state b = new_state # XXX: What if there's no entry for this bucket? #(power, perf) = self.trans_table[idom][bucket][a][b] x = try_to_find_transition(self.trans_table[idom], bucket, a, b) if x == None: print ("What do we do with this?", self.trans_table, idom, bucket, a, b) continue (power, perf) = x if curr_state > new_state: power = -power perf = -perf prop = proposed_transition(domain, new_state, power, perf, curr_state, curr_util) props.append(prop) return props class snapshot_store: """Store power domain snapshots.""" def __init__(self, max_size): """Create a snapshot store device.""" self.max_size = max_size self.records = [] def append(self, snapshot): """Append a snapshot record, deleting old ones if needed.""" if len(self.records) >= self.max_size: self.records.pop() self.records.append(snapshot) class proposed_transition: """A proposal to change the power controls of a device.""" def __init__(self, device, new_state, power_impact, performance_impact, curr_state, curr_util): """Create a proposal.""" self.device = device self.new_state = new_state self.performance_impact = performance_impact self.power_impact = power_impact # XXX: This class does not currently use utilization! self.curr_state = curr_state self.curr_util = curr_util def __repr__(self): """Return string representation of object.""" return str({"device": self.device.get_device().inventory()[0], "new_state": self.new_state, \ "perf": self.performance_impact, "power": self.power_impact, \ "state": self.curr_state, "util": self.curr_util}) def compare_proposals(self, other): """Compare one proposal to another.""" # This routine can be used to sort a list of proposals by "goodness". # Assumptions: (1) no proposal has zero power impact. (2) if the cap # is increasing, all proposals increase performance. # (3) if the cap is decreasing, all proposals cut power. # We therefore employ two factors to determine proposal ranking. # The first is dP / abs(dW) because we always want the most positive # change in performance for _any_ change in power budget. In the # event of a tie, the proposal with the most negative dW wins. dPdW_a = self.performance_impact / abs(self.power_impact) dPdW_b = other.performance_impact / abs(other.power_impact) if dPdW_a > dPdW_b: return -1 elif dPdW_a < dPdW_b: return 1 if self.power_impact < other.power_impact: return -1 elif self.power_impact > other.power_impact: return 1 return 0 pwrkap-7.30/pwrkap/ui_controller.py.template000066400000000000000000000422231114434640500213240ustar00rootroot00000000000000#!/usr/bin/python """GTK UI controller for pwrkap.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import pygtk pygtk.require("2.0") import gtk import gtk.glade import gobject import thread import ui_data import datetime import time import threading import traceback import pwrkap_data import sys HAVE_MATPLOTLIB = True try: from matplotlib.axes import Subplot from matplotlib.figure import Figure # uncomment to select /GTK/GTKAgg/GTKCairo #from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas #from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas except: HAVE_MATPLOTLIB = False DEFAULT_TITLE = "Energy Consumption" GRAPH_DRAW_INTERVAL = 5 UI_DRAW_INTERVAL = 2 DRAW_GRAPH = False class ui_controller: """Binding between gtk controls and various actions.""" def __init__(self, hosts): """Create a UI.""" global DEFAULT_TITLE self.window_name = "monitor_window" try: self.window_tree = gtk.glade.XML("ui.glade", self.window_name) except: try: self.window_tree = gtk.glade.XML("%PREFIX%/share/pwrkap/ui.glade", self.window_name) except: print "Could not find UI file; is pwrkap installed correctly?" sys.exit(2) self.window = self.window_tree.get_widget(self.window_name) self.window.set_title(DEFAULT_TITLE) self.hosts = hosts # Set up the machine list model self.tree = self.window_tree.get_widget("host_names") model = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn("Machine", renderer, text = 1) self.tree.append_column(column) column = gtk.TreeViewColumn("Domain", renderer, text = 2) self.tree.append_column(column) self.tree.set_model(model) self.all_iter = model.insert_before(None, None) model.set_value(self.all_iter, 0, (None, None)) model.set_value(self.all_iter, 1, "All Systems") # GTK Event handling events = { "on_close_button_clicked": self.close_app, "on_set_cap_button_clicked": self.set_cap, "on_max_sample_age_changed": self.set_max_sample_age, "on_host_names_cursor_changed": self.select_power_object_later, "on_show_graph_toggled": self.show_graph_toggle} self.window_tree.signal_autoconnect(events) self.window.connect("destroy", self.close_app) # pwrkap event handling self.pwrkap_events = { ui_data.HOST_CONNECTED: self.host_connected, ui_data.HOST_DISCONNECTED: self.host_disconnected, ui_data.HOST_DOMAIN_DATA_RECEIVED: self.data_seen} # Create the graph graphs = self.create_graph() self.graph_lock = threading.Lock() vbox = self.window_tree.get_widget("main_vbox") if graphs != None: (self.fig, self.canvas) = graphs vbox.pack_start(self.canvas) vbox.reorder_child(self.canvas, 0) else: graph_checkbox = self.window_tree.get_widget("show_graph") graph_checkbox.set_sensitive(False) label = gtk.Label("Graphs unavailable because matplotlib is not installed.") vbox.pack_start(label) vbox.reorder_child(label, 0) # Other housekeeping variables self.cur_host = None self.cur_dom = None self.tree.set_cursor("0") self.selection_did_change = True self.graph_changed = True self.last_graph_draw_time = datetime.datetime.utcnow() self.last_ui_draw_time = datetime.datetime.utcnow() self.in_gtk_update = False self.in_draw_graph = False self.dom_cap = None self.dom_power = None self.dom_energy = None self.dom_util = None #self.window.set_size_request(1024, 768) self.window.show_all() self.connect_signal = threading.Condition() def close_app(self, widget): """Close the program.""" gtk.main_quit() def show_graph_toggle(self, widget): """Enable or disable the graph via check box.""" global DRAW_GRAPH new_value = widget.get_active() if new_value: dialog = gtk.Dialog("Slow graph warning", None, \ gtk.DIALOG_MODAL, # | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES)) label = gtk.Label("Graphing power use of a large number of power domains can\nbe very processor intensive! Are you sure you want to proceed?") dialog.vbox.pack_start(label, padding = 15) dialog.vbox.set_border_width(15) label.show() res = dialog.run() dialog.hide() if res == gtk.RESPONSE_NO: widget.set_active(False) return DRAW_GRAPH = widget.get_active() thread.start_new_thread(self.selection_changed, ()) def connect_hosts(self): """Connect all hosts.""" model = self.tree.get_model() for host in self.hosts: host.add_listener(self) iter = model.insert_after(None, self.all_iter) model.set_value(iter, 0, (host, None)) model.set_value(iter, 1, "%s:%d" % (host.host, host.port)) try: #thread.stack_size(65536) x = 3 except: pass for host in self.hosts: thread.start_new_thread(host.control_loop, ()) self.connect_signal.acquire() self.connect_signal.wait(10) self.connect_signal.release() def find_tree_iter_from_host(self, host): """Return a tree iterator given a host object.""" model = self.tree.get_model() root_iter = model.get_iter_root() while root_iter != None and model.get_value(root_iter, 0)[0] != host: root_iter = model.iter_next(root_iter) return root_iter def pwrkap_event(self, event_type, affected): """Acknowledge a pwrkap host event.""" handler = self.pwrkap_events[event_type] handler(affected) def host_connected(self, affected): """Handle a host connection.""" print "Host connected" self.connect_signal.acquire() self.connect_signal.notify() self.connect_signal.release() gtk.gdk.threads_enter() model = self.tree.get_model() host_iter = self.find_tree_iter_from_host(affected) print (affected, affected.host, affected.port) doms = [] for domain_name in affected.domains.keys(): domain_object = affected.domains[domain_name] doms.append(domain_object) iter = model.insert_after(host_iter, None) model.set_value(iter, 0, (affected, domain_object)) model.set_value(iter, 2, domain_name) gtk.gdk.threads_leave() agg = ui_data.pwrkap_aggregate_domain(doms, "All Domains") gtk.gdk.threads_enter() (x, y) = model.get_value(host_iter, 0) model.set_value(host_iter, 0, (x, agg)) gtk.gdk.threads_leave() self.update_all_aggregate() print "end processing new host" def host_disconnected(self, affected): """Handle a host disconnection.""" model = self.tree.get_model() host_iter = self.find_tree_iter_from_host(affected) dom_iter = model.iter_children(host_iter) gtk.gdk.threads_enter() while dom_iter != None: next = model.iter_next(dom_iter) model.remove(dom_iter) dom_iter = next (x, y) = model.get_value(host_iter, 0) model.set_value(host_iter, 0, (x, None)) gtk.gdk.threads_leave() self.update_all_aggregate() def update_all_aggregate(self): """Update the 'All Systems' aggregate.""" doms = [] model = self.tree.get_model() for host in self.hosts: for dom_name in host.domains.keys(): dom = host.domains[dom_name] doms.append(dom) agg = None if len(doms) != 0: agg = ui_data.pwrkap_aggregate_domain(doms, "All Systems") gtk.gdk.threads_enter() (x, y) = model.get_value(self.all_iter, 0) model.set_value(self.all_iter, 0, (x, agg)) gtk.gdk.threads_leave() self.select_power_object() def data_seen(self, affected): """Handle incoming snapshot data.""" if self.cur_dom != None and self.cur_dom.is_affected_by(affected): self.update_view() def create_graph(self): """Connect matplotlib graph to our gtk/glade window.""" global HAVE_MATPLOTLIB if not HAVE_MATPLOTLIB: return None # Extract GTK background color gtk_bgcolors = gtk.Style().bg bg_color_str = "#%X%X%X" % (round(float(gtk_bgcolors[2].red) / 65536 * 256), round(float(gtk_bgcolors[2].green) / 65536 * 256), round(float(gtk_bgcolors[2].blue) / 65536 * 256)) # Create figure fig = Figure() fig.set_facecolor(bg_color_str) fig.set_edgecolor(bg_color_str) canvas = FigureCanvas(fig) return (fig, canvas) def select_power_object_later(self, widget): """Select new object... later.""" thread.start_new_thread(self.select_power_object, ()) def select_power_object(self): """Slide a new power-manageable object into view. \ NOTE: The GDK lock must be held before calling this function.""" model = self.tree.get_model() (tree, iter) = self.tree.get_selection().get_selected() try: obj = model.get_value(iter, 0) except: return self.cur_host = obj[0] self.cur_dom = obj[1] self.selection_changed() def gtk_update_view(self, draw_graph): """Update all GTK controls.""" def time_to_age_string(time): """Convert seconds to age string.""" if time >= 86400: str = "%d Day" % (time / 86400) elif time >= 3600: str = "%d Hour" % (time / 3600) else: str = "%d Minute" % (time / 60) if str[0] != '1': str = str + "s" return str def fmt_energy(e): """Render energy use as a string.""" e = e / 3600 if e == None: return "Unknown" elif e < 1000: return "%.3fWh" % e elif e < 1000000: return "%.3fKWH" % (e / 1000) elif e < 1000000000: return "%.3fMWH" % (e / 1000000) else: return "%.3fGWH" % (e / 1000000000) global DEFAULT_TITLE, DRAW_GRAPH, HAVE_MATPLOTLIB self.in_gtk_update = True # Update GTK control enablement and values if self.cur_dom != None: # Enable controls self.window_tree.get_widget("power_cap_field").set_sensitive(True) self.window_tree.get_widget("max_sample_age").set_sensitive(True) self.window_tree.get_widget("set_cap_button").set_sensitive(True) # Set max cap if self.cur_dom.get_cap() != None and self.selection_did_change: new_val = 0 if self.dom_cap != None: new_val = self.dom_cap self.window_tree.get_widget("power_cap_field").set_value(new_val) # Set max record age widget = self.window_tree.get_widget("max_sample_age") if self.cur_dom.get_max_sample_age() == None: # Assume 30 minutes is ok (?) widget.set_active(2) else: age_str = time_to_age_string(self.cur_dom.get_max_sample_age()) for i in range(0, len(widget.get_model())): if widget.get_model()[i][0] == age_str: widget.set_active(i) break else: # Disable controls self.window_tree.get_widget("power_cap_field").set_sensitive(False) self.window_tree.get_widget("max_sample_age").set_sensitive(False) self.window_tree.get_widget("set_cap_button").set_sensitive(False) # Update window title title = " - " + DEFAULT_TITLE if self.cur_host != None: title = self.cur_host.get_name() + title if self.cur_dom != None: sep = "" if self.cur_host != None: sep = " : " title = self.cur_dom.get_name() + sep + title self.window.set_title(title) # Update more controls if self.dom_cap == None: self.window_tree.get_widget("power_cap_label").set_text("") self.window_tree.get_widget("power_use_label").set_text("") self.window_tree.get_widget("energy_use_label").set_text("") self.window_tree.get_widget("domain_use_label").set_text("") self.window_tree.get_widget("overcap_label").set_text("") else: self.window_tree.get_widget("power_cap_label").set_text("%dW" % int(self.dom_cap)) self.window_tree.get_widget("power_use_label").set_text("%dW" % int(self.dom_power)) self.window_tree.get_widget("energy_use_label").set_text(fmt_energy(self.dom_energy)) self.window_tree.get_widget("domain_use_label").set_text("%d%%" % self.dom_util) if self.dom_cap < self.dom_power: self.window_tree.get_widget("overcap_label").set_markup("Cap exceeded by %dW!" % (self.dom_power - self.dom_cap)) else: self.window_tree.get_widget("overcap_label").set_text("") # Draw graph if DRAW_GRAPH and self.graph_changed: atime=datetime.datetime.utcnow() self.canvas.draw() btime=datetime.datetime.utcnow() print ("elapsed draw time", (btime-atime)) elif not DRAW_GRAPH and HAVE_MATPLOTLIB: self.fig.clear() self.canvas.draw() self.selection_did_change = False self.graph_changed = False self.in_gtk_update = False def selection_changed(self): """Update the UI because the selection changed. NOTE: The GDK lock must be held before calling this function.""" self.selection_did_change = True self.update_view() def update_view(self): """Update the view. NOTE: The GDK lock must be held before calling this function.""" global UI_DRAW_INTERVAL now = datetime.datetime.utcnow() if not self.selection_did_change and (now - self.last_ui_draw_time).seconds < UI_DRAW_INTERVAL: return self.draw_graph() if self.cur_dom != None: self.dom_cap = self.cur_dom.get_cap() self.dom_power = self.cur_dom.get_power() self.dom_energy = self.cur_dom.get_energy() self.dom_util = pwrkap_data.average_utilization(self.cur_dom.get_utilization_details()) else: self.dom_cap = None self.dom_power = None self.dom_energy = None self.dom_util = None if not self.in_gtk_update: gobject.idle_add(self.gtk_update_view, ()) self.last_ui_draw_time = datetime.datetime.utcnow() def set_cap(self, widget): """Set a power cap due as a result of UI stimulus.""" widget = self.window_tree.get_widget("power_cap_field") if self.cur_dom == None: return self.cur_dom.set_cap(widget.get_value_as_int()) def set_max_sample_age(self, widget): """Set max sample age due as a result of UI stimulus.""" def age_to_time(age_string): """Convert age string to seconds.""" items = age_string.split() time = int(items[0]) units = items[1] if units.startswith("Minute"): time = time * 60 elif units.startswith("Hour"): time = time * 3600 elif units.startswith("Day"): time = time * 86400 return time if self.cur_dom == None: return age_str = widget.get_model()[widget.get_active()][0] age = age_to_time(age_str) self.cur_dom.set_max_sample_age(age) def draw_graph(self): """Draw the graph. NOTE: GDK lock must be held.""" if self.in_draw_graph: return self.graph_lock.acquire() self.in_draw_graph = True self.__draw_graph() self.in_draw_graph = False self.graph_lock.release() def __draw_graph(self): """Draw the graph. NOTE: Graph and GDK lock must be held.""" def time_to_age(time_list): """Convert a list of absolute times to ages.""" new_list = [] now = datetime.datetime.utcnow() nows = time.mktime(now.timetuple()) for t in time_list: new_list.append((nows - t) / 60) return new_list global GRAPH_DRAW_INTERVAL, DRAW_GRAPH, HAVE_MATPLOTLIB if not HAVE_MATPLOTLIB: return if self.cur_dom == None: self.fig.clear() return # Don't draw the graph too frequently if we can help it; # matplotlib is rather slow. now = datetime.datetime.utcnow() if not self.selection_did_change and (now - self.last_graph_draw_time).seconds < GRAPH_DRAW_INTERVAL: return self.last_graph_draw_time = now if not DRAW_GRAPH: return a_plot = datetime.datetime.utcnow() print "Start extract" res = self.cur_dom.get_historical_data_as_lists() if res == None: print "No results to plot?" return (times, cap, power, energy, util_details) = res times = time_to_age(times) b_plot = datetime.datetime.utcnow() print ("elapsed extract time", (b_plot - a_plot)) # Add plot self.fig.clear() self.fig.subplots_adjust(hspace = 0.4) power_axes = self.fig.add_subplot(2, 1, 1) power_axes.grid(True) power_axes.axis('auto') # Plot power data cap_line = power_axes.plot(times, cap, 'r-o', linewidth = 2, aa = True) power_line = power_axes.plot(times, power, 'b-o', linewidth = 2, aa = True) (ymin, ymax) = power_axes.set_ylim() power_axes.set_ylim(ymin = ymin - 5, ymax = ymax + 5) power_axes.set_ylabel("Watts") power_axes.set_title("Power Use vs. Time") power_axes.yaxis.tick_left() # Plot utilization data util_axes = self.fig.add_subplot(2, 1, 2) util_axes.grid(True) colors = ["#7F0000", "#007F00", "#00007F", "#C0C0C0", "#CCCC00", "#00CCCC", "#CC00CC"] color_index = 0 tr = times * 1 tr.reverse() times_reversed = (times * 1).reverse() last_y = [0] * len(times) t = times + tr num_plots = len(util_details.keys()) num_samples = len(times) for dev in util_details.keys(): curr_y = [] for i in range(0, num_samples): curr_y.append((util_details[dev][i] / num_plots) + last_y[i]) last_y.reverse() u = curr_y + last_y util_axes.fill(t, u, facecolor = colors[color_index], \ linewidth = 0, alpha = 0.8) last_y = curr_y color_index = color_index + 1 if color_index >= len(colors): color_index = 0 util_line = util_axes.plot(times, last_y, '-o', color = "black", \ linewidth = 2, aa = True) util_axes.set_ylim(ymin = -5, ymax = 105) util_axes.set_title("Device Use vs. Time") util_axes.set_ylabel("Use (%)") util_axes.set_xlabel("Minutes Ago") # Labels for the plots power_axes.legend((cap_line, power_line), ('Cap', 'Usage'), 'best') util_axes.legend((util_line,), ('Overall Use',), 'best') c_plot = datetime.datetime.utcnow() print ("elapsed plot time", (c_plot - b_plot)) #fp = file("/tmp/goo", "w") #for i in range(0, len(times)): # fp.write("%f %d %d %d\n" % (times[i], cap[i], power[i], util[i])) #fp.close() #self.canvas.draw() self.graph_changed = True pwrkap-7.30/pwrkap/ui_data.py000066400000000000000000000403241114434640500162400ustar00rootroot00000000000000#!/usr/bin/python """Various data objects for the UI.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import cPickle as pickle import socket import datetime import traceback import time import pwrkap_data import threading # Event types HOST_CONNECTED = 1 HOST_DISCONNECTED = 2 HOST_DOMAIN_DATA_RECEIVED = 3 DEFAULT_MAX_AGE = 1800 class historical_data: """Storage for power cap/use data.""" def __init__(self, max_age): """Create storage.""" self.clear() self.max_age = max_age def clear(self): """Clear all data.""" self.time = [] self.seconds = [] self.cap = [] self.power = [] self.energy = [] self.util_details = {} def get_max_sample_age(self): """Retrieve the maximum record age.""" return self.max_age def set_max_sample_age(self, new_max_age): """Set the maximum age that records are kept.""" self.max_age = new_max_age self.scrub_samples() def scrub_samples(self): """Remove old records.""" now = datetime.datetime.utcnow() for i in range(0, len(self.time)): if (now - self.time[i]).seconds <= self.max_age: del self.time[0:i] del self.seconds[0:i] del self.cap[0:i] del self.power[0:i] del self.energy[0:i] for dev in self.util_details.keys(): del self.util_details[dev][0:i] break def retrieve_as_lists(self, since = None): """Retrieve all data as a tuple of lists.""" if since == None: return (self.seconds, self.cap, self.power, \ self.energy, self.util_details) for i in range(0, len(self.time)): if self.time[i] > since: x = {} for dev in self.util_details.keys(): x[dev] = self.util_details[dev][i:] return (self.seconds[i:], self.cap[i:], \ self.power[i:], self.energy[i:], x) return None def store(self, time_stamp, cap, power, energy, util_details): """Store a snapshot of time, cap, power and utilization.""" if len(self.time) > 0: last_time = self.time[-1] if time_stamp <= last_time: return self.time.append(time_stamp) self.seconds.append(time.mktime(time_stamp.timetuple())) self.cap.append(cap) self.power.append(power) self.energy.append(energy) for dev in util_details.keys(): if not self.util_details.has_key(dev): self.util_details[dev] = [0] * (len(self.time) - 1) self.util_details[dev].append(util_details[dev]) self.scrub_samples() def get_most_recent(self): """Retrieve most recent samples.""" if len(self.time) == 0: return None idx = len(self.time) - 1 ud = {} for dev in self.util_details.keys(): ud[dev] = self.util_details[dev][-1] return (self.time[-1], self.cap[-1], self.power[-1], \ self.energy[-1], ud) class pwrkap_host: """Represents a connection to a pwrkap server.""" def __init__(self, host, port): """Connect to a given host:port for pwrkap data.""" self.host = host self.port = port self.domains = {} self.socket = None self.sockfile = None self.exit_loop = False self.event_listeners = [] def get_name(self): """Create name.""" return "%s:%d" % (self.host, self.port) def add_listener(self, obj): """Add an event listener.""" self.event_listeners.append(obj) def remove_listener(self, obj): """Remove an event listener.""" self.event_listeners.remove(obj) def connect(self): """Connect to the server.""" assert self.socket == None self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print "Connecting to %s:%d" % (self.host, self.port) self.socket.connect((self.host, self.port)) self.sockfile = self.socket.makefile() def set_up_socket(self): """Prepare socket for use.""" # Read list of (domain, domain_descr) tuples domains = pickle.load(self.sockfile) for (name, descr) in domains: phd = pwrkap_host_domain(self, self.port, name) self.domains[name] = phd # Read list of old snapshots (time, (domain, snapshot)) tuples # being careful to look for (time, 'live') live_time = None old_data = [] print (self, 1, datetime.datetime.utcnow()) while True: (time, obj) = pickle.load(self.sockfile) if obj == 'live': live_time = time break; old_data.append((time, obj)) print (self, 2, datetime.datetime.utcnow()) # Now recalibrate the timestamps. assert live_time != None now = datetime.datetime.utcnow() time_delta = now - live_time #1-2 and 3-4 are slow # Recalibrate the historical data and store for (time, obj) in old_data: (domain, snap) = obj x = (time + time_delta, snap) if "energy" in snap.keys(): energy = snap["energy"] else: energy = None src_ud = {} dst_ud = {} if snap.has_key("util_details"): src_ud = snap["util_details"] else: src_ud = {"dev": snap["utilization"]} for dev in src_ud: dst_ud["%s:%s:%s:%s" % (self.host, self.port, domain, dev)] = 100.0 * src_ud[dev] self.domains[domain].historical_data.store(time + time_delta, \ snap["cap"], snap["power"], energy, dst_ud) # Set initial values of cap/power/util to the last snapshot # regardless of how old it is for dom_name in self.domains.keys(): self.domains[dom_name].fudge_data() print (self, 3, datetime.datetime.utcnow()) def disconnect(self): """Disconnect from server and erase all data.""" assert self.socket != None self.sockfile.close() self.sockfile = None self.socket.shutdown(socket.SHUT_RDWR) self.socket = None self.domains = {} def dispatch(self): """Dispatch incoming data.""" while True: (time, obj) = pickle.load(self.sockfile) (dom, snap) = obj time = datetime.datetime.utcnow() self.domains[dom].receive_snapshot(time, snap) def exit_control_loop(self): """Exit control loop.""" assert self.socket != None self.exit_loop = True self.sockfile.close() self.socket.shutdown(socket.SHUT_RDWR) def control_loop(self): """Main control loop.""" self.exit_loop = False while not self.exit_loop: try: self.connect() except Exception, e: print e traceback.print_exc() self.socket = None time.sleep(1) continue try: self.set_up_socket() except Exception, e: print e traceback.print_exc() self.sockfile.close() self.socket.shutdown(socket.SHUT_RDWR) self.socket = None continue self.send_event(HOST_CONNECTED) try: self.dispatch() except Exception, e: print e traceback.print_exc() self.disconnect() self.send_event(HOST_DISCONNECTED) def send_command(self, command): """Send a command.""" assert self.sockfile != None pickled = pickle.dump(command, self.sockfile, pickle.HIGHEST_PROTOCOL) self.sockfile.flush() def send_event(self, event_type, affected_object = None): """Notify listeners of an event.""" if affected_object == None: affected_object = self for listener in self.event_listeners: try: listener.pwrkap_event(event_type, affected_object) except Exception, e: traceback.print_exc() print e class pwrkap_client_domain: """Abstract base class for power cap client domains.""" def receive_snapshot(self, time, snap): """Receive a snapshot.""" pass def send_command(self, command): """Send a command.""" pass def set_cap(self, new_cap): """Set a power cap.""" pass def set_max_sample_age(self, new_max_age): """Set the maximum sample age.""" pass def get_cap(self): """Retrieve this domain's power cap.""" pass def get_power(self): """Retrieve this domain's power usage.""" pass def get_energy(self): """Retrieve this domain's energy usage.""" pass def get_utilization_details(self): """Retrieve this domain's utilization data.""" pass def get_max_sample_age(self): """Retrieve this domain's maximum record age.""" pass def is_affected_by(self, other): """Return true if a change to the other domain affects this one.""" pass def get_historical_data_as_lists(self, since = None): """Retrieve sample data as a series of lists.""" pass def get_name(self): """Create name.""" pass class pwrkap_aggregate_domain(pwrkap_client_domain): """Aggregate of multiple power domains.""" def __init__(self, domains, name): """Create an aggregate domain of domains.""" self.domains = domains self.historical_data = historical_data(DEFAULT_MAX_AGE) self.domain_profiles = {} for dom in self.domains: self.domain_profiles[dom] = None self.last_aggregation = None self.aggregation_lock = threading.Lock() self.name = name def get_name(self): """Create name.""" return self.name def unfilled_domain_profiles(self): """Determine if we can use caching mechanism.""" for dom in self.domains: if self.domain_profiles[dom] == None: return True return False def aggregate_snapshots(self, since): """Aggregate snapshots together.""" self.aggregation_lock.acquire() self.__aggregate_snapshots_a(since) self.aggregation_lock.release() def __aggregate_snapshots_a(self, since): """Aggregate snapshots together (no locking).""" def find_next_sample(data): """Find the next sample to process.""" lowest_timestamp = None domain = None for dom in domain_data_keys: x = data[dom] if x[0] >= len(x[1]): continue if lowest_timestamp == None or x[1][x[0]] < lowest_timestamp: lowest_timestamp = x[1][x[0]] domain = dom if domain == None: return None dom_info = data[domain] cursor = dom_info[0] dom_info[0] = cursor + 1 # Pull out the utilization data ud = {} for dev in dom_info[5].keys(): ud[dev] = dom_info[5][dev][cursor] # return domain, timestamp, cap, power, energy, utilization return (domain, lowest_timestamp, dom_info[2][cursor], \ dom_info[3][cursor], dom_info[4][cursor], ud) # Figure out if we need a full scan or if we can re-use # previously calculated data last_time = self.last_aggregation if last_time == None or self.unfilled_domain_profiles(): self.historical_data.clear() last_time = since # Prepare our lists of items domain_data = {} to_read = 0 ztime = datetime.datetime.utcnow() for dom in self.domains: x = dom.get_historical_data_as_lists(last_time) if x == None: continue (t, c, p, e, ud) = x to_read = to_read + len(t) domain_data[dom] = [0, t, c, p, e, ud] print "Ugh, I have %d samples to read." % to_read # Process all samples in order atime = datetime.datetime.utcnow() print ("listmake time", (atime-ztime)) doms = self.domain_profiles.keys() domain_data_keys = domain_data.keys() sample = find_next_sample(domain_data) while sample != None: # Update the domain's profile d = sample[0] xtime = sample[1] self.domain_profiles[d] = sample[2:] sample = find_next_sample(domain_data) if sample != None: assert xtime <= sample[1] # Coalesce all profiles with the same timestamp. xtime = xtime - (xtime % 15) if sample != None: new_xtime = sample[1] - (sample[1] % 15) if new_xtime == xtime: continue # Otherwise, snapshot the world. cap = 0 power = 0 energy = 0 util_details = {} num_doms = len(doms) if num_doms == 0: continue for dom in doms: res = self.domain_profiles[dom] if res == None: num_doms = num_doms - 1 continue (c, p, e, ud) = res cap = cap + c power = power + p if e != None: energy = energy + e util_details.update(ud) utilization = pwrkap_data.average_utilization(util_details) utctime = datetime.datetime.fromtimestamp(xtime) self.historical_data.store(utctime, cap, power, \ energy, util_details) btime = datetime.datetime.utcnow() print ("aggloop", (btime-atime)) self.last_aggregation = datetime.datetime.utcnow() return def receive_snapshot(self, time, snap): """Receive a snapshot.""" assert False def send_command(self, command): """Send a command.""" assert False def set_cap(self, new_cap): """Set a power cap.""" # Figure out old caps so we can redistribute proportionally old_cap = 0 missing_domains = False for dom in self.domains: c = dom.get_cap() if c == None: missing_domains = True old_cap = old_cap + c if missing_domains: print "BOO! Some domain is missing a cap!" return # Now reallocate cap for dom in self.domains: dom.set_cap(new_cap * dom.get_cap() / old_cap) def set_max_sample_age(self, new_max_age): """Set the maximum sample age.""" for dom in self.domains: dom.set_max_sample_age(new_max_age) def get_cap(self): """Retrieve this domain's power cap.""" cap = 0 for dom in self.domains: c = dom.get_cap() if c != None: cap = cap + c return cap def get_power(self): """Retrieve this domain's power usage.""" power = 0 for dom in self.domains: p = dom.get_power() if p != None: power = power + p return power def get_energy(self): """Retrieve this domain's energy usage.""" energy = 0 for dom in self.domains: e = dom.get_energy() if e != None: energy = energy + e return energy def get_utilization_details(self): """Retrieve this domain's utilization.""" ud = {} for dom in self.domains: ud.update(dom.get_utilization_details()) return ud def get_max_sample_age(self): """Retrieve this domain's maximum record age.""" if len(self.domains) == 0: return None return self.domains[0].get_max_sample_age() def is_affected_by(self, other): """Return true if a change to the other domain affects this one.""" if other in self.domains: return True return False def get_historical_data_as_lists(self, since = None): """Retrieve sample data as a series of lists.""" if len(self.domains) == 1: return self.domains[0].get_historical_data_as_lists(since) if since == None: max_age = self.get_max_sample_age() td = datetime.timedelta(0, max_age) since = (datetime.datetime.utcnow() - td) self.aggregate_snapshots(since) return self.historical_data.retrieve_as_lists(since) class pwrkap_host_domain(pwrkap_client_domain): """Represents a power domain on a pwrkap server.""" def __init__(self, pwrkap_host, port, name): """Create a power domain.""" global DEFAULT_MAX_AGE self.historical_data = historical_data(DEFAULT_MAX_AGE) self.name = name self.host = pwrkap_host self.port = port self.cap = None self.power = None self.energy = None self.utilization_details = None self.last_event_trigger = datetime.datetime.utcnow() def get_name(self): """Create name.""" return self.name def receive_snapshot(self, time, snap): """Receive a snapshot.""" if snap.has_key("energy"): energy = snap["energy"] else: energy = None if snap.has_key("util_details"): ud = snap["util_details"] else: ud = {"dev": 100.0 * snap["utilization"]} dst_ud = {} for dev in ud: dst_ud["%s:%s:%s:%s" % (self.host.host, self.port, self.name, dev)] = 100.0 * ud[dev] self.historical_data.store(time, snap["cap"], snap["power"], \ energy, dst_ud) self.cap = snap["cap"] self.power = snap["power"] self.energy = energy self.utilization_details = dst_ud self.host.send_event(HOST_DOMAIN_DATA_RECEIVED, self) def get_historical_data_as_lists(self, since = None): """Retrieve sample data as a series of lists.""" return self.historical_data.retrieve_as_lists(since) def send_command(self, command): """Send a command.""" cmd = [self.name] for x in command: cmd.append(x) self.host.send_command(cmd) def set_cap(self, new_cap): """Set a power cap.""" self.cap = new_cap self.send_command(["cap", "%d" % new_cap]) def set_max_sample_age(self, new_max_age): """Set the maximum age that snapshots are kept.""" self.historical_data.set_max_sample_age(new_max_age) def fudge_data(self): """Set initial values of cached data to the last snapshot.""" res = self.historical_data.get_most_recent() if res == None: return (time, self.cap, self.power, self.energy, self.utilization_details) = res def get_cap(self): """Retrieve this domain's power cap.""" return self.cap def get_power(self): """Retrieve this domain's power usage.""" return self.power def get_energy(self): """Retrieve this domain's energy usage.""" return self.energy def get_utilization_details(self): """Return utilization details.""" return self.utilization_details def get_max_sample_age(self): """Retrieve this domain's maximum record age.""" return self.historical_data.get_max_sample_age() def is_affected_by(self, other): """Return true if a change to the other domain affects this one.""" if other == self: return True return False pwrkap-7.30/pwrkap/util.py000066400000000000000000000016651114434640500156140ustar00rootroot00000000000000#!/usr/bin/python """Helper routines.""" # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. import datetime def read_file_as_array(filename, delim = None): """Return the contents of a file as an array of arrays.""" lines = [] fp = None try: fp = open(filename) except: return None try: try: for line in fp: lines.append(line.split(delim)) except: return None return lines finally: fp.close() def read_line_as_array(filename): """Return the first line of a file as an array.""" x = read_file_as_array(filename) if x == None: return None return x[0] def write_file(filename, data): """Write some data to a sysfs file.""" fp = None try: fp = open(filename, "w") except: return False try: try: fp.write(data) except: return False return True finally: fp.close() def cmp_string_as_number(xs, ys): """Compare two strings numerically.""" x = int(xs) y = int(ys) return x - y pwrkap-7.30/redhat/000077500000000000000000000000001114434640500142205ustar00rootroot00000000000000pwrkap-7.30/redhat/init-script000077500000000000000000000024111114434640500164110ustar00rootroot00000000000000#!/bin/bash # # pwrkap This shell script takes care of starting and stopping # pwrkap # # chkconfig: - 20 70 # description: pwrkap monitors energy use and controls power use. # processname: pwrkap_main.py # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. ### BEGIN INIT INFO # Provides: pwrkap # Required-Start: # Should-Start: # Required-Stop: # Default-Start: 3 5 # Default-Stop: # Description: start pwrkap ### END INIT INFO # Source function library. [ -e /etc/init.d/functions ] && . /etc/init.d/functions #rhel5 [ -e /etc/rc.status ] && . /etc/rc.status #sles10 PATH="/sbin:/bin:/usr/sbin:/usr/bin" [ -x /usr/bin/pwrkap_main ] || exit 0 case "$1" in start) /usr/bin/pwrkap_main ;; stop) ps ax | grep -v grep | grep pwrkap_main.py | while read pid junk; do kill $pid; done ;; restart|force-reload) $0 stop $0 start ;; status) PWRKAPS=`ps ax | grep -v grep | grep pwrkap_main.py -c` if [ $PWRKAPS -lt 1 ]; then echo "pwrkap is not running." elif [ $PWRKAPS -gt 1 ]; then echo "Multiple copies of pwrkap are running. Turn some of them off." else echo "pwrkap is running." fi ;; *) echo "Usage: $0 {start|stop|restart|force-reload|status}" exit 1 ;; esac exit 0 pwrkap-7.30/redhat/pwrkap.spec000066400000000000000000000026201114434640500164000ustar00rootroot00000000000000# spec file for package pwrkap # (C) Copyright IBM Corp. 2008-2009 # Licensed under the GPLv2. %define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0) %define is_rhel %(test -e /etc/redhat-release && echo 1 || echo 0) Name: pwrkap Summary: Energy Monitor and Power Capping software for Linux. Version: 7.20 Release: 1 License: GPL Vendor: Darrick J. Wong BuildArch: noarch Group: Administration Source0: %{name}-%version.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-build %if %is_suse Requires: ipmitool python python-gtk %endif %if %is_rhel Requires: OpenIPMI-tools python pygtk2 %endif %description pwrkap is a set of utilities that monitor computer energy consumption and enforces an upper limit on the amount of power consumed by the computer at any given time. %prep echo Building %{name}-%{version}-%{release} %setup -q -n %{name} %build make PREFIX=%_prefix all %install make INST_PREFIX=%buildroot PREFIX=%_prefix install mkdir -p %buildroot/etc/init.d/ cp -pRdu redhat/init-script %buildroot/etc/init.d/pwrkap %clean make clean %post chkconfig pwrkap on %preun chkconfig pwrkap off %files %defattr(-,root,root) %dir %_prefix/lib/pwrkap/ %dir %_prefix/share/pwrkap/ /etc/init.d/pwrkap %_prefix/lib/pwrkap/* %_prefix/share/pwrkap/* %_prefix/bin/pwrkap_* %changelog * Mon May 21 2008 - djwong (at) us.ibm.com - Initial package creation