Remove all Django-related functionality.

Exporter functionality in `export.py` no longer uses Django to
render pages, and instead generates its own `.json` files.
Additionally, a configuration file is generated that can be
consumed by the in-progress Angular frontend.

Change-Id: I37b5be581dbd784fd009c1040422f1cadb009a3a
This commit is contained in:
Tim Buckley 2015-09-25 18:03:18 -06:00 committed by Austin Clark
parent 9aaee2b681
commit 8be1f587d7
46 changed files with 133 additions and 3203 deletions

1
.gitignore vendored
View File

@ -4,7 +4,6 @@ node_modules
build
app/js/templates.js
app/data
*.py[cod]
# C extensions
*.so

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stackviz.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@ -1,5 +1,3 @@
Django<1.8,>=1.4.2
DjangoRestless>=0.0.10
python-subunit>=0.0.18
testtools>=0.9.30
testrepository>=0.0.18

View File

@ -14,163 +14,171 @@
from __future__ import print_function
import django
import datetime
import gzip
import json
import os
import shutil
from argparse import ArgumentParser
from django.http import Http404
from django.core.urlresolvers import resolve
from django.test import RequestFactory
from functools import partial
from stackviz.parser import tempest_subunit
from stackviz import settings
EXPORT_PATHS = [
'/index.html',
'/tempest_aggregate.html'
]
_base = os.path.dirname(os.path.abspath(__file__))
_tempest_count = 0
def fake_render_view(path):
factory = RequestFactory()
request = factory.get(path)
match = resolve(path)
response = match.func(request, *match.args, **match.kwargs)
if hasattr(response, "render"):
response.render()
return response
def export_single_page(path, dest_dir, use_gzip=False):
dest_file = path
if dest_file.startswith('/'):
dest_file = dest_file[1:]
open_func = open
if use_gzip:
def open_compressed(output_dir, file_name, compress):
if compress:
file_name += ".gz"
open_func = gzip.open
dest_file += ".gz"
else:
open_func = open
try:
content = fake_render_view(path).content
with open_func(os.path.join(dest_dir, dest_file), 'wb') as f:
f.write(content)
except Http404 as ex:
print("Warning: skipping %s due to error: %s" % (path, ex.message))
return open_func(os.path.join(output_dir, file_name), 'wb'), file_name
def init_django(args):
# remove leading / from static URL to give them correct filesystem paths
settings.STATIC_URL = settings.STATIC_URL[1:]
settings.USE_GZIP = args.gzip
settings.OFFLINE = True
def json_date_handler(object):
if isinstance(object, (datetime.datetime, datetime.date)):
return object.isoformat()
if args.repository or args.stream_file or args.stdin:
settings.TEST_REPOSITORIES = []
settings.TEST_STREAMS = []
settings.TEST_STREAM_STDIN = False
return None
if args.repository:
settings.TEST_REPOSITORIES = args.repository
if args.stream_file:
settings.TEST_STREAMS = args.stream_file
def export_tempest_tree(stream, output_stream):
converted = tempest_subunit.convert_stream(stream, strip_details=True)
tree = tempest_subunit.reorganize(converted)
json.dump(tree, output_stream, default=json_date_handler)
output_stream.close()
if args.stdin:
settings.TEST_STREAM_STDIN = True
if args.dstat:
settings.DSTAT_CSV = args.dstat
def export_tempest_raw(stream, output_stream):
converted = tempest_subunit.convert_stream(stream, strip_details=True)
json.dump(converted, output_stream, default=json_date_handler)
output_stream.close()
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stackviz.settings")
django.setup()
def export_tempest_details(stream, output_stream):
converted = tempest_subunit.convert_stream(stream, strip_details=True)
output = {}
for entry in converted:
output[entry['name']] = entry['details']
json.dump(output, output_stream, default=json_date_handler)
output_stream.close()
def export_tempest(provider, output_dir, dstat, compress):
global _tempest_count
ret = []
for i in range(provider.count):
path_base = 'tempest_%s_%d' % (provider.name, i)
if provider.count > 1:
name = '%s (%d)' % (provider.description, i)
else:
name = provider.description
open_ = partial(open_compressed,
output_dir=output_dir,
compress=compress)
stream_raw, path_raw = open_(file_name=path_base + '_raw.json')
export_tempest_raw(provider.get_stream(i), stream_raw)
stream_tree, path_tree = open_(file_name=path_base + '_tree.json')
export_tempest_tree(provider.get_stream(i), stream_tree)
stream_details, path_details = open_(
file_name=path_base + '_details.json')
export_tempest_details(provider.get_stream(i), stream_details)
entry = {
'id': _tempest_count,
'name': name,
'raw': path_raw,
'tree': path_tree,
'details': path_details
}
entry.update({'dstat': dstat} if dstat else {})
ret.append(entry)
_tempest_count += 1
return ret
def export_dstat(path, output_dir, compress):
f = open(path, 'rb')
out_stream, out_file = open_compressed(
output_dir,
'dstat_log.csv',
compress)
shutil.copyfileobj(f, out_stream)
f.close()
out_stream.close()
return out_file
def main():
parser = ArgumentParser(description="Generates a self-contained, static "
"StackViz site at the given path.")
parser = ArgumentParser(description="Generates JSON data files for a "
"StackViz site.")
parser.add_argument("path",
help="The output directory. Will be created if it "
"doesn't already exist.")
parser.add_argument("--ignore-bower",
help="Ignore missing Bower components.",
action="store_true")
parser.add_argument("-z", "--gzip",
help="Enable gzip compression for data files.",
action="store_true")
parser.add_argument("-f", "--stream-file",
action="append",
help="Include the given direct subunit stream.")
help="Include the given direct subunit stream; can be "
"used multiple times.")
parser.add_argument("-r", "--repository",
action="append",
help="A directory containing a `.testrepository` to "
"include. If not provided, the `settings.py` "
"configured values will be used.")
"include; can be used multiple times.")
parser.add_argument("-i", "--stdin",
help="Read a direct subunit stream from standard "
"input.",
action="store_true")
parser.add_argument("--dstat",
help="The path to the DStat log file (CSV-formatted) "
"to include. If not provided, the `settings.py` "
"configured value will be used.")
"to include.")
args = parser.parse_args()
if not args.ignore_bower:
if not os.listdir(os.path.join(_base, 'static', 'components')):
print("Bower components have not been installed, please run "
"`bower install`")
return 1
if os.path.exists(args.path):
if os.listdir(args.path):
print("Destination exists and is not empty, cannot continue")
return 1
else:
if not os.path.exists(args.path):
os.mkdir(args.path)
init_django(args)
dstat = None
if args.dstat:
print("Exporting DStat log")
dstat = export_dstat(args.dstat, args.path, args.gzip)
print("Copying static files ...")
shutil.copytree(os.path.join(_base, 'static'),
os.path.join(args.path, 'static'))
providers = tempest_subunit.get_providers(
args.repository,
args.stream_file,
args.stdin)
for path in EXPORT_PATHS:
print("Rendering:", path)
export_single_page(path, args.path)
tempest_config_entries = []
for provider in tempest_subunit.get_providers().values():
for i in range(provider.count):
param = (provider.name, i)
for provider in providers.values():
print("Exporting Tempest provider: %s (%d)" % (provider.description,
provider.count))
tempest_config_entries.extend(
export_tempest(provider, args.path, dstat, args.gzip)
)
print("Rendering views for tempest run %s #%d" % param)
export_single_page('/tempest_timeline_%s_%d.html' % param,
args.path)
export_single_page('/tempest_results_%s_%d.html' % param,
args.path)
print("Exporting data for tempest run %s #%d" % param)
export_single_page('/tempest_api_tree_%s_%d.json' % param,
args.path, args.gzip)
export_single_page('/tempest_api_raw_%s_%d.json' % param,
args.path, args.gzip)
export_single_page('/tempest_api_details_%s_%d.json' % param,
args.path, args.gzip)
print("Exporting DStat log: dstat_log.csv")
export_single_page('/dstat_log.csv', args.path, args.gzip)
with open(os.path.join(args.path, 'config.json'), 'w') as f:
json.dump({
'tempest': tempest_config_entries
}, f)
if __name__ == '__main__':

View File

@ -1,35 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from stackviz.parser.tempest_subunit import get_providers
from stackviz.settings import OFFLINE
from stackviz.settings import USE_GZIP
def inject_extra_context(request):
ret = {
'use_gzip': USE_GZIP,
'offline': OFFLINE
}
providers = get_providers()
if providers:
default = providers.values()[0]
ret.update({
'tempest_providers': providers.values(),
'tempest_default_provider': default,
})
return ret

View File

