1 # Copyright 2013 The Swarming Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 that
3 # can be found in the LICENSE file.
5 """Various utility functions and classes not specific to any single area."""
10 import logging.handlers
19 from utils import zip_package
22 # Path to (possibly extracted from zip) cacert.pem bundle file.
23 # See get_cacerts_bundle().
25 _ca_certs_lock = threading.Lock()
28 class OptionParserWithLogging(optparse.OptionParser):
29 """Adds --verbose option."""
31 # Set to True to enable --log-file options.
32 enable_log_file = True
34 def __init__(self, verbose=0, log_file=None, **kwargs):
35 kwargs.setdefault('description', sys.modules['__main__'].__doc__)
36 optparse.OptionParser.__init__(self, **kwargs)
37 self.group_logging = optparse.OptionGroup(self, 'Logging')
38 self.group_logging.add_option(
42 help='Use multiple times to increase verbosity')
43 if self.enable_log_file:
44 self.group_logging.add_option(
47 help='The name of the file to store rotating log details')
48 self.group_logging.add_option(
49 '--no-log', action='store_const', const='', dest='log_file',
50 help='Disable log file')
52 def parse_args(self, *args, **kwargs):
53 # Make sure this group is always the last one.
54 self.add_option_group(self.group_logging)
56 options, args = optparse.OptionParser.parse_args(self, *args, **kwargs)
57 levels = [logging.ERROR, logging.INFO, logging.DEBUG]
58 level = levels[min(len(levels) - 1, options.verbose)]
60 logging_console = logging.StreamHandler()
61 logging_console.setFormatter(logging.Formatter(
62 '%(levelname)5s %(module)15s(%(lineno)3d): %(message)s'))
63 logging_console.setLevel(level)
64 logging.getLogger().setLevel(level)
65 logging.getLogger().addHandler(logging_console)
67 if self.enable_log_file and options.log_file:
68 # This is necessary otherwise attached handler will miss the messages.
69 logging.getLogger().setLevel(logging.DEBUG)
71 logging_rotating_file = logging.handlers.RotatingFileHandler(
73 maxBytes=10 * 1024 * 1024,
76 # log files are always at DEBUG level.
77 logging_rotating_file.setLevel(logging.DEBUG)
78 logging_rotating_file.setFormatter(logging.Formatter(
79 '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)3d): %(message)s'))
80 logging.getLogger().addHandler(logging_rotating_file)
85 class Profiler(object):
86 """Context manager that records time spend inside its body."""
87 def __init__(self, name):
89 self.start_time = None
92 self.start_time = time.time()
95 def __exit__(self, _exc_type, _exec_value, _traceback):
96 time_taken = time.time() - self.start_time
97 logging.info('Profiling: Section %s took %3.3f seconds',
98 self.name, time_taken)
101 class Unbuffered(object):
102 """Disable buffering on a file object."""
103 def __init__(self, stream):
106 def write(self, data):
107 self.stream.write(data)
111 def __getattr__(self, attr):
112 return getattr(self.stream, attr)
115 def disable_buffering():
116 """Makes this process and child processes stdout unbuffered."""
117 if not os.environ.get('PYTHONUNBUFFERED'):
118 # Since sys.stdout is a C++ object, it's impossible to do
119 # sys.stdout.write = lambda...
120 sys.stdout = Unbuffered(sys.stdout)
121 os.environ['PYTHONUNBUFFERED'] = 'x'
124 def fix_python_path(cmd):
125 """Returns the fixed command line to call the right python executable."""
127 if out[0] == 'python':
128 out[0] = sys.executable
129 elif out[0].endswith('.py'):
130 out.insert(0, sys.executable)
134 def read_json(filepath):
135 with open(filepath, 'r') as f:
139 def write_json(filepath_or_handle, data, dense):
140 """Writes data into filepath or file handle encoded as json.
142 If dense is True, the json is packed. Otherwise, it is human readable.
145 kwargs = {'sort_keys': True, 'separators': (',',':')}
147 kwargs = {'sort_keys': True, 'indent': 2}
149 if hasattr(filepath_or_handle, 'write'):
150 json.dump(data, filepath_or_handle, **kwargs)
152 with open(filepath_or_handle, 'wb') as f:
153 json.dump(data, f, **kwargs)
156 def format_json(data, dense):
157 """Returns a string with json encoded data.
159 If dense is True, the json is packed. Otherwise, it is human readable.
161 buf = cStringIO.StringIO()
162 write_json(buf, data, dense)
163 return buf.getvalue()
166 def gen_blacklist(regexes):
167 """Returns a lambda to be used as a blacklist."""
168 compiled = [re.compile(i) for i in regexes]
169 return lambda f: any(j.match(f) for j in compiled)
172 def get_bool_env_var(name):
173 """Return True if integer environment variable |name| value is non zero.
175 If environment variable is missing or is set to '0', returns False.
177 return bool(int(os.environ.get(name, '0')))
181 """True if running in non-interactive mode on some bot machine.
183 Examines os.environ for presence of SWARMING_HEADLESS var.
185 headless_env_keys = (
186 # This is Chromium specific. Set when running under buildbot slave.
188 # Set when running under swarm bot.
191 return any(get_bool_env_var(key) for key in headless_env_keys)
194 def get_cacerts_bundle():
195 """Returns path to a file with CA root certificates bundle.
197 Python's ssl module needs a real file on disk, so if code is running from
198 a zip archive, we need to extract the file first.
202 if _ca_certs is not None and os.path.exists(_ca_certs):
204 # Some rogue process clears /tmp and causes cacert.pem to disappear. Extract
205 # to current directory instead. We use our own bundled copy of cacert.pem.
206 _ca_certs = zip_package.extract_resource(utils, 'cacert.pem', temp_dir='.')