blob: 6dfe872c495b130dfa97e9761c5f6b9e9d6fb818 [file]
<html>
<head>
<title>WebM Dashboard</title>
<link rel="stylesheet" type="text/css" href="/css/main.css"/>
<!-- The majority of our javascript -->
<script type="text/javascript" src="/js/jstree/_lib/jquery.js"></script>
<script type="text/javascript" src="/js/jstree/jquery.jstree.js"></script>
<link rel="stylesheet" type="text/css" href="/js/jquery-ui/css/smoothness/jquery-ui-1.8.21.custom.css" />
<script type="text/javascript" src="/js/jquery-ui/js/jquery-ui-1.8.21.custom.min.js"></script>
<!-- Making an AJAX request to a local server function ------------------------>
<script type="text/javascript" src="/js/json2.js"></script>
<script type="text/javascript">
function is_distortion(metric)
{
return (['CxFPS', 'DxFPS'].indexOf(metric) == -1);
};
function range(start, end)
{
var foo = [];
for (var i = start; i <= end; i++)
foo.push(i);
return foo;
}
var MetricState = new Array();
var ConfigState = new Array();
var FileState = new Array();
var CommitState = new Array();
var FileTableState = new Array();
var openList = new Array();
{% if development %}
var development = true;
{% else %}
var development = false;
{% endif %}
var init = true;
var tableinit = true;
// We build up our State lists (if applicable) from our template values.
{% if metrics %}
MetricState = '{{metrics}}'.split(',');
{% endif %}
{% if configs %}
ConfigState = '{{configs}}'.split(',');
{% endif %}
{% if filesets %}
FileState = '{{filesets}}'.split(',');
{% endif %}
{% if commits %}
CommitState = '{{commits}}'.split(',');
{% endif %}
{% if filenames %}
FileTableState = '{{filenames}}'.split(',');
{% endif %}
{% if opentrees %}
openList = '{{opentrees}}'.split(',');
{% endif %}
</script>
<script type="text/javascript" src="/js/treehandler.js"></script>
<!-- the AJAX API for google charts -->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script>
google.load('visualization', '1.0', {'packages':['corechart', 'table']});
function FetchMetric(metricurl, series_name, responses) {
var split_lst = metricurl.split('/');
$.ajax({
type: "GET",
url: metricurl,
success: function(response){
responses.push({response: response, series_name: series_name,
metric: split_lst[2], config: split_lst[3],
filename: split_lst[4], commit: split_lst[5]});
},
error: function(xhr, ajaxOptions, thrownError) {
// Nothing for now
},
});
}
function ChartFiller(baseline) {
var dist_responses = new Array();
var bar_responses = new Array();
var time_responses = new Array();
var queryNum = MetricState.length * ConfigState.length *
FileTableState.length * CommitState.length;
// A loading image
$('#chartdiv').html("<img src='/images/ajax-loader.gif' alt='Loading...' style='display:block; margin:auto;'>");
// Wait until all data is fetched to generate graphs
$(document).ajaxStop(function() {
$(this).unbind("ajaxStop");
if (dist_responses.length > 0) {
drawChart(dist_responses);
}
else {
$('#chartdiv').html("");
$('#tabs2').html("");
}
if (bar_responses.length > 0) {
drawChart2(bar_responses);
}
if (time_responses.length > 0) {
drawTimeSeriesChart(time_responses);
}
});
//var files = FileState.filter(removeParents);
var files = FileTableState;
// Add the baseline to the list of commits, if one is set
var commits = Array();
if (baseline)
commits.push(baseline);
commits = commits.concat(CommitState.filter(removeParents));
// Ew, quadruple nested loop
for (var i = 0; i < MetricState.length; i++) {
// one series per commit (we exclude cases of time series)
if (bar_responses.length == 0 && queryNum && !is_distortion(MetricState[i]) && !(commits[0][0] === "~"))
for (var t = 0; t < commits.length; t++)
bar_responses[t] = new Array();
for (var j = 0; j < ConfigState.length; j++) {
for (var k = 0; k < files.length; k++) {
if (files[k].slice(0, 8) === 'OVERALL:') {
if (queryNum == 1) {
$('#chartdiv').html("Please select a file (or files) to view graph.");
$('#status').html("");
$('#tabs2').html("");
}
$("#commitInfo").dialog('close');
$("#configInfo").dialog('close');
continue;
}
for (var t = 0; t < commits.length; t++) {
// We put together a url
var metric = MetricState[i];
var config = ConfigState[j];
var file = files[k];
var commit = commits[t];
metricurl = "/metric-data/" + metric + "/" + config + "/" + file + "/" + commit
// pretty-up commit hash
if (baseline && t == 0)
commit = commit.slice(0,9) + " (Baseline)";
else
commit = commit.slice(0,9);
// Build a more readable version of the series name
var series_name = []
if (MetricState.length > 1) {
series_name.push(metric);
}
if (ConfigState.length > 1) {
series_name.push(config);
}
if (commit[0] === "~") {
if (files.length > 1) {
series_name.push(file);
}
series_name.push(commit);
series_name = {label: series_name.join(" "), commit: commit};
FetchMetric(metricurl, series_name, time_responses);
}
else if (is_distortion(metric)) {
if (files.length > 1) {
series_name.push(file);
}
series_name.push(commit);
series_name = {label: series_name.join(" "), commit: commit};
FetchMetric(metricurl, series_name, dist_responses);
} else {
if (series_name.length == 0 || files.length > 1) {
series_name.push(file);
}
series_name = {label: series_name.join(" "), commit: commit};
FetchMetric(metricurl, series_name, bar_responses[t]);
}
}
}
}
}
}
function StatisticFiller() {
// This function is reponsible for filling a table with the composite metrics
// for each file
// We ask the backend for a chart to display - based on metric, fileset,
// config, and commit selected.
var metrics = MetricState.join(",");
var configs = ConfigState.join(",");
var files = FileState.filter(removeParents).join(",");
var commits = CommitState.filter(removeParents).join(",");
var compurl = "/average-improvement/" + metrics + "/" + configs + "/" + files + "/" + commits;
if ((MetricState.length * ConfigState.length *
FileState.filter(removeParents).length *
CommitState.filter(removeParents).length) == 0) {
$("#commitInfo").dialog('close');
$("#configInfo").dialog('close');
$('#tabs11').html("Please select a valid run.");
$('#tabs2').html("");
$('#githistory').html("");
$('#chartdiv').html("");
return;
}
$('#tabs11').html("Loading...");
$('#chartdiv').html("");
// What baseline should the curves be compared to?
$.ajax({
type: "GET",
url: compurl,
success: function(response){
if (response.data.length > 0) {
ImprovementTable(response);
}
},
});
}
// ------------------------------------------------------------------------
function ImprovementTable(data_in) {
var baseline = data_in.baseline
data_in = data_in.data
numCol = data_in.length;
var data1 = new google.visualization.DataTable();
var view;
var data2;
var formatter = new google.visualization.NumberFormat(
{suffix: '%', fractionDigits: 3});
var formatter2 = new google.visualization.TableColorFormat();
formatter2.addRange("OVERALL", "OVERALM", '#999');
// We fill our table
data1.addColumn('string', 'Filename');
data1.addColumn('number', data_in[0].col);
data1.addRows(data_in[0].data);
formatter.format(data1, 1);
formatter2.format(data1, 0);
// Make it a chart
view = new google.visualization.DataView(data1);
for (var i = 1; i < numCol; i++){
data2 = new google.visualization.DataTable();
data2.addColumn('string', 'Filename');
data2.addColumn('number', data_in[i].col);
data2.addRows(data_in[i].data);
formatter.format(data2, 1);
formatter2.format(data1, 0);
// A list of numbers from 1 to i
leftColumns = range(1, i);
// Join the two tables into one view
view = new google.visualization.data.join(view, data2, 'full', [[0,0]], leftColumns, [1]);
}
var tableOptions = {width: 'automatic', allowHtml: 'true'};
var table = new google.visualization.Table(document.getElementById("tabs11"));
google.visualization.events.addListener(table, 'select', selectHandler);
google.visualization.events.addListener(table, 'sort', headerHandler);
google.visualization.events.addListener(table, 'ready', setupHandler);
table.draw(view, tableOptions);
var commitstr = CommitState.join(',');
var commits = CommitState.filter(removeParents).join(",");
$("#githistory").html("Loading...");
$("#githistory").load("/history/"+baseline+","+commits);
$('#container').show();
$("#commitInfo").dialog('close');
$("#configInfo").dialog('close');
$('#chartdiv').html("Please select a file (or files) to view graph.");
$('#status').html("");
// We add some bindings to allow for selecting rows of the table
// Add our selection handler
function setupHandler() {
// We now select any default values (in FileTableState)
if (!tableinit)
// If we are not preselecting anything.
return;
var numRows = data_in[0].data.length;
var rows_to_select = [];
for (var i = 0; i < numRows; i++) {
for (var j = 0; j < FileTableState.length; j++) {
if (view.getValue(i, 0) === FileTableState[j])
rows_to_select.push({row: i});
}
}
tableinit = false;
table.setSelection(rows_to_select);
selectHandler();
}
// The selection handler
// Loop through all items in the selection and add them to FileTableState
function selectHandler() {
FileTableState.length = 0; // Clear out the global array
var selection = table.getSelection();
for (var i = 0; i < selection.length; i++) {
var item = selection[i];
// We get the name of the file selected
if (item.row != null && item.column != null) {
var str = view.getFormattedValue(item.row, item.column);
} else if (item.row != null) {
var str = view.getFormattedValue(item.row, 0);
} else if (item.column != null) {
var str = view.getFormattedValue(0, item.column);
}
FileTableState.push(str);
}
ChartFiller(baseline);
if (selection.length == 0) {
// We get rid of tables that are no longer relevant
$("#commitInfo").dialog('close');
$("#configInfo").dialog('close');
$('#chartdiv').html("Please select a file (or files) to view graph.");
$('#status').html("");
$('#tabs2').html("");
}
}
// Called when a header is clicked (not a row)
function headerHandler(event) {
if (commitstr[0] === '~') {
return; // We return early when we are doing a time series
}
$("#commitInfo").dialog('close');
$("#commitInfo").html("Loading...");
$("#commitInfo").load("/commit-info/"+commitstr+"/"+baseline);
$('#commitInfo').dialog('open');
}
}
// -----------------------------------------------------------------------------
function drawChart(data_in) {
var numCurves = data_in.length;
var view;
var data2;
// We have at least one curve
data_in1 = data_in[0].response;
var data1 = new google.visualization.DataTable();
data1.addColumn('number', 'Bitrate');
data1.addColumn('number', 'target bitrate');
data1.addColumn('number', data_in[0].series_name.label);
data1.addRows(data_in1.data);
view = new google.visualization.DataView(data1);
for (var i = 1; i < numCurves; i++){
data_in2 = data_in[i].response.data;
data2 = new google.visualization.DataTable();
data2.addColumn('number', 'Bitrate');
data2.addColumn('number', 'target bitrate');
data2.addColumn('number', data_in[i].series_name.label);
data2.addRows(data_in2);
// Join the two tables into one view
leftColumns = range(2, i + 1);
view = new google.visualization.data.join(view, data2, 'full', [[0,0], [1,1]], leftColumns, [2]);
}
// Set chart options
var chartOptions = {title:'Rate Distortion Curves',
//chartArea: {right: 20, bottom: 10, height: "90%", width: "70%"},
chartArea: {height: "70%", width: "70%"},
vAxis: {title: data_in1.yaxis},
hAxis: {title: 'Bitrate'},
//vAxis: {title: 'y axis'},
'pointSize':2,
'lineWidth':1,
'interpolateNulls':true,
};
var tableOptions = {width: 'automatic'};
// We filter out target_bitrate for the sake of the chart
chart_view = new google.visualization.DataView(view);
chart_view.hideColumns([1]);
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.LineChart(document.getElementById("chartdiv"));
var table = new google.visualization.Table(document.getElementById("tabs2"));
// Add our handlers.
google.visualization.events.addListener(chart, 'select', selectHandler);
google.visualization.events.addListener(chart, 'onmouseover', chartMouseOver);
chart.draw(chart_view, chartOptions);
table.draw(view, tableOptions);
// We now add a function to resize the chart when the page is resized
$(window).resize(function() {
chart.draw(chart_view, chartOptions);
statusbar = document.getElementById('status');
statusbar.style.display = 'none';
});
// The selection handler.
function selectHandler() {
var selection = chart.getSelection();
chart.setSelection();
for (var i = 0; i < selection.length; i++) {
var item = selection[i];
var commits = new Array();
var baseline = '';
// If we click something in the legend
if (item.column != null && item.row == null) {
// We get all the series labels
for (var j = 0; j < numCurves; j++){
var commit = data_in[j].series_name.label;
var lastchar = commit[commit.length-1];
if (lastchar === ')')
baseline = data_in[j].commit;
else
commits.push(data_in[j].commit);
}
$("#commitInfo").dialog('close');
$("#commitInfo").html("Loading...");
$("#commitInfo").load("/commit-info/"+commits.join(',')+"/"+baseline);
$('#commitInfo').dialog('open');
}
// If we click on a data point
else if (item.column != null && item.row != null) {
var metric = data_in[item.column - 1].metric;
var config = data_in[item.column - 1].config;
var filename = data_in[item.column - 1].filename;
var commit = data_in[item.column - 1].commit;
var bitrate = view.getValue(item.row, 1);
// We need metric, file, config, and commit for this data point
var url = metric + "/" + config + '/' + filename + '/' + commit + '/' +
bitrate;
$("#configInfo").dialog('close');
$("#configInfo").html("Loading...");
$("#configInfo").load("/config-info/" + url);
$('#configInfo').dialog('open');
}
}
}
function chartMouseOver(e){
pointDifference(e.row, e.column)
}
function pointDifference(row, col){
// ported over from visualmetrics.py
if(!row || !col)
return;
var cols = chart_view.getNumberOfColumns();
var rows = chart_view.getNumberOfRows();
var sel_bitrate = chart_view.getValue(row, 0 );
var sel_metric = chart_view.getValue(row, col);
var message = "At " + sel_metric.toFixed(2) + " decibels, <em>"
message = message + chart_view.getColumnLabel(col) + "</em> is <ul>"
for( var i=1;i<cols;++i){
var metric_greatest_thats_less = 0;
var rate_greatest_thats_less = 0;
var metric_smallest_thats_greater = 999;
var rate_smallest_thats_greater = 0;
if(i==col)
continue;
// find the lowest metric for this column thats greater than sel_metric and
// the highest metric for this column thats less than the metric
for(var line_count = 0; line_count < rows; ++line_count) {
this_metric = chart_view.getValue(line_count, i)
this_rate = chart_view.getValue(line_count, 0)
if(!this_metric)
continue;
if(this_metric > metric_greatest_thats_less &&
this_metric < sel_metric) {
metric_greatest_thats_less = this_metric;
rate_greatest_thats_less = this_rate;
}
if(this_metric < metric_smallest_thats_greater &&
this_metric > sel_metric) {
metric_smallest_thats_greater = this_metric;
rate_smallest_thats_greater = this_rate;
}
}
if(rate_smallest_thats_greater == 0 || rate_greatest_thats_less == 0) {
message = message + " <li> Couldn't find a point on both sides.</li>"
}
else
{
metric_slope = ( rate_smallest_thats_greater - rate_greatest_thats_less) /
( metric_smallest_thats_greater - metric_greatest_thats_less);
projected_rate = ( sel_metric - metric_greatest_thats_less) *
metric_slope + rate_greatest_thats_less;
difference = 100 * (projected_rate / sel_bitrate - 1);
if (difference > 0)
message = message + "<li> " + difference.toFixed(2) +
"% smaller than <em>" +
chart_view.getColumnLabel(i) + "</em></li> "
else
message = message + "<li> " + -difference.toFixed(2) +
"% bigger than <em>" +
chart_view.getColumnLabel(i) + "</em></li> "
}
}
message = message + "</ul>"
statusbar = document.getElementById('status');
statusbar.innerHTML = "<p>" + message + "</p>";
statusbar.style.display = 'block';
}
}
// -----------------------------------------------------------------------------
function drawChart2(data_in) {
// We make a candlestick chart
function find_min_avg_max(arr) {
var min_ele = arr[0];
var max_ele = arr[0];
var sum = 0;
var count = 0;
for (var k = 0; k < arr.length; k++) {
count += 1;
sum += arr[k];
if (arr[k] < min_ele)
min_ele = arr[k];
else if (arr[k] > max_ele)
max_ele = arr[k];
}
// we assume arr.length >= 1
return [min_ele, sum/count, sum/count, max_ele];
}
var mat = [[]]
// series labels, one per commit
mat[0][0] = "unused";
for (var series = 0; series < data_in.length; series++) {
var series_name = data_in[series][0].series_name;
for (var i = 0; i<4; i++)
mat[0][series*4 + i + 1] = series_name.commit;
}
// one table row per file
for (var rowidx = 0; rowidx < data_in[0].length; rowidx++) {
var row_label = data_in[0][rowidx].series_name.label;
var row = new Array(row_label);
// four columns per series (commit)
for (var series = 0; series < data_in.length; series++) {
var series_data = data_in[series][rowidx].response.data;
var comparr = [];
for (var i = 0; i < series_data.length; i++) {
comparr[i] = series_data[i][0];
}
row = row.concat(find_min_avg_max(comparr));
}
mat[rowidx+1] = row;
}
// Convert to a data table and format for display
var formatter = new google.visualization.NumberFormat(
{fractionDigits: 3});
var data1 = new google.visualization.arrayToDataTable(mat, false);
for (var i = 1; i < mat[1].length; i++)
formatter.format(data1, i);
view = new google.visualization.DataView(data1);
// Set chart options
var chartOptions = {title:'Performance',
chartArea: {height: "70%", width: "70%"},
vAxis: {title: data_in[0][0].response.yaxis},
};
var tableOptions = {width: 'automatic'};
// Instantiate and draw our chart, passing in some options.
var chart2 = new google.visualization.CandlestickChart(document.getElementById("chartdiv"));
var table2 = new google.visualization.Table(document.getElementById("tabs2"));
chart2.draw(view, chartOptions);
table2.draw(view, tableOptions);
// We now add a function to resize the chart when the page is resized
$(window).resize(function() {
chart2.draw(view, chartOptions);
});
// Add our selection handler.
google.visualization.events.addListener(chart2, 'select', selectHandler);
// The selection handler.
function selectHandler() {
var selection = chart2.getSelection();
chart2.setSelection();
for (var i = 0; i < selection.length; i++) {
var item = selection[i];
var commits = new Array();
var baseline = '';
// If we click something in the legend
if (item.column != null && item.row == null) {
for (var j = 0; j < data_in.length; j++){
var series_name = data_in[j][0].series_name;
var commit = series_name.commit;
var lastchar = commit[commit.length-1];
if (lastchar === ')')
baseline = data_in[j][0].commit;
else
commits.push(data_in[j][0].commit);
}
$("#commitInfo").dialog('close');
$("#commitInfo").html("Loading...");
$("#commitInfo").load("/commit-info/"+commits.join(',')+"/"+baseline);
$('#commitInfo').dialog('open');
}
// If we click on a data point
else if (item.column != null && item.row != null) {
var commit_start = view.getColumnLabel(item.column).slice(0,9);
var filename = view.getValue(item.row, 0);
var commit;
var metric;
var config;
// Is there a better way of corresponding points and thier set-ups?
for (var j = 0; j < data_in.length; j++) {
// here data_in[j] is an array of objects - each array being one commit
for (var k = 0; k < data_in[j].length; k++){
var obj = data_in[j][k];
if (obj.commit.slice(0,9) === commit_start){
metric = obj.metric;
commit = obj.commit;
config = obj.config;
break;
}
}
}
// We need metric, file, config, and commit for this data point
var url = metric + "/" + config + '/' + filename + '/' + commit + '/' + '';
$("#configInfo").dialog('close');
$("#configInfo").html("Loading...");
$("#configInfo").load("/config-info/" + url);
$('#configInfo').dialog('open');
}
}
}
}
// -----------------------------------------------------------------------------
function drawTimeSeriesChart(data_in) {
var numCurves = data_in.length;
var view;
var data2;
data_in1 = data_in[0].response;
for (var j = 0; j < data_in1.data.length; j++) {
data_in1.data[j][0] = new Date(data_in1.data[j][0][0], data_in1.data[j][0][1],
data_in1.data[j][0][2], data_in1.data[j][0][3],
data_in1.data[j][0][4], data_in1.data[j][0][5]);
}
var data1 = new google.visualization.DataTable();
var to_graph = {}
data1.addColumn('datetime', 'Datetime');
data1.addColumn('string', 'Commit Id');
data1.addColumn('number', data_in[0].series_name.label);
data1.addRows(data_in1.data);
data1.sort(0);
view = new google.visualization.DataView(data1);
for (var i = 1; i < numCurves; i++){
data_in2 = data_in[i].response.data;
for (var j = 0; j < data_in2.length; j++) {
data_in2[j][0] = new Date(data_in2[j][0][0], data_in2[j][0][1],
data_in2[j][0][2], data_in2[j][0][3],
data_in2[j][0][4], data_in2[j][0][5]);
}
data2 = new google.visualization.DataTable();
data2.addColumn('datetime', 'Datetime');
data2.addColumn('string', 'Commit Id');
data2.addColumn('number', data_in[i].series_name.label);
data2.addRows(data_in2);
data2.sort(0);
leftColumns = range(2, i + 1);
view = new google.visualization.data.join(view, data2, 'full', [[0,0], [1,1]], leftColumns, [2]);
}
chart_view = new google.visualization.DataView(view);
// We filter out the 2nd column (commit data) to display
chart_view.hideColumns([1]);
var chartOptions = {title:'Time Series',
chartArea: {height: "70%", width: "70%"},
vAxis: {title: data_in1.yaxis},
hAxis: {title: 'Date'},
'pointSize':2,
'lineWidth':1,
'interpolateNulls':true,
};
var tableOptions = {width: 'automatic'};
var chart = new google.visualization.LineChart(document.getElementById("chartdiv"));
var table = new google.visualization.Table(document.getElementById("tabs2"));
chart.draw(chart_view, chartOptions);
table.draw(view, tableOptions);
$(window).resize(function() {
chart.draw(chart_view, chartOptions);
});
// Add our selection handler.
google.visualization.events.addListener(chart, 'select', selectHandler);
// The selection handler.
// Loop through all items in the selection and concatenate
// a single message from all of them.
function selectHandler() {
var selection = chart.getSelection();
var commitid = '';
for (var i = 0; i < selection.length; i++) {
var item = selection[i];
if (item.row != null && item.column != null) {
commitid = view.getFormattedValue(item.row, 1);
}
}
if (commitid !== ''){
$("#commitInfo").dialog('close');
$("#commitInfo").html("Loading...");
$("#commitInfo").load("/commit-info/"+commitid+"/");
$('#commitInfo').dialog('open');
}
else {
$('#commitInfo').dialog('close');
}
}
}
</script>
<script>
$(function() {
$( "#topcenter" ).resizable({
handles: 's',
stop: function(event, ui) {
$(this).css("width", '');
}
});
$("#helpdialog").dialog({ autoOpen: false, width: 800 });
$("#helpbutton").bind('click', help);
$("#sharedialog").dialog({ autoOpen: false, width: 800 });
$("#sharebutton").bind('click', share);
$("#commitInfo").dialog({ autoOpen: false, width: 800, position: ['right','top'] });
$("#configInfo").dialog({ autoOpen: false, width: 800, position: ['right','top'] });
$('#container').hide();
// This hides the whitespace used for storing the chart
$(document).click(function() {
if ($('#chartdiv').is(':empty')){
$('#container').hide();
}
else {
$('#container').show();
}
});
});
function help() {
$("#helpdialog").dialog('open');
}
function share() {
// This function creates and displays a custom url for accessing the current
// dashboard view
var url;
if (development)
url = "localhost:8080/";
else
url = "http://www.dashboard.webmproject.org/";
var metrics = MetricState.join(",");
var configs = ConfigState.join(",");
var filesets = FileState.join(",");
var commits = CommitState.filter(removeParents).join(",");
var filenames = FileTableState.join(",");
var opentrees = openList.join(',');
url = url + metrics + "/" + configs + "/" + filesets + "/" + commits +
"/" + filenames + "/" + opentrees;
var hyperurl = '<a href="' + url + '" target="_blank">' + url + "</a>";
$('#shareurl').html(hyperurl);
$("#sharedialog").dialog('open');
}
</script>
<!-- ----------------------------------------------------------------------- -->
</head>
<body>
<div id="header">
<div id="headerLeft" onclick="javascript:treeReset();" style="cursor:pointer;">
WebM Dashboard
</div>
<div id="headerRight">
<div align="right" style="display:table-cell; vertical-align: middle;">
{% if user %}
Welcome, {{ user.nickname }}! <a href="{{ logout_url }}">Sign Out</a>
{% else %}
<a href="{{ login_url }}">Sign In</a>
{% endif %}
</div>
&nbsp;|&nbsp;
<!-- A button to clear out all trees -->
<a href="#null" id="resetbutton" style="display:table-cell; vertical-align: middle;">
Reset
</a>
&nbsp;|&nbsp;
<!-- Help button -->
<div id="helpdialog" title="About this page">
<ul>
<li>To view a graph of performance data, please select some metrics, configurations, filesets and patch sets. </li>
<li> The page will automatically update to reflect what data is available.</li>
<li> Then in the area below, pick a filename to view the graph.</li>
<li> Multiple filenames can be selected with the ctrl or shift keys.</li>
<li>The raw data for a graph is viewable in the "Table" tab.</li>
<li>The reset button unchecks all selected options. </li>
</ul>
</div>
<a href="#null" id="helpbutton" style="display:table-cell; vertical-align: middle;">
Help
</a>
&nbsp;|&nbsp;
<!-- A button to share the current view -->
<div id="sharedialog" title="Share this view">
To share the current view with someone else, use this custom url below:
<div id="shareurl">
</div>
</div>
<a href="#null" id="sharebutton" style="display:table-cell; vertical-align: middle;">
Share
</a>
</div>
</div><!-- End of header -->
<!-- Where the jstrees go -->
<div id="topcenter" class="ui-widget-content ui-resizable">
<!-- TODO: can we locate the icon at S, rather than SE? -->
<div unselectable="on" style="z-index: 1001; -moz-user-select: none;"
class="ui-resizable-handle ui-resizable-se ui-icon ui-icon-arrowthick-2-n-s"></div>
<div id="topcolumnsm">
<h3>Metrics</h3>
<div id="treeHolder">
<div id="treeView1">
</div>
</div>
</div>
<div id="topcolumnsm">
<h3>Configs</h3>
<div id="treeHolder">
<div id="treeView2">
</div>
</div>
</div>
<div id="topcolumnsm">
<h3>File Sets</h3>
<div id="treeHolder">
<div id="treeView3">
</div>
</div>
</div>
<div id="topcolumn">
<h3>Patch Sets</h3>
<div id="treeHolder">
<div id="treeView4">
</div>
</div>
</div>
</div> <!-- end of top -->
<div id="bottomcenter">
<div id="commitInfo" title="Commit Information"> </div>
<div id="configInfo" title="Configuration Information"> </div>
<!-- the tab widget -->
<div id="tabs">
<ul class="tabnav">
<li><a href="#tabs1">Graph</a></li>
<li><a href="#tabs2">Table</a></li>
<li><a href="#tabs3">Commit History</a></li>
</ul>
<div id="tabs1" class="tabdiv">
<div>
<div id="tabs11"></div>
<div id="container">
<div id="dummy"></div>
<div id="chartdiv"></div>
<div id="status"> </div>
</div>
</div>
<div class="clear"></div>
</div>
<div id="tabs2" class="tabdiv">
</div>
<div id="tabs3" class="tabdiv">
<div class="ui-widget-content" id="githistory"></div>
</div>
</div> <!-- end of tabs -->
</div> <!-- end of bottom center -->
<div class="clear"></div>
<div id="footer">
<a href="http://webmproject.org" target="_blank"> The WebM Project </a>
</div> <!-- end of footer -->
</body>
</html>