1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""The database model for an "Anomaly", which represents a step up or down.""" 6 7import sys 8 9from google.appengine.ext import ndb 10 11from dashboard.models import alert 12 13# A string to describe the magnitude of a change from zero to non-zero. 14FREAKIN_HUGE = 'zero-to-nonzero' 15 16# Possible improvement directions for a change. An Anomaly will always have a 17# direction of UP or DOWN, but a test's improvement direction can be UNKNOWN. 18UP, DOWN, UNKNOWN = (0, 1, 4) 19 20 21class Anomaly(alert.Alert): 22 """Represents a change-point or step found in the data series for a Test. 23 24 An Anomaly can be an upward or downward change, and can represent an 25 improvement or a regression. 26 """ 27 # The number of points before and after this anomaly that were looked at 28 # when finding this anomaly. 29 segment_size_before = ndb.IntegerProperty(indexed=False) 30 segment_size_after = ndb.IntegerProperty(indexed=False) 31 32 # The medians of the segments before and after the anomaly. 33 median_before_anomaly = ndb.FloatProperty(indexed=False) 34 median_after_anomaly = ndb.FloatProperty(indexed=False) 35 36 # The standard deviation of the segments before the anomaly. 37 std_dev_before_anomaly = ndb.FloatProperty(indexed=False) 38 39 # The number of points that were used in the before/after segments. 40 # This is also returned by FindAnomalies 41 window_end_revision = ndb.IntegerProperty(indexed=False) 42 43 # In order to estimate how likely it is that this anomaly is due to noise, 44 # t-test may be performed on the points before and after. The t-statistic, 45 # degrees of freedom, and p-value are potentially-useful intermediary results. 46 t_statistic = ndb.FloatProperty(indexed=False) 47 degrees_of_freedom = ndb.FloatProperty(indexed=False) 48 p_value = ndb.FloatProperty(indexed=False) 49 50 # Whether this anomaly represents an improvement; if false, this anomaly is 51 # considered to be a regression. 52 is_improvement = ndb.BooleanProperty(indexed=True, default=False) 53 54 # Whether this anomaly recovered (i.e. if this is a step down, whether there 55 # is a corresponding step up later on, or vice versa.) 56 recovered = ndb.BooleanProperty(indexed=True, default=False) 57 58 @property 59 def percent_changed(self): 60 """The percent change from before the anomaly to after.""" 61 if self.median_before_anomaly == 0.0: 62 return sys.float_info.max 63 difference = self.median_after_anomaly - self.median_before_anomaly 64 return 100 * difference / self.median_before_anomaly 65 66 @property 67 def direction(self): 68 """Whether the change is numerically an increase or decrease.""" 69 if self.median_before_anomaly < self.median_after_anomaly: 70 return UP 71 return DOWN 72 73 def GetDisplayPercentChanged(self): 74 """Gets a string showing the percent change.""" 75 if abs(self.percent_changed) == sys.float_info.max: 76 return FREAKIN_HUGE 77 else: 78 return str('%.1f%%' % abs(self.percent_changed)) 79 80 def SetIsImprovement(self, test=None): 81 """Sets whether the alert is an improvement for the given test.""" 82 if not test: 83 test = self.test.get() 84 # |self.direction| is never equal to |UNKNOWN| (see the definition above) 85 # so when the test improvement direction is |UNKNOWN|, |self.is_improvement| 86 # will be False. 87 self.is_improvement = (self.direction == test.improvement_direction) 88