def _IsAlertRecovered(self, alert_entity): test = alert_entity.GetTestMetadataKey().get() if not test: logging.error( 'TestMetadata %s not found for Anomaly %s, deleting test.', utils.TestPath(alert_entity.GetTestMetadataKey()), alert_entity) 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 > alert_entity.end_revision ] change_points = find_anomalies.FindChangePointsForTest(rows, config) delta_anomaly = (alert_entity.median_after_anomaly - alert_entity.median_before_anomaly) for change in change_points: delta_change = change.median_after - change.median_before if (self._IsOppositeDirection(delta_anomaly, delta_change) and self._IsApproximatelyEqual(delta_anomaly, -delta_change)): logging.debug( 'Anomaly %s recovered; recovery change point %s.', alert_entity.key, change.AsDict()) return True return False
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() 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 _IsAnomalyRecovered(anomaly_entity): """Checks whether anomaly has recovered. We have the measurements for the segment before the anomaly. If we take the measurements for the latest segment after the anomaly, we can find if the anomaly recovered. Args: anomaly_entity: The original regression anomaly. Returns: A tuple (is_anomaly_recovered, measurements), where is_anomaly_recovered is True if anomaly has recovered, and measurements is dictionary of name to value of measurements used to evaluate if anomaly recovered. measurements is None if anomaly has not recovered. """ # 1. Check if the Anomaly entity has std_dev_before_anomaly and # window_end_revision properties which we're using to decide whether or # not it is recovered. if (anomaly_entity.std_dev_before_anomaly is None or anomaly_entity.window_end_revision is None): return False, None test = anomaly_entity.test.get() config = anomaly_config.GetAnomalyConfigDict(test) latest_rows = find_anomalies.GetRowsToAnalyze( test, anomaly_entity.segment_size_after) latest_values = [row.value for row in latest_rows if row.revision > anomaly_entity.window_end_revision] # 2. Segment size filter. if len(latest_values) < anomaly_entity.segment_size_after: return False, None median_before = anomaly_entity.median_before_anomaly median_after = math_utils.Median(latest_values) std_dev_before = anomaly_entity.std_dev_before_anomaly std_dev_after = math_utils.StandardDeviation(latest_values) multiple_of_std_dev = config.get('multiple_of_std_dev', _DEFAULT_MULTIPLE_OF_STD_DEV) min_relative_change = config.get('min_relative_change', _DEFAULT_MIN_RELATIVE_CHANGE) min_absolute_change = config.get('min_absolute_change', _DEFAULT_MIN_ABSOLUTE_CHANGE) # If no improvement direction is provided, use absolute changes. if test.improvement_direction == anomaly.UNKNOWN: absolute_change = abs(median_after - median_before) relative_change = abs(_RelativeChange(median_before, median_after)) else: if test.improvement_direction == anomaly.UP: direction = -1 else: direction = 1 absolute_change = direction * (median_after - median_before) relative_change = direction * _RelativeChange(median_before, median_after) measurements = { 'segment_size_after': anomaly_entity.segment_size_after, 'window_end_revision': anomaly_entity.window_end_revision, 'median_before': median_before, 'median_after': median_after, 'std_dev_before': std_dev_before, 'std_dev_after': std_dev_after, 'multiple_of_std_dev': multiple_of_std_dev, 'min_relative_change': min_relative_change, 'min_absolute_change': min_absolute_change, 'absolute_change': absolute_change, 'relative_change': relative_change, } # 3. If it's an improvement, return. if absolute_change <= 0: return True, measurements # 4. Absolute change filter. if min_absolute_change > 0 and absolute_change >= min_absolute_change: return False, None # 5. Relative change filter. if relative_change >= min_relative_change: return False, None # 6. Standard deviation filter. min_std_dev = min(std_dev_before, std_dev_after) if absolute_change > min_std_dev: return False, None return True, measurements