| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 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 import logging | |
| 6 | |
| 7 from infra.libs.event_mon.chrome_infra_log_pb2 import ChromeInfraEvent | |
| 8 from infra.libs.event_mon.chrome_infra_log_pb2 import ServiceEvent | |
| 9 from infra.libs.event_mon.chrome_infra_log_pb2 import BuildEvent | |
| 10 from infra.libs.event_mon.log_request_lite_pb2 import LogRequestLite | |
| 11 from infra.libs.event_mon import config, router | |
| 12 | |
| 13 | |
| 14 # These constants are part of the API. | |
| 15 EVENT_TYPES = ('START', 'STOP', 'UPDATE', 'CURRENT_VERSION', 'CRASH') | |
| 16 BUILD_EVENT_TYPES = ('SCHEDULER', 'BUILD', 'STEP') | |
| 17 BUILD_RESULTS = (None, 'UNKNOWN', 'SUCCESS', 'FAILURE', 'INFRA_FAILURE') | |
| 18 TIMESTAMP_KINDS = (None, 'UNKNOWN', 'POINT', 'BEGIN', 'END') | |
| 19 | |
| 20 # Maximum size of stack trace sent in an event, in characters. | |
| 21 STACK_TRACE_MAX_SIZE = 1000 | |
| 22 | |
| 23 | |
| 24 def _get_chrome_infra_event(timestamp_kind): | |
| 25 """Compute a basic event. | |
| 26 | |
| 27 Validates the inputs and returns a pre-filled ChromeInfraEvent or | |
| 28 None if any check failed. | |
| 29 | |
| 30 The proto is filled using values provided in setup_monitoring() at | |
| 31 initialization time, and args. | |
| 32 | |
| 33 Args: | |
| 34 timestamp_kind (string): any of ('POINT', 'BEGIN', 'END'). | |
| 35 | |
| 36 Returns: | |
| 37 event (chrome_infra_log_pb2.ChromeInfraEvent): | |
| 38 """ | |
| 39 if timestamp_kind not in TIMESTAMP_KINDS: | |
| 40 logging.error('Invalid value for timestamp_kind: %s' % | |
| 41 str(timestamp_kind)) | |
| 42 return None | |
| 43 | |
| 44 event = ChromeInfraEvent() | |
| 45 event.CopyFrom(config.cache['default_event']) | |
| 46 | |
| 47 if timestamp_kind: | |
| 48 event.timestamp_kind = getattr(ChromeInfraEvent, timestamp_kind) | |
| 49 | |
| 50 return event | |
| 51 | |
| 52 | |
| 53 def _get_log_request_lite(chrome_infra_event, event_timestamp=None): | |
| 54 """Wraps a ChromeInfraEvent into a LogRequestLite. | |
| 55 | |
| 56 Args: | |
| 57 event_timestamp (int or float): timestamp of when the event happened | |
| 58 as a number of milliseconds since the epoch. If None, the current time | |
| 59 is used. | |
| 60 | |
| 61 Returns: | |
| 62 log_event (log_request_lite_pb2.LogRequestLite.LogEventLite): | |
| 63 """ | |
| 64 if not isinstance(event_timestamp, (int, float, None.__class__ )): | |
| 65 logging.error('Invalid type for event_timestamp. Needs a number, got %s' | |
| 66 % str(type(event_timestamp))) | |
| 67 return None | |
| 68 | |
| 69 log_event = LogRequestLite.LogEventLite() | |
| 70 log_event.event_time_ms = event_timestamp or router.time_ms() | |
| 71 log_event.source_extension = chrome_infra_event.SerializeToString() | |
| 72 return log_event | |
| 73 | |
| 74 | |
| 75 def _get_service_event(event_type, | |
| 76 timestamp_kind='POINT', | |
| 77 event_timestamp=None, | |
| 78 code_version=None, | |
| 79 stack_trace=None): | |
| 80 """Compute a ChromeInfraEvent filled with a ServiceEvent. | |
| 81 Arguments are identical to those in send_service_event(), please refer | |
| 82 to this docstring. | |
| 83 | |
| 84 Returns: | |
| 85 event (log_request_lite_pb2.LogRequestLite.LogEventLite): can be None | |
| 86 if there is a major processing issue. | |
| 87 """ | |
| 88 event = _get_chrome_infra_event(timestamp_kind) | |
| 89 if not event: | |
| 90 return None | |
| 91 | |
| 92 if event_type not in EVENT_TYPES: | |
| 93 logging.error('Invalid value for event_type: %s' % str(event_type)) | |
| 94 return None | |
| 95 | |
| 96 event.service_event.type = getattr(ServiceEvent, event_type) | |
| 97 | |
| 98 if code_version is None: | |
| 99 code_version = () | |
| 100 if not isinstance(code_version, (tuple, list)): | |
| 101 logging.error('Invalid type provided to code_version argument in ' | |
| 102 '_get_service_event. Please fix the calling code. ' | |
| 103 'Type provided: %s, expected list, tuple or None.' | |
| 104 % str(type(code_version))) | |
| 105 code_version = () | |
| 106 | |
| 107 for version_d in code_version: | |
| 108 try: | |
| 109 if 'source_url' not in version_d: | |
| 110 logging.error('source_url missing in %s', version_d) | |
| 111 continue | |
| 112 | |
| 113 version = event.service_event.code_version.add() | |
| 114 version.source_url = version_d['source_url'] | |
| 115 if 'revision' in version_d: | |
| 116 # Rely on the url to switch between svn and git because an | |
| 117 # abbreviated sha1 can sometime be confused with an int. | |
| 118 if version.source_url.startswith('svn://'): | |
| 119 version.svn_revision = int(version_d['revision']) | |
| 120 else: | |
| 121 version.git_hash = version_d['revision'] | |
| 122 | |
| 123 if 'version' in version_d: | |
| 124 version.version = version_d['version'] | |
| 125 if 'dirty' in version_d: | |
| 126 version.dirty = version_d['dirty'] | |
| 127 | |
| 128 except TypeError: | |
| 129 logging.exception('Invalid type provided to code_version argument in ' | |
| 130 '_get_service_event. Please fix the calling code.') | |
| 131 continue | |
| 132 | |
| 133 if isinstance(stack_trace, basestring): | |
| 134 if event_type != 'CRASH': | |
| 135 logging.error('stack_trace provide for an event different from CRASH.' | |
| 136 ' Got: %s', event_type) | |
| 137 event.service_event.stack_trace = stack_trace[-STACK_TRACE_MAX_SIZE:] | |
| 138 else: | |
| 139 if stack_trace is not None: | |
| 140 logging.error('stack_trace should be a string, got %s', | |
| 141 stack_trace.__class__.__name__) | |
| 142 | |
| 143 return _get_log_request_lite(event, event_timestamp=event_timestamp) | |
| 144 | |
| 145 | |
| 146 def send_service_event(event_type, | |
| 147 timestamp_kind='POINT', | |
| 148 event_timestamp=None, | |
| 149 code_version=(), | |
| 150 stack_trace=None): | |
| 151 """Send service event. | |
| 152 | |
| 153 Args: | |
| 154 event_type (string): any name of enum ServiceEvent.ServiceEventType. | |
| 155 ('START', 'STOP', 'UPDATE', 'CURRENT_VERSION', 'CRASH') | |
| 156 | |
| 157 Keyword Args: | |
| 158 timestamp_kind (string): any of ('POINT', 'BEGIN', 'END'). | |
| 159 | |
| 160 event_timestamp (int or float): timestamp of when the event happened | |
| 161 as a number of milliseconds since the epoch. If not provided, the | |
| 162 current time is used. | |
| 163 | |
| 164 code_version (list/tuple of dict or None): required keys are | |
| 165 'source_url' -> full url to the repository | |
| 166 'revision' -> (string) git sha1 or svn revision number. | |
| 167 optional keys are | |
| 168 'dirty' -> boolean. True if the local source tree has local | |
| 169 modification. | |
| 170 'version' -> manually-set version number (like 'v2.6.0') | |
| 171 | |
| 172 stack_trace (str): when event_type is 'CRASH', stack trace of the crash | |
| 173 as a string. String is truncated to 1000 characters (the last ones | |
| 174 are kept). Use traceback.format_exc() to get the stack trace from an | |
| 175 exception handler. | |
| 176 | |
| 177 Returns: | |
| 178 success (bool): False if some error happened. | |
| 179 """ | |
| 180 log_event = _get_service_event(event_type, | |
| 181 timestamp_kind=timestamp_kind, | |
| 182 event_timestamp=event_timestamp, | |
| 183 code_version=code_version, | |
| 184 stack_trace=stack_trace) | |
| 185 | |
| 186 return config._router.push_event(log_event) | |
| 187 | |
| 188 | |
| 189 def _get_build_event(event_type, | |
| 190 hostname, | |
| 191 build_name, | |
| 192 build_number=None, | |
| 193 build_scheduling_time=None, | |
| 194 step_name=None, | |
| 195 step_number=None, | |
| 196 result=None, | |
| 197 timestamp_kind='POINT', | |
| 198 event_timestamp=None): | |
| 199 """Compute a ChromeInfraEvent filled with a BuildEvent. | |
| 200 | |
| 201 Arguments are identical to those in send_build_event(), please refer | |
| 202 to this docstring. | |
| 203 | |
| 204 Returns: | |
| 205 event (log_request_lite_pb2.LogRequestLite.LogEventLite): can be None | |
| 206 if there is a major processing issue. | |
| 207 """ | |
| 208 event = _get_chrome_infra_event(timestamp_kind) | |
| 209 | |
| 210 if event_type not in BUILD_EVENT_TYPES: | |
| 211 logging.error('Invalid value for event_type: %s' % str(event_type)) | |
| 212 return None | |
| 213 | |
| 214 event.build_event.type = getattr(BuildEvent, event_type) | |
| 215 if not hostname: | |
| 216 logging.error('hostname must be provided, got %s' % hostname) | |
| 217 else: | |
| 218 event.build_event.host_name = hostname | |
| 219 | |
| 220 if not build_name: | |
| 221 logging.error('build_name must be provided, got %s' % build_name) | |
| 222 else: # avoid sending empty strings | |
| 223 event.build_event.build_name = build_name | |
| 224 | |
| 225 if build_number is not None: # 0 is a valid value | |
| 226 event.build_event.build_number = build_number | |
| 227 if event_type == 'SCHEDULER': | |
| 228 logging.error('build_number should not be provided for a "SCHEDULER"' | |
| 229 ' type, got %s (drop or use BUILD or STEP type)', | |
| 230 build_number) | |
| 231 | |
| 232 if not build_scheduling_time: | |
| 233 logging.error('build_number has been provided (%s), ' | |
| 234 'build_scheduling_time was not. ' | |
| 235 'Provide either both or none.', build_number) | |
| 236 | |
| 237 else: # not using elif because it's not semantically a switch statement. | |
| 238 if build_scheduling_time: | |
| 239 logging.error('build_number has not been provided, ' | |
| 240 'build_scheduling_time was provided (%s). ' | |
| 241 'Both must be present or missing.', | |
| 242 build_scheduling_time) | |
| 243 | |
| 244 if build_scheduling_time: | |
| 245 event.build_event.build_scheduling_time_ms = build_scheduling_time | |
| 246 | |
| 247 if step_name: | |
| 248 event.build_event.step_name = step_name | |
| 249 if event_type != 'STEP': | |
| 250 logging.error('step_name should be provided only for type "STEP", ' | |
| 251 'got %s', event_type) | |
| 252 if step_number is None: # 0 is a valid value | |
| 253 logging.error('step_number was not provided, but got a value for ' | |
| 254 'step_name (%s). Provide either both or none', | |
| 255 step_name) | |
| 256 if build_number is None and build_scheduling_time is None: | |
| 257 logging.error('build information must be provided when step ' | |
| 258 'information is provided. Got nothing in build_name ' | |
| 259 'and build_number') | |
| 260 else: | |
| 261 if step_number is not None: | |
| 262 logging.error('step_number has been provided (%s), ' | |
| 263 'step_name has not. ' | |
| 264 'Both must be present or missing.', | |
| 265 step_number) | |
| 266 | |
| 267 if step_number is not None: | |
| 268 event.build_event.step_number = step_number | |
| 269 | |
| 270 if result not in BUILD_RESULTS: | |
| 271 logging.error('Invalid value for result: %s' % str(result)) | |
| 272 | |
| 273 else: | |
| 274 if result: # can be None | |
| 275 event.build_event.result = getattr(BuildEvent, result) | |
| 276 | |
| 277 if event_type == 'SCHEDULER': | |
| 278 logging.error('A result was provided for a "SCHEDULER" event type ' | |
| 279 '(%s). This is only accepted for BUILD and TEST types.', | |
| 280 result) | |
| 281 | |
| 282 return _get_log_request_lite(event, event_timestamp=event_timestamp) | |
| 283 | |
| 284 | |
| 285 def send_build_event(event_type, | |
| 286 hostname, | |
| 287 build_name, | |
| 288 build_number=None, | |
| 289 build_scheduling_time=None, | |
| 290 step_name=None, | |
| 291 step_number=None, | |
| 292 result=None, | |
| 293 timestamp_kind='POINT', | |
| 294 event_timestamp=None): | |
| 295 """Send a ChromeInfraEvent filled with a BuildEvent | |
| 296 | |
| 297 Args: | |
| 298 event_type (string): any name of enum BuildEvent.BuildEventType. | |
| 299 (listed in infra.libs.event_mon.monitoring.BUILD_EVENT_TYPES) | |
| 300 hostname (string): fqdn of the machine that is running the build / step. | |
| 301 aka the bot name. | |
| 302 build_name (string): name of the builder. | |
| 303 | |
| 304 Keyword args: | |
| 305 build_number (int): as the name says. | |
| 306 build_scheduling_time (int): timestamp telling when the build was | |
| 307 scheduled. This is required when build_number is provided to make it | |
| 308 possibly to distinguish two builds with the same build number. | |
| 309 step_name (str): name of the step. | |
| 310 step_number (int): rank of the step in the build. This is mandatory | |
| 311 if step_name is provided, because step_name is not enough to tell the | |
| 312 order. | |
| 313 result (string): any name of enum BuildEvent.BuildResult. | |
| 314 (listed in infra.libs.event_mon.monitoring.BUILD_RESULTS) | |
| 315 | |
| 316 Returns: | |
| 317 success (bool): False if some error happened. | |
| 318 """ | |
| 319 log_event = _get_build_event(event_type, | |
| 320 hostname, | |
| 321 build_name, | |
| 322 build_number=build_number, | |
| 323 build_scheduling_time=build_scheduling_time, | |
| 324 step_name=step_name, | |
| 325 step_number=step_number, | |
| 326 result=result, | |
| 327 timestamp_kind=timestamp_kind, | |
| 328 event_timestamp=event_timestamp) | |
| 329 | |
| 330 return config._router.push_event(log_event) | |
| OLD | NEW |