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

Annotation of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 30 - (hide 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 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    
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 apollock 29 def main():
155 apollock 30 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 apollock 29
173 apollock 30 config = load_config(options)
174     monitor_disks(config, options)
175    
176 apollock 29 if __name__ == "__main__":
177 apollock 30 main()

  ViewVC Help
Powered by ViewVC 1.1.22