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

Contents of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


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

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 def signal_handler(signal, stack):
39 global keep_running
40 global sigdict
41 keep_running = False
42
43
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 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
70 def log(message):
71 pass
72
73
74 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 global keep_running
130 debug(options, "Monitoring disks")
131 while keep_running:
132 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 debug(options, "Disk idle for %s seconds, but not for long enough (%s)" % (now - config[disk]["timestamp"], config[disk]["wait"]))
164 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 debug(options, "Shutting down")
172
173 def main():
174 global options
175 global signals
176 signals = getsignals()
177 signal.signal(signal.SIGTERM, signal_handler)
178 signal.signal(signal.SIGINT, signal_handler)
179 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
197 config = load_config(options)
198 monitor_disks(config, options)
199
200 if __name__ == "__main__":
201 main()

  ViewVC Help
Powered by ViewVC 1.1.22