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

Contents of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 30 - (show annotations)
Thu Jan 31 02:21:39 2008 UTC (13 years, 10 months ago) by apollock
File MIME type: text/x-python
File size: 5755 byte(s)
Preliminary working code

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

  ViewVC Help
Powered by ViewVC 1.1.22