@ -29,16 +29,11 @@ from testtools import StreamToDict
from testrepository.repository.file import RepositoryFactory
from testrepository.repository.file import RepositoryNotFound
from stackviz import settings
NAME_SCENARIO_PATTERN = re.compile(r'^(.+) \((.+)\)$')
NAME_TAGS_PATTERN = re.compile(r'^(.+)\[(.+)\]$')
_provider_cache = None
class InvalidSubunitProvider(Exception):
pass
@ -168,39 +163,44 @@ class StandardInputProvider(SubunitProvider):
return self.buffer
def get_providers():
def get_providers(repository_paths=None, stream_paths=None, stdin=False):
"""Loads all test providers from locations configured in settings.
:param repository_paths: a list of directory paths containing
'.testrepository' folders to read
:param stream_paths: a list of paths to direct subunit streams
:param stdin: if true, read a subunit stream from standard input
:return: a dict of loaded provider names and their associated
:class:`SubunitProvider` instances
:rtype: dict[str, SubunitProvider]
"""
global _provider_cache
if repository_paths is None:
repository_paths = []
if _provider_cache is not None:
return _provider_cache
if stream_paths is None:
stream_paths = []
_provider_cache = {}
ret = {}
for path in settings.TEST_REPOSITORIES:
for path in repository_paths:
try:
p = RepositoryProvider(path)
_provider_cache[p.name] = p
ret[p.name] = p
except (ValueError, RepositoryNotFound):
continue
for path in settings.TEST_STREAMS:
for path in stream_paths:
try:
p = FileProvider(path)
_provider_cache[p.name] = p
ret[p.name] = p
except InvalidSubunitProvider:
continue
if settings.TEST_STREAM_STDIN:
if stdin:
p = StandardInputProvider()
_provider_cache[p.name] = p
ret[p.name] = p
return _provider_cache
return ret
def _clean_name(name):

View File

@ -1,354 +0,0 @@
/*!
* Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com)
* Code licensed under the Apache License v2.0.
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
*/
body {
background-color: #f8f8f8;
}
#wrapper {
width: 100%;
}
#page-wrapper {
padding: 0 15px;
min-height: 568px;
background-color: #fff;
}
@media(min-width:768px) {
#page-wrapper {
position: inherit;
margin: 0 0 0 250px;
padding: 0 30px;
border-left: 1px solid #e7e7e7;
}
}
.navbar-top-links {
margin-right: 0;
}
.navbar-top-links li {
display: inline-block;
}
.navbar-top-links li:last-child {
margin-right: 15px;
}
.navbar-top-links li a {
padding: 15px;
min-height: 50px;
}
.navbar-top-links .dropdown-menu li {
display: block;
}
.navbar-top-links .dropdown-menu li:last-child {
margin-right: 0;
}
.navbar-top-links .dropdown-menu li a {
padding: 3px 20px;
min-height: 0;
}
.navbar-top-links .dropdown-menu li a div {
white-space: normal;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
width: 310px;
min-width: 0;
}
.navbar-top-links .dropdown-messages {
margin-left: 5px;
}
.navbar-top-links .dropdown-tasks {
margin-left: -59px;
}
.navbar-top-links .dropdown-alerts {
margin-left: -123px;
}
.navbar-top-links .dropdown-user {
right: 0;
left: auto;
}
.sidebar .sidebar-nav.navbar-collapse {
padding-right: 0;
padding-left: 0;
}
.sidebar .sidebar-search {
padding: 15px;
}
.sidebar ul li {
border-bottom: 1px solid #e7e7e7;
}
.sidebar ul li a.active {
background-color: #eee;
}
.sidebar .arrow {
float: right;
}
.sidebar .fa.arrow:before {
content: "\f104";
}
.sidebar .active>a>.fa.arrow:before {
content: "\f107";
}
.sidebar .nav-second-level li,
.sidebar .nav-third-level li {
border-bottom: 0!important;
}
.sidebar .nav-second-level li a {
padding-left: 37px;
}
.sidebar .nav-third-level li a {
padding-left: 52px;
}
@media(min-width:768px) {
.sidebar {
z-index: 1;
position: absolute;
width: 250px;
margin-top: 51px;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
margin-left: auto;
}
}
.btn-outline {
color: inherit;
background-color: transparent;
transition: all .5s;
}
.btn-primary.btn-outline {
color: #428bca;
}
.btn-success.btn-outline {
color: #5cb85c;
}
.btn-info.btn-outline {
color: #5bc0de;
}
.btn-warning.btn-outline {
color: #f0ad4e;
}
.btn-danger.btn-outline {
color: #d9534f;
}
.btn-primary.btn-outline:hover,
.btn-success.btn-outline:hover,
.btn-info.btn-outline:hover,
.btn-warning.btn-outline:hover,
.btn-danger.btn-outline:hover {
color: #fff;
}
.chat {
margin: 0;
padding: 0;
list-style: none;
}
.chat li {
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px dotted #999;
}
.chat li.left .chat-body {
margin-left: 60px;
}
.chat li.right .chat-body {
margin-right: 60px;
}
.chat li .chat-body p {
margin: 0;
}
.panel .slidedown .glyphicon,
.chat .glyphicon {
margin-right: 5px;
}
.chat-panel .panel-body {
height: 350px;
overflow-y: scroll;
}
.login-panel {
margin-top: 25%;
}
.flot-chart {
display: block;
height: 400px;
}
.flot-chart-content {
width: 100%;
height: 100%;
}
.dataTables_wrapper {
position: relative;
clear: both;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
background: 0 0;
}
table.dataTable thead .sorting_asc:after {
content: "\f0de";
float: right;
font-family: fontawesome;
}
table.dataTable thead .sorting_desc:after {
content: "\f0dd";
float: right;
font-family: fontawesome;
}
table.dataTable thead .sorting:after {
content: "\f0dc";
float: right;
font-family: fontawesome;
color: rgba(50,50,50,.5);
}
.btn-circle {
width: 30px;
height: 30px;
padding: 6px 0;
border-radius: 15px;
text-align: center;
font-size: 12px;
line-height: 1.428571429;
}
.btn-circle.btn-lg {
width: 50px;
height: 50px;
padding: 10px 16px;
border-radius: 25px;
font-size: 18px;
line-height: 1.33;
}
.btn-circle.btn-xl {
width: 70px;
height: 70px;
padding: 10px 16px;
border-radius: 35px;
font-size: 24px;
line-height: 1.33;
}
.show-grid [class^=col-] {
padding-top: 10px;
padding-bottom: 10px;
border: 1px solid #ddd;
background-color: #eee!important;
}
.show-grid {
margin: 15px 0;
}
.huge {
font-size: 40px;
}
.panel-green {
border-color: #5cb85c;
}
.panel-green .panel-heading {
border-color: #5cb85c;
color: #fff;
background-color: #5cb85c;
}
.panel-green a {
color: #5cb85c;
}
.panel-green a:hover {
color: #3d8b3d;
}
.panel-red {
border-color: #d9534f;
}
.panel-red .panel-heading {
border-color: #d9534f;
color: #fff;
background-color: #d9534f;
}
.panel-red a {
color: #d9534f;
}
.panel-red a:hover {
color: #b52b27;
}
.panel-yellow {
border-color: #f0ad4e;
}
.panel-yellow .panel-heading {
border-color: #f0ad4e;
color: #fff;
background-color: #f0ad4e;
}
.panel-yellow a {
color: #f0ad4e;
}
.panel-yellow a:hover {
color: #df8a13;
}

View File

