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

Annotation of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.22