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

Annotation of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 39 - (hide annotations)
Sat Feb 2 18:16:36 2008 UTC (16 years, 8 months ago) by apollock
File MIME type: text/x-python
File size: 6447 byte(s)
Logged in the wrong spot

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 37 class Error(Exception):
39     pass
40    
41    
42 apollock 33 def signal_handler(signal, stack):
43     global keep_running
44     keep_running = False
45 apollock 30
46 apollock 33
47     def getsignals():
48     signals = {}
49     for name in dir(signal):
50     if name.startswith("SIG"):
51     signals[getattr(signal, name)] = name
52     return signals
53    
54    
55 apollock 30 def search_path(filename, search_path):
56     file_found = False
57     paths = search_path.split(os.pathsep)
58     for path in paths:
59     if os.path.exists(os.path.join(path, filename)):
60     file_found = True
61     break
62     if file_found:
63     return os.path.abspath(os.path.join(path, filename))
64     else:
65     return None
66    
67    
68     def debug(options, message):
69     if options.debug:
70     print "%s: %s" % (time.asctime(time.localtime()), message)
71    
72 apollock 33
73     def log(message):
74     pass
75    
76    
77 apollock 30 def load_config(options):
78     global spindown_cmd, spindown_cmd_args
79     cf = ConfigParser.SafeConfigParser()
80     try:
81     debug(options, "Attempting to read %s" % options.config)
82     cf.readfp(open(options.config))
83     except IOError, e:
84     print "Got a '%s' trying to read %s" % (e.strerror, options.config)
85     sys.exit(1)
86 apollock 34 except ConfigParser.MissingSectionHeaderError, e:
87     print "Parser error: '%s'" % (e.message)
88     sys.exit(1)
89 apollock 30 if cf.has_option("DEFAULT", "wait"):
90     defaultwait = cf.get("DEFAULT", "wait")
91     else:
92     defaultwait = 600;
93     config = {}
94     # TODO: Need to deal with a malformed config file here
95     for disk in cf.defaults()['disks'].split(","):
96     if cf.has_option(disk, "wait"):
97     wait = cf.get(disk, "wait")
98     else:
99     wait = defaultwait
100     config[disk] = { 'wait': wait, 'last_msio': 0, 'spun_down': False, 'timestamp': 0 }
101     if cf.has_option("DEFAULT", "spindown_cmd"):
102     spindown_cmd = cf.get("DEFAULT", "spindown_cmd")
103     if cf.has_option("DEFAULT", "spindown_cmd_args"):
104     spindown_cmd_args = cf.get("DEFAULT", "spindown_cmd_args")
105     if not spindown_cmd:
106     # Try searching for sg_start
107     spindown_cmd = search_path("sg_start", os.getenv("PATH"))
108     if spindown_cmd:
109     spindown_cmd_args = "--stop --pc=2"
110     else:
111     # We couldn't find anything to spin down the disks
112     print "No disk spinning down command specified or found in $PATH"
113     sys.exit(1)
114     debug(options, "Configuration loaded:")
115     debug(options, "spindown_cmd: %s" % (spindown_cmd))
116     debug(options, "spindown_cmd_args: %s" % (spindown_cmd_args))
117     if options.debug:
118     for disk in config:
119     debug(options, "%s: %s" % (disk, config[disk]))
120     return config
121    
122    
123     def spin_down(options, disk):
124     if not options.noop:
125     if spindown_cmd_args:
126     return os.system("%s %s %s" % (spindown_cmd, spindown_cmd_args, disk)) >> 8
127     else:
128     return os.system("%s %s" % (spindown_cmd, disk)) >> 8
129     else:
130     debug(options, "Not really spinning down the disk")
131     return 0
132    
133    
134     def monitor_disks(config, options):
135 apollock 33 global keep_running
136 apollock 30 debug(options, "Monitoring disks")
137 apollock 33 while keep_running:
138 apollock 30 for disk in config:
139     debug(options, "Considering %s" % (disk))
140     ds = diskstats.DiskStats(disk)
141     msio = None
142     try:
143     msio = ds.diskstat("msio")
144     except diskstats.Error, e:
145     # This disk doesn't exist at this time
146     debug(options, "%s is not present" % (disk))
147     continue
148     if config[disk]["last_msio"] == 0:
149     debug(options, "First time we've considered this disk")
150     config[disk]["last_msio"] = msio
151     config[disk]["timestamp"] = int(time.time())
152     else:
153     if msio == config[disk]["last_msio"]:
154     debug(options, "Disk has been idle since last considered")
155     now = int(time.time())
156 apollock 35 if (now - config[disk]["timestamp"]) >= int(config[disk]["wait"]):
157 apollock 30 debug(options, "Disk eligible for spinning down")
158     # We can spin this disk down
159     if not config[disk]["spun_down"]:
160 apollock 38 if spin_down(options, disk) == 0:
161 apollock 30 debug(options, "Disk spun down")
162     config[disk]["spun_down"] = True
163     else:
164     raise Error("Failed to spin down %s" % disk)
165     else:
166     debug(options, "Disk already spun down")
167     else:
168     # This disk is ineligible for spinning down at this time
169 apollock 31 debug(options, "Disk idle for %s seconds, but not for long enough (%s)" % (now - config[disk]["timestamp"], config[disk]["wait"]))
170 apollock 30 else:
171 apollock 39 debug(options, "Disk not idle (old msio: %s, current msio: %s)" % (config[disk]["last_msio"], msio))
172 apollock 30 config[disk]["last_msio"] = msio
173     config[disk]["timestamp"] = int(time.time())
174 apollock 36 if config[disk]["spun_down"]:
175     debug(options, "%s presumed spun back up by activity" % (disk))
176     config[disk]["spun_down"] = False
177 apollock 30 debug(options, "Sleeping")
178     time.sleep(60)
179 apollock 33 debug(options, "Shutting down")
180 apollock 30
181 apollock 29 def main():
182 apollock 33 global options
183     global signals
184     signals = getsignals()
185     signal.signal(signal.SIGTERM, signal_handler)
186     signal.signal(signal.SIGINT, signal_handler)
187 apollock 30 parser = optparse.OptionParser()
188     parser.add_option("-n", "--dry-run",
189     action="store_true",
190     dest="noop",
191     default=False,
192     help="Don't do anything, just log what would be done")
193     parser.add_option("-c", "--config",
194     action="store",
195     dest="config",
196     default="/etc/usbspindownd.conf",
197     help="Configuration file for usbspindownd")
198     parser.add_option("-d", "--debug",
199     action="store_true",
200     dest="debug",
201     default=False,
202     help="Turn on extra debugging")
203     (options, args) = parser.parse_args()
204 apollock 29
205 apollock 30 config = load_config(options)
206     monitor_disks(config, options)
207    
208 apollock 29 if __name__ == "__main__":
209 apollock 30 main()

  ViewVC Help
Powered by ViewVC 1.1.22