Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Unified Diff: appengine/findit/crash/chrome_crash_data.py

Issue 2673733002: [Predator] Add CrashData class to process raw json crash data. (Closed)
Patch Set: Rebase and fix nits. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | appengine/findit/crash/crash_data.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/findit/crash/chrome_crash_data.py
diff --git a/appengine/findit/crash/chrome_crash_data.py b/appengine/findit/crash/chrome_crash_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..41d6101b9c0e5f53bdd82fbffe089dc32012ab50
--- /dev/null
+++ b/appengine/findit/crash/chrome_crash_data.py
@@ -0,0 +1,214 @@
+# 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 logging
+from collections import namedtuple
+
+from crash import detect_regression_range
+from crash.crash_data import CrashData
+from crash.chromecrash_parser import ChromeCrashParser
+from crash.stacktrace import Stacktrace
+
+
+class ChromeCrashData(CrashData):
+ """Chrome crash report 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._channel = crash_data['customized_data']['channel']
+ self._historical_metadata = crash_data['customized_data'][
+ 'historical_metadata']
+
+ # Delay the stacktrace parsing to the first time when stacktrace property
+ # gets called.
+ self._stacktrace_str = crash_data['stack_trace']
+ self._top_n_frames = top_n_frames
+ self._stacktrace = None
+
+ self._dep_fetcher = dep_fetcher
+ self._crashed_version_deps = None
+
+ self._regression_range = None
+
+ self._dependencies = {}
+ self._dependency_rolls = {}
+
+ def _CrashedVersionDeps(self):
+ """Gets all dependencies related to crashed_version.
+
+ N.B. All dependencies will be returned, no matter whether they appeared in
+ stacktrace or are related to the crash or not.
+ """
+ if self._crashed_version_deps:
+ return self._crashed_version_deps
+
+ self._crashed_version_deps = self._dep_fetcher.GetDependency(
+ self.crashed_version, self.platform) if self._dep_fetcher else {}
+
+ return self._crashed_version_deps
+
+ @property
+ def channel(self):
+ return self._channel
+
+ @property
+ def historical_metadata(self):
+ return self._historical_metadata
+
+ @property
+ def stacktrace(self):
+ """Parses stacktrace and returns parsed ``Stacktrace`` object."""
+ if self._stacktrace:
+ return self._stacktrace
+
+ self._stacktrace = ChromeCrashParser().Parse(
+ self._stacktrace_str, self._CrashedVersionDeps(),
+ signature=self.signature, top_n_frame=self._top_n_frames)
+ if not self._stacktrace:
+ logging.warning('Failed to parse the stacktrace %s',
+ self._stacktrace_str)
+ return self._stacktrace
+
+ @property
+ def regression_range(self):
+ """Detects regression range from ``historical_metadata`` and returns it."""
+ if self._regression_range:
+ return self._regression_range
+
+ 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:
+ self._regression_range = tuple(regression_range)
+
+ return self._regression_range
+
+ @property
+ def dependencies(self):
+ """Get all dependencies that are in the crash stack of stacktrace."""
+ if self._dependencies:
+ return self._dependencies
+
+ self._dependencies = {
+ frame.dep_path: self._CrashedVersionDeps()[frame.dep_path]
+ for frame in self.stacktrace.crash_stack
+ if frame.dep_path and frame.dep_path in self._CrashedVersionDeps()
+ }
+ return self._dependencies
+
+ @property
+ def dependency_rolls(self):
+ """Gets all dependency rolls of ``dependencies`` in regression range."""
+ if self._dependency_rolls:
+ return self._dependency_rolls
+
+ # Short-circuit when we know the deprolls must be empty.
+ if not self.regression_range:
+ logging.warning('Cannot get deps and dep rolls for report without '
+ 'regression range')
+ return self._dependency_rolls
+
+ # Get ``DependencyRoll` objects for all dependencies in the regression
+ # range (for the particular platform that crashed).
+ regression_range_dep_rolls = self._dep_fetcher.GetDependencyRollsDict(
+ self.regression_range[0], self.regression_range[1], self.platform)
+ # Filter out the ones which add or delete a dependency, because we
+ # can't really be sure whether to blame them or not. This rarely
+ # happens, so our inability to decide shouldn't be too much of a problem.
+ def HasBothRevisions(dep_path, dep_roll):
+ has_both_revisions = bool(dep_roll.old_revision) and bool(
+ dep_roll.new_revision)
+ if not has_both_revisions:
+ logging.info(
+ 'Skip %s dependency %s',
+ 'added' if dep_roll.new_revision else 'deleted',
+ dep_path)
+ return has_both_revisions
+
+ # Apply the above filter, and also filter to only retain those
+ # which occur in ``crashed_stack_deps``.
+ self._dependency_rolls = {
+ dep_path: dep_roll
+ for dep_path, dep_roll in regression_range_dep_rolls.iteritems()
+ if HasBothRevisions(dep_path, dep_roll) and dep_path
+ in self.dependencies
+ }
+
+ return self._dependency_rolls
« no previous file with comments | « no previous file | appengine/findit/crash/crash_data.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698