in lnt/server/db/testsuitedb.py [0:0]
def __init__(self, v4db, name, test_suite):
testsuitedb = self
self.v4db = v4db
self.name = name
self.test_suite = test_suite
# Save caches of the various fields.
self.machine_fields = list(self.test_suite.machine_fields)
self.order_fields = list(self.test_suite.order_fields)
self.run_fields = list(self.test_suite.run_fields)
self.sample_fields = list(self.test_suite.sample_fields)
for i,field in enumerate(self.sample_fields):
field.index = i
self.base = sqlalchemy.ext.declarative.declarative_base()
# Create parameterized model classes for this test suite.
class ParameterizedMixin(object):
# Class variable to allow finding the associated test suite from
# model instances.
testsuite = self
# Class variable (expected to be defined by subclasses) to allow
# easy access to the field list for parameterized model classes.
fields = None
def get_field(self, field):
return getattr(self, field.name)
def set_field(self, field, value):
return setattr(self, field.name, value)
db_key_name = self.test_suite.db_key_name
class Machine(self.base, ParameterizedMixin):
__tablename__ = db_key_name + '_Machine'
DEFAULT_BASELINE_REVISION = self.v4db.baseline_revision
fields = self.machine_fields
id = Column("ID", Integer, primary_key=True)
name = Column("Name", String(256), index=True)
# The parameters blob is used to store any additional information
# reported by the run but not promoted into the machine record. Such
# data is stored as a JSON encoded blob.
parameters_data = Column("Parameters", Binary)
# Dynamically create fields for all of the test suite defined
# machine fields.
class_dict = locals()
for item in fields:
if item.name in class_dict:
raise ValueError("test suite defines reserved key %r" % (
name))
class_dict[item.name] = item.column = Column(
item.name, String(256))
def __init__(self, name_value):
self.name = name_value
def __repr__(self):
return '%s_%s%r' % (db_key_name, self.__class__.__name__,
(self.name,))
@property
def parameters(self):
"""dictionary access to the BLOB encoded parameters data"""
return dict(json.loads(self.parameters_data))
@parameters.setter
def parameters(self, data):
self.parameters_data = json.dumps(sorted(data.items()))
def get_baseline_run(self):
ts = Machine.testsuite
user_baseline = ts.get_users_baseline()
if user_baseline:
return self.get_closest_previously_reported_run(
user_baseline.order)
else:
mach_base = Machine.DEFAULT_BASELINE_REVISION
# If we have an int, convert it to a proper string.
if isinstance(mach_base, int):
mach_base = '% 7d' % mach_base
return self.get_closest_previously_reported_run(
ts.Order(llvm_project_revision=mach_base))
def get_closest_previously_reported_run(self, order_to_find):
"""
Find the closest previous run to the requested order, for which
this machine also reported.
"""
# FIXME: Scalability! Pretty fast in practice, but still.
ts = Machine.testsuite
# Search for best order.
best_order = None
for order in ts.query(ts.Order).\
join(ts.Run).\
filter(ts.Run.machine_id == self.id).distinct():
if order >= order_to_find and \
(best_order is None or order < best_order):
best_order = order
# Find the most recent run on this machine that used
# that order.
closest_run = None
if best_order:
closest_run = ts.query(ts.Run)\
.filter(ts.Run.machine_id == self.id)\
.filter(ts.Run.order_id == best_order.id)\
.order_by(ts.Run.start_time.desc()).first()
return closest_run
def __json__(self):
return strip(self.__dict__) # {u'name': self.name, u'MachineID': self.id}
class Order(self.base, ParameterizedMixin):
__tablename__ = db_key_name + '_Order'
# We guarantee that our fields are stored in the order they are
# supposed to be lexicographically compared, the __cmp__ method
# relies on this.
fields = sorted(self.order_fields,
key = lambda of: of.ordinal)
id = Column("ID", Integer, primary_key=True)
# Define two common columns which are used to store the previous and
# next links for the total ordering amongst run orders.
next_order_id = Column("NextOrder", Integer, ForeignKey(
"%s.ID" % __tablename__))
previous_order_id = Column("PreviousOrder", Integer, ForeignKey(
"%s.ID" % __tablename__))
# This will implicitly create the previous_order relation.
next_order = sqlalchemy.orm.relation("Order",
backref=sqlalchemy.orm.backref('previous_order',
uselist=False,
remote_side=id),
primaryjoin='Order.previous_order_id==Order.id',
uselist=False)
# Dynamically create fields for all of the test suite defined order
# fields.
class_dict = locals()
for item in self.order_fields:
if item.name in class_dict:
raise ValueError("test suite defines reserved key %r" % (
name,))
class_dict[item.name] = item.column = Column(
item.name, String(256))
def __init__(self, previous_order_id = None, next_order_id = None,
**kwargs):
self.previous_order_id = previous_order_id
self.next_order_id = next_order_id
# Initialize fields (defaulting to None, for now).
for item in self.fields:
self.set_field(item, kwargs.get(item.name))
def __repr__(self):
fields = dict((item.name, self.get_field(item))
for item in self.fields)
return '%s_%s(%r, %r, **%r)' % (
db_key_name, self.__class__.__name__,
self.previous_order_id, self.next_order_id, fields)
def as_ordered_string(self):
"""Return a readable value of the order object by printing the
fields in lexicographic order."""
# If there is only a single field, return it.
if len(self.fields) == 1:
return self.get_field(self.fields[0])
# Otherwise, print as a tuple of string.
return '(%s)' % (
', '.join(self.get_field(field)
for field in self.fields),)
def __cmp__(self, b):
# SA occassionally uses comparison to check model instances
# verse some sentinels, so we ensure we support comparison
# against non-instances.
if self.__class__ is not b.__class__:
return -1
# Compare each field numerically integer or integral version,
# where possible. We ignore whitespace and convert each dot
# separated component to an integer if is is numeric.
def convert_field(value):
items = value.strip().split('.')
for i,item in enumerate(items):
if item.isdigit():
items[i] = int(item, 10)
return tuple(items)
# Compare every field in lexicographic order.
return cmp(tuple(convert_field(self.get_field(item))
for item in self.fields),
tuple(convert_field(b.get_field(item))
for item in self.fields))
def __json__(self):
order = dict((item.name, self.get_field(item))
for item in self.fields)
order[u'id'] = self.id
return strip(order)
class Run(self.base, ParameterizedMixin):
__tablename__ = db_key_name + '_Run'
fields = self.run_fields
id = Column("ID", Integer, primary_key=True)
machine_id = Column("MachineID", Integer, ForeignKey(Machine.id),
index=True)
order_id = Column("OrderID", Integer, ForeignKey(Order.id),
index=True)
imported_from = Column("ImportedFrom", String(512))
start_time = Column("StartTime", DateTime)
end_time = Column("EndTime", DateTime)
simple_run_id = Column("SimpleRunID", Integer)
# The parameters blob is used to store any additional information
# reported by the run but not promoted into the machine record. Such
# data is stored as a JSON encoded blob.
parameters_data = Column("Parameters", Binary)
machine = sqlalchemy.orm.relation(Machine)
order = sqlalchemy.orm.relation(Order)
# Dynamically create fields for all of the test suite defined run
# fields.
#
# FIXME: We are probably going to want to index on some of these,
# but need a bit for that in the test suite definition.
class_dict = locals()
for item in fields:
if item.name in class_dict:
raise ValueError,"test suite defines reserved key %r" % (
name,)
class_dict[item.name] = item.column = Column(
item.name, String(256))
def __init__(self, machine, order, start_time, end_time):
self.machine = machine
self.order = order
self.start_time = start_time
self.end_time = end_time
self.imported_from = None
def __repr__(self):
return '%s_%s%r' % (db_key_name, self.__class__.__name__,
(self.machine, self.order, self.start_time,
self.end_time))
@property
def parameters(self):
"""dictionary access to the BLOB encoded parameters data"""
return dict(json.loads(self.parameters_data))
@parameters.setter
def parameters(self, data):
self.parameters_data = json.dumps(sorted(data.items()))
def __json__(self):
self.machine
self.order
return strip(self.__dict__)
class Test(self.base, ParameterizedMixin):
__tablename__ = db_key_name + '_Test'
id = Column("ID", Integer, primary_key=True)
name = Column("Name", String(256), unique=True, index=True)
def __init__(self, name):
self.name = name
def __repr__(self):
return '%s_%s%r' % (db_key_name, self.__class__.__name__,
(self.name,))
def __json__(self):
return strip(self.__dict__)
class Profile(self.base):
__tablename__ = db_key_name + '_Profile'
id = Column("ID", Integer, primary_key=True)
created_time = Column("CreatedTime", DateTime)
accessed_time = Column("AccessedTime", DateTime)
filename = Column("Filename", String(256))
counters = Column("Counters", String(512))
def __init__(self, encoded, config, testid):
self.created_time = datetime.datetime.now()
self.accessed_time = datetime.datetime.now()
if config is not None:
self.filename = profile.Profile.saveFromRendered(encoded,
profileDir=config.config.profileDir,
prefix='t-%s-s-' % os.path.basename(testid))
p = profile.Profile.fromRendered(encoded)
s = ','.join('%s=%s' % (k,v)
for k,v in p.getTopLevelCounters().items())
self.counters = s[:512]
def getTopLevelCounters(self):
d = dict()
for i in self.counters.split('='):
k, v = i.split(',')
d[k] = v
return d
def load(self, profileDir):
return profile.Profile.fromFile(os.path.join(profileDir, self.filename))
class Sample(self.base, ParameterizedMixin):
__tablename__ = db_key_name + '_Sample'
fields = self.sample_fields
id = Column("ID", Integer, primary_key=True)
# We do not need an index on run_id, this is covered by the compound
# (Run(ID),Test(ID)) index we create below.
run_id = Column("RunID", Integer, ForeignKey(Run.id))
test_id = Column("TestID", Integer, ForeignKey(Test.id), index=True)
profile_id = Column("ProfileID", Integer, ForeignKey(Profile.id))
run = sqlalchemy.orm.relation(Run)
test = sqlalchemy.orm.relation(Test)
profile = sqlalchemy.orm.relation(Profile)
@staticmethod
def get_primary_fields():
"""
get_primary_fields() -> [SampleField*]
Get the primary sample fields (those which are not associated
with some other sample field).
"""
status_fields = set(s.status_field
for s in self.Sample.fields
if s.status_field is not None)
for field in self.Sample.fields:
if field not in status_fields:
yield field
@staticmethod
def get_metric_fields():
"""
get_metric_fields() -> [SampleField*]
Get the sample fields which represent some kind of metric, i.e.
those which have a value that can be interpreted as better or
worse than other potential values for this field.
"""
for field in self.Sample.fields:
if field.type.name == 'Real':
yield field
@staticmethod
def get_hash_of_binary_field():
"""
get_hash_of_binary_field() -> SampleField
Get the sample field which represents a hash of the binary
being tested. This field will compare equal iff two binaries
are considered to be identical, e.g. two different compilers
producing identical code output.
Returns None if such a field isn't available.
"""
for field in self.Sample.fields:
if field.name == 'hash':
return field
return None
# Dynamically create fields for all of the test suite defined
# sample fields.
#
# FIXME: We might want to index some of these, but for a different
# reason than above. It is possible worth it to turn the compound
# index below into a covering index. We should evaluate this once
# the new UI is up.
class_dict = locals()
for item in self.sample_fields:
if item.name in class_dict:
raise ValueError,"test suite defines reserved key %r" % (
name,)
if item.type.name == 'Real':
item.column = Column(item.name, Float)
elif item.type.name == 'Status':
item.column = Column(item.name, Integer, ForeignKey(
testsuite.StatusKind.id))
elif item.type.name == 'Hash':
item.column = Column(item.name, String)
else:
raise ValueError,(
"test suite defines unknown sample type %r" (
item.type.name,))
class_dict[item.name] = item.column
def __init__(self, run, test, **kwargs):
self.run = run
self.test = test
# Initialize sample fields (defaulting to 0, for now).
for item in self.fields:
self.set_field(item, kwargs.get(item.name, None))
def __repr__(self):
fields = dict((item.name, self.get_field(item))
for item in self.fields)
return '%s_%s(%r, %r, **%r)' % (
db_key_name, self.__class__.__name__,
self.run, self.test, fields)
class FieldChange(self.base, ParameterizedMixin):
"""FieldChange represents a change in between the values
of the same field belonging to two samples from consecutive runs."""
__tablename__ = db_key_name + '_FieldChangeV2'
id = Column("ID", Integer, primary_key = True)
old_value = Column("OldValue", Float)
new_value = Column("NewValue", Float)
start_order_id = Column("StartOrderID", Integer,
ForeignKey("%s_Order.ID" % db_key_name))
end_order_id = Column("EndOrderID", Integer,
ForeignKey("%s_Order.ID" % db_key_name))
test_id = Column("TestID", Integer,
ForeignKey("%s_Test.ID" % db_key_name))
machine_id = Column("MachineID", Integer,
ForeignKey("%s_Machine.ID" % db_key_name))
field_id = Column("FieldID", Integer,
ForeignKey(self.v4db.SampleField.id))
# Could be from many runs, but most recent one is interesting.
run_id = Column("RunID", Integer,
ForeignKey("%s_Run.ID" % db_key_name))
start_order = sqlalchemy.orm.relation(Order,
primaryjoin='FieldChange.'\
'start_order_id==Order.id')
end_order = sqlalchemy.orm.relation(Order,
primaryjoin='FieldChange.'\
'end_order_id==Order.id')
test = sqlalchemy.orm.relation(Test)
machine = sqlalchemy.orm.relation(Machine)
field = sqlalchemy.orm.relation(self.v4db.SampleField,
primaryjoin= \
self.v4db.SampleField.id == \
field_id)
run = sqlalchemy.orm.relation(Run)
def __init__(self, start_order, end_order, machine,
test, field):
self.start_order = start_order
self.end_order = end_order
self.machine = machine
self.field = field
self.test = test
def __repr__(self):
return '%s_%s%r' % (db_key_name, self.__class__.__name__,
(self.start_order, self.end_order,
self.test, self.machine, self.field))
def __json__(self):
self.machine
self.test
self.field
self.run
self.start_order
self.end_order
return strip(self.__dict__)
class Regression(self.base, ParameterizedMixin):
"""Regession hold data about a set of RegressionIndicies."""
__tablename__ = db_key_name + '_Regression'
id = Column("ID", Integer, primary_key=True)
title = Column("Title", String(256), unique=False, index=False)
bug = Column("BugLink", String(256), unique=False, index=False)
state = Column("State", Integer)
def __init__(self, title, bug, state):
self.title = title
self.bug = bug
self.state = state
def __repr__(self):
return '%s_%s:"%s"' % (db_key_name, self.__class__.__name__,
self.title)
def __json__(self):
return strip(self.__dict__)
class RegressionIndicator(self.base, ParameterizedMixin):
""""""
__tablename__ = db_key_name + '_RegressionIndicator'
id = Column("ID", Integer, primary_key=True)
regression_id = Column("RegressionID", Integer,
ForeignKey("%s_Regression.ID" % db_key_name))
field_change_id = Column("FieldChangeID", Integer,
ForeignKey("%s_FieldChangeV2.ID" % db_key_name))
regression = sqlalchemy.orm.relation(Regression)
field_change = sqlalchemy.orm.relation(FieldChange)
def __init__(self, regression, field_change):
self.regression = regression
self.field_change = field_change
def __repr__(self):
return '%s_%s%r' % (db_key_name, self.__class__.__name__,(
self.id, self.regression, self.field_change))
def __json__(self):
return {u'RegressionIndicatorID': self.id,
u'Regression': self.regression,
u'FieldChange': self.field_change}
class ChangeIgnore(self.base, ParameterizedMixin):
"""Changes to ignore in the web interface."""
__tablename__ = db_key_name + '_ChangeIgnore'
id = Column("ID", Integer, primary_key=True)
field_change_id = Column("ChangeIgnoreID", Integer,
ForeignKey("%s_FieldChangeV2.ID" % db_key_name))
field_change = sqlalchemy.orm.relation(FieldChange)
def __init__(self, field_change):
self.field_change = field_change
def __repr__(self):
return '%s_%s%r' % (db_key_name, self.__class__.__name__,(
self.id, self.field_change))
class Baseline(self.base, ParameterizedMixin):
"""Baselines to compare runs to."""
__tablename__ = db_key_name + '_Baseline'
id = Column("ID", Integer, primary_key=True)
name = Column("Name", String(32), unique=True)
comment = Column("Comment", String(256))
order_id = Column("OrderID", Integer,
ForeignKey("%s_Order.ID" % db_key_name),
index=True)
order = sqlalchemy.orm.relation(Order)
def __str__(self):
return "Baseline({})".format(self.name)
self.Machine = Machine
self.Run = Run
self.Test = Test
self.Profile = Profile
self.Sample = Sample
self.Order = Order
self.FieldChange = FieldChange
self.Regression = Regression
self.RegressionIndicator = RegressionIndicator
self.ChangeIgnore = ChangeIgnore
self.Baseline = Baseline
# Create the compound index we cannot declare inline.
sqlalchemy.schema.Index("ix_%s_Sample_RunID_TestID" % db_key_name,
Sample.run_id, Sample.test_id)
# Create the index we use to ensure machine uniqueness.
args = [Machine.name, Machine.parameters_data]
for item in self.machine_fields:
args.append(item.column)
sqlalchemy.schema.Index("ix_%s_Machine_Unique" % db_key_name,
*args, unique = True)
# Add several shortcut aliases, similar to the ones on the v4db.
self.session = self.v4db.session
self.add = self.v4db.add
self.delete = self.v4db.delete
self.commit = self.v4db.commit
self.query = self.v4db.query
self.rollback = self.v4db.rollback