diff --git a/script/formatting/config/pylintrc b/script/formatting/config/pylintrc index 4e911c7..2381a73 100644 --- a/script/formatting/config/pylintrc +++ b/script/formatting/config/pylintrc @@ -13,7 +13,7 @@ profile=no # Add files or directories to the blacklist. They should be base names, not # paths. -ignore=CVS,.git,manage.py,0001_initial.py,0002_enable_compression.py,0003_load_initial_data.py,0004_add_lhs.py,0005_add_workload_field.py,0006_session_hyperparameters.py,credentials.py,create_knob_settings.py +ignore=CVS,.git,manage.py,0001_initial.py,0002_enable_compression.py,0003_load_initial_data.py,0004_add_lhs.py,0005_add_workload_field.py,0006_session_hyperparameters.py,0007_executiontime.py,credentials.py,create_knob_settings.py # ignore-patterns=**/migrations/*.py diff --git a/server/website/website/admin.py b/server/website/website/admin.py index eb60f48..de5ddf5 100644 --- a/server/website/website/admin.py +++ b/server/website/website/admin.py @@ -10,10 +10,10 @@ from django_db_logger.admin import StatusLogAdmin from django_db_logger.models import StatusLog from djcelery import models as djcelery_models -from .models import (BackupData, DBMSCatalog, KnobCatalog, - KnobData, MetricCatalog, MetricData, - PipelineData, PipelineRun, Project, - Result, Session, Workload, Hardware, +from .models import (BackupData, DBMSCatalog, ExecutionTime, + KnobCatalog, KnobData, MetricCatalog, + MetricData, PipelineData, PipelineRun, + Project, Result, Session, Workload, Hardware, SessionKnob) from .types import VarType @@ -175,6 +175,14 @@ class CustomStatusLogAdmin(StatusLogAdmin): list_filter = ('logger_name', 'level') +class ExecutionTimeAdmin(admin.ModelAdmin): + list_display = ('event', 'result', 'exec_time') + + def exec_time(self, instance): # pylint: disable=no-self-use + return '{:.0f} sec'.format(instance.execution_time) + exec_time.short_description = 'Execution Time' + + # Admin classes for website models admin.site.register(DBMSCatalog, DBMSCatalogAdmin) admin.site.register(KnobCatalog, KnobCatalogAdmin) @@ -190,6 +198,7 @@ admin.site.register(PipelineRun, PipelineRunAdmin) admin.site.register(Workload, WorkloadAdmin) admin.site.register(SessionKnob, SessionKnobAdmin) admin.site.register(Hardware, HardwareAdmin) +admin.site.register(ExecutionTime, ExecutionTimeAdmin) # Admin classes for 3rd party models admin.site.unregister(StatusLog) diff --git a/server/website/website/forms.py b/server/website/website/forms.py index 26c8f06..d4e0ecb 100644 --- a/server/website/website/forms.py +++ b/server/website/website/forms.py @@ -17,6 +17,7 @@ class NewResultForm(forms.Form): metrics_after = forms.FileField() knobs = forms.FileField() summary = forms.FileField() + execution_times = forms.CharField(required=False, strip=True) class ProjectForm(forms.ModelForm): diff --git a/server/website/website/migrations/0007_executiontime.py b/server/website/website/migrations/0007_executiontime.py new file mode 100644 index 0000000..a4ac281 --- /dev/null +++ b/server/website/website/migrations/0007_executiontime.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-01-24 00:15 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('website', '0006_session_hyperparameters'), + ] + + operations = [ + migrations.CreateModel( + name='ExecutionTime', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('module', models.CharField(max_length=32)), + ('function', models.CharField(max_length=32)), + ('tag', models.CharField(blank=True, default='', max_length=64)), + ('start_time', models.DateTimeField()), + ('execution_time', models.FloatField()), + ('result', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='website.Result')), + ], + ), + ] diff --git a/server/website/website/models.py b/server/website/website/models.py index b3407b6..5aa3df7 100644 --- a/server/website/website/models.py +++ b/server/website/website/models.py @@ -4,12 +4,15 @@ # Copyright (c) 2017-18, Carnegie Mellon University Database Group # from collections import OrderedDict +from pytz import timezone from django.contrib.auth.models import User from django.db import models, DEFAULT_DB_ALIAS +from django.utils.datetime_safe import datetime from django.utils.timezone import now from .db import target_objectives +from .settings import TIME_ZONE from .types import (DBMSType, LabelStyleType, MetricType, KnobUnitType, PipelineTaskType, VarType, KnobResourceType, WorkloadStatusType, AlgorithmType, StorageType) @@ -498,3 +501,22 @@ class BackupData(BaseModel): raw_summary = models.TextField() knob_log = models.TextField() metric_log = models.TextField() + + +class ExecutionTime(models.Model): + module = models.CharField(max_length=32) + function = models.CharField(max_length=32) + tag = models.CharField(max_length=64, blank=True, default='') + start_time = models.DateTimeField() + execution_time = models.FloatField() # in seconds + result = models.ForeignKey(Result, null=True, blank=True, default=None) + + @property + def event(self): + return '.'.join((e for e in (self.module, self.function, self.tag) if e)) + + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + if isinstance(self.start_time, (int, float)): + self.start_time = datetime.fromtimestamp(int(self.start_time), timezone(TIME_ZONE)) + super().save(force_insert=force_insert, force_update=force_update, using=using, + update_fields=update_fields) diff --git a/server/website/website/views.py b/server/website/website/views.py index e729ce2..18d74b0 100644 --- a/server/website/website/views.py +++ b/server/website/website/views.py @@ -4,12 +4,13 @@ # Copyright (c) 2017-18, Carnegie Mellon University Database Group # # pylint: disable=too-many-lines +import csv import logging -import datetime import os import re import shutil from collections import OrderedDict +from io import StringIO from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required @@ -34,8 +35,9 @@ 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 .models import (BackupData, DBMSCatalog, ExecutionTime, Hardware, KnobCatalog, KnobData, + MetricCatalog, MetricData, PipelineRun, Project, Result, Session, + SessionKnob, User, Workload) from .tasks import (aggregate_target_results, map_workload, train_ddpg, configuration_recommendation, configuration_recommendation_ddpg) from .types import (DBMSType, KnobUnitType, MetricType, @@ -457,12 +459,13 @@ def new_result(request): LOG.warning("Invalid upload code: %s", upload_code) return HttpResponse("Invalid upload code: " + upload_code, status=400) - return handle_result_files(session, request.FILES) + execution_times = form.cleaned_data['execution_times'] + return handle_result_files(session, request.FILES, execution_times) LOG.warning("Request type was not POST") return HttpResponse("Request type was not POST", status=400) -def handle_result_files(session, files): +def handle_result_files(session, files, execution_times=None): from celery import chain # Combine into contiguous files files = {k: b''.join(v.chunks()).decode() for k, v in list(files.items())} @@ -653,6 +656,25 @@ def handle_result_files(session, files): result.task_ids = ','.join(taskmeta_ids) result.save() + + if execution_times: + try: + batch = [] + f = StringIO(execution_times) + reader = csv.reader(f, delimiter=',') + + for module, fn, tag, start_ts, end_ts in reader: + start_ts = float(start_ts) + end_ts = float(end_ts) + exec_time = end_ts - start_ts + start_time = datetime.fromtimestamp(int(start_ts), timezone(TIME_ZONE)) + batch.append( + ExecutionTime(module=module, function=fn, tag=tag, start_time=start_time, + execution_time=exec_time, result=result)) + ExecutionTime.objects.bulk_create(batch) + except Exception: # pylint: disable=broad-except + LOG.warning("Error parsing execution times:\n%s", execution_times, exc_info=True) + return HttpResponse("Result stored successfully! Running tuner...(status={}) Result ID:{} " .format(response.status, result_id))