| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # | 2 # |
| 3 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """Wrapper that does auto-retry and stats logging for command invocation. | 7 """Wrapper that does auto-retry and stats logging for command invocation. |
| 8 | 8 |
| 9 Various command line tools in use: gsutil, curl have spurious failure. | 9 Various command line tools in use: gsutil, curl have spurious failure. |
| 10 This wrapper will track stats to an AppEngine based service to | 10 This wrapper will track stats to an AppEngine based service to |
| 11 help track down the cause of failures, as well as add retry logic. | 11 help track down the cause of failures, as well as add retry logic. |
| 12 """ | 12 """ |
| 13 | 13 |
| 14 | 14 |
| 15 import optparse | 15 import optparse |
| 16 import os | 16 import os |
| 17 import platform | 17 import platform |
| 18 import socket | 18 import socket |
| 19 import subprocess | 19 import subprocess |
| 20 import sys | 20 import sys |
| 21 import threading | 21 import threading |
| 22 import time | 22 import time |
| 23 import urllib | 23 import urllib |
| 24 import uuid | 24 import uuid |
| 25 | 25 |
| 26 | 26 |
| 27 LOG_TIMEOUT = 10 | 27 LOG_TIMEOUT = 10 |
| 28 ON_POSIX = 'posix' in sys.builtin_module_names |
| 28 | 29 |
| 29 | 30 |
| 30 def LogCommand(options, command_id, | 31 def LogCommand(options, command_id, |
| 31 attempt, cmd, returncode, stdout, stderr, runtime): | 32 attempt, cmd, returncode, stdout, stderr, runtime): |
| 32 """Log a command invocation and result to a central location. | 33 """Log a command invocation and result to a central location. |
| 33 | 34 |
| 34 Arguments: | 35 Arguments: |
| 35 options: parsed options | 36 options: parsed options |
| 36 command_id: unique id for this command (shared by all retries) | 37 command_id: unique id for this command (shared by all retries) |
| 37 attempt: which try numbered from 0 | 38 attempt: which try numbered from 0 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 74 def RunWithTimeout(timeout, func, *args, **kwargs): | 75 def RunWithTimeout(timeout, func, *args, **kwargs): |
| 75 wrapper = { 'result': None } | 76 wrapper = { 'result': None } |
| 76 def CallFunc(): | 77 def CallFunc(): |
| 77 wrapper['result'] = func(*args, **kwargs) | 78 wrapper['result'] = func(*args, **kwargs) |
| 78 th = threading.Thread(target=CallFunc) | 79 th = threading.Thread(target=CallFunc) |
| 79 th.start() | 80 th.start() |
| 80 th.join(timeout) | 81 th.join(timeout) |
| 81 return wrapper['result'] | 82 return wrapper['result'] |
| 82 | 83 |
| 83 | 84 |
| 85 def Tee(fd, string_buffer, forward_fd): |
| 86 """Read characters from fd and both append them to a buffer and write them to |
| 87 forward_fd.""" |
| 88 for char in iter(lambda: fd.read(1), ''): |
| 89 string_buffer += char |
| 90 forward_fd.write(char) |
| 91 fd.close() |
| 92 |
| 93 |
| 84 def main(argv): | 94 def main(argv): |
| 85 parser = optparse.OptionParser() | 95 parser = optparse.OptionParser() |
| 86 parser.add_option('-r', '--retries', dest='retries', | 96 parser.add_option('-r', '--retries', dest='retries', |
| 87 type='int', default=10, | 97 type='int', default=10, |
| 88 help='number of times to retry on failure') | 98 help='number of times to retry on failure') |
| 89 parser.add_option('-u', '--logurl', dest='logurl', | 99 parser.add_option('-u', '--logurl', dest='logurl', |
| 90 default='https://command-wrapper.appspot.com/log', | 100 default='https://command-wrapper.appspot.com/log', |
| 91 help='URL to log invocations/failures to') | 101 help='URL to log invocations/failures to') |
| 92 (options, args) = parser.parse_args(args=argv[1:]) | 102 (options, args) = parser.parse_args(args=argv[1:]) |
| 93 | 103 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 106 | 116 |
| 107 # Log that we're even starting. | 117 # Log that we're even starting. |
| 108 RunWithTimeout(LOG_TIMEOUT, LogCommand, | 118 RunWithTimeout(LOG_TIMEOUT, LogCommand, |
| 109 options, command_id, -1, cmd, -1, '', '', 0) | 119 options, command_id, -1, cmd, -1, '', '', 0) |
| 110 | 120 |
| 111 # Try up to a certain number of times. | 121 # Try up to a certain number of times. |
| 112 for r in range(options.retries): | 122 for r in range(options.retries): |
| 113 tm = time.time() | 123 tm = time.time() |
| 114 p = subprocess.Popen(cmd, shell=True, | 124 p = subprocess.Popen(cmd, shell=True, |
| 115 stdout=subprocess.PIPE, | 125 stdout=subprocess.PIPE, |
| 116 stderr=subprocess.PIPE) | 126 stderr=subprocess.PIPE, |
| 117 (p_stdout, p_stderr) = p.communicate() | 127 close_fds=ON_POSIX) |
| 118 sys.stdout.write(p_stdout) | 128 p_stdout = '' |
| 119 sys.stderr.write(p_stderr) | 129 t_stdout = threading.Thread(target=Tee, |
| 130 args=(p.stdout, p_stdout, sys.stdout)) |
| 131 t_stdout.start() |
| 132 |
| 133 p_stderr = '' |
| 134 t_stderr = threading.Thread(target=Tee, |
| 135 args=(p.stderr, p_stderr, sys.stderr)) |
| 136 t_stderr.start() |
| 137 |
| 138 p.wait() |
| 139 |
| 140 t_stdout.join() |
| 141 t_stderr.join() |
| 142 |
| 143 |
| 120 runtime = time.time() - tm | 144 runtime = time.time() - tm |
| 121 accept = RunWithTimeout(LOG_TIMEOUT, LogCommand, | 145 accept = RunWithTimeout(LOG_TIMEOUT, LogCommand, |
| 122 options, command_id, r, cmd, | 146 options, command_id, r, cmd, |
| 123 p.returncode, p_stdout, p_stderr, runtime) | 147 p.returncode, p_stdout, p_stderr, runtime) |
| 124 if accept: | 148 if accept: |
| 125 return p.returncode | 149 return p.returncode |
| 126 if p.returncode == 0: | 150 if p.returncode == 0: |
| 127 return 0 | 151 return 0 |
| 128 print 'Command %s failed with retcode %d, try %d.' % ( | 152 print 'Command %s failed with retcode %d, try %d.' % ( |
| 129 ' '.join(args), p.returncode, r + 1) | 153 ' '.join(args), p.returncode, r + 1) |
| 130 print 'Command %s failed %d retries, giving up.' % ( | 154 print 'Command %s failed %d retries, giving up.' % ( |
| 131 ' '.join(args), options.retries) | 155 ' '.join(args), options.retries) |
| 132 | 156 |
| 133 return p.returncode | 157 return p.returncode |
| 134 | 158 |
| 135 | 159 |
| 136 if __name__ == '__main__': | 160 if __name__ == '__main__': |
| 137 sys.exit(main(sys.argv)) | 161 sys.exit(main(sys.argv)) |
| OLD | NEW |