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

Diff of /usbspindownd/usbspindownd.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 29 by apollock, Tue Jan 29 19:48:35 2008 UTC revision 37 by apollock, Fri Feb 1 02:44:52 2008 UTC
# Line 1  Line 1 
1  #!/usr/bin/python  #!/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  # Daemon to monitor /proc/diskstats and spin down USB drives determined to be
5  # idle  # idle
# Line 21  Line 21 
21  # spinning it down every time we think it's idle  # spinning it down every time we think it's idle
22  #  #
23    
24  def main():  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          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):
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              config[disk]["last_msio"] = msio
172              config[disk]["timestamp"] = int(time.time())
173              if config[disk]["spun_down"]:
174                debug(options, "%s presumed spun back up by activity" % (disk))
175                config[disk]["spun_down"] = False
176              debug(options, "Disk not idle (old msio: %s, current msio: %s)" % (config[disk]["last_msio"], msio))
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__":  if __name__ == "__main__":
209          main()    main()

Legend:
Removed from v.29  
changed lines
  Added in v.37

  ViewVC Help
Powered by ViewVC 1.1.22