@ -1,180 +0,0 @@
.timeline {
position: relative;
padding: 20px 0 20px;
list-style: none;
}
.timeline:before {
content: " ";
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 3px;
margin-left: -1.5px;
background-color: #eeeeee;
}
.timeline > li {
position: relative;
margin-bottom: 20px;
}
.timeline > li:before,
.timeline > li:after {
content: " ";
display: table;
}
.timeline > li:after {
clear: both;
}
.timeline > li:before,
.timeline > li:after {
content: " ";
display: table;
}
.timeline > li:after {
clear: both;
}
.timeline > li > .timeline-panel {
float: left;
position: relative;
width: 46%;
padding: 20px;
border: 1px solid #d4d4d4;
border-radius: 2px;
-webkit-box-shadow: 0 1px 6px rgba(0,0,0,0.175);
box-shadow: 0 1px 6px rgba(0,0,0,0.175);
}
.timeline > li > .timeline-panel:before {
content: " ";
display: inline-block;
position: absolute;
top: 26px;
right: -15px;
border-top: 15px solid transparent;
border-right: 0 solid #ccc;
border-bottom: 15px solid transparent;
border-left: 15px solid #ccc;
}
.timeline > li > .timeline-panel:after {
content: " ";
display: inline-block;
position: absolute;
top: 27px;
right: -14px;
border-top: 14px solid transparent;
border-right: 0 solid #fff;
border-bottom: 14px solid transparent;
border-left: 14px solid #fff;
}
.timeline > li > .timeline-badge {
z-index: 100;
position: absolute;
top: 16px;
left: 50%;
width: 50px;
height: 50px;
margin-left: -25px;
border-radius: 50% 50% 50% 50%;
text-align: center;
font-size: 1.4em;
line-height: 50px;
color: #fff;
background-color: #999999;
}
.timeline > li.timeline-inverted > .timeline-panel {
float: right;
}
.timeline > li.timeline-inverted > .timeline-panel:before {
right: auto;
left: -15px;
border-right-width: 15px;
border-left-width: 0;
}
.timeline > li.timeline-inverted > .timeline-panel:after {
right: auto;
left: -14px;
border-right-width: 14px;
border-left-width: 0;
}
.timeline-badge.primary {
background-color: #2e6da4 !important;
}
.timeline-badge.success {
background-color: #3f903f !important;
}
.timeline-badge.warning {
background-color: #f0ad4e !important;
}
.timeline-badge.danger {
background-color: #d9534f !important;
}
.timeline-badge.info {
background-color: #5bc0de !important;
}
.timeline-title {
margin-top: 0;
color: inherit;
}
.timeline-body > p,
.timeline-body > ul {
margin-bottom: 0;
}
.timeline-body > p + p {
margin-top: 5px;
}
@media(max-width:767px) {
ul.timeline:before {
left: 40px;
}
ul.timeline > li > .timeline-panel {
width: calc(100% - 90px);
width: -moz-calc(100% - 90px);
width: -webkit-calc(100% - 90px);
}
ul.timeline > li > .timeline-badge {
top: 16px;
left: 15px;
margin-left: 0;
}
ul.timeline > li > .timeline-panel {
float: right;
}
ul.timeline > li > .timeline-panel:before {
right: auto;
left: -15px;
border-right-width: 15px;
border-left-width: 0;
}
ul.timeline > li > .timeline-panel:after {
right: auto;
left: -14px;
border-right-width: 14px;
border-left-width: 0;
}
}

View File

@ -1,3 +0,0 @@
.highlight {
font-weight: bold;
}

View File

@ -1,87 +0,0 @@
"use strict";
var originalDetailsContent = null;
var detailsCache = null;
var detailsInProgress = false;
var detailsWaiting = [];
var runId = null;
var loadDetails = function(callback) {
if (detailsCache === null) {
detailsWaiting.push(callback);
if (!detailsInProgress) {
var url = "tempest_api_details_" + runId + ".json";
if ("{{use_gzip}}" === "True") {
url += ".gz";
}
detailsInProgress = true;
d3.json(url, function(error, data) {
if (error) {
throw error;
}
detailsCache = data;
detailsWaiting.forEach(function(cb) {
cb(detailsCache);
});
});
}
} else {
callback(detailsCache);
}
};
var showDetails = function(item) {
var parent = $("#details-dialog");
loadDetails(function(details) {
if (!details.hasOwnProperty(item.name_full)) {
console.log("Details not found for item:", item.name_full);
return;
}
if (originalDetailsContent === null) {
originalDetailsContent = parent.html();
}
parent.empty();
for (var prop in details[item.name_full]) {
$("<h3>").text(prop).appendTo(parent);
$("<pre>").text(details[item.name_full][prop]).appendTo(parent);
}
});
};
function addDialogButton(parentID, run_id) {
//parentID: A string contiaining the parent div id to which the button will be appended
runId=run_id;
var button = $('<button/>',
{
text: 'View Log',
click: function() {$("#details-dialog").dialog("open");}
});
$(parentID).append(button);
$("#details-dialog").dialog({
dialogClass: 'ui-dialog',
autoOpen: false,
width: 800,
height: 500,
buttons: [
{
text: "OK",
click: function() {
$( this ).dialog( "close" );
}
}
]
});
}

View File

@ -1,36 +0,0 @@
$(function() {
$('#side-menu').metisMenu();
});
//Loads the correct sidebar on window load,
//collapses the sidebar on window resize.
// Sets the min-height of #page-wrapper to window size
$(function() {
$(window).bind("load resize", function() {
topOffset = 50;
width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
if (width < 768) {
$('div.navbar-collapse').addClass('collapse');
topOffset = 100; // 2-row-menu
} else {
$('div.navbar-collapse').removeClass('collapse');
}
height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
height = height - topOffset;
if (height < 1) height = 1;
if (height > topOffset) {
$("#page-wrapper").css("min-height", (height) + "px");
}
});
var url = window.location;
var element = $('ul.nav a').filter(function() {
return this.href == url || url.href.indexOf(this.href) == 0;
}).addClass('active').parent().parent().addClass('in').parent();
if (element.is('li')) {
element.addClass('active');
}
});

View File

@ -1,118 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
/*
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Test Runs</div>
<div class="panel-body" id="run-summary-div">
</div>
</div>
</div>
*/
//@param data: JSON data of the test run
function getData(data) {
var num_successes = 0;
var num_failures = 0;
var num_skipped = 0
var total_time = 0;
var longest_test={duration: 0};
function calculateChildrenTime(i) {
var dur = 0;
if (typeof i.duration !== "undefined") {
if (i.status=="success") num_successes++;
else if (i.status=="fail") num_failures++;
else if (i.status=="skip") num_skipped++;
if (longest_test.duration < i.duration)
longest_test = i;
dur = i.duration;
}
else {
for (var k in i.children) {
dur += calculateChildrenTime(i.children[k]);
}
}
return dur;
}
total_time=calculateChildrenTime(data);
var data_dict= { "Successes": num_successes,
"Failures": num_failures,
"Skipped": num_skipped,
"Total Time": total_time.toFixed(2),
"Longest Test": longest_test.name + " ("+longest_test.duration+")"};
return data_dict;
}
function createTable(data, entry) {
var container = $("<div>")
.addClass('col-lg-6' )
.appendTo($("#run-summary-div"));
var panel = $("<div>")
.addClass('panel panel-default')
.appendTo(container);
var head = $("<div>")
.addClass("panel-heading")
.appendTo(panel);
head.append($("<a>", {
href: 'tempest_timeline_' + entry.provider + '_' + entry.run + '.html',
text: entry.providerDescription + ", run #" + entry.run
}));
var body = $("<div>")
.addClass("panel-body")
.appendTo(panel);
var table = $("<table>")
.addClass("table table-bordered table-hover table-striped")
.appendTo(body);
var data_dict = getData(data);
for (var key in data_dict) {
$("<tr>")
.append($("<td>").text(key))
.append($("<td>").text(data_dict[key]))
.appendTo(table);
}
}
//@param run_id: The method is passed the latest run_id so it can populate the tables moving backwards
function createTables(entries) {
entries.forEach(function(entry) {
//TODO: Sort tables when inserting so they appear in correct order
d3.json(entry.url, function(error, data) {
if (error) throw error;
//create a table for the info
// TODO: entry now has provider description, etc which should be
// shown (categorized?)
createTable(data, entry);
});
})
}

View File

@ -1,177 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
var runId = null;
var providerName = null;
function populateTable(d, textColor) {
var oldtbl = document.getElementById("result-table-div");
oldtbl.innerHTML = "";
var tbl = document.createElement('table');
tbl.setAttribute("id","test-table");
tbl.setAttribute("class","table table-bordered table-hover table-striped");
if (typeof d.children == "undefined") {
for (var key in d) {
if (key=="status" || key=="name_full" || key=="name" || key=="duration" || key=="tags" || key=="timestamps") {
var row = tbl.insertRow();
var td1 = row.insertCell();
var td2 = row.insertCell();
td1.innerHTML = key;
td2.innerHTML = d[key];
}
}
document.getElementById("result-table-div").appendChild(tbl);
document.getElementById("table-heading").innerHTML=d.name;
addDialogButton("#result-table-div",providerName + "_" + runId);
showDetails(d);
}
else {
for (var j in d.children) {
var row = tbl.insertRow();
var td1 = row.insertCell();
var td2 = row.insertCell();
td1.innerHTML = d.children[j].name;
td2.innerHTML = calculateChildrenTime(d.children[j]).toFixed(2);
td1.style.color = textColor(d.children[j]);
document.getElementById("result-table-div").appendChild(tbl);
document.getElementById("table-heading").innerHTML=d.name +
": " + calculateChildrenTime(d).toFixed(2) + " seconds"
$( "table-test" ).DataTable();
}
}
}
function calculateChildrenTime(i) {
var dur = 0;
if (typeof i.duration !== "undefined") {
dur = i.duration;
}
else {
for (var k in i.children) {
dur += calculateChildrenTime(i.children[k]);
}
}
return dur;
}
function displayFailingTests(d) {
document.getElementById("failure-table-div").innerHTML="";
var tbl = document.createElement('table');
tbl.setAttribute("id","failure-table");
tbl.setAttribute("class","table table-bordered table-hover table-striped");
function findFailingTests(i,result) {
if (i.status == "fail") {
result.push(i);
}
else {
for (var k in i.children) {
findFailingTests(i.children[k],result);
}
}
return;
}
var failureList=[];
findFailingTests(d,failureList);
for (var row in failureList) {
var newRow = tbl.insertRow();
newRow.setAttribute("class","failure-row");
var td1 = newRow.insertCell();
var td2 = newRow.insertCell();
td1.innerHTML = failureList[row].name_full;
td2.innerHTML = parseFloat(failureList[row].duration).toFixed(2);
}
document.getElementById("failure-table-div").appendChild(tbl);
$( "#failure-table-div" ).hide();
}
function createSunburst(url, provider_name, run_id) {
runId = run_id;
providerName = provider_name;
var width = 700,
height = 500,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("#sunburst").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.duration; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
d3.json(url, function(error, root) {
if (error) throw error;
displayFailingTests(root);
var path = svg.selectAll("path")
.data(partition.nodes(root))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.name); })
.on("click", click);
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
populateTable(d,mouse);
}
function mouse(d) { return color(d.name); }
});
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
}

