Exemple #1
0
def _MakeBuildbucketBisectJob(bisect_job):
    """Creates a bisect job object that the buildbucket service can use.

  Args:
    bisect_job: The entity (try_job.TryJob) off of which to create the
        buildbucket job.

  Returns:
    A buildbucket_job.BisectJob object populated with the necessary attributes
    to pass it to the buildbucket service to start the job.
  """
    config = bisect_job.GetConfigDict()
    if bisect_job.job_type != 'bisect':
        raise request_handler.InvalidInputError(
            'Recipe only supports bisect jobs at this time.')
    if not bisect_job.master_name.startswith('ChromiumPerf'):
        raise request_handler.InvalidInputError(
            'Recipe is only implemented on for tests run on chromium.perf '
            '(and chromium.perf.fyi).')
    return buildbucket_job.BisectJob(
        good_revision=config['good_revision'],
        bad_revision=config['bad_revision'],
        test_command=config['command'],
        metric=config['metric'],
        repeats=config['repeat_count'],
        timeout_minutes=config['max_time_minutes'],
        truncate=config['truncate_percent'],
        bug_id=bisect_job.bug_id,
        gs_bucket='chrome-perf',
        original_bot_name=config['original_bot_name'],
    )
 def _EditEntity(self):
     """Edits an existing entity according to the request parameters."""
     name = self.request.get('edit-name')
     if not name:
         raise request_handler.InvalidInputError('No name given.')
     entity = self._model_class.get_by_id(name)
     if not entity:
         raise request_handler.InvalidInputError(
             'Entity "%s" does not exist, cannot edit.' % name)
     self._UpdateAndReportResults(entity)
Exemple #3
0
    def _ShowAlertsForKeys(self, keys):
        """Show alerts for |keys|.

    Query for anomalies with overlapping revision. The |keys|
    parameter for group_report is a comma-separated list of urlsafe strings
    for Keys for Anomaly entities. (Each key corresponds to an alert)

    Args:
      keys: Comma-separated list of urlsafe strings for Anomaly keys.
    """
        urlsafe_keys = keys.split(',')
        try:
            keys = [ndb.Key(urlsafe=k) for k in urlsafe_keys]
        # Errors that can be thrown here include ProtocolBufferDecodeError
        # in google.net.proto.ProtocolBuffer. We want to catch any errors here
        # because they're almost certainly urlsafe key decoding errors.
        except Exception:
            raise request_handler.InvalidInputError(
                'Invalid Anomaly key given.')

        requested_anomalies = utils.GetMulti(keys)

        for i, anomaly_entity in enumerate(requested_anomalies):
            if anomaly_entity is None:
                raise request_handler.InvalidInputError(
                    'No Anomaly found for key %s.' % urlsafe_keys[i])

        if not requested_anomalies:
            raise request_handler.InvalidInputError('No anomalies found.')

        sheriff_key = requested_anomalies[0].sheriff
        min_range = utils.MinimumAlertRange(requested_anomalies)
        if min_range:
            query = anomaly.Anomaly.query(
                anomaly.Anomaly.sheriff == sheriff_key)
            query = query.order(-anomaly.Anomaly.timestamp)
            anomalies = query.fetch(limit=_QUERY_LIMIT)

            # Filter out anomalies that have been marked as invalid or ignore.
            # Include all anomalies with an overlapping revision range that have
            # been associated with a bug, or are not yet triaged.
            anomalies = [
                a for a in anomalies if a.bug_id is None or a.bug_id > 0
            ]
            anomalies = _GetOverlaps(anomalies, min_range[0], min_range[1])

            # Make sure alerts in specified param "keys" are included.
            key_set = {a.key for a in anomalies}
            for anomaly_entity in requested_anomalies:
                if anomaly_entity.key not in key_set:
                    anomalies.append(anomaly_entity)
        else:
            anomalies = requested_anomalies
        self._ShowAlerts(anomalies)
 def _AddEntity(self):
     """Adds adds a new entity according to the request parameters."""
     name = self.request.get('add-name')
     if not name:
         raise request_handler.InvalidInputError(
             'No name given when adding new ')
     if self._model_class.get_by_id(name):
         raise request_handler.InvalidInputError(
             'Entity "%s" already exists, cannot add.' % name)
     entity = self._model_class(id=name)
     self._UpdateAndReportResults(entity)
 def _GetAndValidateConfigContents(self):
     """Returns a config dict if one could be gotten, or None otherwise."""
     config = self.request.get('config')
     if not config:
         raise request_handler.InvalidInputError(
             'No config contents given.')
     try:
         config_dict = json.loads(config)
     except (ValueError, TypeError) as json_parse_error:
         raise request_handler.InvalidInputError(str(json_parse_error))
     if not isinstance(config_dict, dict):
         raise request_handler.InvalidInputError('Config was not a dict.')
     return config_dict
