Bug 1174877 - Updates to make perf alert analysis code useful for perfherder

* Using machine history is now optional (perfherder doesn't track it, and
  we don't think we need it)
* Performance datums, analyze_t take keyword arguments to make API more
  intuitive
* Various other minor updates to make code easier to understand
This commit is contained in:
William Lachance 2015-06-15 16:44:00 -04:00
Родитель eeba95595c
Коммит e1077aca41
2 изменённых файлов: 67 добавлений и 51 удалений

Просмотреть файл

@ -37,15 +37,16 @@ class TestTalosAnalyzer(unittest.TestCase):
def get_data(self):
times = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
values = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]
return [PerfDatum(t, t, t, float(v), t, t) for t, v in zip(times, values)]
return [PerfDatum(t, float(v)) for t, v in zip(times, values)]
def test_analyze_t(self):
a = TalosAnalyzer()
data = self.get_data()
a.addData(data)
result = [(d.time, d.state) for d in a.analyze_t(5, 5, 2, 15, 5)]
result = [(d.push_timestamp, d.state) for d in
a.analyze_t(back_window=5, fore_window=5, t_threshold=2,
machine_threshold=15, machine_history_size=5)]
self.assertEqual(result, [
(1, 'good'),
(2, 'good'),
@ -78,13 +79,16 @@ class TestTalosAnalyzer(unittest.TestCase):
payload = SampleData.get_perf_data(os.path.join('graphs', filename))
runs = payload['test_runs']
data = [PerfDatum(r[0], r[6], r[2], r[3], r[1][1], r[2], r[1][2]) for r in runs]
data = [PerfDatum(r[2], r[3], testrun_id=r[0], machine_id=r[6],
testrun_timestamp=r[2], buildid=r[1][1],
revision=r[1][2]) for r in runs]
a = TalosAnalyzer()
a.addData(data)
results = a.analyze_t(BACK_WINDOW, FORE_WINDOW, THRESHOLD,
MACHINE_THRESHOLD, MACHINE_HISTORY_SIZE)
regression_timestamps = [d.timestamp for d in results if d.state == 'regression']
regression_timestamps = [d.testrun_timestamp for d in results if
d.state == 'regression']
self.assertEqual(regression_timestamps, expected_timestamps)
if __name__ == '__main__':

Просмотреть файл

@ -61,27 +61,30 @@ def calc_t(w1, w2, weight_fn=None):
class PerfDatum(object):
__slots__ = ('testrun_id', 'machine_id', 'timestamp', 'value', 'buildid',
'time', 'revision', 'run_number', 'last_other', 'historical_stats',
'forward_stats', 't', 'state')
def __init__(self, testrun_id, machine_id, timestamp, value, buildid, time,
def __init__(self, push_timestamp, value, testrun_timestamp=None,
buildid=None, testrun_id=None, machine_id=None,
revision=None, state='good'):
# Date code was pushed
self.push_timestamp = push_timestamp
# Value of this point
self.value = value
# Which build was this
self.buildid = buildid
# timestamp when test was run
if testrun_timestamp:
self.testrun_timestamp = testrun_timestamp
else:
# in some cases we may not have information on when test was run
# in that case just pretend its the same as when it was pushed
self.testrun_timestamp = push_timestamp
# Which test run was this
self.testrun_id = testrun_id
# Which machine is this
self.machine_id = machine_id
# Talos timestamp
self.timestamp = timestamp
# Value of this point
self.value = value
# Which build was this
self.buildid = buildid
# Date code was pushed
self.time = time
# What revision this data is for
self.revision = revision
# t-test score
self.t = 0
# Whether a machine issue or perf regression is found
@ -89,24 +92,28 @@ class PerfDatum(object):
def __cmp__(self, o):
return cmp(
(self.time, self.timestamp),
(o.time, o.timestamp),
(self.push_timestamp, self.testrun_timestamp),
(o.push_timestamp, o.testrun_timestamp),
)
def __eq__(self, o):
return cmp(
(self.timestamp, self.value, self.buildid, self.machine_id),
(o.timestamp, o.value, o.buildid, o.machine_id),
(self.testrun_timestamp, self.value, self.buildid, self.machine_id),
(o.testrun_timestamp, o.value, o.buildid, o.machine_id),
) == 0
def __ne__(self, o):
return not self == o
def __repr__(self):
return "<%s: %.3f, %i, %s>" % (self.buildid, self.value, self.timestamp, self.machine_id)
return "<%s: %.3f, %i, %s>" % (self.buildid, self.value,
self.testrun_timestamp, self.machine_id)
def __str__(self):
return "Build %s on %s %s %s %s" % (self.buildid, self.timestamp, self.time, self.value, self.machine_id)
return "Build %s on %s %s %s %s" % (self.buildid,
self.testrun_timestamp,
self.push_timestamp, self.value,
self.machine_id)
class TalosAnalyzer:
@ -124,9 +131,11 @@ class TalosAnalyzer:
for d in self.machine_history.values():
d.sort()
def analyze_t(self, j, k, threshold, machine_threshold, machine_history_size):
def analyze_t(self, back_window=12, fore_window=12, t_threshold=7,
machine_threshold=None, machine_history_size=None):
# Use T-Tests
# Analyze test data using T-Tests, comparing data[i-j:i] to data[i:i+k]
(j, k) = (back_window, fore_window)
good_data = []
num_points = len(self.data) - k + 1
@ -139,6 +148,18 @@ class TalosAnalyzer:
# start of the window.
jw.reverse()
di.historical_stats = analyze(jw)
di.forward_stats = analyze(kw)
if len(jw) >= j:
di.t = abs(calc_t(jw, kw, linear_weights))
else:
# Assume it's ok, we don't have enough data
di.t = 0
if machine_threshold is None:
good_data.append(di)
else:
my_history = self.machine_history[di.machine_id]
my_history_index = my_history.index(di)
my_data = [d.value for d in self.machine_history[di.machine_id][my_history_index-machine_history_size+1:my_history_index+1]]
@ -150,15 +171,6 @@ class TalosAnalyzer:
other_data.insert(0, dl.value)
l -= 1
di.historical_stats = analyze(jw)
di.forward_stats = analyze(kw)
if len(jw) >= j:
di.t = abs(calc_t(jw, kw, linear_weights))
else:
# Assume it's ok, we don't have enough data
di.t = 0
if len(other_data) >= k*2 and len(my_data) >= machine_history_size:
m_t = calc_t(other_data, my_data, linear_weights)
else:
@ -182,7 +194,7 @@ class TalosAnalyzer:
# find where regressions most likely happened.
for i in range(1, len(good_data) - 1):
di = good_data[i]
if di.t <= threshold:
if di.t <= t_threshold:
continue
# Check the adjacent points