1418 lines
56 KiB
Python
1418 lines
56 KiB
Python
#
|
|
# OtterTune - views.py
|
|
#
|
|
# Copyright (c) 2017-18, Carnegie Mellon University Database Group
|
|
#
|
|
# pylint: disable=too-many-lines
|
|
import logging
|
|
import datetime
|
|
import re
|
|
import time
|
|
from collections import OrderedDict
|
|
|
|
from django.contrib.auth import authenticate, login, logout
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib.auth import update_session_auth_hash
|
|
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
|
from django.contrib.auth.forms import PasswordChangeForm
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.files.base import ContentFile
|
|
from django.db.utils import IntegrityError
|
|
from django.forms.models import model_to_dict
|
|
from django.http import HttpResponse, QueryDict
|
|
from django.shortcuts import redirect, render, get_object_or_404
|
|
from django.template.context_processors import csrf
|
|
from django.template.defaultfilters import register
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.utils.datetime_safe import datetime
|
|
from django.utils.timezone import now
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from pytz import timezone
|
|
|
|
from . import utils
|
|
from .db import parser, target_objectives
|
|
from .forms import NewResultForm, ProjectForm, SessionForm, SessionKnobForm
|
|
from .models import (BackupData, DBMSCatalog, KnobCatalog, KnobData, MetricCatalog, User, Hardware,
|
|
MetricData, Project, Result, Session, Workload, SessionKnob, PipelineRun)
|
|
from .tasks import (aggregate_target_results, map_workload, train_ddpg,
|
|
configuration_recommendation, configuration_recommendation_ddpg)
|
|
from .types import (DBMSType, KnobUnitType, MetricType,
|
|
TaskType, VarType, WorkloadStatusType, AlgorithmType)
|
|
from .utils import JSONUtil, LabelUtil, MediaUtil, TaskUtil, ConversionUtil
|
|
from .settings import TIME_ZONE
|
|
|
|
from .set_default_knobs import set_default_knobs
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
# For the html template to access dict object
|
|
@register.filter
|
|
def get_item(dictionary, key):
|
|
return dictionary.get(key)
|
|
|
|
|
|
def signup_view(request):
|
|
if request.user.is_authenticated():
|
|
return redirect(reverse('home_projects'))
|
|
if request.method == 'POST':
|
|
post = request.POST
|
|
form = UserCreationForm(post)
|
|
if form.is_valid():
|
|
form.save()
|
|
new_post = QueryDict(mutable=True)
|
|
new_post.update(post)
|
|
new_post['password'] = post['password1']
|
|
request.POST = new_post
|
|
return login_view(request)
|
|
else:
|
|
LOG.warning("Signup form is not valid: %s", str(form.errors))
|
|
else:
|
|
form = UserCreationForm()
|
|
token = {}
|
|
token.update(csrf(request))
|
|
token['form'] = form
|
|
return render(request, 'signup.html', token)
|
|
|
|
|
|
def change_password_view(request):
|
|
if not request.user.is_authenticated():
|
|
return redirect(reverse('home_project'))
|
|
if request.method == 'POST':
|
|
form = PasswordChangeForm(request.user, request.POST)
|
|
if form.is_valid():
|
|
user = form.save()
|
|
update_session_auth_hash(request, user)
|
|
return redirect(reverse('home_projects'))
|
|
else:
|
|
form = PasswordChangeForm(request.user)
|
|
token = {}
|
|
token.update(csrf(request))
|
|
token['form'] = form
|
|
return render(request, 'change_password.html', token)
|
|
|
|
|
|
def login_view(request):
|
|
if request.user.is_authenticated():
|
|
return redirect(reverse('home_projects'))
|
|
if request.method == 'POST':
|
|
post = request.POST
|
|
form = AuthenticationForm(None, post)
|
|
if form.is_valid():
|
|
login(request, form.get_user())
|
|
return redirect(reverse('home_projects'))
|
|
else:
|
|
LOG.warning("Login form is not valid: %s", str(form.errors))
|
|
else:
|
|
form = AuthenticationForm()
|
|
token = {}
|
|
token.update(csrf(request))
|
|
token['form'] = form
|
|
return render(request, 'login.html', token)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def logout_view(request):
|
|
logout(request)
|
|
return redirect(reverse('login'))
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def redirect_home(request): # pylint: disable=unused-argument
|
|
return redirect(reverse('home_projects'))
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def home_projects_view(request):
|
|
form_labels = Project.get_labels()
|
|
form_labels.update(LabelUtil.style_labels({
|
|
'button_create': 'create a new project',
|
|
'button_delete': 'delete selected projects',
|
|
}))
|
|
form_labels['title'] = 'Your Projects'
|
|
projects = Project.objects.filter(user=request.user)
|
|
show_descriptions = any([proj.description for proj in projects])
|
|
context = {
|
|
"projects": projects,
|
|
"labels": form_labels,
|
|
"show_descriptions": show_descriptions
|
|
}
|
|
context.update(csrf(request))
|
|
return render(request, 'home_projects.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def create_or_edit_project(request, project_id=''):
|
|
form_kwargs = dict(user_id=request.user.pk, project_id=project_id)
|
|
if request.method == 'POST':
|
|
if project_id == '':
|
|
form = ProjectForm(request.POST, **form_kwargs)
|
|
if not form.is_valid():
|
|
return render(request, 'edit_project.html', {'form': form})
|
|
project = form.save(commit=False)
|
|
project.user = request.user
|
|
ts = now()
|
|
project.creation_time = ts
|
|
project.last_update = ts
|
|
else:
|
|
project = get_object_or_404(Project, pk=project_id, user=request.user)
|
|
form_kwargs.update(instance=project)
|
|
form = ProjectForm(request.POST, **form_kwargs)
|
|
if not form.is_valid():
|
|
return render(request, 'edit_project.html', {'form': form})
|
|
project.last_update = now()
|
|
|
|
project.save()
|
|
return redirect(reverse('project_sessions', kwargs={'project_id': project.pk}))
|
|
else:
|
|
if project_id == '':
|
|
project = None
|
|
form = ProjectForm(**form_kwargs)
|
|
else:
|
|
project = Project.objects.get(pk=project_id)
|
|
form_kwargs.update(instance=project)
|
|
form = ProjectForm(**form_kwargs)
|
|
context = {
|
|
'project': project,
|
|
'form': form,
|
|
}
|
|
return render(request, 'edit_project.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def delete_project(request):
|
|
pids = request.POST.getlist('projects', [])
|
|
Project.objects.filter(pk__in=pids, user=request.user).delete()
|
|
return redirect(reverse('home_projects'))
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def project_sessions_view(request, project_id):
|
|
sessions = Session.objects.filter(project=project_id)
|
|
project = Project.objects.get(pk=project_id)
|
|
form_labels = Session.get_labels()
|
|
form_labels.update(LabelUtil.style_labels({
|
|
'button_delete': 'delete selected session',
|
|
'button_create': 'create a new session',
|
|
}))
|
|
form_labels['title'] = "Your Sessions"
|
|
for session in sessions:
|
|
session.session_type_name = Session.TUNING_OPTIONS[session.tuning_session]
|
|
session.algorithm_name = AlgorithmType.name(session.algorithm)
|
|
|
|
context = {
|
|
"sessions": sessions,
|
|
"project": project,
|
|
"labels": form_labels,
|
|
}
|
|
context.update(csrf(request))
|
|
return render(request, 'project_sessions.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def session_view(request, project_id, session_id):
|
|
project = get_object_or_404(Project, pk=project_id)
|
|
session = get_object_or_404(Session, pk=session_id)
|
|
|
|
# All results from this session
|
|
results = Result.objects.filter(session=session)
|
|
|
|
# Group the session's results by DBMS & workload
|
|
dbmss = {}
|
|
workloads = {}
|
|
dbmss_ids = set()
|
|
workloads_ids = set()
|
|
for res in results:
|
|
if res.dbms_id not in dbmss_ids:
|
|
dbmss_ids.add(res.dbms_id)
|
|
res_dbms = res.dbms
|
|
dbmss[res_dbms.key] = res_dbms
|
|
|
|
if res.workload_id not in workloads_ids:
|
|
workloads_ids.add(res.workload_id)
|
|
res_workload = res.workload
|
|
workloads[res_workload.name] = set()
|
|
workloads[res_workload.name].add(res_workload)
|
|
|
|
# Sort so names will be ordered in the sidebar
|
|
workloads = OrderedDict([(k, sorted(list(v))) for
|
|
k, v in sorted(workloads.items())])
|
|
dbmss = OrderedDict(sorted(dbmss.items()))
|
|
|
|
if len(workloads) > 0:
|
|
# Set the default workload to whichever is first
|
|
default_workload, default_confs = next(iter(list(workloads.items())))
|
|
default_confs = ','.join([str(c.pk) for c in default_confs])
|
|
else:
|
|
# Set the default to display nothing if there are no results yet
|
|
default_workload = 'show_none'
|
|
default_confs = 'none'
|
|
|
|
default_metrics = [session.target_objective]
|
|
metric_meta = target_objectives.get_metric_metadata(
|
|
session.dbms.pk, session.target_objective)
|
|
|
|
knobs = SessionKnob.objects.get_knobs_for_session(session)
|
|
knob_names = [knob["name"] for knob in knobs if knob["tunable"]]
|
|
|
|
session.session_type_name = Session.TUNING_OPTIONS[session.tuning_session]
|
|
session.algorithm_name = AlgorithmType.name(session.algorithm)
|
|
|
|
form_labels = Session.get_labels()
|
|
form_labels['title'] = "Session Info"
|
|
|
|
context = {
|
|
'project': project,
|
|
'dbmss': dbmss,
|
|
'workloads': workloads,
|
|
'results_per_page': [10, 50, 100, 500, 1000],
|
|
'default_dbms': session.dbms.key,
|
|
'default_results_per_page': 10,
|
|
'default_equidistant': "on",
|
|
'default_workload': default_workload,
|
|
'defaultspe': default_confs,
|
|
'metrics': list(metric_meta.keys()),
|
|
'metric_meta': metric_meta,
|
|
'default_metrics': default_metrics,
|
|
'knob_names': knob_names,
|
|
'filters': [],
|
|
'session': session,
|
|
'results': results,
|
|
'labels': form_labels,
|
|
}
|
|
context.update(csrf(request))
|
|
return render(request, 'session.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def create_or_edit_session(request, project_id, session_id=''):
|
|
project = get_object_or_404(Project, pk=project_id, user=request.user)
|
|
form_kwargs = dict(user_id=request.user.pk, project_id=project_id, session_id=session_id)
|
|
if request.method == 'POST':
|
|
if not session_id:
|
|
# Create a new session from the form contents
|
|
form = SessionForm(request.POST, **form_kwargs)
|
|
if not form.is_valid():
|
|
return render(request, 'edit_session.html',
|
|
{'project': project, 'form': form, 'session': None})
|
|
session = form.save(commit=False)
|
|
session.user = request.user
|
|
session.project = project
|
|
ts = now()
|
|
session.creation_time = ts
|
|
session.last_update = ts
|
|
session.upload_code = MediaUtil.upload_code_generator()
|
|
session.save()
|
|
set_default_knobs(session)
|
|
else:
|
|
# Update an existing session with the form contents
|
|
session = Session.objects.get(pk=session_id)
|
|
form_kwargs.update(instance=session)
|
|
form = SessionForm(request.POST, **form_kwargs)
|
|
if not form.is_valid():
|
|
return render(request, 'edit_session.html',
|
|
{'project': project, 'form': form, 'session': session})
|
|
if form.cleaned_data['gen_upload_code'] is True:
|
|
session.upload_code = MediaUtil.upload_code_generator()
|
|
session.last_update = now()
|
|
form.save()
|
|
session.save()
|
|
return redirect(reverse('session', kwargs={'project_id': project_id,
|
|
'session_id': session.pk}))
|
|
else:
|
|
if session_id:
|
|
# Return a pre-filled form for editing an existing session
|
|
session = Session.objects.get(pk=session_id)
|
|
form_kwargs.update(instance=session)
|
|
form = SessionForm(**form_kwargs)
|
|
else:
|
|
# Return a new form with defaults for creating a new session
|
|
session = None
|
|
form_kwargs.update(
|
|
initial={
|
|
'dbms': DBMSCatalog.objects.get(
|
|
type=DBMSType.POSTGRES, version='9.6'),
|
|
'algorithm': AlgorithmType.GPR,
|
|
'target_objective': target_objectives.default(),
|
|
})
|
|
form = SessionForm(**form_kwargs)
|
|
context = {
|
|
'project': project,
|
|
'session': session,
|
|
'form': form,
|
|
}
|
|
return render(request, 'edit_session.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def edit_knobs(request, project_id, session_id):
|
|
project = get_object_or_404(Project, pk=project_id, user=request.user)
|
|
session = get_object_or_404(Session, pk=session_id, user=request.user)
|
|
if request.method == 'POST':
|
|
form = SessionKnobForm(request.POST)
|
|
if not form.is_valid():
|
|
return render(request, 'edit_knobs.html',
|
|
{'project': project, 'session': session, 'form': form})
|
|
instance = form.instance
|
|
instance.session = session
|
|
instance.knob = KnobCatalog.objects.get(dbms=session.dbms,
|
|
name=form.cleaned_data["name"])
|
|
SessionKnob.objects.filter(session=instance.session, knob=instance.knob).delete()
|
|
instance.save()
|
|
return HttpResponse(status=204)
|
|
else:
|
|
knobs = SessionKnob.objects.filter(session=session).prefetch_related(
|
|
'knob').order_by('-tunable', 'knob__name')
|
|
forms = []
|
|
for knob in knobs:
|
|
knob_values = model_to_dict(knob)
|
|
knob_values['session'] = session
|
|
knob_values['name'] = knob.knob.name
|
|
forms.append(SessionKnobForm(initial=knob_values))
|
|
context = {
|
|
'project': project,
|
|
'session': session,
|
|
'forms': forms
|
|
}
|
|
return render(request, 'edit_knobs.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def delete_session(request, project_id):
|
|
sids = request.POST.getlist('sessions', [])
|
|
Session.objects.filter(pk__in=sids, user=request.user).delete()
|
|
return redirect(reverse(
|
|
'project_sessions',
|
|
kwargs={'project_id': project_id}))
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def result_view(request, project_id, session_id, result_id):
|
|
target = get_object_or_404(Result, pk=result_id)
|
|
session = target.session
|
|
|
|
# default_metrics = [session.target_objective]
|
|
metric_meta = target_objectives.get_metric_metadata(session.dbms.pk, session.target_objective)
|
|
# metric_data = JSONUtil.loads(target.metric_data.data)
|
|
|
|
# default_metrics = {mname: metric_data[mname] * metric_meta[mname].scale
|
|
# for mname in default_metrics}
|
|
|
|
status = None
|
|
if target.task_ids is not None:
|
|
tasks = TaskUtil.get_tasks(target.task_ids)
|
|
status, _ = TaskUtil.get_task_status(tasks)
|
|
if status is None:
|
|
status = 'UNAVAILABLE'
|
|
|
|
next_conf_available = True if status == 'SUCCESS' else False
|
|
form_labels = Result.get_labels()
|
|
form_labels.update(LabelUtil.style_labels({
|
|
'status': 'status',
|
|
'next_conf_available': 'next configuration'
|
|
}))
|
|
form_labels['title'] = 'Result Info'
|
|
context = {
|
|
'result': target,
|
|
'metric_meta': metric_meta,
|
|
'status': status,
|
|
'next_conf_available': next_conf_available,
|
|
'labels': form_labels,
|
|
'project_id': project_id,
|
|
'session_id': session_id
|
|
}
|
|
return render(request, 'result.html', context)
|
|
|
|
|
|
@csrf_exempt
|
|
def new_result(request):
|
|
if request.method == 'POST':
|
|
form = NewResultForm(request.POST, request.FILES)
|
|
|
|
if not form.is_valid():
|
|
LOG.warning("New result form is not valid: %s", str(form.errors))
|
|
return HttpResponse("New result form is not valid: " + str(form.errors), status=400)
|
|
upload_code = form.cleaned_data['upload_code']
|
|
try:
|
|
session = Session.objects.get(upload_code=upload_code)
|
|
except Session.DoesNotExist:
|
|
LOG.warning("Invalid upload code: %s", upload_code)
|
|
return HttpResponse("Invalid upload code: " + upload_code, status=400)
|
|
|
|
return handle_result_files(session, request.FILES)
|
|
LOG.warning("Request type was not POST")
|
|
return HttpResponse("Request type was not POST", status=400)
|
|
|
|
|
|
def handle_result_files(session, files):
|
|
from celery import chain
|
|
# Combine into contiguous files
|
|
files = {k: b''.join(v.chunks()).decode() for k, v in list(files.items())}
|
|
|
|
# Load the contents of the controller's summary file
|
|
summary = JSONUtil.loads(files['summary'])
|
|
|
|
# If database crashed on restart, pull latest result and worst throughput so far
|
|
if 'error' in summary and summary['error'] == "DB_RESTART_ERROR":
|
|
|
|
LOG.debug("Error in restarting database")
|
|
# Find worst throughput
|
|
past_configs = MetricData.objects.filter(session=session)
|
|
worst_throughput = None
|
|
for curr_config in past_configs:
|
|
throughput = JSONUtil.loads(curr_config.data)[session.target_objective]
|
|
metric_meta = target_objectives.get_instance(
|
|
session.dbms.pk, session.target_objective)
|
|
if metric_meta.improvement == target_objectives.MORE_IS_BETTER:
|
|
if worst_throughput is None or throughput < worst_throughput:
|
|
worst_throughput = throughput
|
|
else:
|
|
if worst_throughput is None or throughput > worst_throughput:
|
|
worst_throughput = throughput
|
|
LOG.debug("Worst throughput so far is:%d", worst_throughput)
|
|
|
|
result = Result.objects.filter(session=session).order_by("-id").first()
|
|
backup_data = BackupData.objects.filter(result=result).first()
|
|
last_conf = JSONUtil.loads(result.next_configuration)
|
|
last_conf = last_conf["recommendation"]
|
|
|
|
# Copy latest data and modify
|
|
knob_data = result.knob_data
|
|
knob_data.pk = None
|
|
all_knobs = JSONUtil.loads(knob_data.knobs)
|
|
for knob in all_knobs.keys():
|
|
for tunable_knob in last_conf.keys():
|
|
if tunable_knob in knob:
|
|
all_knobs[knob] = last_conf[tunable_knob]
|
|
knob_data.knobs = JSONUtil.dumps(all_knobs)
|
|
|
|
data_knobs = JSONUtil.loads(knob_data.data)
|
|
last_conf = parser.convert_dbms_knobs(result.dbms.pk, last_conf)
|
|
for knob in data_knobs.keys():
|
|
for tunable_knob in last_conf.keys():
|
|
if tunable_knob in knob:
|
|
data_knobs[knob] = last_conf[tunable_knob]
|
|
|
|
knob_data.data = JSONUtil.dumps(data_knobs)
|
|
knob_data.name = knob_data.name + '*'
|
|
knob_data.creation_time = now()
|
|
knob_data.save()
|
|
knob_data = KnobData.objects.filter(session=session).order_by("-id").first()
|
|
|
|
metric_data = result.metric_data
|
|
metric_cpy = JSONUtil.loads(metric_data.data)
|
|
metric_cpy["throughput_txn_per_sec"] = worst_throughput
|
|
metric_cpy = JSONUtil.dumps(metric_cpy)
|
|
metric_data.pk = None
|
|
metric_data.name = metric_data.name + '*'
|
|
metric_data.data = metric_cpy
|
|
metric_data.creation_time = now()
|
|
metric_data.save()
|
|
metric_data = MetricData.objects.filter(session=session).order_by("-id").first()
|
|
|
|
result.pk = None
|
|
result.knob_data = knob_data
|
|
result.metric_data = metric_data
|
|
result.creation_time = now()
|
|
result.observation_start_time = now()
|
|
result.observation_end_time = now()
|
|
result.save()
|
|
result = Result.objects.filter(session=session).order_by("-id").first()
|
|
|
|
backup_data.pk = None
|
|
backup_data.result = result
|
|
backup_data.creation_time = now()
|
|
backup_data.save()
|
|
|
|
else:
|
|
dbms_type = DBMSType.type(summary['database_type'])
|
|
dbms_version = summary['database_version'] # TODO: fix parse_version_string
|
|
workload_name = summary['workload_name']
|
|
observation_time = summary['observation_time']
|
|
start_time = datetime.fromtimestamp(
|
|
# int(summary['start_time']), # unit: seconds
|
|
int(float(summary['start_time']) / 1000), # unit: ms
|
|
timezone(TIME_ZONE))
|
|
end_time = datetime.fromtimestamp(
|
|
# int(summary['end_time']), # unit: seconds
|
|
int(float(summary['end_time']) / 1000), # unit: ms
|
|
timezone(TIME_ZONE))
|
|
|
|
# Check if workload name only contains alpha-numeric, underscore and hyphen
|
|
if not re.match('^[a-zA-Z0-9_-]+$', workload_name):
|
|
return HttpResponse('Your workload name ' + workload_name + ' contains '
|
|
'invalid characters! It should only contain '
|
|
'alpha-numeric, underscore(_) and hyphen(-)')
|
|
|
|
try:
|
|
# Check that we support this DBMS and version
|
|
dbms = DBMSCatalog.objects.get(
|
|
type=dbms_type, version=dbms_version)
|
|
except ObjectDoesNotExist:
|
|
return HttpResponse('{} v{} is not yet supported.'.format(
|
|
dbms_type, dbms_version))
|
|
|
|
if dbms != session.dbms:
|
|
return HttpResponse('The DBMS must match the type and version '
|
|
'specified when creating the session. '
|
|
'(expected=' + session.dbms.full_name + ') '
|
|
'(actual=' + dbms.full_name + ')')
|
|
|
|
# Load, process, and store the knobs in the DBMS's configuration
|
|
knob_dict, knob_diffs = parser.parse_dbms_knobs(
|
|
dbms.pk, JSONUtil.loads(files['knobs']))
|
|
tunable_knob_dict = parser.convert_dbms_knobs(
|
|
dbms.pk, knob_dict)
|
|
knob_data = KnobData.objects.create_knob_data(
|
|
session, JSONUtil.dumps(knob_dict, pprint=True, sort=True),
|
|
JSONUtil.dumps(tunable_knob_dict, pprint=True, sort=True), dbms)
|
|
LOG.debug(knob_data.data)
|
|
|
|
# Load, process, and store the runtime metrics exposed by the DBMS
|
|
initial_metric_dict, initial_metric_diffs = parser.parse_dbms_metrics(
|
|
dbms.pk, JSONUtil.loads(files['metrics_before']))
|
|
final_metric_dict, final_metric_diffs = parser.parse_dbms_metrics(
|
|
dbms.pk, JSONUtil.loads(files['metrics_after']))
|
|
metric_dict = parser.calculate_change_in_metrics(
|
|
dbms.pk, initial_metric_dict, final_metric_dict)
|
|
initial_metric_diffs.extend(final_metric_diffs)
|
|
numeric_metric_dict = parser.convert_dbms_metrics(
|
|
dbms.pk, metric_dict, observation_time, session.target_objective)
|
|
metric_data = MetricData.objects.create_metric_data(
|
|
session, JSONUtil.dumps(metric_dict, pprint=True, sort=True),
|
|
JSONUtil.dumps(numeric_metric_dict, pprint=True, sort=True), dbms)
|
|
|
|
# Create a new workload if this one does not already exist
|
|
workload = Workload.objects.create_workload(
|
|
dbms, session.hardware, workload_name, session.project)
|
|
|
|
# Save this result
|
|
result = Result.objects.create_result(
|
|
session, dbms, workload, knob_data, metric_data,
|
|
start_time, end_time, observation_time)
|
|
result.save()
|
|
|
|
# Workload is now modified so backgroundTasks can make calculation
|
|
workload.status = WorkloadStatusType.MODIFIED
|
|
workload.save()
|
|
|
|
# Save all original data
|
|
backup_data = BackupData.objects.create(
|
|
result=result, raw_knobs=files['knobs'],
|
|
raw_initial_metrics=files['metrics_before'],
|
|
raw_final_metrics=files['metrics_after'],
|
|
raw_summary=files['summary'],
|
|
knob_log=knob_diffs,
|
|
metric_log=initial_metric_diffs)
|
|
backup_data.save()
|
|
|
|
session.project.last_update = now()
|
|
session.last_update = now()
|
|
session.project.save()
|
|
session.save()
|
|
|
|
if session.tuning_session == 'no_tuning_session':
|
|
return HttpResponse("Result stored successfully!")
|
|
|
|
result_id = result.pk
|
|
response = None
|
|
if session.algorithm == AlgorithmType.GPR:
|
|
response = chain(aggregate_target_results.s(result.pk, session.algorithm),
|
|
map_workload.s(),
|
|
configuration_recommendation.s()).apply_async()
|
|
elif session.algorithm == AlgorithmType.DDPG:
|
|
response = chain(train_ddpg.s(result.pk),
|
|
configuration_recommendation_ddpg.s()).apply_async()
|
|
elif session.algorithm == AlgorithmType.DNN:
|
|
response = chain(aggregate_target_results.s(result.pk, session.algorithm),
|
|
map_workload.s(),
|
|
configuration_recommendation.s()).apply_async()
|
|
|
|
taskmeta_ids = []
|
|
current_task = response
|
|
while current_task:
|
|
taskmeta_ids.insert(0, current_task.id)
|
|
current_task = current_task.parent
|
|
|
|
result.task_ids = ','.join(taskmeta_ids)
|
|
result.save()
|
|
return HttpResponse("Result stored successfully! Running tuner...(status={}) Result ID:{} "
|
|
.format(response.status, result_id))
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def dbms_knobs_reference(request, dbms_name, version, knob_name):
|
|
knob = get_object_or_404(KnobCatalog, dbms__type=DBMSType.type(dbms_name),
|
|
dbms__version=version, name=knob_name)
|
|
labels = KnobCatalog.get_labels()
|
|
list_items = OrderedDict()
|
|
if knob.category is not None:
|
|
list_items[labels['category']] = knob.category
|
|
list_items[labels['scope']] = knob.scope
|
|
list_items[labels['tunable']] = knob.tunable
|
|
list_items[labels['vartype']] = VarType.name(knob.vartype)
|
|
if knob.unit != KnobUnitType.OTHER:
|
|
list_items[labels['unit']] = knob.unit
|
|
list_items[labels['default']] = knob.default
|
|
if knob.minval is not None:
|
|
list_items[labels['minval']] = knob.minval
|
|
if knob.maxval is not None:
|
|
list_items[labels['maxval']] = knob.maxval
|
|
if knob.enumvals is not None:
|
|
list_items[labels['enumvals']] = knob.enumvals
|
|
if knob.summary is not None:
|
|
description = knob.summary
|
|
if knob.description is not None:
|
|
description += knob.description
|
|
list_items[labels['summary']] = description
|
|
|
|
context = {
|
|
'title': knob.name,
|
|
'dbms': knob.dbms,
|
|
'is_used': knob.tunable,
|
|
'used_label': 'TUNABLE',
|
|
'list_items': list_items,
|
|
}
|
|
return render(request, 'dbms_reference.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def dbms_metrics_reference(request, dbms_name, version, metric_name):
|
|
metric = get_object_or_404(
|
|
MetricCatalog, dbms__type=DBMSType.type(dbms_name),
|
|
dbms__version=version, name=metric_name)
|
|
labels = MetricCatalog.get_labels()
|
|
list_items = OrderedDict()
|
|
list_items[labels['scope']] = metric.scope
|
|
list_items[labels['vartype']] = VarType.name(metric.vartype)
|
|
list_items[labels['summary']] = metric.summary
|
|
context = {
|
|
'title': metric.name,
|
|
'dbms': metric.dbms,
|
|
'is_used': metric.metric_type == MetricType.COUNTER,
|
|
'used_label': MetricType.name(metric.metric_type),
|
|
'list_items': list_items,
|
|
}
|
|
return render(request, 'dbms_reference.html', context=context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def knob_data_view(request, project_id, session_id, data_id): # pylint: disable=unused-argument
|
|
knob_data = get_object_or_404(KnobData, pk=data_id)
|
|
labels = KnobData.get_labels()
|
|
labels.update(LabelUtil.style_labels({
|
|
'featured_data': 'tunable dbms parameters',
|
|
'all_data': 'all dbms parameters',
|
|
}))
|
|
labels['title'] = 'DBMS Configuration'
|
|
context = {
|
|
'labels': labels,
|
|
'data_type': 'knobs'
|
|
}
|
|
return dbms_data_view(request, context, knob_data)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def metric_data_view(request, project_id, session_id, data_id): # pylint: disable=unused-argument
|
|
metric_data = get_object_or_404(MetricData, pk=data_id)
|
|
labels = MetricData.get_labels()
|
|
labels.update(LabelUtil.style_labels({
|
|
'featured_data': 'numeric dbms metrics',
|
|
'all_data': 'all dbms metrics',
|
|
}))
|
|
labels['title'] = 'DBMS Metrics'
|
|
context = {
|
|
'labels': labels,
|
|
'data_type': 'metrics'
|
|
}
|
|
return dbms_data_view(request, context, metric_data)
|
|
|
|
|
|
def dbms_data_view(request, context, dbms_data):
|
|
if context['data_type'] == 'knobs':
|
|
model_class = KnobData
|
|
filter_fn = parser.filter_tunable_knobs
|
|
obj_data = dbms_data.knobs
|
|
else:
|
|
model_class = MetricData
|
|
filter_fn = parser.filter_numeric_metrics
|
|
obj_data = dbms_data.metrics
|
|
|
|
dbms_id = dbms_data.dbms.pk
|
|
all_data_dict = JSONUtil.loads(obj_data)
|
|
featured_dict = filter_fn(dbms_id, all_data_dict)
|
|
|
|
if 'compare' in request.GET and request.GET['compare'] != 'none':
|
|
comp_id = request.GET['compare']
|
|
compare_obj = model_class.objects.get(pk=comp_id)
|
|
comp_data = compare_obj.knobs if \
|
|
context['data_type'] == 'knobs' else compare_obj.metrics
|
|
comp_dict = JSONUtil.loads(comp_data)
|
|
comp_featured_dict = filter_fn(dbms_id, comp_dict)
|
|
|
|
all_data = [(k, v, comp_dict[k]) for k, v in list(all_data_dict.items())]
|
|
featured_data = [(k, v, comp_featured_dict[k])
|
|
for k, v in list(featured_dict.items())]
|
|
else:
|
|
comp_id = None
|
|
all_data = list(all_data_dict.items())
|
|
featured_data = list(featured_dict.items())
|
|
peer_data = model_class.objects.filter(
|
|
dbms=dbms_data.dbms, session=dbms_data.session)
|
|
peer_data = [peer for peer in peer_data if peer.pk != dbms_data.pk]
|
|
|
|
context['all_data'] = all_data
|
|
context['featured_data'] = featured_data
|
|
context['dbms_data'] = dbms_data
|
|
context['compare'] = comp_id
|
|
context['peer_data'] = peer_data
|
|
return render(request, 'dbms_data.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def workload_view(request, project_id, session_id, wkld_id): # pylint: disable=unused-argument
|
|
workload = get_object_or_404(Workload, pk=wkld_id)
|
|
session = get_object_or_404(Session, pk=session_id)
|
|
|
|
knob_confs = KnobData.objects.filter(dbms=session.dbms,
|
|
session=session)
|
|
knob_conf_map = {}
|
|
for conf in knob_confs:
|
|
latest_result = Result.objects.filter(
|
|
session=session, knob_data=conf, workload=workload).order_by(
|
|
'-observation_end_time').first()
|
|
if not latest_result:
|
|
continue
|
|
knob_conf_map[conf.name] = [conf, latest_result]
|
|
knob_conf_map = OrderedDict(sorted(list(knob_conf_map.items()), key=lambda x: x[1][0].pk))
|
|
default_knob_confs = [c for c, _ in list(knob_conf_map.values())][:5]
|
|
LOG.debug("default_knob_confs: %s", default_knob_confs)
|
|
|
|
metric_meta = target_objectives.get_metric_metadata(session.dbms.pk, session.target_objective)
|
|
default_metrics = [session.target_objective]
|
|
|
|
labels = Workload.get_labels()
|
|
labels['title'] = 'Workload Information'
|
|
context = {'workload': workload,
|
|
'knob_confs': knob_conf_map,
|
|
'metric_meta': metric_meta,
|
|
'knob_data': default_knob_confs,
|
|
'default_metrics': default_metrics,
|
|
'labels': labels,
|
|
'session_id': session_id}
|
|
return render(request, 'workload.html', context)
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def download_next_config(request):
|
|
data = request.GET
|
|
result_id = data['id']
|
|
res = Result.objects.get(pk=result_id)
|
|
response = HttpResponse(res.next_configuration,
|
|
content_type='text/plain')
|
|
response['Content-Disposition'] = 'attachment; filename=result_' + str(result_id) + '.cnf'
|
|
return response
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def download_debug_info(request, project_id, session_id): # pylint: disable=unused-argument
|
|
session = Session.objects.get(pk=session_id)
|
|
content, filename = utils.dump_debug_info(session, pretty_print=False)
|
|
file = ContentFile(content.getvalue())
|
|
response = HttpResponse(file, content_type='application/x-gzip')
|
|
response['Content-Length'] = file.size
|
|
response['Content-Disposition'] = 'attachment; filename={}.tar.gz'.format(filename)
|
|
return response
|
|
|
|
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def tuner_status_view(request, project_id, session_id, result_id): # pylint: disable=unused-argument
|
|
res = Result.objects.get(pk=result_id)
|
|
|
|
tasks = TaskUtil.get_tasks(res.task_ids)
|
|
|
|
overall_status, num_completed = TaskUtil.get_task_status(tasks)
|
|
if overall_status in ['PENDING', 'RECEIVED', 'STARTED', None]:
|
|
completion_time = 'N/A'
|
|
total_runtime = 'N/A'
|
|
else:
|
|
completion_time = tasks[-1].date_done
|
|
total_runtime = (completion_time - res.creation_time).total_seconds()
|
|
total_runtime = '{0:.2f} seconds'.format(total_runtime)
|
|
|
|
task_info = [(tname, task) for tname, task in
|
|
zip(list(TaskType.TYPE_NAMES.values()), tasks)]
|
|
|
|
context = {"id": result_id,
|
|
"result": res,
|
|
"overall_status": overall_status,
|
|
"num_completed": "{} / {}".format(num_completed, 3),
|
|
"completion_time": completion_time,
|
|
"total_runtime": total_runtime,
|
|
"tasks": task_info}
|
|
|
|
return render(request, "task_status.html", context)
|
|
|
|
|
|
# Data Format
|
|
# error
|
|
# metrics as a list of selected metrics
|
|
# results
|
|
# data for each selected metric
|
|
# meta data for the metric
|
|
# Result list for the metric in a folded list
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def get_workload_data(request):
|
|
data = request.GET
|
|
|
|
workload = get_object_or_404(Workload, pk=data['id'])
|
|
session = get_object_or_404(Session, pk=data['session_id'])
|
|
if session.user != request.user:
|
|
return render(request, '404.html')
|
|
|
|
results = Result.objects.filter(workload=workload)
|
|
result_data = {r.pk: JSONUtil.loads(r.metric_data.data) for r in results}
|
|
results = sorted(results, key=lambda x: int(result_data[x.pk][target_objectives.THROUGHPUT]))
|
|
|
|
default_metrics = [session.target_objective]
|
|
metrics = request.GET.get('met', ','.join(default_metrics)).split(',')
|
|
metrics = [m for m in metrics if m != 'none']
|
|
if len(metrics) == 0:
|
|
metrics = default_metrics
|
|
|
|
data_package = {'results': [],
|
|
'error': 'None',
|
|
'metrics': metrics}
|
|
metric_meta = target_objectives.get_metric_metadata(session.dbms.pk, session.target_objective)
|
|
for met in data_package['metrics']:
|
|
met_info = metric_meta[met]
|
|
data_package['results'].append({'data': [[]], 'tick': [],
|
|
'unit': met_info.unit,
|
|
'lessisbetter': met_info.improvement,
|
|
'metric': met_info.pprint})
|
|
|
|
added = set()
|
|
knob_confs = data['conf'].split(',')
|
|
i = len(knob_confs)
|
|
for r in results:
|
|
metric_data = JSONUtil.loads(r.metric_data.data)
|
|
if r.knob_data.pk in added or str(r.knob_data.pk) not in knob_confs:
|
|
continue
|
|
added.add(r.knob_data.pk)
|
|
data_val = metric_data[met] * met_info.scale
|
|
data_package['results'][-1]['data'][0].append([
|
|
i,
|
|
data_val,
|
|
r.pk,
|
|
data_val])
|
|
data_package['results'][-1]['tick'].append(r.knob_data.name)
|
|
i -= 1
|
|
data_package['results'][-1]['data'].reverse()
|
|
data_package['results'][-1]['tick'].reverse()
|
|
|
|
return HttpResponse(JSONUtil.dumps(data_package), content_type='application/json')
|
|
|
|
|
|
# Data Format:
|
|
# error
|
|
# results
|
|
# all result data after the filters for the table
|
|
# timelines
|
|
# data for each benchmark & metric pair
|
|
# meta data for the pair
|
|
# data as a map<DBMS name, result list>
|
|
@login_required(login_url=reverse_lazy('login'))
|
|
def get_timeline_data(request):
|
|
result_labels = Result.get_labels()
|
|
columnnames = [
|
|
result_labels['id'],
|
|
result_labels['creation_time'],
|
|
result_labels['knob_data'],
|
|
result_labels['metric_data'],
|
|
result_labels['workload'],
|
|
]
|
|
data_package = {
|
|
'error': 'None',
|
|
'timelines': [],
|
|
'knobtimelines': [],
|
|
'columnnames': columnnames,
|
|
}
|
|
|
|
session = get_object_or_404(Session, pk=request.GET['session'])
|
|
if session.user != request.user:
|
|
return HttpResponse(JSONUtil.dumps(data_package), content_type='application/json')
|
|
|
|
default_metrics = [session.target_objective]
|
|
metric_meta = target_objectives.get_metric_metadata(session.dbms.pk, session.target_objective)
|
|
for met in default_metrics:
|
|
met_info = metric_meta[met]
|
|
columnnames.append(met_info.pprint + ' (' + met_info.short_unit + ')')
|
|
|
|
results_per_page = int(request.GET['nres'])
|
|
|
|
# Get all results related to the selected session, sort by time
|
|
results = Result.objects.filter(session=session)\
|
|
.select_related('knob_data', 'metric_data', 'workload')
|
|
results = sorted(results, key=lambda x: x.observation_end_time)
|
|
|
|
display_type = request.GET['wkld']
|
|
if display_type == 'show_none':
|
|
workloads = []
|
|
metrics = default_metrics
|
|
results = []
|
|
else:
|
|
metrics = request.GET.get('met', ','.join(default_metrics)).split(',')
|
|
metrics = [m for m in metrics if m != 'none']
|
|
if len(metrics) == 0:
|
|
metrics = default_metrics
|
|
workloads = [display_type]
|
|
workload_confs = [wc for wc in request.GET['spe'].strip().split(',') if wc != '']
|
|
results = [r for r in results if str(r.workload.pk) in workload_confs]
|
|
|
|
metric_datas = {r.pk: JSONUtil.loads(r.metric_data.data) for r in results}
|
|
result_list = []
|
|
for res in results:
|
|
entry = [
|
|
res.pk,
|
|
res.observation_end_time.astimezone(timezone(TIME_ZONE)).strftime("%Y-%m-%d %H:%M:%S"),
|
|
res.knob_data.name,
|
|
res.metric_data.name,
|
|
res.workload.name]
|
|
for met in metrics:
|
|
entry.append(metric_datas[res.pk][met] * metric_meta[met].scale)
|
|
entry.extend([
|
|
'',
|
|
res.knob_data.pk,
|
|
res.metric_data.pk,
|
|
res.workload.pk
|
|
])
|
|
result_list.append(entry)
|
|
data_package['results'] = result_list
|
|
|
|
# For plotting charts
|
|
for metric in metrics:
|
|
met_info = metric_meta[metric]
|
|
for wkld in workloads:
|
|
w_r = [r for r in results if r.workload.name == wkld]
|
|
if len(w_r) == 0:
|
|
continue
|
|
|
|
data = {
|
|
'workload': wkld,
|
|
'units': met_info.unit,
|
|
'lessisbetter': met_info.improvement,
|
|
'data': {},
|
|
'baseline': "None",
|
|
'metric': metric,
|
|
'print_metric': met_info.pprint,
|
|
}
|
|
|
|
for dbms in request.GET['dbms'].split(','):
|
|
d_r = [r for r in w_r if r.dbms.key == dbms]
|
|
d_r = d_r[-results_per_page:]
|
|
out = []
|
|
for res in d_r:
|
|
metric_data = JSONUtil.loads(res.metric_data.data)
|
|
out.append([
|
|
res.observation_end_time.astimezone(timezone(TIME_ZONE)).
|
|
strftime("%m-%d-%y %H:%M"),
|
|
metric_data[metric] * met_info.scale,
|
|
"",
|
|
str(res.pk)
|
|
])
|
|
|
|
if len(out) > 0:
|
|
data['data'][dbms] = out
|
|
|
|
data_package['timelines'].append(data)
|
|
|
|
knobs = SessionKnob.objects.get_knobs_for_session(session)
|
|
knob_names = [knob["name"] for knob in knobs if knob["tunable"]]
|
|
knobs = request.GET.get('knb', ','.join(knob_names)).split(',')
|
|
knobs = [knob for knob in knobs if knob != "none"]
|
|
LOG.debug("Knobs plotted: %s", str(knobs))
|
|
for knob in knobs:
|
|
data = {
|
|
'units': KnobUnitType.TYPE_NAMES[KnobCatalog.objects.filter(name=knob)[0].unit],
|
|
'data': [],
|
|
'knob': knob,
|
|
}
|
|
for res in results:
|
|
knob_data = JSONUtil.loads(res.knob_data.data)
|
|
data['data'].append([
|
|
res.observation_end_time.astimezone(timezone(TIME_ZONE)).
|
|
strftime("%m-%d-%y %H:%M"),
|
|
knob_data[knob],
|
|
"",
|
|
str(res.pk)
|
|
])
|
|
data_package['knobtimelines'].append(data)
|
|
return HttpResponse(JSONUtil.dumps(data_package), content_type='application/json')
|
|
|
|
|
|
# get the lastest result
|
|
def give_result(request, upload_code): # pylint: disable=unused-argument
|
|
|
|
def _failed_response(_latest_result, _tasks, _num_completed, _status, _msg):
|
|
_msg = "{}\nSTATUS: {}\nRESULT ID: {}\n".format(_msg, _status, _latest_result)
|
|
if tasks:
|
|
_failed_task_idx = min(len(_tasks) - 1, _num_completed + 1)
|
|
_failed_task = _tasks[_failed_task_idx]
|
|
_msg += "TRACEBACK: {}".format(_failed_task.traceback)
|
|
return HttpResponse(_msg, status=400)
|
|
|
|
try:
|
|
session = Session.objects.get(upload_code=upload_code)
|
|
except Session.DoesNotExist:
|
|
LOG.warning("Invalid upload code: %s", upload_code)
|
|
return HttpResponse("Invalid upload code: " + upload_code, status=400)
|
|
|
|
latest_result = Result.objects.filter(session=session).latest('creation_time')
|
|
tasks = TaskUtil.get_tasks(latest_result.task_ids)
|
|
overall_status, num_completed = TaskUtil.get_task_status(tasks)
|
|
|
|
if overall_status == 'SUCCESS':
|
|
# The task status is set to SUCCESS before the next config is saved in
|
|
# the latest result so we must wait for it to be updated
|
|
max_wait_sec = 20
|
|
elapsed_sec = 0
|
|
while not latest_result.next_configuration and elapsed_sec <= max_wait_sec:
|
|
time.sleep(5)
|
|
elapsed_sec += 5
|
|
latest_result = Result.objects.get(id=latest_result.pk)
|
|
LOG.debug("Waiting for the next config for result %s to be updated... "
|
|
"(elapsed: %ss): %s", latest_result.pk, elapsed_sec,
|
|
model_to_dict(latest_result))
|
|
|
|
if not latest_result.next_configuration:
|
|
LOG.warning(
|
|
"Failed to get the next configuration from the latest result after %ss: %s",
|
|
elapsed_sec, model_to_dict(latest_result))
|
|
overall_status = 'FAILURE'
|
|
response = _failed_response(latest_result, tasks, num_completed, overall_status,
|
|
'Failed to get the next configuration.')
|
|
else:
|
|
response = HttpResponse(JSONUtil.dumps(latest_result.next_configuration),
|
|
content_type='application/json')
|
|
|
|
elif overall_status in ('FAILURE', 'REVOKED', 'RETRY'):
|
|
response = _failed_response(latest_result, tasks, num_completed, overall_status,
|
|
'Celery failed to get the next configuration.')
|
|
|
|
else: # overall_status in ('PENDING', 'RECEIVED', 'STARTED'):
|
|
response = HttpResponse("{}: Result not ready".format(overall_status), status=202)
|
|
|
|
return response
|
|
|
|
|
|
# get the lastest result
|
|
def get_debug_info(request, upload_code): # pylint: disable=unused-argument
|
|
pprint = bool(int(request.GET.get('pp', False)))
|
|
try:
|
|
session = Session.objects.get(upload_code=upload_code)
|
|
except Session.DoesNotExist:
|
|
LOG.warning("Invalid upload code: %s", upload_code)
|
|
return HttpResponse("Invalid upload code: " + upload_code, status=400)
|
|
|
|
content, filename = utils.dump_debug_info(session, pretty_print=pprint)
|
|
file = ContentFile(content.getvalue())
|
|
response = HttpResponse(file, content_type='application/x-gzip')
|
|
response['Content-Length'] = file.size
|
|
response['Content-Disposition'] = 'attachment; filename={}.tar.gz'.format(filename)
|
|
return response
|
|
|
|
|
|
def train_ddpg_loops(request, session_id): # pylint: disable=unused-argument
|
|
session = get_object_or_404(Session, pk=session_id, user=request.user) # pylint: disable=unused-variable
|
|
results = Result.objects.filter(session=session_id)
|
|
for result in results:
|
|
train_ddpg(result.pk)
|
|
return HttpResponse()
|
|
|
|
|
|
@csrf_exempt
|
|
def alt_get_info(request, name): # pylint: disable=unused-argument
|
|
# Backdoor method for getting basic info
|
|
if name == 'constants':
|
|
info = utils.get_constants()
|
|
response = HttpResponse(JSONUtil.dumps(info))
|
|
else:
|
|
LOG.warning("Invalid name for info request: %s", name)
|
|
response = HttpResponse("Invalid name for info request: {}".format(name), status=400)
|
|
return response
|
|
|
|
|
|
@csrf_exempt
|
|
def alt_set_constants(request):
|
|
constants = JSONUtil.loads(request.POST.get('constants', '{}'))
|
|
for name, value in constants.items():
|
|
try:
|
|
utils.set_constant(name, value)
|
|
except AttributeError as e:
|
|
LOG.warning(e)
|
|
return HttpResponse(e, status=400)
|
|
return HttpResponse("Successfully updated constants: {}".format(
|
|
', '.join('{}={}'.format(k, v) for k, v in constants.items())))
|
|
|
|
|
|
@csrf_exempt
|
|
def alt_create_user(request):
|
|
response = dict(created=False, error=None, user=None)
|
|
if request.method != 'POST':
|
|
err_msg = "Request was not a post!"
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
data = {k: v[0] for k, v in request.POST.lists()}
|
|
missing = [k for k in ('username', 'password') if k not in data]
|
|
if missing:
|
|
err_msg = "Request is missing required data: {}".format(', '.join(missing))
|
|
LOG.warning(err_msg)
|
|
response.update(error=err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
user, created = utils.create_user(**data)
|
|
response.update(user=user, created=created)
|
|
if created:
|
|
LOG.info("Successfully created user '%s': %s", data['username'], user)
|
|
status = 200
|
|
else:
|
|
err_msg = "ERROR: User '{}' already exists: {}".format(data['username'], user)
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
status = 400
|
|
|
|
response['user'] = model_to_dict(response['user'])
|
|
return HttpResponse(JSONUtil.dumps(response), status=status)
|
|
|
|
|
|
@csrf_exempt
|
|
def alt_delete_user(request):
|
|
response = dict(deleted=False, error=None, delete_info=None)
|
|
if request.method != 'POST':
|
|
err_msg = "Request was not a post!"
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
data = {k: v[0] for k, v in request.POST.lists()}
|
|
missing = [k for k in ('username',) if k not in data]
|
|
if missing:
|
|
err_msg = "Request is missing required data: {}".format(', '.join(missing))
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
delete_info, deleted = utils.delete_user(**data)
|
|
response.update(deleted=deleted, delete_info=delete_info)
|
|
if deleted:
|
|
LOG.info("Successfully deleted user '%s': %s", data['username'], delete_info)
|
|
status = 200
|
|
else:
|
|
err_msg = "User '{}' does not exist".format(data['username'])
|
|
LOG.warning(err_msg)
|
|
response.update(error=err_msg)
|
|
status = 400
|
|
|
|
return HttpResponse(JSONUtil.dumps(response), status=status)
|
|
|
|
|
|
@csrf_exempt
|
|
def alt_create_or_edit_project(request):
|
|
response = dict(created=False, error=None, project=None)
|
|
if request.method != 'POST':
|
|
err_msg = "Request was not a post!"
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
data = {k: v[0] for k, v in request.POST.lists()}
|
|
missing = [k for k in ('username', 'password', 'name') if k not in data]
|
|
if missing:
|
|
err_msg = "Request is missing required data: {}".format(', '.join(missing))
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
username = data.pop('username')
|
|
password = data.pop('password')
|
|
user = authenticate(User, username=username, password=password)
|
|
if not user:
|
|
err_msg = "ERROR: Unable to authenticate user '{}'.".format(username)
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
project_name = data.pop('name')
|
|
ts = now()
|
|
|
|
if request.path == reverse('backdoor_create_project'):
|
|
try:
|
|
project = Project.objects.create(user=user, name=project_name, last_update=ts,
|
|
creation_time=ts, **data)
|
|
except IntegrityError:
|
|
err_msg = "ERROR: Project '{}' already exists.".format(project_name)
|
|
project = Project.objects.get(user=user, name=project_name)
|
|
response.update(error=err_msg, project=model_to_dict(project))
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
else:
|
|
project = get_object_or_404(Project, name=project_name, user=user)
|
|
for k, v in data.items():
|
|
setattr(project, k, v)
|
|
project.last_update = ts
|
|
project.save()
|
|
|
|
response.update(created=True, project=model_to_dict(project))
|
|
return HttpResponse(JSONUtil.dumps(response))
|
|
|
|
|
|
@csrf_exempt
|
|
def alt_create_or_edit_session(request):
|
|
response = dict(created=False, error=None, session=None)
|
|
if request.method != 'POST':
|
|
err_msg = "Request was not a post!"
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
data = {k: v[0] for k, v in request.POST.lists()}
|
|
if 'hardware' in data:
|
|
err_msg = "Custom hardware objects are not supported."
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
required_fields = ('username', 'password', 'project_name', 'name', 'dbms_type',
|
|
'dbms_version')
|
|
missing = [k for k in required_fields if k not in data]
|
|
if missing:
|
|
err_msg = "Request is missing required data: {}".format(', '.join(missing))
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
username = data.pop('username')
|
|
password = data.pop('password')
|
|
user = authenticate(User, username=username, password=password)
|
|
if not user:
|
|
err_msg = "ERROR: Unable to authenticate user '{}'.".format(username)
|
|
response.update(error=err_msg)
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
project = get_object_or_404(Project, name=data.pop('project_name'), user=user)
|
|
dbms_type = DBMSType.type(data.pop('dbms_type'))
|
|
dbms = get_object_or_404(DBMSCatalog, type=dbms_type, version=data.pop('dbms_version'))
|
|
|
|
session_name = data.pop('name')
|
|
if 'algorithm' in data:
|
|
data['algorithm'] = AlgorithmType.type(data['algorithm'])
|
|
session_knobs = data.pop('session_knobs', None)
|
|
disable_others = data.pop('disable_others', False)
|
|
ts = now()
|
|
|
|
if request.path == reverse('backdoor_create_session'):
|
|
hardware, _ = Hardware.objects.get_or_create(pk=1)
|
|
upload_code = data.pop('upload_code', None) or MediaUtil.upload_code_generator()
|
|
try:
|
|
session = Session.objects.create(user=user, project=project, dbms=dbms,
|
|
name=session_name, hardware=hardware,
|
|
upload_code=upload_code, creation_time=ts,
|
|
last_update=ts, **data)
|
|
except IntegrityError:
|
|
err_msg = "ERROR: Session '{}' already exists.".format(session_name)
|
|
session = Session.objects.get(user=user, project=project, name=session_name)
|
|
response.update(error=err_msg, project=model_to_dict(session))
|
|
LOG.warning(err_msg)
|
|
return HttpResponse(JSONUtil.dumps(response), status=400)
|
|
|
|
set_default_knobs(session)
|
|
else:
|
|
session = get_object_or_404(Session, name=session_name, project=project, user=user)
|
|
for k, v in data.items():
|
|
setattr(session, k, v)
|
|
|
|
# Corner case: when running LHS, when the tunable knobs and/or their ranges change
|
|
# then we must delete the pre-generated configs since they are no longer valid.
|
|
if session_knobs and session.tuning_session == 'lhs':
|
|
session.lhs_samples = '[]'
|
|
|
|
session.last_update = ts
|
|
session.save()
|
|
|
|
if session_knobs:
|
|
session_knobs = JSONUtil.loads(session_knobs)
|
|
SessionKnob.objects.set_knob_min_max_tunability(session, session_knobs,
|
|
disable_others=disable_others)
|
|
res = model_to_dict(session)
|
|
res['dbms_id'] = res['dbms']
|
|
res['dbms'] = session.dbms.full_name
|
|
res['hardware_id'] = res['hardware']
|
|
res['hardware'] = model_to_dict(session.hardware)
|
|
res['algorithm'] = AlgorithmType.name(res['algorithm'])
|
|
sk = SessionKnob.objects.get_knobs_for_session(session)
|
|
sess_knobs = []
|
|
for knob in sk:
|
|
sess_knobs.append(dict(
|
|
minval=knob['minval'],
|
|
maxval=knob['maxval'],
|
|
tunable=knob['tunable']))
|
|
res['session_knobs'] = sess_knobs
|
|
response.update(created=True, session=res)
|
|
return HttpResponse(JSONUtil.dumps(response))
|
|
|
|
|
|
# integration test
|
|
@csrf_exempt
|
|
def pipeline_data_ready(request): # pylint: disable=unused-argument
|
|
LOG.debug("Latest pipeline run: %s", PipelineRun.objects.get_latest())
|
|
if PipelineRun.objects.get_latest() is None:
|
|
response = "Pipeline data ready: False"
|
|
else:
|
|
response = "Pipeline data ready: True"
|
|
return HttpResponse(response)
|
|
|
|
|
|
# integration test
|
|
@csrf_exempt
|
|
def create_test_website(request): # pylint: disable=unused-argument
|
|
if User.objects.filter(username='ottertune_test_user').exists():
|
|
User.objects.filter(username='ottertune_test_user').delete()
|
|
if Hardware.objects.filter(pk=1).exists():
|
|
test_hardware = Hardware.objects.get(pk=1)
|
|
else:
|
|
test_hardware = Hardware.objects.create(pk=1)
|
|
|
|
test_user = User.objects.create_user(username='ottertune_test_user',
|
|
password='ottertune_test_user')
|
|
test_project = Project.objects.create(user=test_user, name='ottertune_test_project',
|
|
creation_time=now(), last_update=now())
|
|
# create no tuning session
|
|
s1 = Session.objects.create(name='test_session_no_tuning', tuning_session='no_tuning_session',
|
|
dbms_id=1, hardware=test_hardware, project=test_project,
|
|
creation_time=now(), last_update=now(), user=test_user,
|
|
upload_code='ottertuneTestNoTuning')
|
|
set_default_knobs(s1)
|
|
# create gpr session
|
|
s2 = Session.objects.create(name='test_session_gpr', tuning_session='tuning_session',
|
|
dbms_id=1, hardware=test_hardware, project=test_project,
|
|
creation_time=now(), last_update=now(), algorithm=AlgorithmType.GPR,
|
|
upload_code='ottertuneTestTuningGPR', user=test_user)
|
|
set_default_knobs(s2)
|
|
# create dnn session
|
|
s3 = Session.objects.create(name='test_session_dnn', tuning_session='tuning_session',
|
|
dbms_id=1, hardware=test_hardware, project=test_project,
|
|
creation_time=now(), last_update=now(), algorithm=AlgorithmType.DNN,
|
|
upload_code='ottertuneTestTuningDNN', user=test_user)
|
|
set_default_knobs(s3)
|
|
# create ddpg session
|
|
s4 = Session.objects.create(name='test_session_ddpg', tuning_session='tuning_session',
|
|
dbms_id=1, hardware=test_hardware, project=test_project,
|
|
creation_time=now(), last_update=now(), user=test_user,
|
|
upload_code='ottertuneTestTuningDDPG', algorithm=AlgorithmType.DDPG)
|
|
set_default_knobs(s4)
|
|
response = HttpResponse("Success: create test website successfully")
|
|
return response
|