def _IsAnomalyRecovered(anomaly_entity): """Checks whether an Anomaly has recovered. An Anomaly will be considered "recovered" if there's a change point in the series after the Anomaly with roughly equal magnitude and opposite direction. Args: anomaly_entity: The original regression Anomaly. Returns: True if the Anomaly should be marked as recovered, False otherwise. """ test = anomaly_entity.test.get() if not test: logging.error('Test %s not found for Anomaly %s, deleting test.', utils.TestPath(anomaly_entity.test), anomaly_entity) anomaly_entity.key.delete() return False config = anomaly_config.GetAnomalyConfigDict(test) max_num_rows = config.get('max_window_size', find_anomalies.DEFAULT_NUM_POINTS) rows = [ r for r in find_anomalies.GetRowsToAnalyze(test, max_num_rows) if r.revision > anomaly_entity.end_revision ] change_points = find_anomalies.FindChangePointsForTest(rows, config) delta_anomaly = (anomaly_entity.median_after_anomaly - anomaly_entity.median_before_anomaly) for change in change_points: delta_change = change.median_after - change.median_before if (_IsOppositeDirection(delta_anomaly, delta_change) and _IsApproximatelyEqual(delta_anomaly, -delta_change)): logging.debug('Anomaly %s recovered; recovery change point %s.', anomaly_entity.key, change.AsDict()) return True return False
def _MigrateStoppageAlerts(old_parent_key, new_parent_key): """Copies the StoppageAlert entities from one test to another. Args: old_parent_key: Source TestMetadata entity key. new_parent_key: Destination TestMetadata entity key. Returns: A list of Future objects for StoppageAlert puts and deletes. """ alerts_to_update = stoppage_alert.StoppageAlert.GetAlertsForTest( old_parent_key, limit=_MAX_DATASTORE_PUTS_PER_PUT_MULTI_CALL) if not alerts_to_update: return [] futures = [] for entity in alerts_to_update: new_entity = stoppage_alert.StoppageAlert(parent=ndb.Key( 'StoppageAlertParent', utils.TestPath(new_parent_key)), id=entity.key.id(), mail_sent=entity.mail_sent, recovered=entity.recovered) futures.append(entity.key.delete_async()) futures.append(new_entity.put_async()) return futures
def testGet_WithValidTestPath_ShowsChart(self): test_key = self._AddSampleData() test_path = utils.TestPath(test_key) response = self.testapp.get('/debug_alert?test_path=%s' % test_path) self.assertIn('id="plot"', response.body)
def testGet_WithBogusParameterNames_ParameterIgnored(self, simulate_mock): test_key = self._AddSampleData() response = self.testapp.get('/debug_alert?test_path=%s&config=%s' % (utils.TestPath(test_key), '{"foo":0.75}')) simulate_mock.assert_called_once_with(mock.ANY) self.assertNotIn('"foo"', response.body)
def SuitePath(alert): path_parts = utils.TestPath(alert.test).split('/') return '%s/%s' % (path_parts[0], path_parts[2])
def test_path(self): """Slash-separated list of key parts, 'master/bot/suite/chart/...'.""" return utils.TestPath(self.key)
def get(self): """Gets the page for viewing recently added points. Request parameters: pattern: A test path pattern with asterisk wildcards (optional). Outputs: A page showing recently added points. """ # Construct a query for recently added Row entities. query = graph_data.Row.query() query = query.order(-graph_data.Row.timestamp) # If a maximum number of tests was specified, use it; fall back on default. try: max_tests = int(self.request.get('max_tests', _MAX_MATCHING_TESTS)) except ValueError: max_tests = _MAX_MATCHING_TESTS # If a test path pattern was specified, filter the query to include only # Row entities that belong to a test that matches the pattern. test_pattern = self.request.get('pattern') num_originally_matching_tests = 0 if test_pattern: test_paths = list_tests.GetTestsMatchingPattern( test_pattern, only_with_rows=True) if not test_paths: self.RenderHtml( 'new_points.html', { 'pattern': test_pattern, 'error': 'No tests matching pattern: %s' % test_pattern, }) return # If test_keys contains too many tests, then this query will exceed a # memory limit or time out. So, limit the number of tests and let the # user know that this has happened. num_originally_matching_tests = len(test_paths) if num_originally_matching_tests > max_tests: test_paths = test_paths[:max_tests] test_keys = map(utils.OldStyleTestKey, test_paths) query = query.filter(graph_data.Row.parent_test.IN(test_keys)) # If a valid number of points was given, use it. Otherwise use the default. try: num_points = int( self.request.get('num_points', _DEFAULT_NUM_POINTS)) except ValueError: num_points = _DEFAULT_NUM_POINTS # Fetch the Row entities. rows = query.fetch(limit=num_points) # Make a list of dicts which will be passed to the template. row_dicts = [] for row in rows: row_dicts.append({ 'test': utils.TestPath(row.parent_test), 'added_time': row.timestamp.strftime('%Y-%m-%d %H:%M:%S %Z'), 'revision': row.revision, 'value': row.value, 'error': row.error, }) error_message = '' if num_originally_matching_tests > max_tests: error_message = ( 'Pattern originally matched %s tests; only showing ' 'points from the first %s tests.' % (num_originally_matching_tests, max_tests)) # Render the template with the row information that was fetched. self.RenderHtml( 'new_points.html', { 'pattern': test_pattern, 'num_points': num_points, 'max_tests': max_tests, 'rows': row_dicts, 'error': error_message, })
def _ListSubTestCacheKey(test_key): """Returns the sub-tests list cache key for a test suite.""" parts = utils.TestPath(test_key).split('/') master, bot, suite = parts[0:3] return graph_data.LIST_TESTS_SUBTEST_CACHE_KEY % (master, bot, suite)
def _SubTestPath(test_key): """Returns the part of a test path starting from after the test suite.""" full_test_path = utils.TestPath(test_key) parts = full_test_path.split('/') assert len(parts) > 3 return '/'.join(parts[3:])
def SuitePath(alert): path_parts = utils.TestPath(alert.GetTestMetadataKey()).split('/') return '%s/%s' % (path_parts[0], path_parts[2])
def testGetTestMetadataKey_TestMetadata(self): a = anomaly.Anomaly(test=utils.TestKey('a/b/c/d')) k = a.GetTestMetadataKey() self.assertEqual('TestMetadata', k.kind()) self.assertEqual('a/b/c/d', k.id()) self.assertEqual('a/b/c/d', utils.TestPath(k))
def GetBotNamesFromAlerts(alerts): """Gets a set with the names of the bots related to some alerts.""" # a.test is the key of a TestMetadata entity, and the TestPath is a path like # master_name/bot_name/test_suite_name/metric... return {utils.TestPath(a.test).split('/')[1] for a in alerts}
def testTestPath_Container(self): key = ndb.Key('TestContainer', 'm/b/suite/metric') self.assertEqual('m/b/suite/metric', utils.TestPath(key))
def testTestPath_TestMetadata(self): key = ndb.Key('TestMetadata', 'm/b/suite/metric') self.assertEqual('m/b/suite/metric', utils.TestPath(key))
def testTestPath_Test(self): key = ndb.Key('Master', 'm', 'Bot', 'b', 'Test', 'suite', 'Test', 'metric') self.assertEqual('m/b/suite/metric', utils.TestPath(key))