blob: 87265807ac8a74e4165b16f2759598d36d2084af [file] [log] [blame]
# Copyright 2015 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.
"""The database model for an "Anomaly", which represents a step up or down."""
import sys
from google.appengine.ext import ndb
from dashboard.models import alert
# A string to describe the magnitude of a change from zero to non-zero.
FREAKIN_HUGE = 'zero-to-nonzero'
# Possible improvement directions for a change. An Anomaly will always have a
# direction of UP or DOWN, but a test's improvement direction can be UNKNOWN.
UP, DOWN, UNKNOWN = (0, 1, 4)
class Anomaly(alert.Alert):
"""Represents a change-point or step found in the data series for a test.
An Anomaly can be an upward or downward change, and can represent an
improvement or a regression.
"""
# The number of points before and after this anomaly that were looked at
# when finding this anomaly.
segment_size_before = ndb.IntegerProperty(indexed=False)
segment_size_after = ndb.IntegerProperty(indexed=False)
# The medians of the segments before and after the anomaly.
median_before_anomaly = ndb.FloatProperty(indexed=False)
median_after_anomaly = ndb.FloatProperty(indexed=False)
# The standard deviation of the segments before the anomaly.
std_dev_before_anomaly = ndb.FloatProperty(indexed=False)
# The number of points that were used in the before/after segments.
# This is also returned by FindAnomalies
window_end_revision = ndb.IntegerProperty(indexed=False)
# In order to estimate how likely it is that this anomaly is due to noise,
# t-test may be performed on the points before and after. The t-statistic,
# degrees of freedom, and p-value are potentially-useful intermediary results.
t_statistic = ndb.FloatProperty(indexed=False)
degrees_of_freedom = ndb.FloatProperty(indexed=False)
p_value = ndb.FloatProperty(indexed=False)
# Whether this anomaly represents an improvement; if false, this anomaly is
# considered to be a regression.
is_improvement = ndb.BooleanProperty(indexed=True, default=False)
# Whether this anomaly recovered (i.e. if this is a step down, whether there
# is a corresponding step up later on, or vice versa.)
recovered = ndb.BooleanProperty(indexed=True, default=False)
@property
def percent_changed(self):
"""The percent change from before the anomaly to after."""
if self.median_before_anomaly == 0.0:
return sys.float_info.max
difference = self.median_after_anomaly - self.median_before_anomaly
return 100 * difference / self.median_before_anomaly
@property
def direction(self):
"""Whether the change is numerically an increase or decrease."""
if self.median_before_anomaly < self.median_after_anomaly:
return UP
return DOWN
def GetDisplayPercentChanged(self):
"""Gets a string showing the percent change."""
if abs(self.percent_changed) == sys.float_info.max:
return FREAKIN_HUGE
else:
return str('%.1f%%' % abs(self.percent_changed))
def SetIsImprovement(self, test=None):
"""Sets whether the alert is an improvement for the given test."""
if not test:
test = self.GetTestMetadataKey().get()
# |self.direction| is never equal to |UNKNOWN| (see the definition above)
# so when the test improvement direction is |UNKNOWN|, |self.is_improvement|
# will be False.
self.is_improvement = (self.direction == test.improvement_direction)