def _MakeBuildbucketBisectJob(bisect_job):
    """Creates a bisect job object that the buildbucket service can use.

  Args:
    bisect_job: The entity (try_job.TryJob) off of which to create the
        buildbucket job.

  Returns:
    A buildbucket_job.BisectJob object populated with the necessary attributes
    to pass it to the buildbucket service to start the job.
  """
    config = bisect_job.GetConfigDict()
    if bisect_job.job_type not in ['bisect', 'bisect-fyi']:
        raise request_handler.InvalidInputError(
            'Recipe only supports bisect jobs at this time.')

    # Recipe bisect supports 'perf' and 'return_code' test types only.
    # TODO (prasadv): Update bisect form on dashboard to support test_types.
    test_type = 'perf'
    if config.get('bisect_mode') == 'return_code':
        test_type = config['bisect_mode']

    return buildbucket_job.BisectJob(
        try_job_id=bisect_job.key.id(),
        good_revision=config['good_revision'],
        bad_revision=config['bad_revision'],
        test_command=config['command'],
        metric=config['metric'],
        repeats=config['repeat_count'],
        timeout_minutes=config['max_time_minutes'],
        bug_id=bisect_job.bug_id,
        gs_bucket='chrome-perf',
        recipe_tester_name=config['recipe_tester_name'],
        test_type=test_type,
        required_initial_confidence=config.get('required_initial_confidence'))
    def post(self):
        """Updates the user-selected anomaly threshold configuration.

    Request parameters:
      add-edit: Either 'add' if adding a new config, or 'edit'.
      add-name: A new anomaly config name, if adding one.
      edit-name: An existing anomaly config name, if editing one.
      patterns: Newline-separated list of test path patterns to monitor.

    Depending on the specific sub-class, this will also take other
    parameters for specific properties of the entity being edited.
    """
        try:
            edit_type = self.request.get('add-edit')
            if edit_type == 'add':
                self._AddEntity()
            elif edit_type == 'edit':
                self._EditEntity()
            else:
                raise request_handler.InvalidInputError(
                    'Invalid value for add-edit.')
        except request_handler.InvalidInputError as error:
            message = str(
                error) + ' Model class: ' + self._model_class.__name__
            self.RenderHtml('result.html', {'errors': [message]})
Exemple #8
0
    def post(self):
        """Returns dynamic data for /group_report with some set of alerts.

    The set of alerts is determined by the keys, bug ID or revision given.

    Request parameters:
      keys: A comma-separated list of urlsafe Anomaly keys (optional).
      bug_id: A bug number on the Chromium issue tracker (optional).
      rev: A revision number (optional).

    Outputs:
      JSON for the /group_report page XHR request.
    """
        keys = self.request.get('keys')
        bug_id = self.request.get('bug_id')
        rev = self.request.get('rev')

        try:
            if bug_id:
                self._ShowAlertsWithBugId(bug_id)
            elif keys:
                self._ShowAlertsForKeys(keys)
            elif rev:
                self._ShowAlertsAroundRevision(rev)
            else:
                # TODO(qyearsley): Instead of just showing an error here, show a form
                # where the user can input a bug ID or revision.
                raise request_handler.InvalidInputError(
                    'No anomalies specified.')
        except request_handler.InvalidInputError as error:
            self.response.out.write(json.dumps({'error': str(error)}))
