/[svn.andrew.net.au]/usbspindownd/usbspindownd.py
ViewVC logotype

Annotation of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 33 - (hide annotations)
Thu Jan 31 07:58:04 2008 UTC (16 years, 7 months ago) by apollock
File MIME type: text/x-python
File size: 6182 byte(s)
Added signal handling

1 apollock 29 #!/usr/bin/python
2 apollock 30 # vi:set ts=2:et:sw=2
3 apollock 29 #
4     # Daemon to monitor /proc/diskstats and spin down USB drives determined to be
5     # idle
6     #
7     # Author: Andrew Pollock <me@andrew.net.au>
8     # Copyright: (c) 2008 Andrew Pollock <me@andrew.net.au>
9     #
10    
11     #
12     # Need a config file that contains the drives to watch and how long they must
13     # be idle for before spinning down
14     #
15     # Need to determine if sg_start is installed, and if so, where
16     #
17     # Need to monitor field 13 of /proc/diskstats for each drive. Value unchanged
18     # from last time means idle, value changed means not-idle and presumed spun-up
19     #
20     # Need to maintain a flag for when we've spun the drive down, so we don't keep
21     # spinning it down every time we think it's idle
22     #
23    
24 apollock 30 import diskstats
25     import syslog
26     import optparse
27     import ConfigParser
28     import sys
29     import os
30     import time
31 apollock 33 import signal
32 apollock 30
33     spindown_cmd = ""
34     spindown_cmd_args = ""
35 apollock 33 keep_running = True
36     signals = {}
37 apollock 30
38 apollock 33 def signal_handler(signal, stack):
39     global keep_running
40     global sigdict
41     keep_running = False
42 apollock 30
43 apollock 33
44     def getsignals():
45     signals = {}
46     for name in dir(signal):
47     if name.startswith("SIG"):
48     signals[getattr(signal, name)] = name
49     return signals
50    
51    
52 apollock 30 def search_path(filename, search_path):
53     file_found = False
54     paths = search_path.split(os.pathsep)
55     for path in paths:
56     if os.path.exists(os.path.join(path, filename)):
57     file_found = True
58     break
59     if file_found:
60     return os.path.abspath(os.path.join(path, filename))
61     else:
62     return None
63    
64    
65     def debug(options, message):
66     if options.debug:
67     print "%s: %s" % (time.asctime(time.localtime()), message)
68    
69 apollock 33
70     def log(message):
71     pass
72    
73    
74 apollock 30 def load_config(options):
75     global spindown_cmd, spindown_cmd_args
76     cf = ConfigParser.SafeConfigParser()
77     try:
78     debug(options, "Attempting to read %s" % options.config)
79     cf.readfp(open(options.config))
80     except IOError, e:
81     print "Got a '%s' trying to read %s" % (e.strerror, options.config)
82     sys.exit(1)
83     if cf.has_option("DEFAULT", "wait"):
84     defaultwait = cf.get("DEFAULT", "wait")
85     else:
86     defaultwait = 600;
87     config = {}
88     # TODO: Need to deal with a malformed config file here
89     for disk in cf.defaults()['disks'].split(","):
90     if cf.has_option(disk, "wait"):
91     wait = cf.get(disk, "wait")
92     else:
93     wait = defaultwait
94     config[disk] = { 'wait': wait, 'last_msio': 0, 'spun_down': False, 'timestamp': 0 }
95     if cf.has_option("DEFAULT", "spindown_cmd"):
96     spindown_cmd = cf.get("DEFAULT", "spindown_cmd")
97     if cf.has_option("DEFAULT", "spindown_cmd_args"):
98     spindown_cmd_args = cf.get("DEFAULT", "spindown_cmd_args")
99     if not spindown_cmd:
100     # Try searching for sg_start
101     spindown_cmd = search_path("sg_start", os.getenv("PATH"))
102     if spindown_cmd:
103     spindown_cmd_args = "--stop --pc=2"
104     else:
105     # We couldn't find anything to spin down the disks
106     print "No disk spinning down command specified or found in $PATH"
107     sys.exit(1)
108     debug(options, "Configuration loaded:")
109     debug(options, "spindown_cmd: %s" % (spindown_cmd))
110     debug(options, "spindown_cmd_args: %s" % (spindown_cmd_args))
111     if options.debug:
112     for disk in config:
113     debug(options, "%s: %s" % (disk, config[disk]))
114     return config
115    
116    
117     def spin_down(options, disk):
118     if not options.noop:
119     if spindown_cmd_args:
120     return os.system("%s %s %s" % (spindown_cmd, spindown_cmd_args, disk)) >> 8
121     else:
122     return os.system("%s %s" % (spindown_cmd, disk)) >> 8
123     else:
124     debug(options, "Not really spinning down the disk")
125     return 0
126    
127    
128     def monitor_disks(config, options):
129 apollock 33 global keep_running
130 apollock 30 debug(options, "Monitoring disks")
131 apollock 33 while keep_running:
132 apollock 30 for disk in config:
133     debug(options, "Considering %s" % (disk))
134     ds = diskstats.DiskStats(disk)
135     msio = None
136     try:
137     msio = ds.diskstat("msio")
138     except diskstats.Error, e:
139     # This disk doesn't exist at this time
140     debug(options, "%s is not present" % (disk))
141     continue
142     if config[disk]["last_msio"] == 0:
143     debug(options, "First time we've considered this disk")
144     config[disk]["last_msio"] = msio
145     config[disk]["timestamp"] = int(time.time())
146     else:
147     if msio == config[disk]["last_msio"]:
148     debug(options, "Disk has been idle since last considered")
149     now = int(time.time())
150     if (now - config[disk]["timestamp"]) >= config[disk]["wait"]:
151     debug(options, "Disk eligible for spinning down")
152     # We can spin this disk down
153     if not config[disk]["spun_down"]:
154     if spin_down(disk):
155     debug(options, "Disk spun down")
156     config[disk]["spun_down"] = True
157     else:
158     raise Error("Failed to spin down %s" % disk)
159     else:
160     debug(options, "Disk already spun down")
161     else:
162     # This disk is ineligible for spinning down at this time
163 apollock 31 debug(options, "Disk idle for %s seconds, but not for long enough (%s)" % (now - config[disk]["timestamp"], config[disk]["wait"]))
164 apollock 30 else:
165     debug(options, "Disk not idle (old msio: %s, current msio: %s)" % (config[disk]["last_msio"], msio))
166     config[disk]["last_msio"] = msio
167     config[disk]["timestamp"] = int(time.time())
168     config[disk]["spun_down"] = False
169     debug(options, "Sleeping")
170     time.sleep(60)
171 apollock 33 debug(options, "Shutting down")
172 apollock 30
173 apollock 29 def main():
174 apollock 33 global options
175     global signals
176     signals = getsignals()
177     signal.signal(signal.SIGTERM, signal_handler)
178     signal.signal(signal.SIGINT, signal_handler)
179 apollock 30 parser = optparse.OptionParser()
180     parser.add_option("-n", "--dry-run",
181     action="store_true",
182     dest="noop",
183     default=False,
184     help="Don't do anything, just log what would be done")
185     parser.add_option("-c", "--config",
186     action="store",
187     dest="config",
188     default="/etc/usbspindownd.conf",
189     help="Configuration file for usbspindownd")
190     parser.add_option("-d", "--debug",
191     action="store_true",
192     dest="debug",
193     default=False,
194     help="Turn on extra debugging")
195     (options, args) = parser.parse_args()
196 apollock 29
197 apollock 30 config = load_config(options)
198     monitor_disks(config, options)
199    
200 apollock 29 if __name__ == "__main__":
201 apollock 30 main()

  ViewVC Help
Powered by ViewVC 1.1.22