blob: 8d7d95487a843614c9e8edb1a4ce0c4d698abddf [file]
# 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 analysis.crash_data import CrashData, SIGNATURE_MAX_LENGTH
from analysis.dependency_analyzer import DependencyAnalyzer
from analysis.uma_sampling_profiler_parser import UMASamplingProfilerParser
from decorators import cached_property
# TODO(cweakliam): Rename CrashData to something more generic now that Predator
# deals with regressions as well as crashes
class UMASamplingProfilerData(CrashData):
"""Data about a performance regression/improvement from UMA Sampling Profiler.
Properties:
identifiers (dict): The key value pairs to uniquely identify a
``CrashData``.
crashed_version (str): The version of project in which the regression
occurred.
signature (str): The signature of the regression.
platform (str): The platform name; e.g., 'win', 'mac', 'linux', 'android',
'ios', etc.
stacktrace (Stacktrace): Collection of call stacks storing data about the
functions involved in the regression.
regression_range (pair or None): a pair of the last-good and first-bad
versions.
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.stacks``.
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 regression occurred, (2) neither add nor delete
a dependency, and (3) are also keys of ``dependencies``.
"""
def __init__(self, regression_data, dep_fetcher):
"""Initialize with raw data sent by UMA Sampling Profiler.
Args:
regression_data (dict): Dicts sent through Pub/Sub by UMA Sampling
Profiler. For example:
{
'platform': 'win',
'process_type': 'BROWSER_PROCESS',
# this field may not be present, depending on the collection_trigger:
'startup_phase': 'MAIN_LOOP_START',
'thread_type': 'UI_THREAD',
'collection_trigger': 'PROCESS_STARTUP',
'chrome_releases': [
{'version': '54.0.2834.0', 'channel': 'canary'},
{'version': '54.0.2835.0', 'channel': 'canary'},
],
# Unique identifier for collection scenario and release pair associated
# with this regression subtree.
'configuration_id': '37497AC5DD7ACEC4',
# Depth of the root of the subtree in the stacks, with the root at depth
# 0:
'subtree_root_depth': 19,
# Unique identifier for the subtree:
'subtree_id': 'AEF6F487C2EE7935',
# Id of the subtree root function:
'subtree_root_id': '9F4E0F78CF2B2668',
# Ids of the significant changed nodes under the root. In the case of a
# renamed function, one id will be present for the old function and one
# for the new function:
'subtree_change_ids': ['EEC7F9CAAE0BDE58','817FAD6EAEBCCF14'],
'subtree_stacks': [<list of stacks in dict form>],
}
dep_fetcher (ChromeDependencyFetcher): Dependency fetcher that can fetch
all dependencies related to a regression version.
"""
self._platform = regression_data['platform']
self.process_type = regression_data['process_type']
# startup_phase may not be present, depending on the collection_trigger
self.startup_phase = regression_data.get('startup_phase')
self.thread_type = regression_data['thread_type']
self.collection_trigger = regression_data['collection_trigger']
self.chrome_releases = regression_data['chrome_releases']
self.configuration_id = regression_data['configuration_id']
# Unique identifier for the subtree:
self.subtree_id = regression_data['subtree_id']
self.subtree_root_id = regression_data['subtree_root_id']
self.subtree_change_ids = regression_data['subtree_change_ids']
# Depth of the root of the subtree in the stacks, with the root at depth 0:
self.subtree_root_depth = regression_data['subtree_root_depth']
self.subtree_stacks = regression_data['subtree_stacks']
self._crashed_version = regression_data['chrome_releases'][1]['version']
self._raw_stacktrace = ''
self._dependency_analyzer = DependencyAnalyzer(self._platform,
self._crashed_version,
self.regression_range,
dep_fetcher)
self._redo = regression_data.get('redo', False)
@cached_property
def stacktrace(self):
"""Parses ``subtree_stacks`` dict and returns ``Stacktrace`` object."""
stacktrace = UMASamplingProfilerParser().Parse(
self.subtree_stacks, self.subtree_root_depth,
self._dependency_analyzer.regression_version_deps)
if not stacktrace:
logging.warning('Failed to parse the stacktrace %s',
self.subtree_stacks)
return stacktrace
@property
def regression_range(self):
"""Pair of versions between which the regression/improvement occurred."""
before = self.chrome_releases[0]['version']
after = self.chrome_releases[1]['version']
return before, after
@cached_property
def dependencies(self):
"""Get all dependencies that are in the stacktrace."""
return self._dependency_analyzer.GetDependencies(
self.stacktrace.stacks 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.stacks if self.stacktrace else [])
@property
def identifiers(self):
return {'configuration_id': self.configuration_id,
'subtree_id': self.subtree_id}
@property
def signature(self):
subtree_root = self.subtree_stacks[0]['frames'][self.subtree_root_depth]
function_name = subtree_root.get('function_name', 'unsymbolized function')
return function_name[:SIGNATURE_MAX_LENGTH]