blob: 71ee455e314bc435dc67cd91028671f9de4c4a93 [file] [log] [blame]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import logging
from analysis import detect_regression_range
from analysis.chromecrash_parser import CracasCrashParser
from analysis.chromecrash_parser import FracasCrashParser
from analysis.crash_data import CrashData
from analysis.dependency_analyzer import DependencyAnalyzer
from decorators import cached_property
class ChromeCrashData(CrashData):
"""Parsed Chrome crash data from Cracas/Fracas.
Properties:
identifiers (dict): The key value pairs to uniquely identify a
``CrashData``.
crashed_version (str): The version of project in which the crash occurred.
signature (str): The signature of the crash.
platform (str): The platform name; e.g., 'win', 'mac', 'linux', 'android',
'ios', etc.
stacktrace (Stacktrace): The stacktrace of the crash. N.B., this is
an object generated by parsing the string containing the stack trace;
we do not store the string itself.
regression_range (pair or None): a pair of the last-good and first-bad
versions. N.B., because this is an input, it is up to clients
to call ``DetectRegressionRange`` (or whatever else) in order to
provide this information. In addition, while this class does
support storing ``None`` to indicate a missing regression range
(because the ClusterFuzz client wants that feature), the
CL-classifier doesn't actually support that so you won't get a
very good Culprit. The Component- and project-classifiers do still
return some results at least.
dependencies (dict): A dict from dependency paths to
``Dependency`` objects. The keys are all those deps which are
used by both the ``crashed_version`` of the code, and at least
one frame in the ``stacktrace.crash_stack``.
dependency_rolls (dict) A dict from dependency
paths to ``DependencyRoll`` objects. The keys are all those
dependencies which (1) occur in the regression range for the
``platform`` where the crash occurred, (2) neither add nor delete
a dependency, and (3) are also keys of ``dependencies``.
"""
def __init__(self, crash_data, dep_fetcher, top_n_frames=None):
"""
Args:
crash_data (dict): Dicts sent through Pub/Sub by Cracas/Fracas. Example:
{
'stack_trace': 'CRASHED [0x43507378...',
# The Chrome version that produced the stack trace above.
'chrome_version': '52.0.2743.41',
# Client could provide customized data.
'customized_data': {
'trend_type': 'd', # see supported types below
'channel': 'beta',
# Historical data about crash per million pageload by Chrome
# version. (Right now last 20 versions)
'historical_metadata': [
{
'report_number': 0,
'cpm': 0.0,
'client_number': 0,
'chrome_version': '51.0.2704.103'
},
...
{
'report_number': 10,
'cpm': 2.1,
'client_number': 8,
'chrome_version': '53.0.2768.0'
},
]
},
'platform': 'mac', # On which platform the crash occurs.
'client_id': 'fracas', # Identify which client this request is from.
'signature': '[ThreadWatcher UI hang] base::RunLoopBase::Run',
'crash_identifiers': { # A list of key-value to identify a crash.
'platform': 'mac',
'version': '52.0.2743.41',
'process_type': 'browser',
'channel': 'beta',
# Signature for the stack trace.
'signature': '[ThreadWatcher UI hang] base::RunLoopBase::Run'
}
}
dep_fetcher (ChromeDependencyFetcher): Dependency fetcher that can fetch
all dependencies related to crashed version.
top_n_frames (int): number of the frames in stacktrace we should parse.
"""
super(ChromeCrashData, self).__init__(crash_data)
self._crashed_version = crash_data['chrome_version']
self._channel = crash_data['customized_data']['channel']
self._historical_metadata = crash_data['customized_data'][
'historical_metadata']
self._identifiers = crash_data['crash_identifiers']
self._top_n_frames = top_n_frames
self._dependency_analyzer = DependencyAnalyzer(self._platform,
self._crashed_version,
self.regression_range,
dep_fetcher)
@property
def channel(self):
return self._channel
@property
def historical_metadata(self):
return self._historical_metadata
@cached_property
def stacktrace(self):
"""Parses stacktrace and returns parsed ``Stacktrace`` object."""
stacktrace = self.StacktraceParser().Parse(
self._raw_stacktrace,
self._dependency_analyzer.regression_version_deps,
top_n_frames=self._top_n_frames)
if not stacktrace:
logging.warning('Failed to parse the stacktrace')
return stacktrace
@cached_property
def regression_range(self):
"""Detects regression range from ``historical_metadata`` and returns it."""
regression_range = detect_regression_range.DetectRegressionRange(
self.historical_metadata)
if regression_range is None: # pragma: no cover
logging.warning('Got ``None`` for the regression range.')
else:
regression_range = tuple(regression_range)
return regression_range
@cached_property
def dependencies(self):
"""Get all dependencies that are in the crash stack of stacktrace."""
return self._dependency_analyzer.GetDependencies(
[self.stacktrace.crash_stack] if self.stacktrace else [])
@cached_property
def dependency_rolls(self):
"""Gets all dependency rolls of ``dependencies`` in regression range."""
return self._dependency_analyzer.GetDependencyRolls(
[self.stacktrace.crash_stack] if self.stacktrace else [])
@property
def identifiers(self):
return self._identifiers
@classmethod
def StacktraceParser(cls):
"""The class of stacktrace parser."""
raise NotImplementedError()
class FracasCrashData(ChromeCrashData):
@classmethod
def StacktraceParser(cls):
"""The class of stacktrace parser."""
return FracasCrashParser()
class CracasCrashData(ChromeCrashData):
@classmethod
def StacktraceParser(cls):
"""The class of stacktrace parser."""
return CracasCrashParser()
@property
def raw_stacktrace(self):
return json.dumps(self._raw_stacktrace)
@property
def redo(self):
return True