View File

@ -1,661 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*global d3:false*/
var statusColorMap = {
"success": "LightGreen",
"fail": "Crimson",
"skip": "DodgerBlue"
};
var binaryMinIndex = function(min, array, func) {
"use strict";
var left = 0;
var right = array.length - 1;
while (left < right) {
var mid = Math.floor((left + right) / 2);
if (min < func(array[mid])) {
right = mid - 1;
} else if (min > func(array[mid])) {
left = mid + 1;
} else {
right = mid;
}
}
if (left >= array.length) {
return array.length - 1;
} else if (func(array[left]) <= min) {
return left;
} else {
return left - 1;
}
};
var binaryMaxIndex = function(max, array, func) {
"use strict";
var left = 0;
var right = array.length - 1;
while (left < right) {
var mid = Math.floor((left + right) / 2);
if (max < func(array[mid])) {
right = mid - 1;
} else if (max > func(array[mid])) {
left = mid + 1;
} else {
right = mid;
}
}
if (right < 0) {
return 0;
} else if (func(array[right]) <= max) {
return right + 1; // exclusive index
} else {
return right;
}
};
var parseWorker = function(tags) {
"use strict";
for (var i = 0; i < tags.length; i++) {
if (!tags[i].startsWith("worker")) {
continue;
}
return parseInt(tags[i].split("-")[1]);
}
return null;
};
var getDstatLanes = function(data, mins, maxes) {
if (!data) {
return [];
}
var row = data[0];
var lanes = [];
if ('total_cpu_usage_usr' in row && 'total_cpu_usage_sys' in row) {
lanes.push([{
scale: d3.scale.linear().domain([0, 100]),
value: function(d) {
return d.total_cpu_usage_wai;
},
color: "rgba(224, 188, 188, 1)",
text: "CPU wait"
}, {
scale: d3.scale.linear().domain([0, 100]),
value: function(d) {
return d.total_cpu_usage_usr + d.total_cpu_usage_sys;
},
color: "rgba(102, 140, 178, 0.75)",
text: "CPU (user+sys)"
}]);
}
if ('memory_usage_used' in row) {
lanes.push([{
scale: d3.scale.linear().domain([0, maxes.memory_usage_used]),
value: function(d) { return d.memory_usage_used; },
color: "rgba(102, 140, 178, 0.75)",
text: "Memory"
}]);
}
if ('net_total_recv' in row && 'net_total_send' in row) {
lanes.push([{
scale: d3.scale.linear().domain([0, maxes.net_total_recv]),
value: function(d) { return d.net_total_recv; },
color: "rgba(224, 188, 188, 1)",
text: "Net Down"
}, {
scale: d3.scale.linear().domain([0, maxes.net_total_send]),
value: function(d) { return d.net_total_send; },
color: "rgba(102, 140, 178, 0.75)",
text: "Net Up",
type: "line"
}]);
}
if ('dsk_total_read' in row && 'dsk_total_writ' in row) {
lanes.push([{
scale: d3.scale.linear().domain([0, maxes.dsk_total_read]),
value: function(d) { return d.dsk_total_read; },
color: "rgba(224, 188, 188, 1)",
text: "Disk Read",
type: "line"
}, {
scale: d3.scale.linear().domain([0, maxes.dsk_total_writ]),
value: function(d) { return d.dsk_total_writ; },
color: "rgba(102, 140, 178, 0.75)",
text: "Disk Write",
type: "line"
}]);
}
return lanes;
};
var initTimeline = function(options, data, timeExtents) {
"use strict";
var container = $(options.container);
// http://bl.ocks.org/bunkat/2338034
var margin = { top: 20, right: 10, bottom: 10, left: 80 };
var width = container.width() - margin.left - margin.right;
var height = 550 - margin.top - margin.bottom;
// filter dstat data immediately. if no timestamps overlap, we want to throw
// it away quickly
options.dstatData = options.dstatData.slice(
binaryMinIndex(timeExtents[0], options.dstatData, function(d) { return d.system_time; }),
binaryMaxIndex(timeExtents[1], options.dstatData, function(d) { return d.system_time; })
);
var dstatLanes;
if (options.dstatData.length > 2) {
dstatLanes = getDstatLanes(
options.dstatData,
options.dstatMinimums,
options.dstatMaximums);
} else {
dstatLanes = [];
}
var miniHeight = data.length * 12 + 30;
var dstatHeight = dstatLanes.length * 30 + 30;
var mainHeight = height - miniHeight - dstatHeight - 10;
var x = d3.time.scale()
.range([0, width])
.domain(timeExtents);
var x1 = d3.scale.linear().range([0, width]);
var y1 = d3.scale.linear()
.domain([0, data.length])
.range([0, mainHeight]);
var y2 = d3.scale.linear()
.domain([0, data.length])
.range([0, miniHeight]);
var y3 = d3.scale.linear()
.domain([0, dstatLanes.length])
.range([0, dstatHeight]);
var chart = d3.select(options.container)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "chart");
var defs = chart.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", mainHeight);
var main = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", width)
.attr("height", mainHeight)
.attr("class", "main");
var laneLines = main.append("g");
var laneLabels = main.append("g");
var itemGroups = main.append("g");
var dstatOffset = margin.top + mainHeight;
var dstatGroup = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + dstatOffset + ")")
.attr("width", width)
.attr("height", dstatHeight);
dstatLanes.forEach(function(lane, i) {
var laneGroup = dstatGroup.append("g");
var text = laneGroup.append("text")
.attr("y", function(d) { return y3(i + 0.5); })
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.style("font", "10px sans-serif");
var dy = 0;
// precompute some known info for each lane's paths
lane.forEach(function(pathDef) {
var laneHeight = 0.8 * y3(1);
if ('text' in pathDef) {
text.append("tspan")
.attr("x", -margin.right)
.attr("dy", dy)
.text(pathDef.text)
.attr("fill", function(d) { return pathDef.color; });
dy += 10;
}
pathDef.scale.range([laneHeight, 0]);
pathDef.path = laneGroup.append("path");
if (pathDef.type === "line") {
pathDef.area = d3.svg.line()
.x(function(d) { return x1(d.system_time); })
.y(function(d) { return y3(i) + pathDef.scale(pathDef.value(d)); });
pathDef.path
.style("stroke", pathDef.color)
.style("stroke-width", "1.5px")
.style("fill", "none");
//.style("shape-rendering", 'crispEdges');
} else {
pathDef.area = d3.svg.area()
.x(function(d) { return x1(d.system_time); })
.y0(function(d) { return y3(i) + laneHeight; })
.y1(function(d) {
return y3(i) + pathDef.scale(pathDef.value(d));
});
pathDef.path.style("fill", pathDef.color);
}
});
});
var cursorGroup = main.append("g")
.style("opacity", 0)
.style("pointer-events", "none");
var cursor = cursorGroup.append("line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", y1(-0.1))
.attr("stroke", "blue");
var cursorText = cursorGroup.append("text")
.attr("x", 0)
.attr("y", -10)
.attr("dy", "-.5ex")
.text("")
.style("text-anchor", "middle")
.style("font", "9px sans-serif");
var miniOffset = margin.top + mainHeight + dstatHeight;
var mini = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + miniOffset + ")")
.attr("width", width)
.attr("height", mainHeight)
.attr("class", "mini");
var miniGroups = mini.append("g");
// performance hack: performance in Firefox as of 39.0 is poor due to some
// d3 bugs
// Limit the initial selection to ~1/6th of the total to make things
// bearable (user can still increase as desired)
var start = timeExtents[0];
var end = timeExtents[1];
var reducedEnd = new Date(start.getTime() + ((end - start) / 8));
var brush = d3.svg.brush()
.x(x)
.extent([start, reducedEnd]);
chart.on("mouseout", function() {
cursorGroup.style("opacity", 0);
});
chart.on("mousemove", function() {
var pos = d3.mouse(this);
var px = pos[0];
var py = pos[1];
if (px >= margin.left && px < (width + margin.left) &&
py > margin.top && py < (mainHeight + margin.top)) {
var relX = px - margin.left;
var currentTime = new Date(x1.invert(relX));
cursorGroup.style("opacity", "0.5");
cursorGroup.attr("transform", "translate(" + relX + ", 0)");
cursorText.text(d3.time.format("%X")(currentTime));
}
});
function updateLanes() {
var lines = laneLines.selectAll(".laneLine")
.data(data, function(d) { return d.key; });
lines.enter().append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("stroke", "lightgray")
.attr("class", "laneLine");
lines.attr("y1", function(d, i) { return y1(i - 0.1); })
.attr("y2", function(d, i) { return y1(i - 0.1); });
lines.exit().remove();
var labels = laneLabels.selectAll(".laneLabel")
.data(data, function(d) { return d.key; });
labels.enter().append("text")
.text(function(d) { return "Worker #" + d.key; })
.attr("x", -margin.right)
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneLabel");
labels.attr("y", function(d, i) { return y1(i + 0.5); });
labels.exit().remove();
cursor.attr("y2", y1(data.length - 0.1));
}
function updateItems() {
var minExtent = brush.extent()[0];
var maxExtent = brush.extent()[1];
// filter visible items to include only those within the current extent
// additionally prune extremely small values to improve performance
var visibleItems = data.map(function(group) {
return {
key: group.key,
values: group.values.filter(function(e) {
if (x1(e.end_date) - x1(e.start_date) < 2) {
return false;
}
if (e.start_date > maxExtent || e.end_date < minExtent) {
return false;
}
return true;
})
};
});
var groups = itemGroups.selectAll("g")
.data(visibleItems, function(d) { return d.key; });
groups.enter().append("g");
var rects = groups.selectAll("rect")
.data(function(d) { return d.values; }, function(d) { return d.name; });
rects.enter().append("rect")
.attr("y", function(d) { return y1(parseWorker(d.tags)); })
.attr("height", 0.8 * y1(1))
.attr("stroke", 'rgba(100, 100, 100, 0.25)')
.attr("clip-path", "url(#clip)");
rects
.attr("x", function(d) {
return x1(d.start_date);
})
.attr("width", function(d) {
return x1(d.end_date) - x1(d.start_date);
})
.attr("fill", function(d) { return statusColorMap[d.status]; })
.on("mouseover", options.onMouseover)
.on("mouseout", options.onMouseout)
.on("click", options.onClick);
rects.exit().remove();
groups.exit().remove();
}
function updateDstat() {
if (dstatLanes.length === 0) {
return;
}
var minExtent = brush.extent()[0];
var maxExtent = brush.extent()[1];
var dstat = options.dstatData;
var timeFunc = function(d) { return d.system_time; };
var visibleEntries = dstat.slice(
binaryMinIndex(minExtent, dstat, timeFunc),
binaryMaxIndex(maxExtent, dstat, timeFunc)
);
// apply the current dataset (visibleEntries) to each dstat path
//
dstatLanes.forEach(function(lane) {
lane.forEach(function(pathDef) {
pathDef.path
.datum(visibleEntries)
.attr("d", pathDef.area);
});
});
}
function updateMiniItems() {
var groups = miniGroups.selectAll("g")
.data(data, function(d) { return d.key; });
groups.enter().append("g");
var rects = groups.selectAll("rect").data(
function(d) { return d.values; },
function(d) { return d.name; });
rects.enter().append("rect")
.attr("y", function(d) { return y2(parseWorker(d.tags) + 0.5) - 5; })
.attr("height", 10);
rects.attr("x", function(d) { return x(d.start_date); })
.attr("width", function(d) { return x(d.end_date) - x(d.start_date); })
.attr("stroke", 'rgba(100, 100, 100, 0.25)')
.attr("fill", function(d) { return statusColorMap[d.status]; });
rects.exit().remove();
groups.exit().remove();
}
function update() {
x1.domain(brush.extent());
updateLanes();
updateItems();
updateDstat();
}
brush.on("brush", update);
mini.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", 1)
.attr("height", miniHeight - 1)
.attr("fill", "dodgerblue")
.attr("fill-opacity", 0.365);
updateMiniItems();
update();
$(window).resize(function() {
var brushExtent = brush.extent();
width = container.width() - margin.left - margin.right;
x.range([0, width]);
x1.range([0, width]);
chart.attr("width", container.width());
defs.attr("width", width);
main.attr("width", width);
mini.attr("width", width);
laneLines.selectAll(".laneLine").attr("x2", width);
brush.extent(brushExtent);
updateMiniItems();
update();
});
};
function fillArrayRight(array) {
// "fill" the array to the right, overwriting empty values with the next
// non-empty value to the left
// only false values will be overwritten (e.g. "", null, etc)
for (var i = 0; i < array.length - 1; i++) {
if (!array[i + 1]) {
array[i + 1] = array[i];
}
}
}
function mergeNames(primary, secondary) {
// "zip" together strings in the same position in each array, and do some
// basic cleanup of results
var ret = [];
for (var i = 0; i < primary.length; i++) {
ret.push((primary[i] + '_' + secondary[i]).replace(/[ /]/g, '_'));
}
return ret;
}
function chainLoadDstat(path, yearOverride, callback) {
"use strict";
d3.text(path, function(error, data) {
if (error) {
callback([]);
return;
}
var primaryNames = null;
var secondaryNames = null;
var names = null;
var minimums = {};
var maximums = {};
// assume UTC - may not necessarily be the case?
// dstat doesn't include the year in its logs, so we'll need to copy it
// from the subunit logs
var dateFormat = d3.time.format.utc("%d-%m %H:%M:%S");
var parsed = d3.csv.parseRows(data, function(row, i) {
if (i <= 4) { // header rows - ignore
return null;
} else if (i == 5) { // primary
primaryNames = row;
fillArrayRight(primaryNames);
return null;
} else if (i == 6) { // secondary
secondaryNames = row;
names = mergeNames(primaryNames, secondaryNames);
return null;
} else {
var ret = {};
for (var col = 0; col < row.length; col++) {
var name = names[col];
var value = row[col];
if (name == "system_time") {
value = dateFormat.parse(value);
value.setFullYear(1900 + yearOverride);
} else {
value = parseFloat(value);
}
if (!(name in minimums) || value < minimums[name]) {
minimums[name] = value;
}
if (!(name in maximums) || value > maximums[name]) {
maximums[name] = value;
}
ret[name] = value;
}
return ret;
}
});
callback(parsed, minimums, maximums);
});
}
function loadTimeline(path, options) { // eslint-disable-line no-unused-vars
"use strict";
d3.json(path, function(error, data) {
if (error) {
throw error;
}
var minStart = null;
var maxEnd = null;
data.forEach(function(d) {
/*eslint-disable camelcase*/
d.start_date = new Date(d.timestamps[0]);
if (minStart === null || d.start_date < minStart) {
minStart = d.start_date;
}
d.end_date = new Date(d.timestamps[1]);
if (maxEnd === null || d.end_date > maxEnd) {
maxEnd = d.end_date;
}
/*eslint-enable camelcase*/
});
data = data.filter(function (d) { return d.duration > 0; });
var nested = d3.nest()
.key(function(d) { return parseWorker(d.tags); })
.sortKeys(d3.ascending)
.entries(data);
// include dstat if available
if (options.dstatPath && !options.dstatData) {
var year = data[0].start_date.getYear();
chainLoadDstat(options.dstatPath, year, function(data, mins, maxes) {
options.dstatData = data;
options.dstatMinimums = mins;
options.dstatMaximums = maxes;
initTimeline(options, nested, [ minStart, maxEnd ]);
});
} else {
initTimeline(options, nested, [ minStart, maxEnd ]);
}
});
}

