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
예제 #2
0
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
예제 #3
0
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