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

Contents of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 35 - (show annotations)
Thu Jan 31 19:06:27 2008 UTC (15 years, 4 months ago) by apollock
File MIME type: text/x-python
File size: 6283 byte(s)
intify ints in the config

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 keep_running = False
41
42
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 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
69 def log(message):
70 pass
71
72
73 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 except ConfigParser.MissingSectionHeaderError, e:
83 print "Parser error: '%s'" % (e.message)
84 sys.exit(1)
85 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 global keep_running
132 debug(options, "Monitoring disks")
133 while keep_running:
134 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"]) >= int(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 debug(options, "Disk idle for %s seconds, but not for long enough (%s)" % (now - config[disk]["timestamp"], config[disk]["wait"]))
166 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 debug(options, "Shutting down")
174
175 def main():
176 global options
177 global signals
178 signals = getsignals()
179 signal.signal(signal.SIGTERM, signal_handler)
180 signal.signal(signal.SIGINT, signal_handler)
181 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
199 config = load_config(options)
200 monitor_disks(config, options)
201
202 if __name__ == "__main__":
203 main()

  ViewVC Help
Powered by ViewVC 1.1.22