| OLD | NEW |
| (Empty) |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """Various utility functions and classes not specific to any single area.""" | |
| 6 | |
| 7 import logging | |
| 8 import logging.handlers | |
| 9 import optparse | |
| 10 import os | |
| 11 import sys | |
| 12 import time | |
| 13 import traceback | |
| 14 | |
| 15 | |
| 16 class OptionParserWithLogging(optparse.OptionParser): | |
| 17 """Adds --verbose option.""" | |
| 18 | |
| 19 # Set to True to enable --log-file options. | |
| 20 enable_log_file = True | |
| 21 | |
| 22 def __init__(self, verbose=0, log_file=None, **kwargs): | |
| 23 kwargs.setdefault('description', sys.modules['__main__'].__doc__) | |
| 24 optparse.OptionParser.__init__(self, **kwargs) | |
| 25 self.add_option( | |
| 26 '-v', '--verbose', | |
| 27 action='count', | |
| 28 default=verbose, | |
| 29 help='Use multiple times to increase verbosity') | |
| 30 if self.enable_log_file: | |
| 31 self.add_option( | |
| 32 '-l', '--log-file', | |
| 33 default=log_file, | |
| 34 help='The name of the file to store rotating log details') | |
| 35 self.add_option( | |
| 36 '--no-log', action='store_const', const='', dest='log_file', | |
| 37 help='Disable log file') | |
| 38 | |
| 39 def parse_args(self, *args, **kwargs): | |
| 40 options, args = optparse.OptionParser.parse_args(self, *args, **kwargs) | |
| 41 levels = [logging.ERROR, logging.INFO, logging.DEBUG] | |
| 42 level = levels[min(len(levels) - 1, options.verbose)] | |
| 43 | |
| 44 logging_console = logging.StreamHandler() | |
| 45 logging_console.setFormatter(logging.Formatter( | |
| 46 '%(levelname)5s %(module)15s(%(lineno)3d): %(message)s')) | |
| 47 logging_console.setLevel(level) | |
| 48 logging.getLogger().setLevel(level) | |
| 49 logging.getLogger().addHandler(logging_console) | |
| 50 | |
| 51 if self.enable_log_file and options.log_file: | |
| 52 # This is necessary otherwise attached handler will miss the messages. | |
| 53 logging.getLogger().setLevel(logging.DEBUG) | |
| 54 | |
| 55 logging_rotating_file = logging.handlers.RotatingFileHandler( | |
| 56 options.log_file, | |
| 57 maxBytes=10 * 1024 * 1024, | |
| 58 backupCount=5, | |
| 59 encoding='utf-8') | |
| 60 # log files are always at DEBUG level. | |
| 61 logging_rotating_file.setLevel(logging.DEBUG) | |
| 62 logging_rotating_file.setFormatter(logging.Formatter( | |
| 63 '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)3d): %(message)s')) | |
| 64 logging.getLogger().addHandler(logging_rotating_file) | |
| 65 | |
| 66 return options, args | |
| 67 | |
| 68 | |
| 69 class Profiler(object): | |
| 70 """Context manager that records time spend inside its body.""" | |
| 71 def __init__(self, name): | |
| 72 self.name = name | |
| 73 self.start_time = None | |
| 74 | |
| 75 def __enter__(self): | |
| 76 self.start_time = time.time() | |
| 77 return self | |
| 78 | |
| 79 def __exit__(self, _exc_type, _exec_value, _traceback): | |
| 80 time_taken = time.time() - self.start_time | |
| 81 logging.info('Profiling: Section %s took %3.3f seconds', | |
| 82 self.name, time_taken) | |
| 83 | |
| 84 | |
| 85 class Unbuffered(object): | |
| 86 """Disable buffering on a file object.""" | |
| 87 def __init__(self, stream): | |
| 88 self.stream = stream | |
| 89 | |
| 90 def write(self, data): | |
| 91 self.stream.write(data) | |
| 92 if '\n' in data: | |
| 93 self.stream.flush() | |
| 94 | |
| 95 def __getattr__(self, attr): | |
| 96 return getattr(self.stream, attr) | |
| 97 | |
| 98 | |
| 99 def disable_buffering(): | |
| 100 """Makes this process and child processes stdout unbuffered.""" | |
| 101 if not os.environ.get('PYTHONUNBUFFERED'): | |
| 102 # Since sys.stdout is a C++ object, it's impossible to do | |
| 103 # sys.stdout.write = lambda... | |
| 104 sys.stdout = Unbuffered(sys.stdout) | |
| 105 os.environ['PYTHONUNBUFFERED'] = 'x' | |
| 106 | |
| 107 | |
| 108 def fix_python_path(cmd): | |
| 109 """Returns the fixed command line to call the right python executable.""" | |
| 110 out = cmd[:] | |
| 111 if out[0] == 'python': | |
| 112 out[0] = sys.executable | |
| 113 elif out[0].endswith('.py'): | |
| 114 out.insert(0, sys.executable) | |
| 115 return out | |
| 116 | |
| 117 | |
| 118 def report_error(error): | |
| 119 """Prints a error to stderr, wrapping it into header and footer. | |
| 120 | |
| 121 That way errors can be reliably extracted from logs. It's indented to be used | |
| 122 only for non recoverable unexpected errors. Is should NOT be used for input | |
| 123 validation, command line argument errors, etc. | |
| 124 | |
| 125 Arguments: | |
| 126 error: error message string (possibly multiple lines) or an instance of | |
| 127 Exception subclass. In the later case a traceback will also be | |
| 128 reported. It's assumed that |report_error| is called in an except | |
| 129 block where |error| was caught. | |
| 130 """ | |
| 131 print >> sys.stderr, '[------ Swarming Error ------]' | |
| 132 print >> sys.stderr, str(error) | |
| 133 if isinstance(error, Exception): | |
| 134 print >> sys.stderr, traceback.format_exc(), | |
| 135 print >> sys.stderr, '[----------------------------]' | |
| OLD | NEW |