Remove obsolete code and database info.
This commit is contained in:
Родитель
63fcafd3b0
Коммит
131b1b41f3
2
TODO
2
TODO
|
@ -1,2 +0,0 @@
|
|||
UPDATE.php: make sequential
|
||||
migrate-2.php: create a sequence out of what used to be a stamp
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
|
||||
$migrate = function() {
|
||||
mysql_query("DROP TABLE awfy_regression_breakdown_noise");
|
||||
mysql_query("DROP TABLE awfy_regression_score_noise");
|
||||
mysql_query("ALTER TABLE `awfy_suite` DROP `confidence_factor`;");
|
||||
mysql_query("ALTER TABLE `awfy_suite_test` DROP `confidence_factor`;");
|
||||
mysql_query("ALTER TABLE `awfy_machine` DROP `confidence_runs`");
|
||||
};
|
||||
|
||||
$rollback = function() {
|
||||
|
||||
mysql_query("CREATE TABLE `awfy_regression_breakdown_noise` (
|
||||
`id` int(10) UNSIGNED NOT NULL,
|
||||
`machine_id` int(10) UNSIGNED NOT NULL,
|
||||
`mode_id` int(10) UNSIGNED NOT NULL,
|
||||
`suite_test_id` int(10) UNSIGNED NOT NULL,
|
||||
`noise` double NOT NULL,
|
||||
`disabled` tinyint(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='avg_consequent_diff'");
|
||||
|
||||
mysql_query("ALTER TABLE `awfy_regression_breakdown_noise`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `machine_id` (`machine_id`,`mode_id`,`suite_test_id`)");
|
||||
|
||||
mysql_query("ALTER TABLE `awfy_regression_breakdown_noise`
|
||||
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT");
|
||||
|
||||
mysql_query("CREATE TABLE `awfy_regression_score_noise` (
|
||||
`id` int(10) UNSIGNED NOT NULL,
|
||||
`machine_id` int(10) UNSIGNED NOT NULL,
|
||||
`mode_id` int(10) UNSIGNED NOT NULL,
|
||||
`suite_version_id` int(10) UNSIGNED NOT NULL,
|
||||
`noise` double NOT NULL,
|
||||
`disabled` tinyint(1) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='avg_consequent_diff'");
|
||||
|
||||
mysql_query("ALTER TABLE `awfy_regression_score_noise`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `machine_id` (`machine_id`),
|
||||
ADD KEY `mode_id` (`mode_id`),
|
||||
ADD KEY `suite_version_id` (`suite_version_id`)");
|
||||
|
||||
mysql_query("ALTER TABLE `awfy_regression_score_noise`
|
||||
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT");
|
||||
|
||||
mysql_query("ALTER TABLE `awfy_suite` ADD `confidence_factor` FLOAT NOT NULL DEFAULT '1' AFTER `visible`");
|
||||
mysql_query("ALTER TABLE `awfy_suite_test` ADD `confidence_factor` FLOAT NOT NULL DEFAULT '1' AFTER `visible`");
|
||||
mysql_query("ALTER TABLE `awfy_machine` ADD `confidence_runs` TINYINT NOT NULL DEFAULT '5' AFTER `message`");
|
||||
};
|
|
@ -1,109 +0,0 @@
|
|||
# vim: set ts=4 sw=4 tw=99 et:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from optparse import OptionParser
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.append("../server")
|
||||
import awfy
|
||||
import tables
|
||||
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]")
|
||||
parser.add_option("-n", "--non-existing", dest="nonexistonly", action="store_true", default=False,
|
||||
help="Only compute noise of items that don't have one yet.")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
def median(x):
|
||||
x.sort()
|
||||
length = len(x)
|
||||
if length % 2 == 1:
|
||||
return x[length/2]
|
||||
else:
|
||||
return (x[(length-1)/2] + x[(length+1)/2]) / 2.0
|
||||
|
||||
def avg_diff(machine, suite, mode, first):
|
||||
prev = first
|
||||
current = first.next()
|
||||
diffs = []
|
||||
while current:
|
||||
diff = abs(prev.get('score') - current.get('score'))
|
||||
if diff != 0:
|
||||
diffs.append(diff)
|
||||
|
||||
prev = current
|
||||
current = current.next()
|
||||
if len(diffs) == 0:
|
||||
return None
|
||||
return median(diffs)
|
||||
|
||||
def equal_sign(a, b):
|
||||
if a >= 0 and b >= 0:
|
||||
return True
|
||||
if a <= 0 and b <= 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def avg_consequent_diff(machine, suite, mode, first):
|
||||
prev = first
|
||||
current = first.next()
|
||||
diffs = []
|
||||
consequent = 0
|
||||
while current:
|
||||
diff = prev.get('score') - current.get('score')
|
||||
if diff != 0:
|
||||
if equal_sign(diff, consequent):
|
||||
consequent += diff
|
||||
else:
|
||||
diffs.append(abs(consequent))
|
||||
consequent = diff
|
||||
|
||||
prev = current
|
||||
current = current.next()
|
||||
if consequent != 0:
|
||||
diffs.append(abs(consequent))
|
||||
if len(diffs) == 0:
|
||||
return None
|
||||
return median(diffs)
|
||||
|
||||
for machine in tables.Machine.all():
|
||||
if (machine.id != 28 and # mac os x 32 bit shell
|
||||
machine.id != 29 and # mac os x 64 bit shell
|
||||
machine.id != 26 and # ffos flame browser
|
||||
machine.id != 30 and # mac os x browser
|
||||
machine.id != 17): # windows 8 browser
|
||||
continue
|
||||
|
||||
for mode in tables.Mode.allWith(machine):
|
||||
for suite in tables.SuiteVersion.all():
|
||||
# Don't update, only insert new ones, if requested
|
||||
if tables.RegressionScoreNoise(machine, suite, mode).id != 0 and options.nonexistonly:
|
||||
continue
|
||||
|
||||
first = tables.Score.firstOfRecent(machine, suite, mode)
|
||||
if not first:
|
||||
continue
|
||||
diff = avg_consequent_diff(machine, suite, mode, first)
|
||||
if not diff:
|
||||
continue
|
||||
tables.RegressionScoreNoise.insertOrUpdate(machine, suite, mode, diff)
|
||||
print suite.get('name'), mode.get('name'), diff
|
||||
awfy.db.commit()
|
||||
for suite in tables.SuiteTest.all():
|
||||
# Don't update, only insert new ones, if requested
|
||||
if tables.RegressionBreakdownNoise(machine, suite, mode).id != 0 and options.nonexistonly:
|
||||
continue
|
||||
|
||||
first = tables.Breakdown.firstOfRecent(machine, suite, mode)
|
||||
if not first:
|
||||
continue
|
||||
diff = avg_consequent_diff(machine, suite, mode, first)
|
||||
if not diff:
|
||||
continue
|
||||
tables.RegressionBreakdownNoise.insertOrUpdate(machine, suite, mode, diff)
|
||||
print suite.get('name'), mode.get('name'), diff
|
||||
awfy.db.commit()
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
# vim: set ts=4 sw=4 tw=99 et:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
pwd = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append("../server")
|
||||
import awfy
|
||||
import tables
|
||||
|
||||
RUNS = 20
|
||||
|
||||
def isOutliner(item):
|
||||
score = item.score()
|
||||
nexts = [i.get("score") for i in score.nexts_no_outliners(RUNS)]
|
||||
prevs = [i.get("score") for i in score.prevs_no_outliners(RUNS)]
|
||||
if len(nexts) != RUNS:
|
||||
return False
|
||||
if len(prevs) != RUNS:
|
||||
return False
|
||||
if not item.score().hasNoise():
|
||||
return False
|
||||
avg_nexts = sum(nexts)/len(nexts)
|
||||
avg_prevs = sum(prevs)/len(prevs)
|
||||
|
||||
if abs(avg_nexts - avg_prevs) < score.noise() / tables.NOISE_FACTOR:
|
||||
return True
|
||||
return False
|
||||
|
||||
def isEmptyRegression(item):
|
||||
score = item.score()
|
||||
if score.prev().get("build").get("cset") == score.get("build").get("cset"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def isBiModal(item):
|
||||
def divide(li):
|
||||
h = max(li)
|
||||
l = min(li)
|
||||
highs = []
|
||||
lows = []
|
||||
for i in li:
|
||||
if h-i < i-l:
|
||||
highs.append(i)
|
||||
else:
|
||||
lows.append(i)
|
||||
return highs, lows
|
||||
|
||||
|
||||
score = item.score()
|
||||
nexts = [i.get("score") for i in score.nexts(RUNS)]
|
||||
prevs = [i.get("score") for i in score.prevs(RUNS)]
|
||||
nexts_h, nexts_l = divide(nexts)
|
||||
prevs_h, prevs_l = divide(prevs)
|
||||
|
||||
if len(nexts_h) < 4:
|
||||
return False
|
||||
if len(nexts_l) < 4:
|
||||
return False
|
||||
if len(prevs_h) < 4:
|
||||
return False
|
||||
if len(prevs_l) < 4:
|
||||
return False
|
||||
if not score.hasNoise():
|
||||
return False
|
||||
avg_nexts_h = sum(nexts_h)/len(nexts_h)
|
||||
avg_nexts_l = sum(nexts_l)/len(nexts_l)
|
||||
avg_prevs_h = sum(prevs_h)/len(prevs_h)
|
||||
avg_prevs_l = sum(prevs_l)/len(prevs_l)
|
||||
|
||||
if abs(avg_nexts_h - avg_prevs_h) >= score.noise() / tables.NOISE_FACTOR:
|
||||
return False
|
||||
if abs(avg_nexts_l - avg_prevs_l) >= score.noise() / tables.NOISE_FACTOR:
|
||||
return False
|
||||
return True
|
||||
|
||||
def zoomNoRegression(item):
|
||||
PARTITIONS = 5
|
||||
CRUNS = 100 / PARTITIONS * PARTITIONS
|
||||
AMOUNT = CRUNS / PARTITIONS
|
||||
|
||||
def _avg(items):
|
||||
if len(items) == 0:
|
||||
return 0
|
||||
return sum(items)/len(items)
|
||||
|
||||
def partition(li):
|
||||
nli = []
|
||||
for i in range(PARTITIONS):
|
||||
nli.append(_avg(li[i*AMOUNT:i*AMOUNT+AMOUNT]))
|
||||
return nli
|
||||
|
||||
def _noise(li):
|
||||
noise = []
|
||||
for i in range(len(li)-1):
|
||||
noise.append(abs(li[i] - li[i+1]))
|
||||
return noise
|
||||
|
||||
def outliner(li, i, noise):
|
||||
if i == 0:
|
||||
return False
|
||||
elif len(li) - 1 == i:
|
||||
return False
|
||||
if abs(li[i-1] - li[i+1]) < noise:
|
||||
if abs(li[i] - li[i+1]) > noise and abs(li[i] - li[i-1]) > noise:
|
||||
return True
|
||||
return False
|
||||
|
||||
def remove_outliners(li, noise):
|
||||
return [li[i] for i in range(len(li)) if not outliner(li, i, noise)]
|
||||
|
||||
score = item.score()
|
||||
nexts = [score.get("score")] + [i.get("score") for i in score.nexts(CRUNS - 1)]
|
||||
prevs = [i.get("score") for i in score.prevs(CRUNS)]
|
||||
if len(nexts) != CRUNS:
|
||||
return False
|
||||
if len(prevs) != CRUNS:
|
||||
return False
|
||||
|
||||
nexts = partition(nexts)
|
||||
prevs = partition(prevs)
|
||||
noise = _avg(_noise(nexts) + _noise(prevs))
|
||||
|
||||
nexts = remove_outliners(nexts, noise)
|
||||
prevs = remove_outliners(prevs, noise)
|
||||
noise = _avg(_noise(nexts) + _noise(prevs))
|
||||
if abs(nexts[0] - prevs[0]) > noise:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
modes = [14,16,20,21,22,23,25,26,27,28,29,31,32,33,35]
|
||||
for regression in tables.Regression.where({'status':'unconfirmed'}):
|
||||
if regression.get("build").get("mode_id") not in modes:
|
||||
continue
|
||||
|
||||
allRemoved = True
|
||||
for item in regression.regressions():
|
||||
if item.get("noise"):
|
||||
continue
|
||||
|
||||
print "item"
|
||||
|
||||
if isEmptyRegression(item):
|
||||
print "remove", regression.id
|
||||
item.score().dump()
|
||||
item.update({"noise":"1"})
|
||||
continue
|
||||
|
||||
if isOutliner(item):
|
||||
print "remove", regression.id
|
||||
item.score().dump()
|
||||
item.update({"noise":"1"})
|
||||
continue
|
||||
|
||||
if isBiModal(item):
|
||||
print "remove", regression.id
|
||||
item.score().dump()
|
||||
item.update({"noise":"1"})
|
||||
continue
|
||||
|
||||
if zoomNoRegression(item):
|
||||
print "remove", regression.id
|
||||
item.score().dump()
|
||||
item.update({"noise":"1"})
|
||||
continue
|
||||
|
||||
allRemoved = False
|
||||
|
||||
if allRemoved:
|
||||
print "remove item"
|
||||
regression.update({"status":"noise"})
|
||||
|
||||
awfy.db.commit()
|
|
@ -1,141 +0,0 @@
|
|||
# vim: set ts=4 sw=4 tw=99 et:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
pwd = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(pwd + "../server")
|
||||
import awfy
|
||||
import tables
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]")
|
||||
parser.add_option( "--dry-run", dest="dryrun", action="store_true", default=False,
|
||||
help="Don't commit the new regressions to the database yet.")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
def notProcessedRuns():
|
||||
# Only look at reports in the last week
|
||||
newer = int(time.time() - 60 * 60 * 24 * 7)
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
FROM awfy_run \
|
||||
WHERE finish_stamp > "+str(newer)+" AND \
|
||||
status = 1 AND \
|
||||
detector != 1 AND \
|
||||
machine in (28,29,26,17,30)")
|
||||
runs = []
|
||||
for row in c.fetchall():
|
||||
runs.append(tables.Run(row[0]))
|
||||
return runs
|
||||
|
||||
def regressed(score):
|
||||
# Disabled. So no testing
|
||||
if score.disabled():
|
||||
return False
|
||||
|
||||
# Lower than threshold, no regression.
|
||||
change = score.change()
|
||||
if change is None:
|
||||
return None
|
||||
if abs(change) <= score.noise():
|
||||
return False
|
||||
|
||||
#Don't report outliners
|
||||
if score.outliner():
|
||||
return False
|
||||
|
||||
# Don't report if same revision
|
||||
if score.prev() is not None:
|
||||
if score.get('build').get('cset') == score.prev().get('build').get('cset'):
|
||||
return False
|
||||
|
||||
# average change over multiple runs.
|
||||
change = score.avg_change()
|
||||
|
||||
# No change, so wait for more data before reporting.
|
||||
if change is None:
|
||||
return None
|
||||
|
||||
# Next is not available. Wait for that before reporting.
|
||||
if not score.next():
|
||||
return None
|
||||
if score.next().avg_change() is None:
|
||||
return None
|
||||
|
||||
# Next has a bigger change. Regression is more likely to be that.
|
||||
if change >= 0 and score.next().avg_change() > change:
|
||||
return False
|
||||
if change <= 0 and score.next().avg_change() < change:
|
||||
return False
|
||||
|
||||
# If there is a prev, test that prev change is smaller
|
||||
if score.prev():
|
||||
if change >= 0 and score.prev().avg_change() >= change:
|
||||
return False
|
||||
if change <= 0 and score.prev().avg_change() <= change:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
import time
|
||||
os.environ['TZ'] = "Europe/Amsterdam"
|
||||
time.tzset()
|
||||
|
||||
start = time.time()
|
||||
for run in notProcessedRuns():
|
||||
scores = run.getScoresAndBreakdowns()
|
||||
finish = True
|
||||
print "run:", run.get("id")
|
||||
for score in scores:
|
||||
if not score.sane():
|
||||
continue
|
||||
regressed_ = regressed(score)
|
||||
|
||||
# Not enough info yet
|
||||
if regressed_ is None:
|
||||
finish = False
|
||||
|
||||
if regressed_ is True:
|
||||
score.dump()
|
||||
if not options.dryrun:
|
||||
build = score.get("build_id")
|
||||
prev_build = score.prev().get("build_id")
|
||||
regression = [regression for regression in tables.Regression.where({
|
||||
"build_id": build,
|
||||
"prev_build_id": prev_build
|
||||
})]
|
||||
if len(regression) == 0:
|
||||
regression_id = tables.Regression.insert({
|
||||
"build_id": build,
|
||||
"prev_build_id": prev_build
|
||||
})
|
||||
tables.RegressionStatus.insert({"regression_id": regression_id,
|
||||
"name": "awfy",
|
||||
"extra": "Submitted",
|
||||
"stamp":"UNIX_TIMESTAMP()"})
|
||||
else:
|
||||
regression_id = regression[0].id
|
||||
|
||||
try:
|
||||
if score.__class__ == tables.Score:
|
||||
tables.RegressionScore.insert({"regression_id": regression_id,
|
||||
"score_id": score.get("id")})
|
||||
elif score.__class__ == tables.Breakdown:
|
||||
tables.RegressionBreakdown.insert({"regression_id": regression_id,
|
||||
"breakdown_id": score.get("id")})
|
||||
else:
|
||||
assert False
|
||||
except:
|
||||
pass
|
||||
if finish and not options.dryrun:
|
||||
run.update({"detector": "1"})
|
||||
tables.DBTable.maybeflush()
|
||||
|
||||
awfy.db.commit()
|
|
@ -1,35 +0,0 @@
|
|||
# vim: set ts=4 sw=4 tw=99 et:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.append("../server")
|
||||
import awfy
|
||||
|
||||
c = awfy.db.cursor()
|
||||
#TODO: ONLY DELETE IF REPORTED BY REGRESSION DETECTOR
|
||||
c.execute("SELECT id, build_id \
|
||||
FROM awfy_regression \
|
||||
WHERE status = 'unconfirmed'")
|
||||
for row in c.fetchall():
|
||||
c.execute("DELETE FROM awfy_regression_breakdown \
|
||||
WHERE build_id = %s", row[1])
|
||||
c.execute("DELETE FROM awfy_regression_score \
|
||||
WHERE build_id = %s", row[1])
|
||||
c.execute("DELETE FROM awfy_regression_status \
|
||||
WHERE regression_id = %s", row[0])
|
||||
c.execute("DELETE FROM awfy_regression \
|
||||
WHERE id = %s", row[0])
|
||||
c.execute("UPDATE awfy_run \
|
||||
LEFT JOIN awfy_build ON awfy_run.id = awfy_build.run_id \
|
||||
SET detector = 0 \
|
||||
WHERE awfy_build.id = %s", row[1])
|
||||
|
||||
print row[0]
|
||||
#TODO: remove detector status from all itmes higher than the lowest run id
|
||||
#c.execute("UPDATE awfy_run \
|
||||
# SET detector = 0 \
|
||||
# WHERE id > 249273")
|
||||
awfy.db.commit()
|
614
server/tables.py
614
server/tables.py
|
@ -149,19 +149,6 @@ class Run(DBTable):
|
|||
def table():
|
||||
return "awfy_run"
|
||||
|
||||
@staticmethod
|
||||
def fromSortOrder(machine_id, sort_order):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
FROM awfy_run \
|
||||
WHERE machine = %s AND \
|
||||
sort_order = %s", (machine_id, sort_order))
|
||||
rows = c.fetchall()
|
||||
if len(rows) == 0:
|
||||
return None
|
||||
assert len(rows) == 1
|
||||
return Run(rows[0][0])
|
||||
|
||||
def initialize(self):
|
||||
if self.initialized:
|
||||
return
|
||||
|
@ -170,16 +157,6 @@ class Run(DBTable):
|
|||
self.cached["machine_id"] = self.cached["machine"]
|
||||
del self.cached["machine"]
|
||||
|
||||
def getScoresAndBreakdowns(self):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
FROM awfy_build \
|
||||
WHERE run_id = %s", (self.id,))
|
||||
scores = []
|
||||
for row in c.fetchall():
|
||||
scores += Build(row[0]).getScoresAndBreakdowns()
|
||||
return scores
|
||||
|
||||
def getScores(self):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
|
@ -190,9 +167,6 @@ class Run(DBTable):
|
|||
scores += Build(row[0]).getScores()
|
||||
return scores
|
||||
|
||||
def finishStamp(self):
|
||||
pass
|
||||
|
||||
class SuiteTest(DBTable):
|
||||
def __init__(self, id):
|
||||
DBTable.__init__(self, id)
|
||||
|
@ -229,137 +203,10 @@ class Mode(DBTable):
|
|||
def __init__(self, id):
|
||||
DBTable.__init__(self, id)
|
||||
|
||||
@classmethod
|
||||
def allWith(class_, machine):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT distinct(awfy_build.mode_id) \
|
||||
FROM awfy_build \
|
||||
LEFT JOIN awfy_run ON awfy_build.run_id = awfy_run.id \
|
||||
WHERE machine = %s", (machine.get("id"),))
|
||||
for row in c.fetchall():
|
||||
if row[0] == 0:
|
||||
continue
|
||||
yield Mode(row[0])
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_mode"
|
||||
|
||||
class Regression(DBTable):
|
||||
def __init__(self, id):
|
||||
DBTable.__init__(self, id)
|
||||
|
||||
def regressions(self):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id FROM awfy_regression_breakdown \
|
||||
WHERE regression_id = %s", (self.id,))
|
||||
for row in c.fetchall():
|
||||
if row[0] == 0:
|
||||
continue
|
||||
yield RegressionBreakdown(row[0])
|
||||
c.execute("SELECT id FROM awfy_regression_score \
|
||||
WHERE regression_id = %s", (self.id,))
|
||||
for row in c.fetchall():
|
||||
if row[0] == 0:
|
||||
continue
|
||||
yield RegressionScore(row[0])
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_regression"
|
||||
|
||||
class RegressionScore(DBTable):
|
||||
|
||||
def score(self):
|
||||
return self.get("score")
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_regression_score"
|
||||
|
||||
class RegressionScoreNoise(DBTable):
|
||||
def __init__(self, machine, suite, mode):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id FROM "+self.table()+" \
|
||||
WHERE machine_id = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_version_id = %s", (machine.get("id"), mode.get("id"), suite.get("id")))
|
||||
row = c.fetchone()
|
||||
id = row[0] if row else 0
|
||||
DBTable.__init__(self, id)
|
||||
|
||||
@classmethod
|
||||
def insertOrUpdate(class_, machine, suite, mode, noise):
|
||||
try:
|
||||
RegressionScoreNoise.insert({
|
||||
"machine_id": machine.get("id"),
|
||||
"suite_version_id": suite.get("id"),
|
||||
"mode_id": mode.get("id"),
|
||||
"noise": noise
|
||||
})
|
||||
except:
|
||||
c = awfy.db.cursor()
|
||||
c.execute("UPDATE "+class_.table()+" \
|
||||
SET noise = %s \
|
||||
WHERE machine_id = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_version_id = %s", (noise, machine.get("id"), mode.get("id"),
|
||||
suite.get("id")))
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_regression_score_noise"
|
||||
|
||||
class RegressionBreakdown(DBTable):
|
||||
|
||||
def score(self):
|
||||
return self.get("breakdown")
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_regression_breakdown"
|
||||
|
||||
class RegressionBreakdownNoise(DBTable):
|
||||
def __init__(self, machine, suite, mode):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id FROM "+self.table()+" \
|
||||
WHERE machine_id = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_test_id = %s", (machine.get("id"), mode.get("id"), suite.get("id")))
|
||||
row = c.fetchone()
|
||||
id = row[0] if row else 0
|
||||
DBTable.__init__(self, id)
|
||||
|
||||
@classmethod
|
||||
def insertOrUpdate(class_, machine, suite, mode, noise):
|
||||
try:
|
||||
RegressionBreakdownNoise.insert({
|
||||
"machine_id": machine.get("id"),
|
||||
"suite_test_id": suite.get("id"),
|
||||
"mode_id": mode.get("id"),
|
||||
"noise": noise
|
||||
})
|
||||
except:
|
||||
c = awfy.db.cursor()
|
||||
c.execute("UPDATE "+class_.table()+" \
|
||||
SET noise = %s \
|
||||
WHERE machine_id = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_test_id = %s", (noise, machine.get("id"), mode.get("id"),
|
||||
suite.get("id")))
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_regression_breakdown_noise"
|
||||
|
||||
class RegressionStatus(DBTable):
|
||||
def __init__(self, id):
|
||||
DBTable.__init__(self, id)
|
||||
|
||||
@staticmethod
|
||||
def table():
|
||||
return "awfy_regression_status"
|
||||
|
||||
class Build(DBTable):
|
||||
def __init__(self, id):
|
||||
DBTable.__init__(self, id)
|
||||
|
@ -391,231 +238,10 @@ class Build(DBTable):
|
|||
scores.append(Score(row[0]))
|
||||
return scores
|
||||
|
||||
def getScoresAndBreakdowns(self):
|
||||
scores = self.getScores()
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT awfy_breakdown.id \
|
||||
FROM awfy_breakdown \
|
||||
LEFT JOIN awfy_score ON awfy_score.id = score_id \
|
||||
WHERE build_id = %s", (self.id,))
|
||||
for row in c.fetchall():
|
||||
scores.append(Breakdown(row[0]))
|
||||
return scores
|
||||
|
||||
class RegressionTools(DBTable):
|
||||
def __init__(self, id):
|
||||
DBTable.__init__(self, id)
|
||||
|
||||
def outliner(self):
|
||||
if self.next() is None:
|
||||
return False
|
||||
if self.prev() is None:
|
||||
return False
|
||||
if not self.hasNoise():
|
||||
return False
|
||||
if abs(self.next().get("score") - self.prev().get("score")) < self.noise():
|
||||
if (abs(self.next().get("score") - self.get("score")) > self.noise() and
|
||||
abs(self.prev().get("score") - self.get("score")) > self.noise()):
|
||||
return True
|
||||
prevs, _ = self.avg_prevs_nexts()
|
||||
_, nexts = self.next().avg_prevs_nexts()
|
||||
if prevs is None or nexts is None:
|
||||
return False
|
||||
if abs(prevs-nexts) <= self.noise():
|
||||
# ?? should this be prevs - self.get("score") ? maybe ?
|
||||
if (abs(self.get('score') - self.prev().get('score')) > self.noise() and
|
||||
abs(self.get('score') - self.next().get('score')) > self.noise()):
|
||||
return True
|
||||
return False
|
||||
|
||||
def next(self):
|
||||
self.initialize()
|
||||
if "next" not in self.cached:
|
||||
self.cached["next"] = self.compute_next()
|
||||
return self.cached["next"]
|
||||
|
||||
def compute_next(self):
|
||||
nexts = self.prefetch_next(10)
|
||||
|
||||
prev = self
|
||||
prev.cached["next"] = None
|
||||
for score in nexts:
|
||||
prev.initialize()
|
||||
prev.cached["next"] = score
|
||||
score.initialize()
|
||||
score.cached["prev"] = prev
|
||||
prev = score
|
||||
|
||||
return self.cached["next"]
|
||||
|
||||
def prev(self):
|
||||
self.initialize()
|
||||
if "prev" not in self.cached:
|
||||
self.cached["prev"] = self.compute_prev()
|
||||
if self.cached["prev"]:
|
||||
self.cached["prev"].initialize()
|
||||
self.cached["prev"].cached["next"] = self
|
||||
else:
|
||||
pass
|
||||
return self.cached["prev"]
|
||||
|
||||
def compute_prev(self):
|
||||
prevs = self.prefetch_prev(10)
|
||||
|
||||
next_ = self
|
||||
next_.cached["prev"] = None
|
||||
for score in prevs:
|
||||
next_.initialize()
|
||||
next_.cached["prev"] = score
|
||||
score.initialize()
|
||||
score.cached["next"] = next_
|
||||
next_ = score
|
||||
|
||||
return self.cached["prev"]
|
||||
|
||||
def prevs(self, amount):
|
||||
prevs = []
|
||||
point = self
|
||||
while len(prevs) < amount:
|
||||
point = point.prev()
|
||||
if not point:
|
||||
break
|
||||
prevs.append(point)
|
||||
return prevs
|
||||
|
||||
def nexts(self, amount):
|
||||
nexts = []
|
||||
point = self
|
||||
while len(nexts) < amount:
|
||||
point = point.next()
|
||||
if not point:
|
||||
break
|
||||
nexts.append(point)
|
||||
return nexts
|
||||
|
||||
def prevs_no_outliners(self, amount):
|
||||
# note this removes outliners
|
||||
prevs = []
|
||||
point = self
|
||||
while len(prevs) < amount:
|
||||
point = point.prev()
|
||||
if not point:
|
||||
break
|
||||
if point.outliner():
|
||||
continue
|
||||
prevs.append(point)
|
||||
return prevs
|
||||
|
||||
def nexts_no_outliners(self, amount):
|
||||
# note this removes outliners
|
||||
nexts = []
|
||||
point = self
|
||||
while len(nexts) < amount:
|
||||
point = point.next()
|
||||
if not point:
|
||||
break
|
||||
if point.outliner():
|
||||
continue
|
||||
nexts.append(point)
|
||||
return nexts
|
||||
|
||||
def avg_prevs_no_outliners(self):
|
||||
avg_prevs, _ = self.avg_prevs_nexts_no_outliners()
|
||||
return avg_prevs
|
||||
|
||||
def avg_nexts_no_outliners(self):
|
||||
_, avg_nexts = self.avg_prevs_nexts_no_outliners()
|
||||
return avg_nexts
|
||||
|
||||
def avg_prevs_nexts_no_outliners(self):
|
||||
self.initialize()
|
||||
if "avg_prevs_no_outliners" not in self.cached:
|
||||
self.cached["avg_prevs_no_outliners"], self.cached["avg_nexts_no_outliners"] = self.compute_avg_prevs_nexts_no_outliners()
|
||||
return self.cached["avg_prevs_no_outliners"], self.cached["avg_nexts_no_outliners"]
|
||||
|
||||
def avg_prevs_nexts(self):
|
||||
self.initialize()
|
||||
if "avg_prevs" not in self.cached:
|
||||
self.cached["avg_prevs"], self.cached["avg_nexts"] = self.compute_avg_prevs_nexts()
|
||||
return self.cached["avg_prevs"], self.cached["avg_nexts"]
|
||||
|
||||
def compute_avg_prevs_nexts_no_outliners(self):
|
||||
"Compute the change in runs before and after the current run"
|
||||
# How many runs do we need to test?
|
||||
runs = self.runs()
|
||||
|
||||
# Get scores before and after this run.
|
||||
prevs = [i.get('score') for i in self.prevs_no_outliners(runs)]
|
||||
nexts = [self.get('score')] + [i.get('score') for i in self.nexts_no_outliners(runs - 1)]
|
||||
|
||||
p_weight = [len(prevs)-i for i in range(len(prevs))]
|
||||
n_weight = [len(nexts)-i for i in range(len(nexts))]
|
||||
prevs = [prevs[i]*p_weight[i] for i in range(len(prevs))]
|
||||
nexts = [nexts[i]*n_weight[i] for i in range(len(nexts))]
|
||||
|
||||
# Not enough data to compute change.
|
||||
if len(nexts) != runs:
|
||||
return None, None
|
||||
|
||||
avg_prevs = sum(prevs)
|
||||
avg_nexts = sum(nexts)
|
||||
|
||||
# Handle edge cases.
|
||||
if avg_prevs != 0:
|
||||
avg_prevs /= sum(p_weight)
|
||||
if avg_nexts != 0:
|
||||
avg_nexts /= sum(n_weight)
|
||||
|
||||
return avg_prevs, avg_nexts
|
||||
|
||||
def compute_avg_prevs_nexts(self):
|
||||
"Compute the change in runs before and after the current run"
|
||||
# How many runs do we need to test?
|
||||
runs = self.runs()
|
||||
|
||||
# Get scores before and after this run.
|
||||
prevs = [i.get('score') for i in self.prevs(runs)]
|
||||
nexts = [self.get('score')] + [i.get('score') for i in self.nexts(runs - 1)]
|
||||
|
||||
p_weight = [len(prevs)-i for i in range(len(prevs))]
|
||||
n_weight = [len(nexts)-i for i in range(len(nexts))]
|
||||
prevs = [prevs[i]*p_weight[i] for i in range(len(prevs))]
|
||||
nexts = [nexts[i]*n_weight[i] for i in range(len(nexts))]
|
||||
|
||||
# Not enough data to compute change.
|
||||
if len(nexts) != runs:
|
||||
return None, None
|
||||
|
||||
avg_prevs = sum(prevs)
|
||||
avg_nexts = sum(nexts)
|
||||
|
||||
# Handle edge cases.
|
||||
if avg_prevs != 0:
|
||||
avg_prevs /= sum(p_weight)
|
||||
if avg_nexts != 0:
|
||||
avg_nexts /= sum(n_weight)
|
||||
|
||||
return avg_prevs, avg_nexts
|
||||
|
||||
def change(self):
|
||||
prevs, nexts = self.avg_prevs_nexts_no_outliners()
|
||||
if not prevs or not nexts:
|
||||
return None
|
||||
|
||||
return abs(prevs - nexts)
|
||||
|
||||
def avg_change(self):
|
||||
prevs, nexts = self.avg_prevs_nexts_no_outliners()
|
||||
if not prevs or not nexts:
|
||||
return None
|
||||
|
||||
if prevs == 0:
|
||||
return float("inf")
|
||||
|
||||
change = (prevs - nexts) / (prevs)
|
||||
return change
|
||||
|
||||
|
||||
class Score(RegressionTools):
|
||||
def __init__(self, id):
|
||||
RegressionTools.__init__(self, id)
|
||||
|
@ -624,19 +250,6 @@ class Score(RegressionTools):
|
|||
def table():
|
||||
return "awfy_score"
|
||||
|
||||
@staticmethod
|
||||
def fromBuildAndSuite(build_id, suite_version_id):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
FROM awfy_score \
|
||||
WHERE build_id = %s AND \
|
||||
suite_version_id = %s", (build_id, suite_version_id))
|
||||
rows = c.fetchall()
|
||||
if len(rows) == 0:
|
||||
return None
|
||||
assert len(rows) == 1
|
||||
return Score(rows[0][0])
|
||||
|
||||
def getBreakdowns(self):
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT awfy_breakdown.id \
|
||||
|
@ -647,116 +260,6 @@ class Score(RegressionTools):
|
|||
breakdowns.append(Breakdown(row[0]))
|
||||
return breakdowns
|
||||
|
||||
def sane(self):
|
||||
if self.get("suite_version_id") == -1:
|
||||
return False
|
||||
if self.get("suite_version_id") == 0:
|
||||
return False
|
||||
if not self.hasNoise():
|
||||
return False
|
||||
return True
|
||||
|
||||
def prefetch_next(self, limit = 1):
|
||||
sort_order = self.get("build").get("run").get("sort_order")
|
||||
machine = self.get("build").get("run").get("machine_id")
|
||||
mode = self.get("build").get("mode_id")
|
||||
suite = self.get("suite_version_id")
|
||||
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT awfy_score.id \
|
||||
FROM awfy_score \
|
||||
INNER JOIN awfy_build ON awfy_build.id = awfy_score.build_id \
|
||||
INNER JOIN awfy_run ON awfy_run.id = awfy_build.run_id \
|
||||
WHERE sort_order > %s AND \
|
||||
machine = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_version_id = %s AND \
|
||||
status = 1 \
|
||||
ORDER BY sort_order ASC \
|
||||
LIMIT "+str(limit), (sort_order, machine, mode, suite))
|
||||
rows = c.fetchall()
|
||||
return [Score(row[0]) for row in rows]
|
||||
|
||||
def prefetch_prev(self, limit = 1):
|
||||
sort_order = self.get("build").get("run").get("sort_order")
|
||||
machine = self.get("build").get("run").get("machine_id")
|
||||
mode = self.get("build").get("mode_id")
|
||||
suite = self.get("suite_version_id")
|
||||
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT awfy_score.id \
|
||||
FROM awfy_score \
|
||||
INNER JOIN awfy_build ON awfy_build.id = awfy_score.build_id \
|
||||
INNER JOIN awfy_run ON awfy_run.id = awfy_build.run_id \
|
||||
WHERE sort_order < %s AND \
|
||||
machine = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_version_id = %s AND \
|
||||
status = 1 \
|
||||
ORDER BY sort_order DESC \
|
||||
LIMIT "+str(limit), (sort_order, machine, mode, suite))
|
||||
rows = c.fetchall()
|
||||
return [Score(row[0]) for row in rows]
|
||||
|
||||
def runs(self):
|
||||
runs = max(1, self.get('build').get('run').get('machine').get("confidence_runs"))
|
||||
runs *= self.get('suite_version').get('suite').get("confidence_factor")
|
||||
runs *= RUNS_FACTOR
|
||||
runs = int(round(runs))
|
||||
return runs
|
||||
|
||||
def disabled(self):
|
||||
return RegressionScoreNoise(self.get('build').get('run').get('machine'),
|
||||
self.get('suite_version'),
|
||||
self.get('build').get('mode')).get('disabled')
|
||||
|
||||
def noise(self):
|
||||
noise = RegressionScoreNoise(self.get('build').get('run').get('machine'),
|
||||
self.get('suite_version'),
|
||||
self.get('build').get('mode')).get('noise')
|
||||
return NOISE_FACTOR*noise
|
||||
|
||||
def hasNoise(self):
|
||||
return RegressionScoreNoise(self.get('build').get('run').get('machine'),
|
||||
self.get('suite_version'),
|
||||
self.get('build').get('mode')).id != 0
|
||||
|
||||
@classmethod
|
||||
def firstOfRecent(class_, machine, suite, mode):
|
||||
assert machine.__class__ == Machine
|
||||
assert suite.__class__ == SuiteVersion
|
||||
assert mode.__class__ == Mode
|
||||
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
FROM (SELECT awfy_score.id, sort_order \
|
||||
FROM awfy_score \
|
||||
INNER JOIN awfy_build ON awfy_build.id = awfy_score.build_id \
|
||||
INNER JOIN awfy_run ON awfy_run.id = awfy_build.run_id \
|
||||
WHERE machine = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_version_id = %s AND \
|
||||
status = 1 \
|
||||
ORDER BY sort_order DESC \
|
||||
LIMIT 1000) tmp \
|
||||
ORDER BY sort_order ASC \
|
||||
LIMIT 1", (machine.get("id"), mode.get("id"), suite.get("id")))
|
||||
row = c.fetchone()
|
||||
if row:
|
||||
return Score(row[0])
|
||||
return None
|
||||
|
||||
def dump(self):
|
||||
import datetime
|
||||
print datetime.datetime.fromtimestamp(
|
||||
int(self.get("build").get("run").get("finish_stamp"))
|
||||
).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
print "", self.get("build").get("run").get("machine").get("description"),
|
||||
print "", self.get("build").get("mode").get("name"),
|
||||
print "", self.get("suite_version").get("name")+":", self.avg_change(),
|
||||
print "", self.prev().get("score") if self.prev() else "", self.get("score"),
|
||||
print " ("+str(self.runs())+" runs, "+str(self.noise())+")"
|
||||
|
||||
class Breakdown(RegressionTools):
|
||||
def __init__(self, id):
|
||||
RegressionTools.__init__(self, id)
|
||||
|
@ -772,120 +275,3 @@ class Breakdown(RegressionTools):
|
|||
return Score(self.get("score_id")).get("build")
|
||||
|
||||
return super(Breakdown, self).get(field)
|
||||
|
||||
def sane(self):
|
||||
if self.get("suite_test_id") == 0:
|
||||
return False
|
||||
if self.get("suite_test_id") == -1:
|
||||
return False
|
||||
if self.get("suite_test").get("suite_version_id") == -1:
|
||||
return False
|
||||
if self.get("suite_test").get("suite_version_id") == 0:
|
||||
return False
|
||||
if not self.hasNoise():
|
||||
return False
|
||||
return True
|
||||
|
||||
def prefetch_next(self, limit = 1):
|
||||
sort_order = self.get("build").get("run").get("sort_order")
|
||||
machine = self.get("build").get("run").get("machine_id")
|
||||
mode = self.get("build").get("mode_id")
|
||||
suite = self.get("suite_test_id")
|
||||
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT awfy_breakdown.id \
|
||||
FROM awfy_breakdown \
|
||||
INNER JOIN awfy_score ON awfy_score.id = awfy_breakdown.score_id \
|
||||
INNER JOIN awfy_build ON awfy_build.id = awfy_score.build_id \
|
||||
INNER JOIN awfy_run ON awfy_run.id = awfy_build.run_id \
|
||||
WHERE sort_order > %s AND \
|
||||
machine = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_test_id = %s AND \
|
||||
status = 1 \
|
||||
ORDER BY sort_order ASC \
|
||||
LIMIT "+str(limit), (sort_order, machine, mode, suite))
|
||||
rows = c.fetchall()
|
||||
return [Breakdown(row[0]) for row in rows]
|
||||
|
||||
def prefetch_prev(self, limit = 1):
|
||||
sort_order = self.get("build").get("run").get("sort_order")
|
||||
machine = self.get("build").get("run").get("machine_id")
|
||||
mode = self.get("build").get("mode_id")
|
||||
suite = self.get("suite_test_id")
|
||||
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT awfy_breakdown.id \
|
||||
FROM awfy_breakdown \
|
||||
INNER JOIN awfy_score ON awfy_score.id = awfy_breakdown.score_id \
|
||||
INNER JOIN awfy_build ON awfy_build.id = awfy_score.build_id \
|
||||
INNER JOIN awfy_run ON awfy_run.id = awfy_build.run_id \
|
||||
WHERE sort_order < %s AND \
|
||||
machine = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_test_id = %s AND \
|
||||
status = 1 \
|
||||
ORDER BY sort_order DESC \
|
||||
LIMIT "+str(limit), (sort_order, machine, mode, suite))
|
||||
rows = c.fetchall()
|
||||
return [Breakdown(row[0]) for row in rows]
|
||||
|
||||
@classmethod
|
||||
def firstOfRecent(class_, machine, suite, mode):
|
||||
assert machine.__class__ == Machine
|
||||
assert suite.__class__ == SuiteTest
|
||||
assert mode.__class__ == Mode
|
||||
|
||||
c = awfy.db.cursor()
|
||||
c.execute("SELECT id \
|
||||
FROM (SELECT awfy_breakdown.id, sort_order \
|
||||
FROM awfy_breakdown \
|
||||
INNER JOIN awfy_score ON awfy_score.id = awfy_breakdown.score_id \
|
||||
INNER JOIN awfy_build ON awfy_build.id = awfy_score.build_id \
|
||||
INNER JOIN awfy_run ON awfy_run.id = awfy_build.run_id \
|
||||
WHERE machine = %s AND \
|
||||
mode_id = %s AND \
|
||||
suite_test_id = %s AND \
|
||||
status = 1 \
|
||||
ORDER BY sort_order DESC \
|
||||
LIMIT 1000) tmp \
|
||||
ORDER BY sort_order ASC \
|
||||
LIMIT 1", (machine.get("id"), mode.get("id"), suite.get("id")))
|
||||
row = c.fetchone()
|
||||
if row:
|
||||
return Breakdown(row[0])
|
||||
return None
|
||||
|
||||
def runs(self):
|
||||
runs = max(1, self.get('build').get('run').get('machine').get("confidence_runs"))
|
||||
runs *= self.get('suite_test').get("confidence_factor")
|
||||
runs *= RUNS_FACTOR
|
||||
runs = int(round(runs))
|
||||
return runs
|
||||
|
||||
def disabled(self):
|
||||
return RegressionBreakdownNoise(self.get('build').get('run').get('machine'),
|
||||
self.get('suite_test'),
|
||||
self.get('build').get('mode')).get('disabled')
|
||||
|
||||
def noise(self):
|
||||
noise = RegressionBreakdownNoise(self.get('build').get('run').get('machine'),
|
||||
self.get('suite_test'),
|
||||
self.get('build').get('mode')).get('noise')
|
||||
return NOISE_FACTOR*noise
|
||||
|
||||
def hasNoise(self):
|
||||
return RegressionBreakdownNoise(self.get('build').get('run').get('machine'),
|
||||
self.get('suite_test'),
|
||||
self.get('build').get('mode')).id != 0
|
||||
|
||||
def dump(self):
|
||||
import datetime
|
||||
print datetime.datetime.fromtimestamp(
|
||||
int(self.get("build").get("run").get("finish_stamp"))
|
||||
).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
print "", self.get("build").get("run").get("machine").get("description"),
|
||||
print "", self.get("build").get("mode").get("name"),
|
||||
print "", self.get("suite_test").get("suite_version").get("name")+":", self.get("suite_test").get("name")+":", self.avg_change(),
|
||||
print "", self.prev().get("score") if self.prev() else "", self.get("score"),
|
||||
print " ("+str(self.runs())+" runs, "+str(self.noise())+", "+str(self.change())+")"
|
||||
|
|
Загрузка…
Ссылка в новой задаче