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

Contents of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


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

1 #!/usr/bin/python
2 # vi:set ts=2:et:sw=2
3 #
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 import diskstats
25 import syslog
26 import optparse
27 import ConfigParser
28 import sys
29 import os
30 import time
31 import signal
32
33 spindown_cmd = ""
34 spindown_cmd_args = ""
35 keep_running = True
36 signals = {}
37
38 class Error(Exception):
39 pass
40
41
42 def signal_handler(signal, stack):
43 global keep_running
44 keep_running = False
45
46
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 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
73 def log(message):
74 pass
75
76
77 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 except ConfigParser.MissingSectionHeaderError, e:
87 print "Parser error: '%s'" % (e.message)
88 sys.exit(1)
89 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 global keep_running
136 debug(options, "Monitoring disks")
137 while keep_running:
138 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 if (now - config[disk]["timestamp"]) >= int(config[disk]["wait"]):
157 debug(options, "Disk eligible for spinning down")
158 # We can spin this disk down
159 if not config[disk]["spun_down"]:
160 if spin_down(options, disk) == 0:
161 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 debug(options, "Disk idle for %s seconds, but not for long enough (%s)" % (now - config[disk]["timestamp"], config[disk]["wait"]))
170 else:
171 debug(options, "Disk not idle (old msio: %s, current msio: %s)" % (config[disk]["last_msio"], msio))
172 config[disk]["last_msio"] = msio
173 config[disk]["timestamp"] = int(time.time())
174 if config[disk]["spun_down"]:
175 debug(options, "%s presumed spun back up by activity" % (disk))
176 config[disk]["spun_down"] = False
177 debug(options, "Sleeping")
178 time.sleep(60)
179 debug(options, "Shutting down")
180
181 def main():
182 global options
183 global signals
184 signals = getsignals()
185 signal.signal(signal.SIGTERM, signal_handler)
186 signal.signal(signal.SIGINT, signal_handler)
187 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
205 config = load_config(options)
206 monitor_disks(config, options)
207
208 if __name__ == "__main__":
209 main()

  ViewVC Help
Powered by ViewVC 1.1.22