View File

@ -1,104 +0,0 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
window.addEventListener('load', function() {
//default panel display
$("#runs-panel").hide();
$("#gerrit-panel").show();
$("#run-metadata-panel").hide();
//dict containing all run_metadata objects, keyed by run_id
var RUN_METADATA = {};
//sets the run metdata of the associated run in the proper div
function show_run_metadata(id) {
$("#run-metadata-table").html("<thead><th>Key</th><th>Value</th></thead>");
var meta = RUN_METADATA[id];
for (var i in meta) {
var obj = meta[i];
var row = $("<tr><td>" + obj['key'] + "</td>" +
"<td>" + obj['value'] + "</td></tr>");
$("#run-metadata-table").append(row);
}
}
//run_metadata will be queried from subunit2sql when given run_id
function get_run_metadata(request, run_id) {
$.getJSON((request),function(metadata) {
RUN_METADATA[run_id]=metadata;
});
}
//Takes a list of runs and creates a pretty div for each
function display_runs(data) {
$("#runs-panel").show();
$("#runs-panel").append("<ul id=\"runs-list\"></ul>");
for (var i in data) {
var run_obj = data[i];
//get run_metadata
var request = 'upstream_api_run_id_' + run_obj['id'] + '.json';
get_run_metadata(request, run_obj['id']);
var li = $("<li class =\"run-li\" id=\"run-li-" + i + "\" value=\"" + run_obj['id'] + "\"></li>");
//on mouseover, show the run_metadata for this run object (li)
$(li).hover(
function () {
$(this).addClass("highlight");
show_run_metadata($(this).attr("value"));
},
function () {
$(this).removeClass("highlight");
}
);
$(li.append("<a href=" + run_obj['artifacts'] + " target=\"_blank\">" + run_obj['artifacts'] + "\n</a>"));
$("#runs-list").append(li);
$("#runs-panel-heading").html("Displaying " + i + " Runs");
}
$("#run-metadata-panel").show();
}
$('#gerrit-id').keypress(function (e) {
if (e.which == 13) {
$( "#gerrit-id-button" ).click();
return false;
}
});
//click triggers the api call that returns the run data
$('#gerrit-id-button').click(function() {
var request = 'upstream_api_changeid_'+$("#gerrit-id").val()+'.json';
$("#runs-panel").append("<a href=\"https://review.openstack.org/" + $("#gerrit-id").val() +
"/\" target=\"_blank\"><h2> Change ID: " + $("#gerrit-id").val() + "</h2></a>");
$("#gerrit-panel").html("Loading Test Runs...");
$.getJSON((request),function(data) {
$("#gerrit-panel").hide();
display_runs(data);
});
});
});

