3 # Copyright (C) 2011, 2012 GRNET S.A.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 """Utility to monitor the progress of image deployment
22 A small utility to monitor the progress of image deployment
23 by watching the contents of /proc/<pid>/io and producing
35 MSG_TYPE = "image-copy-progress"
38 def parse_arguments(args):
39 from optparse import OptionParser
42 kw['usage'] = "%prog [options] command [args...]"
44 "%prog runs 'command' with the specified arguments, monitoring the " \
45 "number of bytes read by it. 'command' is assumed to be " \
46 "A program used to install the OS for a Ganeti instance. %prog " \
47 "periodically issues notifications of type 'copy-progress'."
49 parser = OptionParser(**kw)
50 parser.disable_interspersed_args()
51 parser.add_option("-r", "--read-bytes",
52 action="store", type="int", dest="read_bytes",
53 metavar="BYTES_TO_READ",
54 help="The expected number of bytes to be read, "
55 "used to compute input progress",
58 "-o", "--output_fd", dest="output", default=None, metavar="FILE",
59 type="int", help="Write output notifications to this file descriptor")
61 (opts, args) = parser.parse_args(args)
63 if opts.read_bytes is None:
64 sys.stderr.write("Fatal: Option '-r' is mandatory.\n")
68 if opts.output is None:
69 sys.stderr.write("Fatal: Option '-o' is mandatory.\n")
74 sys.stderr.write("Fatal: You need to specify the command to run.\n")
81 def report_wait_status(pid, status):
82 if os.WIFEXITED(status):
83 sys.stderr.write("Child PID = %d exited, status = %d\n" %
84 (pid, os.WEXITSTATUS(status)))
85 elif os.WIFSIGNALED(status):
86 sys.stderr.write("Child PID = %d died by signal, signal = %d\n" %
87 (pid, os.WTERMSIG(status)))
88 elif os.WIFSTOPPED(status):
89 sys.stderr.write("Child PID = %d stopped by signal, signal = %d\n" %
90 (pid, os.WSTOPSIG(status)))
92 sys.stderr.write("Internal error: Unhandled case, "
93 "PID = %d, status = %d\n" % (pid, status))
98 def send_message(to, message):
99 message['timestamp'] = time.time()
100 os.write(to, "%s\n" % json.dumps(message))
104 (opts, args) = parse_arguments(sys.argv[1:])
110 # Make sure we die with the parent and are not left behind
111 # WARNING: This uses the prctl(2) call and is Linux-specific.
112 prctl.set_pdeathsig(signal.SIGHUP)
114 # exec command specified in arguments,
115 # searching the $PATH, keeping all environment
116 os.execvpe(args[0], args, os.environ)
117 sys.stderr.write("execvpe failed, exiting with non-zero status")
121 iofname = "/proc/%d/io" % pid
122 iof = open(iofname, "r", 0) # 0: unbuffered open
123 sys.stderr.write("%s: created child PID = %d, monitoring file %s\n" %
124 (sys.argv[0], pid, iofname))
127 message['type'] = MSG_TYPE
128 message['total'] = opts.read_bytes
131 # check if the child process is still alive
132 (wpid, status) = os.waitpid(pid, os.WNOHANG)
134 report_wait_status(pid, status)
135 if (os.WIFEXITED(status) or os.WIFSIGNALED(status)):
136 if not (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0):
139 message['position'] = message['total']
140 message['progress'] = float(100)
141 send_message(out, message)
145 for l in iof.readlines():
146 if l.startswith("rchar:"):
147 message['position'] = int(l.split(': ')[1])
148 message['progress'] = float(0) if opts.read_bytes == 0 \
149 else float("%2.2f" % (
150 message['position'] * 100.0 / message['total']))
151 send_message(out, message)
157 if __name__ == "__main__":
160 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :