ottertune/server/website/website/models.py

439 lines
15 KiB
Python

#
# OtterTune - models.py
#
# Copyright (c) 2017-18, Carnegie Mellon University Database Group
#
from collections import OrderedDict
from django.contrib.auth.models import User
from django.db import models, DEFAULT_DB_ALIAS
from django.utils.timezone import now
from .db import target_objectives
from .types import (DBMSType, LabelStyleType, MetricType, KnobUnitType,
PipelineTaskType, VarType, KnobResourceType,
WorkloadStatusType, AlgorithmType, StorageType)
class BaseModel(models.Model):
def __str__(self):
return self.__unicode__()
def __unicode__(self):
return getattr(self, 'name', str(self.pk))
@classmethod
def get_labels(cls, style=LabelStyleType.DEFAULT_STYLE):
from .utils import LabelUtil
labels = {}
fields = cls._meta.get_fields()
for field in fields:
try:
verbose_name = field.verbose_name
if field.name == 'id':
verbose_name = cls._model_name() + ' id'
labels[field.name] = verbose_name
except AttributeError:
pass
return LabelUtil.style_labels(labels, style)
@classmethod
def _model_name(cls):
return cls.__name__
class Meta: # pylint: disable=no-init
abstract = True
class DBMSCatalog(BaseModel):
type = models.IntegerField(choices=DBMSType.choices())
version = models.CharField(max_length=16)
@property
def name(self):
return DBMSType.name(self.type)
@property
def key(self):
return '{}_{}'.format(self.name, self.version)
@property
def full_name(self):
return '{} v{}'.format(self.name, self.version)
def __unicode__(self):
return self.full_name
class KnobCatalog(BaseModel):
dbms = models.ForeignKey(DBMSCatalog)
name = models.CharField(max_length=128)
vartype = models.IntegerField(choices=VarType.choices(), verbose_name="variable type")
unit = models.IntegerField(choices=KnobUnitType.choices())
category = models.TextField(null=True)
summary = models.TextField(null=True, verbose_name='description')
description = models.TextField(null=True)
scope = models.CharField(max_length=16)
minval = models.CharField(max_length=32, null=True, verbose_name="minimum value")
maxval = models.CharField(max_length=32, null=True, verbose_name="maximum value")
default = models.TextField(verbose_name="default value")
enumvals = models.TextField(null=True, verbose_name="valid values")
context = models.CharField(max_length=32)
tunable = models.BooleanField(verbose_name="tunable")
resource = models.IntegerField(choices=KnobResourceType.choices(), default=4)
class MetricCatalog(BaseModel):
dbms = models.ForeignKey(DBMSCatalog)
name = models.CharField(max_length=128)
vartype = models.IntegerField(choices=VarType.choices())
summary = models.TextField(null=True, verbose_name='description')
scope = models.CharField(max_length=16)
metric_type = models.IntegerField(choices=MetricType.choices())
class Project(BaseModel):
user = models.ForeignKey(User)
name = models.CharField(max_length=64, verbose_name="project name")
description = models.TextField(null=True, blank=True)
creation_time = models.DateTimeField()
last_update = models.DateTimeField()
def delete(self, using=DEFAULT_DB_ALIAS, keep_parents=False):
sessions = Session.objects.filter(project=self)
for x in sessions:
x.delete()
super(Project, self).delete(using, keep_parents)
class Meta: # pylint: disable=no-init
unique_together = ('user', 'name')
class Hardware(BaseModel):
@property
def name(self):
return '{} CPUs, {}GB RAM, {}GB {}'.format(
self.cpu, self.memory, self.storage, StorageType.name(self.storage_type))
cpu = models.IntegerField(default=4, verbose_name='Number of CPUs')
memory = models.IntegerField(default=16, verbose_name='Memory (GB)')
storage = models.IntegerField(default=32, verbose_name='Storage (GB)')
storage_type = models.IntegerField(choices=StorageType.choices(),
default=StorageType.SSD, verbose_name='Storage Type')
additional_specs = models.TextField(null=True, default=None)
class Meta: # pylint: disable=no-init
unique_together = ('cpu', 'memory', 'storage', 'storage_type')
class Session(BaseModel):
TUNING_OPTIONS = OrderedDict([
("tuning_session", "Tuning Session"),
("no_tuning_session", "No Tuning"),
("randomly_generate", "Randomly Generate"),
("lhs", "Run LHS")
])
user = models.ForeignKey(User)
name = models.CharField(max_length=64, verbose_name="session name")
description = models.TextField(null=True, blank=True)
dbms = models.ForeignKey(DBMSCatalog)
hardware = models.ForeignKey(Hardware)
algorithm = models.IntegerField(choices=AlgorithmType.choices(),
default=AlgorithmType.GPR)
lhs_samples = models.TextField(default="[]")
ddpg_actor_model = models.BinaryField(null=True, blank=True)
ddpg_critic_model = models.BinaryField(null=True, blank=True)
ddpg_reply_memory = models.BinaryField(null=True, blank=True)
dnn_model = models.BinaryField(null=True, blank=True)
project = models.ForeignKey(Project)
creation_time = models.DateTimeField()
last_update = models.DateTimeField()
upload_code = models.CharField(max_length=30, unique=True)
tuning_session = models.CharField(choices=TUNING_OPTIONS.items(),
max_length=64, default='tuning_session',
verbose_name='session type')
target_objective = models.CharField(
max_length=64, default=target_objectives.default())
def clean(self):
if self.target_objective is None:
self.target_objective = target_objectives.default()
def delete(self, using=DEFAULT_DB_ALIAS, keep_parents=False):
SessionKnob.objects.get(session=self).delete()
results = Result.objects.filter(session=self)
for r in results:
r.knob_data.delete()
r.metric_data.delete()
r.delete()
super(Session, self).delete(using=DEFAULT_DB_ALIAS, keep_parents=False)
class Meta: # pylint: disable=no-init
unique_together = ('user', 'project', 'name')
class SessionKnobManager(models.Manager):
@staticmethod
def get_knobs_for_session(session):
# Returns a dict of the knob
knobs = KnobCatalog.objects.filter(dbms=session.dbms)
knob_dicts = list(knobs.values())
for i, _ in enumerate(knob_dicts):
if SessionKnob.objects.filter(session=session, knob=knobs[i]).exists():
new_knob = SessionKnob.objects.filter(session=session, knob=knobs[i])[0]
knob_dicts[i]["minval"] = new_knob.minval
knob_dicts[i]["maxval"] = new_knob.maxval
knob_dicts[i]["tunable"] = new_knob.tunable
knob_dicts = [knob for knob in knob_dicts if knob["tunable"]]
return knob_dicts
@staticmethod
def get_knob_min_max_tunability(session):
# Returns a dict of the knob
knobs = KnobCatalog.objects.filter(dbms=session.dbms)
knob_dicts = list(knobs.values())
session_knob_dicts = {}
for i, _ in enumerate(knob_dicts):
if SessionKnob.objects.filter(session=session, knob=knobs[i]).exists():
new_knob = SessionKnob.objects.filter(session=session, knob=knobs[i])[0]
min_max_tunability = {"minval": new_knob.minval,
"maxval": new_knob.maxval,
"tunable": new_knob.tunable}
session_knob_dicts[new_knob.name] = min_max_tunability
return session_knob_dicts
@staticmethod
def set_knob_min_max_tunability(session, knob_dicts):
# Returns a dict of the knob
session_knobs = SessionKnob.objects.filter(session=session)
for session_knob in session_knobs:
if knob_dicts.__contains__(session_knob.name):
session_knob.minval = knob_dicts[session_knob.name]["minval"]
session_knob.maxval = knob_dicts[session_knob.name]["maxval"]
session_knob.tunable = knob_dicts[session_knob.name]["tunable"]
session_knob.save()
class SessionKnob(BaseModel):
@property
def name(self):
return self.knob.name
objects = SessionKnobManager()
session = models.ForeignKey(Session)
knob = models.ForeignKey(KnobCatalog)
minval = models.CharField(max_length=32, null=True, verbose_name="minimum value")
maxval = models.CharField(max_length=32, null=True, verbose_name="maximum value")
tunable = models.BooleanField(verbose_name="tunable")
class DataModel(BaseModel):
session = models.ForeignKey(Session)
name = models.CharField(max_length=50)
creation_time = models.DateTimeField()
data = models.TextField()
dbms = models.ForeignKey(DBMSCatalog)
class Meta: # pylint: disable=no-init
abstract = True
class DataManager(models.Manager):
@staticmethod
def create_name(data_obj, key):
ts = data_obj.creation_time.strftime("%m-%d-%y")
return key + '@' + ts + '#' + str(data_obj.pk)
class KnobDataManager(DataManager):
def create_knob_data(self, session, knobs, data, dbms):
try:
return KnobData.objects.get(session=session,
knobs=knobs)
except KnobData.DoesNotExist:
knob_data = self.create(session=session,
knobs=knobs,
data=data,
dbms=dbms,
creation_time=now())
knob_data.name = self.create_name(knob_data, dbms.key)
knob_data.save()
return knob_data
class KnobData(DataModel):
objects = KnobDataManager()
knobs = models.TextField()
class MetricDataManager(DataManager):
def create_metric_data(self, session, metrics, data, dbms):
metric_data = self.create(session=session,
metrics=metrics,
data=data,
dbms=dbms,
creation_time=now())
metric_data.name = self.create_name(metric_data, dbms.key)
metric_data.save()
return metric_data
class MetricData(DataModel):
objects = MetricDataManager()
metrics = models.TextField()
class WorkloadManager(models.Manager):
def create_workload(self, dbms, hardware, name):
# (dbms,hardware,name) should be unique for each workload
try:
return Workload.objects.get(dbms=dbms, hardware=hardware, name=name)
except Workload.DoesNotExist:
return self.create(dbms=dbms,
hardware=hardware,
name=name)
class Workload(BaseModel):
# __DEFAULT_FMT = '{db}_{hw}_UNASSIGNED'.format
objects = WorkloadManager()
dbms = models.ForeignKey(DBMSCatalog)
hardware = models.ForeignKey(Hardware)
name = models.CharField(max_length=128, verbose_name='workload name')
status = models.IntegerField(choices=WorkloadStatusType.choices(),
default=WorkloadStatusType.MODIFIED,
editable=False)
def delete(self, using=DEFAULT_DB_ALIAS, keep_parents=False):
# The results should not have corresponding workloads.
# results = Result.objects.filter(workload=self)
# if results.exists():
# raise Exception("Cannot delete {} workload since results exist. ".format(self.name))
# Delete PipelineData with corresponding workloads
pipelinedatas = PipelineData.objects.filter(workload=self)
for x in pipelinedatas:
x.delete()
super(Workload, self).delete(using, keep_parents)
class Meta: # pylint: disable=no-init
unique_together = ("dbms", "hardware", "name")
# @property
# def isdefault(self):
# return self.cluster_name == self.default
#
# @property
# def default(self):
# return self.__DEFAULT_FMT(db=self.dbms.pk,
# hw=self.hardware.pk)
#
# @staticmethod
# def get_default(dbms_id, hw_id):
# return Workload.__DEFAULT_FMT(db=dbms_id,
# hw=hw_id)
class ResultManager(models.Manager):
def create_result(self, session, dbms, workload,
knob_data, metric_data,
observation_start_time,
observation_end_time,
observation_time,
task_ids=None,
next_config=None):
return self.create(
session=session,
dbms=dbms,
workload=workload,
knob_data=knob_data,
metric_data=metric_data,
observation_start_time=observation_start_time,
observation_end_time=observation_end_time,
observation_time=observation_time,
task_ids=task_ids,
next_configuration=next_config,
creation_time=now())
class Result(BaseModel):
objects = ResultManager()
session = models.ForeignKey(Session, verbose_name='session name')
dbms = models.ForeignKey(DBMSCatalog)
workload = models.ForeignKey(Workload)
knob_data = models.ForeignKey(KnobData)
metric_data = models.ForeignKey(MetricData)
creation_time = models.DateTimeField()
observation_start_time = models.DateTimeField()
observation_end_time = models.DateTimeField()
observation_time = models.FloatField()
task_ids = models.CharField(max_length=180, null=True)
next_configuration = models.TextField(null=True)
def __unicode__(self):
return str(self.pk)
class PipelineRunManager(models.Manager):
def get_latest(self):
return self.all().exclude(end_time=None).first()
class PipelineRun(models.Model):
objects = PipelineRunManager()
start_time = models.DateTimeField()
end_time = models.DateTimeField(null=True)
def __unicode__(self):
return str(self.pk)
def __str__(self):
return self.__unicode__()
class Meta: # pylint: disable=no-init
ordering = ["-id"]
class PipelineData(models.Model):
pipeline_run = models.ForeignKey(PipelineRun, verbose_name='group')
task_type = models.IntegerField(choices=PipelineTaskType.choices())
workload = models.ForeignKey(Workload)
data = models.TextField()
creation_time = models.DateTimeField()
class Meta: # pylint: disable=no-init
unique_together = ("pipeline_run", "task_type", "workload")
class BackupData(BaseModel):
result = models.ForeignKey(Result)
raw_knobs = models.TextField()
raw_initial_metrics = models.TextField()
raw_final_metrics = models.TextField()
raw_summary = models.TextField()
knob_log = models.TextField()
metric_log = models.TextField()