diff options
Diffstat (limited to 'qtip')
-rw-r--r-- | qtip/api/controllers/metric.py | 25 | ||||
-rw-r--r-- | qtip/api/controllers/plan.py | 31 | ||||
-rw-r--r-- | qtip/api/swagger/swagger.yaml | 194 | ||||
-rw-r--r-- | qtip/cli/commands/cmd_metric.py | 54 | ||||
-rw-r--r-- | qtip/loader/plan.py | 58 | ||||
-rw-r--r-- | qtip/loader/qpi.py | 3 | ||||
-rw-r--r-- | qtip/web/bench/forms.py (renamed from qtip/loader/metric.py) | 15 | ||||
-rw-r--r-- | qtip/web/bench/migrations/0005_auto_20170720_2115.py | 35 | ||||
-rw-r--r-- | qtip/web/bench/migrations/0006_auto_20170722_0135.py | 25 | ||||
-rw-r--r-- | qtip/web/bench/models.py | 26 | ||||
-rw-r--r-- | qtip/web/bench/urls.py | 3 | ||||
-rw-r--r-- | qtip/web/bench/utils.py | 27 | ||||
-rw-r--r-- | qtip/web/bench/views.py | 63 | ||||
-rw-r--r-- | qtip/web/templates/bench/run.html | 15 | ||||
-rw-r--r-- | qtip/web/templates/bench/task_detail.html | 17 | ||||
-rw-r--r-- | qtip/web/templates/bench/task_list.html | 22 |
16 files changed, 233 insertions, 380 deletions
diff --git a/qtip/api/controllers/metric.py b/qtip/api/controllers/metric.py deleted file mode 100644 index 96cd985c..00000000 --- a/qtip/api/controllers/metric.py +++ /dev/null @@ -1,25 +0,0 @@ -############################################################################## -# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import httplib - -from qtip.api.controllers import common -from qtip.loader import metric - - -def list_metrics(): - metrics = list(metric.MetricSpec.list_all()) - metrics_by_name = [m['name'] for m in metrics] - return {'metrics': metrics_by_name}, httplib.OK - - -@common.check_endpoint_for_error(resource='Metric') -def get_metric(name): - metric_spec = metric.MetricSpec(name) - return metric_spec.content diff --git a/qtip/api/controllers/plan.py b/qtip/api/controllers/plan.py deleted file mode 100644 index 00593878..00000000 --- a/qtip/api/controllers/plan.py +++ /dev/null @@ -1,31 +0,0 @@ -############################################################################## -# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import httplib - -from qtip.api.controllers import common -from qtip.base import error -from qtip.loader import plan - - -def list_plans(): - plans = list(plan.Plan.list_all()) - plans_by_name = [p['name'] for p in plans] - return {'plans': plans_by_name}, httplib.OK - - -@common.check_endpoint_for_error(resource='Plan') -def get_plan(name): - plan_spec = plan.Plan(name) - return plan_spec.content - - -@common.check_endpoint_for_error(resource='Plan', operation='Run') -def run_plan(name, action="run"): - raise error.ToBeDoneError('run_plan', 'plan') diff --git a/qtip/api/swagger/swagger.yaml b/qtip/api/swagger/swagger.yaml index 51c3ebb8..8cb0e23b 100644 --- a/qtip/api/swagger/swagger.yaml +++ b/qtip/api/swagger/swagger.yaml @@ -16,95 +16,6 @@ consumes: produces: - application/json paths: - /plans: - get: - summary: List all plans - operationId: qtip.api.controllers.plan.list_plans - tags: - - Plan - - Standalone - responses: - 200: - description: A list of plans - schema: - type: array - items: - $ref: '#/definitions/Plans' - 501: - description: Resource not implemented - schema: - $ref: '#/definitions/Error' - default: - description: Unexpected error - schema: - $ref: '#/definitions/Error' - /plans/{name}: - get: - summary: Get a plan by plan name - operationId: qtip.api.controllers.plan.get_plan - tags: - - Plan - - Standalone - parameters: - - name: name - in: path - description: Plan name - required: true - type: string - responses: - 200: - description: Plan information - schema: - $ref: '#/definitions/Plan' - 404: - description: Plan not found - schema: - $ref: '#/definitions/Error' - 501: - description: Resource not implemented - schema: - $ref: '#/definitions/Error' - default: - description: Unexpected error - schema: - $ref: '#/definitions/Error' - post: - summary: Run a plan and return results - operationId: qtip.api.controllers.plan.run_plan - tags: - - Plan - - Standalone - parameters: - - name: name - in: path - description: Plan name - required: true - type: string - - name: action - in: query - description: action for a plan - required: true - type: string - responses: - 200: - description: Result of the run of the plan - #TODO (akhil) define schema - 404: - description: Plan not found - schema: - $ref: '#/definitions/Error' - 400: - description: Invalid parameters - schema: - $ref: '#/definitions/Error' - 501: - description: Resource not implemented - schema: - $ref: '#/definitions/Error' - default: - description: Unexpected error - schema: - $ref: '#/definitions/Error' /qpis: get: summary: List all QPIs @@ -158,112 +69,7 @@ paths: description: Unexpected error schema: $ref: '#/definitions/Error' - /metrics: - get: - summary: List all metrics - operationId: qtip.api.controllers.metric.list_metrics - tags: - - Metric - - Standalone - - Agent - responses: - 200: - description: A list of metrics - schema: - items: - $ref: '#/definitions/Metrics' - 501: - description: Resource not implemented - schema: - $ref: '#/definitions/Error' - default: - description: Unexpected error - schema: - $ref: '#/definitions/Error' - /metrics/{name}: - get: - summary: Get a metric - operationId: qtip.api.controllers.metric.get_metric - tags: - - Metric - - Standalone - - Agent - parameters: - - name: name - in: path - description: Metric name - required: true - type: string - responses: - 200: - description: Metric information - schema: - $ref: '#/definitions/Metric' - 404: - description: Metric not found - schema: - $ref: '#/definitions/Error' - 501: - description: Resource not implemented - schema: - $ref: '#/definitions/Error' - default: - description: Unexpected error - schema: - $ref: '#/definitions/Error' definitions: - Plan: - type: object - required: - - name - properties: - name: - type: string - description: - type: string - info: - type: object - config: - type: object - QPIs: - type: array - items: - type: object - Plans: - type: object - required: - - plans - properties: - plans: - type: array - items: - type: string - Metric: - type: object - required: - - name - properties: - name: - type: string - description: - type: string - links: - type: array - items: - type: string - workloads: - type: array - items: - type: string - Metrics: - type: object - required: - - metrics - properties: - metrics: - type: array - items: - type: string QPI: type: object required: diff --git a/qtip/cli/commands/cmd_metric.py b/qtip/cli/commands/cmd_metric.py deleted file mode 100644 index 0a385898..00000000 --- a/qtip/cli/commands/cmd_metric.py +++ /dev/null @@ -1,54 +0,0 @@ -############################################################################## -# Copyright (c) 2017 taseer94@gmail.com and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import click -from colorama import Fore -import os - -from qtip.base.error import InvalidContentError -from qtip.base.error import NotFoundError -from qtip.cli import utils -from qtip.loader.metric import MetricSpec - - -@click.group() -def cli(): - ''' Performance Metrics Group ''' - pass - - -@cli.command('list', help='List all the Metric Groups') -def cmd_list(): - metrics = MetricSpec.list_all() - table = utils.table('Metrics', metrics) - click.echo(table) - - -@cli.command('show', help='View details of a Metric') -@click.argument('name') -def show(name): - try: - metric = MetricSpec('{}.yaml'.format(name)) - except NotFoundError as nf: - click.echo(Fore.RED + "ERROR: metric spec: " + nf.message) - except InvalidContentError as ice: - click.echo(Fore.RED + "ERROR: metric spec " + ice.message) - else: - cnt = metric.content - output = utils.render('metric', cnt) - click.echo(output) - - -@cli.command('run', help='Run performance test') -@click.argument('name') -@click.option('-p', '--path', help='Path to store results') -def run(name, path): - runner_path = os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir, - 'runner/runner.py') - os.system('python {0} -b {1} -d {2}'.format(runner_path, name, path)) diff --git a/qtip/loader/plan.py b/qtip/loader/plan.py deleted file mode 100644 index e15651a3..00000000 --- a/qtip/loader/plan.py +++ /dev/null @@ -1,58 +0,0 @@ -############################################################################## -# Copyright (c) 2016 ZTE Corp and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from qtip.base.constant import BaseProp -from qtip.collector import CollectorProp as CProp -from qtip.collector.logfile import LogfileCollector -from qtip.loader.yaml_file import YamlFileLoader -from qtip.loader.qpi import QPISpec - - -# TODO(yujunz) more elegant way to load module dynamically -def load_collector(type_name): - if type_name == LogfileCollector.TYPE: - return LogfileCollector - else: - raise Exception("Invalid collector type: {}".format(type_name)) - - -class Plan(YamlFileLoader): - """ - a benchmark plan is consist of configuration and a QPI list - """ - - RELATIVE_PATH = 'plan' - - def __init__(self, name, paths=None): - super(Plan, self).__init__(name, paths) - - _config = self.content[PlanProp.CONFIG] - - self.collectors = [load_collector(c[CProp.TYPE])(c, self) - for c in _config[PlanProp.COLLECTORS]] - - self.qpis = [QPISpec(qpi, paths=paths) - for qpi in self.content[PlanProp.QPIS]] - - -class PlanProp(BaseProp): - # plan - INFO = 'info' - - FACILITY = 'facility' - ENGINEER = 'engineer' - - CONFIG = 'config' - - DRIVER = 'driver' - COLLECTORS = 'collectors' - REPORTER = 'reporter' - - QPIS = 'QPIs' diff --git a/qtip/loader/qpi.py b/qtip/loader/qpi.py index 73da61e9..2a85766e 100644 --- a/qtip/loader/qpi.py +++ b/qtip/loader/qpi.py @@ -8,7 +8,6 @@ ############################################################################## from yaml_file import YamlFileLoader -from metric import MetricSpec from qtip.base.constant import SpecProp from qtip.util.formula import Formula @@ -34,5 +33,3 @@ class Section(object): self.name = content[SpecProp.NAME] self.weight = content[SpecProp.WEIGHT] self.formula = Formula(content[SpecProp.FORMULA]) - self.metrics = [MetricSpec(record, paths=paths) - for record in content[SpecProp.METRICS]] diff --git a/qtip/loader/metric.py b/qtip/web/bench/forms.py index 842fcdbf..d897aca7 100644 --- a/qtip/loader/metric.py +++ b/qtip/web/bench/forms.py @@ -1,5 +1,5 @@ ############################################################################## -# Copyright (c) 2016 ZTE Corp and others. +# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 @@ -7,10 +7,13 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from yaml_file import YamlFileLoader +from django import forms -class MetricSpec(YamlFileLoader): - """metrics in QTIP are categorized by performance test tools, such as - dhrystone, whetstone and etc""" - RELATIVE_PATH = 'metric' +import models + + +class TaskForm(forms.ModelForm): + class Meta: + model = models.Task + fields = ['repo'] diff --git a/qtip/web/bench/migrations/0005_auto_20170720_2115.py b/qtip/web/bench/migrations/0005_auto_20170720_2115.py new file mode 100644 index 00000000..5bd81a2c --- /dev/null +++ b/qtip/web/bench/migrations/0005_auto_20170720_2115.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-07-20 21:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bench', '0004_auto_20170715_0325'), + ] + + operations = [ + migrations.RenameField( + model_name='repo', + old_name='github_link', + new_name='git_link', + ), + migrations.AlterField( + model_name='task', + name='end_time', + field=models.DateTimeField(null=True), + ), + migrations.AlterField( + model_name='task', + name='log', + field=models.FilePathField(), + ), + migrations.AlterField( + model_name='task', + name='run_time', + field=models.DurationField(null=True), + ), + ] diff --git a/qtip/web/bench/migrations/0006_auto_20170722_0135.py b/qtip/web/bench/migrations/0006_auto_20170722_0135.py new file mode 100644 index 00000000..5d066702 --- /dev/null +++ b/qtip/web/bench/migrations/0006_auto_20170722_0135.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-07-22 01:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bench', '0005_auto_20170720_2115'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='status', + field=models.CharField(choices=[('P', 'Pending'), ('IP', 'In progress'), ('F', 'Finished')], default='P', max_length=20), + ), + migrations.AlterField( + model_name='task', + name='log', + field=models.FileField(upload_to='logs'), + ), + ] diff --git a/qtip/web/bench/models.py b/qtip/web/bench/models.py index 863af739..3f0439d9 100644 --- a/qtip/web/bench/models.py +++ b/qtip/web/bench/models.py @@ -18,15 +18,33 @@ from django.urls import reverse class Repo(models.Model): name = models.CharField(max_length=200, blank=False) - github_link = models.URLField(unique=True) + git_link = models.URLField(unique=True) def get_absolute_url(self): return reverse('repo_update', args=[self.pk]) + def __str__(self): + return "%s, %s" % (self.name, self.git_link) + class Task(models.Model): + TASK_STATUS_CHOICES = ( + ('P', 'Pending'), + ('IP', 'In progress'), + ('F', 'Finished') + ) + start_time = models.DateTimeField(auto_now_add=True) - end_time = models.DateTimeField() - run_time = models.DurationField() + status = models.CharField(choices=TASK_STATUS_CHOICES, default='P', max_length=20) + end_time = models.DateTimeField(null=True) + run_time = models.DurationField(null=True) repo = models.ForeignKey('Repo', on_delete=models.DO_NOTHING) - log = models.TextField() + log = models.FileField(upload_to='logs') + + def save(self, **kwargs): + if self.end_time: + self.run_time = self.end_time - self.start_time + super(Task, self).save(kwargs) + + def get_absolute_url(self): + return reverse('task_view', args=[self.pk]) diff --git a/qtip/web/bench/urls.py b/qtip/web/bench/urls.py index 3af4eb83..ae9738b6 100644 --- a/qtip/web/bench/urls.py +++ b/qtip/web/bench/urls.py @@ -18,4 +18,7 @@ urlpatterns = [ url('^', include('django.contrib.auth.urls')), url('^repos/$', views.ReposView.as_view(), name='repos'), url('^repos/(?P<pk>\d+)$', views.RepoUpdate.as_view(), name='repo_update'), + url('^run/$', views.Run.as_view(), name='run'), + url('^tasks/$', views.Logs.as_view(), name='tasks'), + url('^tasks/(?P<pk>\d+)$', views.TaskView.as_view(), name='task_view'), ] diff --git a/qtip/web/bench/utils.py b/qtip/web/bench/utils.py new file mode 100644 index 00000000..aac388e0 --- /dev/null +++ b/qtip/web/bench/utils.py @@ -0,0 +1,27 @@ +############################################################################## +# Copyright (c) 2017 akhil.batra@research.iiit.ac.in and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import os +from subprocess import Popen, PIPE, STDOUT +import shutil + + +def run(repo): + if os.path.exists(repo.name): + shutil.rmtree(repo.name) + os.mkdir(repo.name) + os.chdir(repo.name) + output = Popen(("git clone %s ." % repo.git_link).split(), stdout=PIPE, stderr=STDOUT) + for line in output.stdout: + yield line + output.wait() + output = Popen("ansible-playbook run.yml".split(), stdout=PIPE, stderr=STDOUT) + for line in output.stdout: + yield line + os.chdir("../") diff --git a/qtip/web/bench/views.py b/qtip/web/bench/views.py index 786b67d5..6de5e4cc 100644 --- a/qtip/web/bench/views.py +++ b/qtip/web/bench/views.py @@ -9,16 +9,20 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from multiprocessing import Process from django.contrib.auth.mixins import LoginRequiredMixin -# from django.utils.decorators import method_decorator from django.views.generic.edit import CreateView, UpdateView +from django.views.generic.list import ListView +from django.views.generic.detail import DetailView +from django.views import View +from django.shortcuts import render, redirect +from django.core.files.base import ContentFile +from django.utils import timezone +import forms import models - -# from django.shortcuts import render - -# Create your views here. +import utils class ReposView(LoginRequiredMixin, CreateView): @@ -39,3 +43,52 @@ class RepoUpdate(LoginRequiredMixin, UpdateView): context = super(RepoUpdate, self).get_context_data(**kwargs) context["repos"] = self.model.objects.all() return context + + +class Run(LoginRequiredMixin, View): + template_name = 'bench/run.html' + form_class = forms.TaskForm + + def get(self, request): + task_form = self.form_class() + return render(request, self.template_name, {'form': task_form}) + + def post(self, request): + task_form = self.form_class(request.POST) + if task_form.is_valid(): + new_task = task_form.save() + new_task.log.save("run_%s.log" % new_task.pk, ContentFile('')) + p = Process(target=self.start_task, args=(new_task,)) + p.start() + return redirect('tasks') + p.join() + + def start_task(self, task): + task = models.Task.objects.get(pk=task.pk) + task.status = 'IP' + task.save() + with open(task.log.path, "a") as logfile: + for line in utils.run(task.repo): + logfile.write(line) + now = timezone.now() + task = models.Task.objects.get(pk=task.pk) + task.end_time = now + task.status = 'F' + task.save() + + +class Logs(LoginRequiredMixin, ListView): + model = models.Task + + +class TaskView(LoginRequiredMixin, DetailView): + model = models.Task + + def get_context_data(self, **kwargs): + context = super(TaskView, self).get_context_data(**kwargs) + try: + with open(context['object'].log.path, "r") as log_file: + context['log'] = log_file.read() + except ValueError: + context['log'] = "No log to show" + return context diff --git a/qtip/web/templates/bench/run.html b/qtip/web/templates/bench/run.html new file mode 100644 index 00000000..8eaa251b --- /dev/null +++ b/qtip/web/templates/bench/run.html @@ -0,0 +1,15 @@ +{% extends 'bench/base.html' %} +{% block content %} + <div> + <h3>Repos</h3> + {% for repo in repos %} + <li>{{ repo.name }}</li> + {% endfor %} + </div> + <form role="form" method="post" action="">{% csrf_token %} + {{ form }} + <div class=input-field" style="margin:30px 20px"> + <button type="submit" value="Login"/>Submit</button> + </div> + </form> +{% endblock %}
\ No newline at end of file diff --git a/qtip/web/templates/bench/task_detail.html b/qtip/web/templates/bench/task_detail.html new file mode 100644 index 00000000..d715022b --- /dev/null +++ b/qtip/web/templates/bench/task_detail.html @@ -0,0 +1,17 @@ +{% extends 'bench/base.html' %} +{% block content %} + <div> + <h3>Task</h3> + <table> + <tr> + <td>{{ object.repo}}</td><td>{{ object.get_status_display }}</td><td>{{ object.start_time }}</td> + <td>{{ object.end_time }}</td><td>{{ object.duration }}</td><td>{{ object.log }}</td> + </tr> + </table> + <div> + <p> + {{ log | linebreaks }} + </p> + </div> + </div> +{% endblock %}
\ No newline at end of file diff --git a/qtip/web/templates/bench/task_list.html b/qtip/web/templates/bench/task_list.html new file mode 100644 index 00000000..188d6b81 --- /dev/null +++ b/qtip/web/templates/bench/task_list.html @@ -0,0 +1,22 @@ +{% extends 'bench/base.html' %} +{% block content %} + <div> + <h3>Tasks Log</h3> + <table> + <th>Repo</th> + <th>Status</th> + <th>Start Time</th> + <th>End Time</th> + <th>Run time</th> + {% for task in object_list %} + <tr onclick="location.href='{% url 'task_view' task.id %}'"> + <td>{{ task.repo }}</td> + <td>{{ task.get_status_display }}</td> + <td>{{ task.start_time }}</td> + <td>{{ task.end_time }}</td> + <td>{{ task.run_time }}</td> + </tr> + {% endfor %} + <table> + </div> +{% endblock %}
\ No newline at end of file |