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

Annotation of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 36 - (hide annotations)
Thu Jan 31 19:22:32 2008 UTC (13 years, 10 months ago) by apollock
File MIME type: text/x-python
File size: 6410 byte(s)
Correct invocation of spin_down()

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 apollock 35 if (now - config[disk]["timestamp"]) >= int(config[disk]["wait"]):
153 apollock 30 debug(options, "Disk eligible for spinning down")
154     # We can spin this disk down
155     if not config[disk]["spun_down"]:
156 apollock 36 if spin_down(options, disk):
157 apollock 30 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     config[disk]["last_msio"] = msio
168     config[disk]["timestamp"] = int(time.time())
169 apollock 36 if config[disk]["spun_down"]:
170     debug(options, "%s presumed spun back up by activity" % (disk))
171     config[disk]["spun_down"] = False
172     debug(options, "Disk not idle (old msio: %s, current msio: %s)" % (config[disk]["last_msio"], msio))
173 apollock 30 debug(options, "Sleeping")
174     time.sleep(60)
175 apollock 33 debug(options, "Shutting down")
176 apollock 30
177 apollock 29 def main():
178 apollock 33 global options
179     global signals
180     signals = getsignals()
181     signal.signal(signal.SIGTERM, signal_handler)
182     signal.signal(signal.SIGINT, signal_handler)
183 apollock 30 parser = optparse.OptionParser()
184     parser.add_option("-n", "--dry-run",
185     action="store_true",
186     dest="noop",
187     default=False,
188     help="Don't do anything, just log what would be done")
189     parser.add_option("-c", "--config",
190     action="store",
191     dest="config",
192     default="/etc/usbspindownd.conf",
193     help="Configuration file for usbspindownd")
194     parser.add_option("-d", "--debug",
195     action="store_true",
196     dest="debug",
197     default=False,
198     help="Turn on extra debugging")
199     (options, args) = parser.parse_args()
200 apollock 29
201 apollock 30 config = load_config(options)
202     monitor_disks(config, options)
203    
204 apollock 29 if __name__ == "__main__":
205 apollock 30 main()

  ViewVC Help
Powered by ViewVC 1.1.22