View File

@ -1,14 +0,0 @@
{% extends 'template.html' %}
{% block title %}Devstack Latest Results{% endblock %}
{% block body %}
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Latest Results</h1>
</div>
<!-- /.col-lg-12 -->
</div>
{% endblock %}

View File

@ -1,53 +0,0 @@
{% extends 'template.html' %}
{% load staticfiles %}
{% block title %}Index{% endblock %}
{% block head-extra %}
<!-- Script for summary page-->
<script src="{% static 'js/summary.js' %}"></script>
{% endblock %}
{% block body %}
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Local Run Summary</h1>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Tempest Runs</div>
<div class="panel-body" id="run-summary-div">
</div>
</div>
</div>
</div>
<script>
var urlSuffix = "";
if ("{{use_gzip}}" === "True") {
urlSuffix = ".gz";
}
var tempestRuns = [];
/* {% for provider in tempest_providers %} begin generated */
/* {% for index in provider.indexes %} */
tempestRuns.push({
provider: "{{provider.name}}",
providerDescription: "{{provider.description}}",
run: parseInt("{{index}}"),
url: "tempest_api_tree_{{provider.name}}_{{index}}.json" + urlSuffix
});
/* {% endfor %} */
/* {% endfor %} end generated */
window.addEventListener('load', createTables( tempestRuns ));
</script>
{% endblock %}

View File

@ -1,61 +0,0 @@
<div class="sidebar-nav navbar-collapse">
<ul class="nav" id="side-menu">
<li>
<a href="index.html"><i class="fa fa-pie-chart fa-fw"></i> Overview</a>
</li>
<!--<li>
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> DevStack<span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li>
<a href="/devstack/results"><i class="fa fa-clock-o fa-fw"></i> Results</a>
</li>
<li>
<a href="/devstack/"><i class="fa fa-calendar fa-fw"></i> History</a>
</li>
<li>
<a href="/devstack/"><i class="fa fa-database fa-fw"></i> Compare</a>
</li>
</ul>
--><!-- /.nav-second-level --><!--
</li>-->
<li>
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Tempest<span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li>
<a href="tempest_results_{{ tempest_default_provider.name }}_{{ tempest_default_provider.count | add:'-1' }}.html">
<i class="fa fa-clock-o fa-fw"></i> Sunburst
</a>
</li>
<li>
<a href="tempest_timeline_{{ tempest_default_provider.name }}_{{ tempest_default_provider.count | add:'-1' }}.html">
<i class="fa fa-calendar fa-fw"></i> Timeline
</a>
</li>
<!--<li>
<a href="/tempest/"><i class="fa fa-database fa-fw"></i> Compare</a>
</li>-->
{% if not offline %}
<li class="online">
<a href="tempest_aggregate.html"><i class="fa fa-bar-chart-o fa-fw"></i> Aggregate Results</a>
</li>
{% endif %}
</ul>
<!-- /.nav-second-level -->
</li>
{% if not offline %}
<li class="online">
<a href="#"><i class="fa fa-bar-chart-o fa-fw"></i> Upstream<span class="fa arrow"></span></a>
<ul class="nav nav-second-level">
<li>
<a href="upstream_test.html"><i class="fa fa-bar-chart-o fa-fw"></i> Test Stats</a>
</li>
<li>
<a href="upstream_run.html"><i class="fa fa-clock-o fa-fw"></i> Run Data</a>
</li>
</ul>
<!-- /.nav-second-level -->
</li>
{% endif %}
</ul>
</div>
<!-- /.sidebar-collapse -->

View File

@ -1,14 +0,0 @@
{% extends 'template.html' %}
{% block title %}Aggregate Results{% endblock %}
{% block body %}
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Aggregate Tempest Results</h1>
</div>
<!-- /.col-lg-12 -->
</div>
{% endblock %}

View File

@ -1,99 +0,0 @@
{% extends 'template.html' %}
{% load staticfiles %}
{% block title %}Tempest Results{% endblock %}
{% block head-extra %}
<!-- Scripts for visualization-->
<script src="{% static 'js/sunburst.js' %}"></script>
{% endblock %}
{% block body %}
<div id="details-dialog" title="Details Output">
<p>Details not found for test.</p>
</div>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Results from Run #{{run_id}}</h1>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="panel panel-default">
<div class="panel-heading">
Runtime Diagram
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
View Run...
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
{% for provider in tempest_providers %}
<li class="dropdown-header">{{ provider.description }}</li>
{% for index in provider.indexes %}
<li><a href="tempest_results_{{ provider.name }}_{{ index }}.html">Run #{{ index }}</a></li>
{% endfor %}
{% endfor %}
</ul>
</div>
</div>
</div>
<div class="panel-body">
<div id="sunburst"></div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading" id="table-heading">Test Run Info</div>
<div class="panel-body">
<div class="table-responsive" id="result-table-div">
<table class="table table-bordered table-hover table-striped" id="test-table">
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading"><button type="button" id="show-hide-failures">Show/Hide Failures</button></div>
<div class="panel-body">
<div class="table-responsive" id="failure-table-div">
</div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function() {
$("#details-dialog").hide();
var url = "tempest_api_tree_{{provider_name}}_{{run_id}}.json";
if ("{{use_gzip}}" === "True") {
url += ".gz";
}
createSunburst( url, "{{provider_name}}", {{run_id}} );
});
</script>
<script>
$(document).ready(function(){
$("#show-hide-failures").click(function() {
$("#failure-table-div").toggle();
});
});
</script>
{% endblock %}

View File