Exemple #9
0
 def testPost_RunCount2_ExceptionInPerformBisect_CustomMetricNotTicked(
     self, mock_tick, mock_perform_bisect):
   mock_perform_bisect.side_effect = request_handler.InvalidInputError()
   try_job.TryJob(
       bug_id=111, status='failed',
       last_ran_timestamp=datetime.datetime.now() - datetime.timedelta(days=8),
       run_count=2).put()
   self.testapp.post('/auto_bisect')
   self.assertEqual(0, mock_tick.call_count)
Exemple #10
0
    def _ShowAlertsWithBugId(self, bug_id):
        """Show alerts for |bug_id|.

    Args:
      bug_id: A bug ID (as an int or string). Could be also be a pseudo-bug ID,
          such as -1 or -2 indicating invalid or ignored.
    """
        if not _IsInt(bug_id):
            raise request_handler.InvalidInputError('Invalid bug ID "%s".' %
                                                    bug_id)
        bug_id = int(bug_id)
        anomaly_query = anomaly.Anomaly.query(anomaly.Anomaly.bug_id == bug_id)
        anomalies = anomaly_query.fetch(limit=_DISPLAY_LIMIT)
        stoppage_alert_query = stoppage_alert.StoppageAlert.query(
            stoppage_alert.StoppageAlert.bug_id == bug_id)
        stoppage_alerts = stoppage_alert_query.fetch(limit=_DISPLAY_LIMIT)
        self._ShowAlerts(anomalies + stoppage_alerts, bug_id)
Exemple #11
0
  def _ShowAlertsAroundRevision(self, rev):
    """Shows a alerts whose revision range includes the given revision.

    Args:
      rev: A revision number, as a string.
    """
    if not _IsInt(rev):
      raise request_handler.InvalidInputError('Invalid rev "%s".' % rev)
    rev = int(rev)

    # We can't make a query that has two inequality filters on two different
    # properties (start_revision and end_revision). Therefore we first query
    # Anomaly entities based on one of these, then filter the resulting list.
    anomaly_query = anomaly.Anomaly.query(anomaly.Anomaly.end_revision >= rev)
    anomaly_query = anomaly_query.order(anomaly.Anomaly.end_revision)
    anomalies = anomaly_query.fetch(limit=_QUERY_LIMIT)
    anomalies = [a for a in anomalies if a.start_revision <= rev]
    stoppage_alert_query = stoppage_alert.StoppageAlert.query(
        stoppage_alert.StoppageAlert.end_revision == rev)
    stoppage_alerts = stoppage_alert_query.fetch(limit=_DISPLAY_LIMIT)
    self._ShowAlerts(anomalies + stoppage_alerts)
def _ValidatePatterns(test_path_patterns):
    """Raises an exception if any test path patterns are invalid."""
    for pattern in test_path_patterns:
        if not _IsValidTestPathPattern(pattern):
            raise request_handler.InvalidInputError(
                'Invalid test path pattern: "%s"' % pattern)
