~ubuntu-branches/ubuntu/natty/powernap/natty-proposed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/usr/bin/python
#
#    powernapd - monitor a system process table; if IDLE amount of time
#               goes by with no MONITORED_PROCESSES running, run ACTION
#
#    Copyright (C) 2009-2011 Canonical Ltd.
#
#    Authors: Dustin Kirkland <kirkland@canonical.com>
#             Andres Rodriguez <andreserl@canonical.com>
#
#    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, version 3 of the License.
#
#    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, see <http://www.gnu.org/licenses/>.

# Imports
import commands
import logging, logging.handlers
import os
import re
import signal
import sys
import time
import socket, traceback
import struct
from powernap import powernap


# Initialize Powernap. This initialization loads the config file.
try:
    powernap = powernap.PowerNap()
    os.putenv("ACTION_METHOD", str(powernap.ACTION_METHOD))
except:
    print("Unable to initialize PowerNap")
    sys.exit(1)

# Define globals
global LOCK, CONFIG, MONITORS, in_powersave_mode
LOCK = "/var/run/%s.pid" % powernap.PKG
LOG = "/var/log/%s.log" % powernap.PKG
in_powersave_mode = False

logging.basicConfig(filename=LOG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d_%H:%M:%S', level=logging.DEBUG,)

# Generic debug function
def debug(level, msg):
    if level >= (logging.ERROR - 10*powernap.DEBUG):
        logging.log(level, msg)

# Generic error function
def error(msg):
    debug(logging.ERROR, msg)
    sys.exit(1)

# Lock function, using a pidfile in /var/run
def establish_lock():
    if os.path.exists(LOCK):
        f = open(LOCK,'r')
        pid = f.read()
        f.close()
        error("Another instance is running [%s]" % pid)
    else:
        try:
            f = open(LOCK,'w')
        except:
            error("Administrative privileges are required to run %s" % powernap.PKG);
        f.write(str(os.getpid()))
        f.close()
        # Set signal handlers
        signal.signal(signal.SIGHUP, signal_handler)
        signal.signal(signal.SIGINT, signal_handler)
        signal.signal(signal.SIGQUIT, signal_handler)
        signal.signal(signal.SIGTERM, signal_handler)
        signal.signal(signal.SIGIO, signal.SIG_IGN)
        #signal.signal(signal.SIGIO, input_singal_handler)
        signal.signal(signal.SIGUSR1, take_action_handler)
        signal.signal(signal.SIGUSR2, take_recover_action_handler)

# Clean up lock file on termination signals
def signal_handler(signal, frame):
    if os.path.exists(LOCK):
        os.remove(LOCK)
    debug(logging.INFO, "Stopping %s" % powernap.PKG)
    sys.exit(1)

# Handler warns users when an USB Input device has been connected or
# disconnected. This is only useful for [InputMonitor] to avoid errors.
def input_signal_handler(signal, frame):
    debug(logging.WARNING, "A monitored [InputMonitor] device has been disconnected or reconnected.")

# Send a message to system users, that we're about to take an action,
# and sleep for a grace period
def warn_users():
    timestamp = time.strftime("%Y-%m-%d_%H:%M:%S")
    msg1 = "[%s] PowerNap will take the following action in [%s] seconds: [%s]" % (timestamp, powernap.GRACE_SECONDS, powernap.ACTION)
    msg2 = "To cancel this operation, press any key in any terminal"
    debug(logging.WARNING, msg1)
    if powernap.WARN:
        commands.getoutput("echo '%s\n%s' | wall" % (msg1, msg2))

# TODO: notify authorities about action taken
def notify_authorities():
    debug(logging.WARNING, "Taking action [%s]" % powernap.ACTION)

# Recover action for ACTION_METHOD=0 (pm-powersave).
def take_recover_action():
    debug(logging.WARNING, "Taking recover action [%s]" % powernap.RECOVER_ACTION)
    os.system(powernap.RECOVER_ACTION)
    debug(logging.DEBUG, "Reseting counters after taking recover action")
    for monitor in MONITORS:
        monitor._absent_seconds = 0

# Handler for asynchronous external signals
def take_recover_action_handler(signal, frame):
    global in_powersave_mode
    if powernap.ACTION_METHOD == 0 and in_powersave_mode:
        take_recover_action()
        in_powersave_mode = False

# Zero the counters and take the action
def take_action():
    notify_authorities()
    debug(logging.DEBUG, "Reseting counters prior to taking action")
    for monitor in MONITORS:
        monitor._absent_seconds = 0
    os.system(powernap.ACTION)

# Handler for asynchronous external signals
def take_action_handler(signal, frame):
    global in_powersave_mode
    take_action()
    if powernap.ACTION_METHOD == 0:
        in_powersave_mode = True

def powernapd_loop():
    global in_powersave_mode
    # Starting the Monitors
    for monitor in MONITORS:
        debug(logging.DEBUG, "Starting [%s:%s]" % (monitor._type, monitor._name))
        monitor.start()

    grace_seconds = powernap.GRACE_SECONDS
    in_powersave_mode = False
    users_warned = False

    while 1:
        debug(logging.DEBUG, "Sleeping [%d] seconds" % powernap.INTERVAL_SECONDS)
        time.sleep(powernap.INTERVAL_SECONDS)
        # Examine monitor activity, compute absent time of each monitored monitor process
        debug(logging.DEBUG, "Examining Monitors")
        absent_monitors = 0
        grace_monitors = 0
        for monitor in MONITORS:
            debug(logging.DEBUG, "  Looking for [%s] %s" % (monitor._name, monitor._type))
            if monitor.active():
                monitor._absent_seconds = 0
                grace_seconds = powernap.GRACE_SECONDS
                users_warned = False
                debug(logging.DEBUG, "    Activity found, reset absent time [%d/%d]" % (monitor._absent_seconds, powernap.ABSENT_SECONDS))
                if in_powersave_mode and powernap.ACTION_METHOD == 0:
                    take_recover_action()
                    in_powersave_mode = False
                    break
            elif in_powersave_mode:
               # activity during POWERSAVE mode. Only increments absent_seconds and provides logging.
               monitor._absent_seconds += powernap.INTERVAL_SECONDS
               debug(logging.DEBUG, "    Activity not found, increment absent time [%d/%d]" % (monitor._absent_seconds, powernap.ABSENT_SECONDS))
            else:
                # activity not found, increment absent time
                monitor._absent_seconds += powernap.INTERVAL_SECONDS
                debug(logging.DEBUG, "    Activity not found, increment absent time [%d/%d]" % (monitor._absent_seconds, powernap.ABSENT_SECONDS))

                if monitor._absent_seconds >= (powernap.ABSENT_SECONDS - powernap.GRACE_SECONDS):
                    # If monitor is absent for (ABSENT_SECONDS - GRACE_SECONDS), we consider it in GRACE PERIOD.
                    grace_monitors += 1

                if monitor._absent_seconds >= powernap.ABSENT_SECONDS:
                    # activity missing for >= absent_seconds threshold, mark absent
                    debug(logging.DEBUG, "    Activity absent for >= threshold, so mark absent")
                    absent_monitors += 1

        # GRACE PERIOD: Time between ABSENT_SECONDS and (ABSENT_SECONDS - GRACE_SECONDS)
        # If all monitors are in their own GRACE period
        if grace_monitors > 0 and grace_monitors == len(MONITORS):
            if users_warned is False and powernap.ACTION_METHOD != 0:
                # Only display warn_users() wall message if action is something other than
		# powersave, and only when initially entering to GRACE_PERIOD.
                # If in GRACE_PERIOD but a warn_users() already issued, then ignore condition.
                warn_users()
                users_warned = True
            debug(logging.WARNING, "Entered into GRACE PERIOD. Action [%s] will be taken in [%d] seconds" % (powernap.ACTION, grace_seconds))
            grace_seconds -= powernap.INTERVAL_SECONDS
            if grace_seconds == -1:
                # Reset flags to original if powerwake finished GRACE PERIOD and ready to take action
                grace_seconds = powernap.GRACE_SECONDS
                users_warned = False

        # Determine if action needs to be taken
        if absent_monitors > 0 and absent_monitors == len(MONITORS):
            take_action()

            # If ACTION_METHOD is PowerSave then flag. Use is when tracking Monitor Activity
            if powernap.ACTION_METHOD == 0:
                in_powersave_mode = True


# "Forking a Daemon Process on Unix" from The Python Cookbook
def daemonize (stdin="/dev/null", stdout="/var/log/%s.log" % powernap.PKG, stderr="/var/log/%s.err" % powernap.PKG):
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        sys.stderr.write("fork #1 failed: (%d) %sn" % (e.errno, e.strerror))
        sys.exit(1)
    os.chdir("/")
    os.setsid()
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        sys.stderr.write("fork #2 failed: (%d) %sn" % (e.errno, e.strerror))
        sys.exit(1)
    f = open(LOCK,'w')
    f.write(str(os.getpid()))
    f.close()
    for f in sys.stdout, sys.stderr: f.flush()
    si = file(stdin, 'r')
    so = file(stdout, 'a+')
    se = file(stderr, 'a+', 0)
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())


# Main program
if __name__ == '__main__':
    # Ensure that only one instance runs
    establish_lock()
    daemonize()
    try:
        # Run the main powernapd loop
        MONITORS = powernap.get_monitors()
        debug(logging.INFO, "Starting %s" % powernap.PKG)
        powernapd_loop()
    finally:
        # Clean up the lock file
        if os.path.exists(LOCK):
            os.remove(LOCK)