@ -1,269 +0,0 @@
{% extends 'template.html' %}
{% load staticfiles %}
{% block title %}Tempest: Execution Timeline (run #{{run_id}}){% endblock %}
{% block head-extra %}
<style>
#timeline-info table {
table-layout: fixed;
width: 100%;
word-wrap: break-word
}
#timeline-info table td:nth-child(1) {
width: 20%;
}
#timeline-log pre {
overflow-x: scroll;
white-space: nowrap;
}
</style>
{% endblock %}
{% block body %}
<div id="details-dialog" title="Details Output">
<p>Details not found for test.</p>
</div>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Execution Timeline ({{provider_name}}, run #{{run_id}})</h1>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-clock-o fa-fw"></i> Timeline
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
View Run...
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
{% for provider in tempest_providers %}
<li class="dropdown-header">{{ provider.description }}</li>
{% for index in provider.indexes %}
<li><a href="tempest_timeline_{{ provider.name }}_{{ index }}.html">Run #{{ index }}</a></li>
{% endfor %}
{% endfor %}
</ul>
</div>
</div>
</div>
<div id="timeline-container" class="panel-body">
</div>
</div>
</div>
<div class="col-lg-4">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-info fa-fw"></i> Info
</div>
<div id="timeline-info" class="panel-body">
<em>Mouse over an item to view info.</em>
</div>
</div>
</div>
</div>
<script src="{% static 'js/timeline.js' %}"></script>
<script>
var originalInfoContent = null;
var originalDetailsContent = null;
var detailsCache = null;
var detailsInProgress = false;
var detailsWaiting = [];
var showInfo = function(item) {
var parent = $("#timeline-info");
if (originalInfoContent === null) {
originalInfoContent = parent.html();
}
var e = $("<table>", {
"class": 'table table-bordered table-hover table-striped'
});
var nameParts = item.name.split(".");
var pkg = nameParts.slice(0, nameParts.length - 2).join('.');
e.append($("<tr>")
.append($("<td>", { text: 'Name' }))
.append($("<td>", { text: nameParts[nameParts.length - 1] })));
e.append($("<tr>")
.append($("<td>", { text: 'Class' }))
.append($("<td>", { text: nameParts[nameParts.length - 2] })));
e.append($("<tr>")
.append($("<td>", { text: 'Module' }))
.append($("<td>", { text: pkg })));
e.append($("<tr>")
.append($("<td>", { text: 'Status' }))
.append($("<td>", { text: item.status })));
e.append($("<tr>")
.append($("<td>", { text: 'Tags' }))
.append($("<td>", { text: item.tags.join(", ") })));
e.append($("<tr>")
.append($("<td>", { text: 'Duration' }))
.append($("<td>", { text: item.duration + " seconds" })));
parent.empty();
e.appendTo(parent);
addDialogButton(parent);
};
var hideInfo = function() {
$("#timeline-info").html(originalInfoContent);
};
var loadDetails = function(callback) {
if (detailsCache === null) {
detailsWaiting.push(callback);
if (!detailsInProgress) {
var url = "tempest_api_details_{{provider_name}}_{{run_id}}.json";
if ("{{use_gzip}}" === "True") {
url += ".gz";
}
detailsInProgress = true;
d3.json(url, function(error, data) {
if (error) {
throw error;
}
detailsCache = data;
detailsWaiting.forEach(function(cb) {
cb(detailsCache);
});
});
}
} else {
callback(detailsCache);
}
};
var showDetails = function(item) {
var parent = $("#details-dialog");
showInfo(item);
loadDetails(function(details) {
if (!details.hasOwnProperty(item.name)) {
console.log("Details not found for item:", item.name);
return;
}
if (originalDetailsContent === null) {
originalDetailsContent = parent.html();
}
parent.empty();
for (var prop in details[item.name]) {
$("<h3>").text(prop).appendTo(parent);
$("<pre>").text(details[item.name][prop]).appendTo(parent);
}
});
};
var hideDetails = function() {
$("#timeline-details").html(originalDetailsContent);
};
window.addEventListener('load', function() {
$("#details-dialog").hide();
var selectedItem = null;
var selectedValue = null;
var url = "tempest_api_raw_{{provider_name}}_{{run_id}}.json";
if ("{{use_gzip}}" === "True") {
url += ".gz";
}
var dstatUrl = "dstat_log.csv";
if ("{{use_gzip}}" === "True") {
dstatUrl += ".gz";
}
loadTimeline(url, {
dstatPath: dstatUrl,
container: $("#timeline-container")[0],
onClick: function(d) {
var self = d3.select(this);
// deselect old item, if any
if (selectedItem !== null) {
if (selectedItem.attr("data-old-fill")) {
selectedItem.attr("fill", selectedItem.attr("data-old-fill"));
selectedItem.attr("data-old-fill", null);
}
if (selectedValue.name === d.name) {
// remove selection on 2nd click - don't continue
selectedItem = null;
selectedValue = null;
hideDetails();
return;
}
selectedItem = null;
}
// select new item
if (!self.attr("data-old-fill")) {
self.attr("data-old-fill", self.attr("fill"));
}
self.attr("fill", "goldenrod");
selectedItem = self;
selectedValue = d;
showDetails(d);
},
onMouseover: function(d) {
if (selectedItem !== null) {
return;
}
var self = d3.select(this);
if (!self.attr("data-old-fill")) {
self.attr("data-old-fill", self.attr("fill"));
}
d3.select(this).attr("fill", "darkturquoise");
showInfo(d);
},
onMouseout: function(d) {
if (selectedItem !== null) {
return;
}
var self = d3.select(this);
if (self.attr("data-old-fill")) {
self.attr("fill", self.attr("data-old-fill"));
self.attr("data-old-fill", null);
}
hideInfo();
}
});
});
</script>
{% endblock %}

View File

@ -1,114 +0,0 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{% block title %}{% endblock %}</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'components/bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- MetisMenu CSS -->
<link href="{% static 'components/metisMenu/dist/metisMenu.min.css' %}" rel="stylesheet">
<!-- Timeline CSS -->
<link href="{% static 'css/timeline.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'css/sb-admin-2.css' %}" rel="stylesheet">
<!-- Morris Charts CSS -->
<link href="{% static 'components/morrisjs/morris.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href="{% static 'components/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<!-- DataTables CSS -->
<link href="{% static 'components/datatables/media/css/jquery.dataTables.min.css' %}" rel="stylesheet" type="text/css">
<!-- jQueryUI CSS -->
<link href="{%static 'components/jquery-ui/themes/base/jquery-ui.css' %}" rel="stylesheet" type="text/css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- jQuery -->
<script src="{% static 'components/jquery/dist/jquery.min.js' %}"></script>
<!-- Bootstrap Core JavaScript -->
<script src="{% static 'components/bootstrap/dist/js/bootstrap.min.js' %}"></script>
<!-- Metis Menu Plugin JavaScript -->
<script src="{% static 'components/metisMenu/dist/metisMenu.min.js' %}"></script>
<!-- Custom Theme JavaScript -->
<script src="{% static 'js/sb-admin-2.js' %}"></script>
<!-- d3.js -->
<script src="{% static 'components/d3/d3.js' %}"></script>
<!-- DataTable for jQuery -->
<script src="{% static 'components/datatables/media/js/jquery.dataTables.min.js' %}"></script>
<!-- jQueryUI -->
<script src="{% static 'components/jquery-ui/jquery-ui.js' %}"></script>
<script src="{% static 'js/log-dialog.js' %}"></script>
{% block head-extra %}{% endblock %}
</head>
<body>
<div id="wrapper">
<!-- Navigation -->
<nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="index.html">StackViz</a>
</div>
<!-- /.navbar-header -->
<ul class="nav navbar-top-links navbar-right">
</ul>
<!-- /.navbar-top-links -->
<div class="navbar-default sidebar" role="navigation">
{% include 'menu.html' %}
</div>
<!-- /.navbar-static-side -->
</nav>
<div id="page-wrapper">
{% block body %}{% endblock %}
</div>
<!-- /#page-wrapper -->
</div>
<!-- /#wrapper -->
</body>
</html>

View File

@ -1,49 +0,0 @@
{% extends 'template.html' %}
{% load staticfiles %}
{% block title %}Upstream Run Metadata{% endblock %}
{% block head-extra %}
<script src="{% static 'js/upstream_run.js' %}"></script>
<link href="{% static 'css/upstream_run.css' %}" rel="stylesheet" type="text/css">
{% endblock %}
{% block body %}
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Upstream Run Data</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<div class="row">
<div class="col-lg-6">
<div class="panel panel-default">
<div class="panel panel-heading" id="runs-panel-heading">Analyze Run</div>
<div class="panel panel-body">
<div id="gerrit-panel">
Enter a Gerrit Change ID (six-digit): <input type="text" id="gerrit-id">
<input id="gerrit-id-button" type="button" value="Submit">
</div>
<div id="runs-panel"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="panel panel-default" id="run-metadata-panel">
<div class="panel panel-heading">Run Metadata</div>
<div class="panel panel-body">
<div class="table-responsive" id="run-metadata-table-div">
<table class="table table-bordered table-hover table-striped" id="run-metadata-table"></table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,30 +0,0 @@
{% extends 'template.html' %}
{% block title %}Upstream Test Stats{% endblock %}
{% block body %}
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Upstream Test Stats</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<div class="row">
<div class="col-lg-8">
<div class="panel panel-default">
<div class="panel panel-heading" id="runs-panel-heading">Compare Tests</div>
<div class="panel panel-body">
<div class="col-lg-4">
<div id="test-1" class="span6">Test</div>
</div>
<div class="col-lg-4">
<div id="test-avg" class="span6">Avg</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,29 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import include
from django.conf.urls import patterns
from django.conf.urls import url
from stackviz.views.index import IndexView
urlpatterns = patterns(
'',
url(r'^$', IndexView.as_view()),
url(r'^index.html$', IndexView.as_view(), name="index"),
url(r'^tempest_', include('stackviz.views.tempest.urls')),
url(r'^devstack_', include('stackviz.views.devstack.urls')),
url(r'^upstream_', include('stackviz.views.upstream.urls')),
url(r'^dstat_', include('stackviz.views.dstat.urls'))
)

View File

@ -1,19 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
class ResultsView(TemplateView):
template_name = 'devstack/results.html'

View File

@ -1,23 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import patterns
from django.conf.urls import url
from stackviz.views.devstack.results import ResultsView
urlpatterns = patterns('',
url(r'^results$', ResultsView.as_view()),
)

View File

@ -1 +0,0 @@
__author__ = 'tim'

View File

@ -1,47 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.http import Http404
from django.http import HttpResponse
from django.views.generic import View
from stackviz import settings
_cached_csv = None
def _load_csv():
global _cached_csv
if _cached_csv:
return _cached_csv
try:
with open(settings.DSTAT_CSV, 'r') as f:
_cached_csv = f.readlines()
return _cached_csv
except IOError:
return None
class DStatCSVEndpoint(View):
def get(self, request):
csv = _load_csv()
if not csv:
raise Http404("DStat log could not be loaded at path %s"
% settings.DSTAT_CSV)
return HttpResponse(csv, content_type="text/csv")

View File

@ -1,21 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import patterns
from django.conf.urls import url
from api import DStatCSVEndpoint
urlpatterns = patterns('', url(r'^log.csv$', DStatCSVEndpoint.as_view()))

View File

@ -1,19 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
class IndexView(TemplateView):
template_name = 'index.html'

View File

@ -1,19 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
class AggregateResultsView(TemplateView):
template_name = 'tempest/aggregate.html'

View File

@ -1,136 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.http import Http404
from restless.views import Endpoint
from stackviz.parser.tempest_subunit import convert_stream
from stackviz.parser.tempest_subunit import get_providers
from stackviz.parser.tempest_subunit import reorganize
#: Cached results from loaded subunit logs indexed by their run number
_cached_run = {}
#: Cached results converted into tree form
_cached_tree = {}
#: Cached results for loaded subunit logs without details stripped out. Indexed
#: initially by log number, but contains nested dicts indexed by the test name.
_cached_details = {}
class NoRunDataException(Http404):
pass
class ProviderNotFoundException(Http404):
pass
class RunNotFoundException(Http404):
pass
class TestNotFoundException(Http404):
pass
def _load_run(provider_name, run_id):
if (provider_name, run_id) in _cached_run:
return _cached_run[provider_name, run_id]
providers = get_providers()
if not providers:
raise NoRunDataException("No test providers could be loaded")
if provider_name not in providers:
raise ProviderNotFoundException("Requested subunit provider could not "
"be found")
p = providers[provider_name]
try:
# assume first repo for now
stream = p.get_stream(run_id)
# strip details for now
# TODO(provide method for getting details on demand)
# (preferably for individual tests to avoid bloat)
converted_run = convert_stream(stream, strip_details=True)
_cached_run[provider_name, run_id] = converted_run
return converted_run
except KeyError:
raise RunNotFoundException("Requested test run could not be found")
def _load_tree(provider, run_id):
if (provider, run_id) in _cached_tree:
return _cached_tree[provider, run_id]
run = _load_run(provider, run_id)
tree = reorganize(run)
_cached_tree[provider, run_id] = tree
return tree
def _load_details(provider_name, run_id, test_name):
if (provider_name, run_id) not in _cached_details:
providers = get_providers()
if not providers:
raise NoRunDataException("No test providers could be loaded")
if provider_name not in providers:
raise ProviderNotFoundException("Requested subunit provider could "
"not be found: " + provider_name)
provider = providers[provider_name]
try:
stream = provider.get_stream(run_id)
converted_run = convert_stream(stream, strip_details=False)
# remap dict to allow direct access to details via test name
dest = {}
for entry in converted_run:
dest[entry['name']] = entry['details']
_cached_details[provider_name, run_id] = dest
except (KeyError, IndexError):
raise RunNotFoundException("Requested test run could not be found")
details_map = _cached_details[provider_name, run_id]
if test_name is None:
return details_map
else:
if test_name in details_map:
return details_map[test_name]
else:
raise TestNotFoundException(
"Requested test could not be found in run")
class TempestRunRawEndpoint(Endpoint):
def get(self, request, provider_name, run_id):
return _load_run(provider_name, int(run_id))
class TempestRunTreeEndpoint(Endpoint):
def get(self, request, provider_name, run_id):
return _load_tree(provider_name, int(run_id))
class TempestRunDetailsEndpoint(Endpoint):
def get(self, request, run_id, provider_name, test_name=None):
return _load_details(provider_name, int(run_id), test_name)

View File

@ -1,26 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
class ResultsView(TemplateView):
template_name = 'tempest/results.html'
def get_context_data(self, **kwargs):
context = super(ResultsView, self).get_context_data(**kwargs)
context['provider_name'] = self.kwargs['provider_name']
context['run_id'] = self.kwargs['run_id']
return context

View File

@ -1,26 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
class TimelineView(TemplateView):
template_name = 'tempest/timeline.html'
def get_context_data(self, **kwargs):
context = super(TimelineView, self).get_context_data(**kwargs)
context['provider_name'] = self.kwargs['provider_name']
context['run_id'] = self.kwargs['run_id']
return context

View File

@ -1,52 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import patterns
from django.conf.urls import url
from aggregate import AggregateResultsView
from results import ResultsView
from timeline import TimelineView
from api import TempestRunDetailsEndpoint
from api import TempestRunRawEndpoint
from api import TempestRunTreeEndpoint
urlpatterns = patterns(
'',
url(r'^results_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).html$',
ResultsView.as_view(),
name='tempest_results'),
url(r'^timeline_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).html$',
TimelineView.as_view(),
name='tempest_timeline'),
url(r'^api_tree_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).json$',
TempestRunTreeEndpoint.as_view(),
name='tempest_api_tree'),
url(r'^api_raw_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).json$',
TempestRunRawEndpoint.as_view(),
name='tempest_api_raw'),
url(r'^api_details_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+).json$',
TempestRunDetailsEndpoint.as_view()),
url(r'^api_details_(?P<provider_name>[\w_\.]+)_(?P<run_id>\d+)_'
r'(?P<test_name>[^/]+).json$',
TempestRunDetailsEndpoint.as_view()),
url(r'^aggregate.html$',
AggregateResultsView.as_view(),
name='tempest_aggregate_results'),
)

View File

@ -1,78 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from restless.views import Endpoint
from subunit2sql.db import api
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
def _get_runs(change_id):
"""Returns the dict of run objects associated with a changeID
When given the change_id of a Gerrit change, a connection will be made to
the upstream subunit2sql db and query all run meta having that change_id
:param change_id: the Gerrit change_id to query
:return: a json dict of run_meta objects
"""
engine = create_engine('mysql://query:query@logstash.openstack.org' +
':3306/subunit2sql')
Session = sessionmaker(bind=engine)
session = Session()
list_of_runs = api.get_runs_by_key_value(key="build_change",
value=change_id,
session=session)
ret_list = []
for run in list_of_runs:
ret_list.append(run.to_dict())
return ret_list
def _get_metadata(run_id):
"""Returns a dict of run_metadata objects associated with a run_id
:param run_id:
:return:
"""
engine = create_engine('mysql://query:query@logstash.openstack.org' +
':3306/subunit2sql')
Session = sessionmaker(bind=engine)
session = Session()
metadata = api.get_run_metadata(run_id, session=session)
ret_list = []
for meta in metadata:
ret_list.append(meta.to_dict())
return ret_list
class GerritURLEndpoint(Endpoint):
def get(self, request, change_id):
return _get_runs(change_id)
class RunMetadataEndpoint(Endpoint):
def get(self, request, run_id):
return _get_metadata(run_id)

View File

@ -1,19 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
class RunView(TemplateView):
template_name = 'upstream/run.html'

View File

@ -1,23 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.views.generic import TemplateView
# TODO(Planned functionality)
# Compare one specific test against its moving average
#
class TestView(TemplateView):
template_name = 'upstream/test.html'

View File

@ -1,41 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import patterns
from django.conf.urls import url
from run import RunView
from test import TestView
from api import GerritURLEndpoint
from api import RunMetadataEndpoint
urlpatterns = patterns('',
url(r'^run.html$',
RunView.as_view(),
name='run_metadata'),
url(r'^test.html$',
TestView.as_view(),
name='test_data'),
url(r'^api_changeid_(?P<change_id>\d+).json$',
GerritURLEndpoint.as_view(),
name='gerrit_url'),
url(r'^api_run_id_(?P<run_id>[a-zA-Z0-9!$* '
'\t\r\n\-]+).json$',
RunMetadataEndpoint.as_view(),
name='run_metadata_url')
)

View File

@ -1,28 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
WSGI config for stackviz project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stackviz.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

View File

@ -32,4 +32,4 @@ commands = oslo_debug_helper {posargs}
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,app,test,build,node_modules