Exemple #13
0
class StartNewBisectForBugTest(testing_common.TestCase):

  def setUp(self):
    super(StartNewBisectForBugTest, self).setUp()
    self.SetCurrentUser('*****@*****.**')
    namespaced_stored_object.Set(
        start_try_job._TESTER_DIRECTOR_MAP_KEY,
        {
            'ChromiumPerf': {
                'linux_perf_tester': 'linux_perf_bisector',
                'win64_nv_tester': 'linux_perf_bisector',
            }
        })

  @mock.patch.object(auto_bisect.start_try_job, 'PerformBisect')
  def testStartNewBisectForBug_StartsBisect(self, mock_perform_bisect):
    testing_common.AddTests(
        ['ChromiumPerf'], ['linux-release'], {'sunspider': {'score': {}}})
    test_key = utils.TestKey('ChromiumPerf/linux-release/sunspider/score')
    anomaly.Anomaly(
        bug_id=111, test=test_key,
        start_revision=300100, end_revision=300200,
        median_before_anomaly=100, median_after_anomaly=200).put()
    auto_bisect.StartNewBisectForBug(111)
    job = try_job.TryJob.query(try_job.TryJob.bug_id == 111).get()
    mock_perform_bisect.assert_called_once_with(job)

  def testStartNewBisectForBug_RevisionTooLow_ReturnsError(self):
    testing_common.AddTests(
        ['ChromiumPerf'], ['linux-release'], {'sunspider': {'score': {}}})
    test_key = utils.TestKey('ChromiumPerf/linux-release/sunspider/score')
    anomaly.Anomaly(
        bug_id=222, test=test_key,
        start_revision=1200, end_revision=1250,
        median_before_anomaly=100, median_after_anomaly=200).put()
    result = auto_bisect.StartNewBisectForBug(222)
    self.assertEqual({'error': 'Invalid "good" revision: 1199.'}, result)

  @mock.patch.object(
      auto_bisect.start_try_job, 'PerformBisect',
      mock.MagicMock(side_effect=request_handler.InvalidInputError(
          'Some reason')))
  def testStartNewBisectForBug_InvalidInputErrorRaised_ReturnsError(self):
    testing_common.AddTests(['Foo'], ['bar'], {'sunspider': {'score': {}}})
    test_key = utils.TestKey('Foo/bar/sunspider/score')
    anomaly.Anomaly(
        bug_id=345, test=test_key,
        start_revision=300100, end_revision=300200,
        median_before_anomaly=100, median_after_anomaly=200).put()
    result = auto_bisect.StartNewBisectForBug(345)
    self.assertEqual({'error': 'Some reason'}, result)

  @mock.patch.object(auto_bisect.start_try_job, 'PerformBisect')
  def testStartNewBisectForBug_WithDefaultRevs_StartsBisect(
      self, mock_perform_bisect):
    testing_common.AddTests(
        ['ChromiumPerf'], ['linux-release'], {'sunspider': {'score': {}}})
    test_key = utils.TestKey('ChromiumPerf/linux-release/sunspider/score')
    testing_common.AddRows(
        'ChromiumPerf/linux-release/sunspider/score',
        {
            1199: {
                'a_default_rev': 'r_foo',
                'r_foo': '9e29b5bcd08357155b2859f87227d50ed60cf857'
            },
            1250: {
                'a_default_rev': 'r_foo',
                'r_foo': 'fc34e5346446854637311ad7793a95d56e314042'
            }
        })
    anomaly.Anomaly(
        bug_id=333, test=test_key,
        start_revision=1200, end_revision=1250,
        median_before_anomaly=100, median_after_anomaly=200).put()
    auto_bisect.StartNewBisectForBug(333)
    job = try_job.TryJob.query(try_job.TryJob.bug_id == 333).get()
    mock_perform_bisect.assert_called_once_with(job)

  def testStartNewBisectForBug_UnbisectableTest_ReturnsError(self):
    testing_common.AddTests(['V8'], ['x86'], {'v8': {'sunspider': {}}})
    # The test suite "v8" is in the black-list of test suite names.
    test_key = utils.TestKey('V8/x86/v8/sunspider')
    anomaly.Anomaly(
        bug_id=444, test=test_key,
        start_revision=155000, end_revision=155100,
        median_before_anomaly=100, median_after_anomaly=200).put()
    result = auto_bisect.StartNewBisectForBug(444)
    self.assertEqual({'error': 'Could not select a test.'}, result)