improved frontend for benchmarking

This commit is contained in:
Pedro 2015-01-12 17:24:24 +01:00
parent e7764eae7a
commit b262cf9fe6
7 changed files with 297 additions and 97 deletions

5
frontend-hyrise/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,119 @@
var HyriseConnector = function(endpointUrl) {
this._endpointUrl = endpointUrl;
}
HyriseConnector.prototype.executeSQL = function(query, callback, errorCallback) {
var url = encodeURI(this._endpointUrl);
var self = this;
jQuery.ajax({
type: "POST",
url: url,
dataType: 'json',
data: {
performance: true,
sql: query
},
success: function(result) {
if (typeof result.real_size === "undefined") {
result.real_size = 0;
result.rows = [];
result.header = [];
if (!result.performanceData) result.performanceData = [];
}
self._formatPerformanceData(result);
callback(result);
},
error: errorCallback
});
return this;
};
HyriseConnector.prototype._formatPerformanceData = function(object) {
var performanceData = object.performanceData;
var totalTime = 0;
var queryTaskTime = 0;
$.each(performanceData, function(i, data) {
data.time_ms = data.endTime - data.startTime;
totalTime += data.time_ms;
if (data.name === 'SQLQueryTask') queryTaskTime += data.time_ms;
});
object.performanceData = {
totalTime: totalTime,
queryTaskTime: queryTaskTime,
operators: performanceData
}
};
HyriseConnector.prototype.benchmarkSQL = function(query, numRuns, callback) {
var self = this;
function __aggregateData(allData) {
var result = {
totalTime: 0,
queryTaskTime: 0,
numRuns: allData.length,
operators: []
};
var operatorMap = {};
$.each(allData, function(i, run) {
var perfData = run.performanceData;
result.totalTime += perfData.totalTime;
result.queryTaskTime += perfData.queryTaskTime;
$.each(perfData.operators, function(i, data) {
if (!(data.id in operatorMap)) {
operatorMap[data.id] = data;
} else {
operatorMap[data.id].duration += data.duration;
operatorMap[data.id].startTime += data.startTime;
operatorMap[data.id].endTime += data.endTime;
operatorMap[data.id].time_ms += data.time_ms;
}
});
});
// Calc average and Transform into array
result.totalTime /= result.numRuns;
result.queryTaskTime /= result.numRuns;
$.each(operatorMap, function(id, data) {
data.duration /= result.numRuns;
data.startTime /= result.numRuns;
data.time_ms /= result.numRuns;
result.operators.push(data);
});
callback({
performanceData: result
});
}
var allData = [];
var n = 0;
function __run() {
++n;
self.executeSQL(query, function(result) {
allData.push(result);
// Run again or return aggregated Data
if (n < numRuns) __run();
else __aggregateData(allData);
});
}
__run();
};

View File

@ -1,30 +0,0 @@
var HyriseSQLConnector = function(endpointUrl) {
this._endpointUrl = endpointUrl;
}
HyriseSQLConnector.prototype.executeSQLQuery = function(query, callback, error_callback) {
var url = encodeURI(this._endpointUrl);
jQuery.ajax({
type: "POST",
url: url,
dataType: 'json',
data: {
performance: true,
sql: query
},
success: function(result) {
if (typeof result.real_size === "undefined") {
result.real_size = 0;
result.rows = [];
result.header = [];
}
callback(result);
},
error: error_callback
});
return this;
};

View File

@ -1,9 +1,9 @@
<html> <html>
<head> <head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"> <link href="bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="hyrise-sql-connector.js"></script> <script type="text/javascript" src="hyrise-connector.js"></script>
<script type="text/javascript" src="ui.js"></script> <script type="text/javascript" src="ui.js"></script>
<link href="style.css" rel="stylesheet" /> <link href="style.css" rel="stylesheet" />
<title>Hyrise SQL Frontend</title> <title>Hyrise SQL Frontend</title>
@ -11,7 +11,7 @@
<body> <body>
<div class="content"> <div class="content">
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-5">
<!-- Config --> <!-- Config -->
<div class="row"> <div class="row">
<div class="form-group col-sm-12"> <div class="form-group col-sm-12">
@ -21,36 +21,54 @@
<!-- Sample Queries --> <!-- Sample Queries -->
<div class="row" style="margin-bottom: 10px;"> <div class="row" style="margin-bottom: 10px;">
<div class="col-sm-8" id="sampleQueries"></div> <div class="col-sm-12" id="sampleQueries"></div>
<div class="col-sm-4" id="buggyQueries"></div> <!-- <div class="col-sm-4" id="buggyQueries"></div> -->
</div> </div>
<!-- Input --> <!-- Input -->
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<textarea id="queryInput" class="form-control" style="height: 300px; resize: none;" placeholder="Enter your SQL query here..."></textarea> <textarea id="queryInput" class="form-control" placeholder="Enter your SQL query here..."></textarea>
</div> </div>
</div> </div>
<!-- Submit --> <!-- Submit -->
<div class="row" style="margin: 10px 0px;"> <div class="row" style="margin: 10px 0px;">
<div class="col-sm-12"> <div class="col-sm-6">
<button type="button" class="btn btn-primary" id="submitBtn">Submit Query (Shift + Enter)</button> <button type="button" class="btn btn-primary" id="submitBtn">Submit Query (Shift + Enter)</button>
</div> </div>
<div class="col-sm-6">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1"># times</span>
<input type="text" class="form-control" id="benchmarkInput" placeholder="Number of times to run" value="10" />
<span class="input-group-btn">
<button class="btn btn-primary" type="button" id="benchmarkBtn">Benchmark</button>
</span>
</div><!-- /input-group -->
</div> </div>
</div>
</div>
<div class="col-lg-7">
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<!-- View: Performance Data --> <!-- View: Performance Data -->
<h3>Performance Data</h3> <h3>Performance Data (<span id="timeTotal">?</span>ms)</h3>
<table id="performanceDataTable" class="table table-bordered table-striped table-hover"> <table id="performanceDataTable" class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th data-key="id">ID</th>
<th>Name</th> <th data-key="name">Name</th>
<th>Duration</th> <th data-key="duration">Duration</th>
<th>Start Time</th> <!-- <th>Start Time</th> -->
<th>End Time</th> <!-- <th>End Time</th> -->
<th data-key="time_ms">Time (ms)</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
@ -58,8 +76,8 @@
</div> </div>
</div> </div>
</div> <div class="row">
<div class="col-lg-6"> <div class="col-sm-12">
<!-- View: Result Table --> <!-- View: Result Table -->
<h3>Results (first 100 rows)</h3> <h3>Results (first 100 rows)</h3>
<div id="msgContainer" class="alert alert-success" role="alert"> <div id="msgContainer" class="alert alert-success" role="alert">
@ -68,6 +86,9 @@
<table id="resultTable" class="table table-bordered table-striped table-hover"></table> <table id="resultTable" class="table table-bordered table-striped table-hover"></table>
</div> </div>
</div> </div>
</div>
</div>
</div> </div>
</body> </body>
</html> </html>

4
frontend-hyrise/jquery-1.11.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -21,3 +21,9 @@ table {
margin: auto; margin: auto;
margin-top: 10px; margin-top: 10px;
} }
textarea#queryInput {
font-family: 'Monospace';
height: 500px;
resize: none;
}

View File

@ -1,42 +1,63 @@
function SetUiStateRunning() {
$('#resultTable').html('');
$('#resultInfo').html('waiting for result...');
$('#msgContainer').attr('class', 'alert alert-warning');
$('#performanceDataTable tbody').html('');
}
function SetUiStateError(msg) {
$('#resultInfo').html(msg);
$('#msgContainer').attr('class', 'alert alert-danger');
$('#performanceDataTable tbody').html('');
}
function SetUiStateSuccess(msg) {
$('#msgContainer').attr('class', 'alert alert-success');
$('#resultInfo').html(msg);
}
function GetHyriseUrl() {
var endpointUrl = $('#endpointInput').val();
return endpointUrl;
}
function GetQuery() {
var query = $('#queryInput').val();
// Check whether a part of the query has been selected
var selectedText = GetSelectedText();
if (query.indexOf(selectedText) >= 0) {
query = selectedText;
}
return query;
}
/** /**
* Bootstrap * Bootstrap
*/ */
$(function() { $(function() {
loadSampleQueries('sample-queries.sql'); LoadSampleQueries('sample-queries.sql');
// Simple query submit
$('#submitBtn').click(function() { $('#submitBtn').click(function() {
$('#resultTable').html(''); SetUiStateRunning();
$('#resultInfo').html('waiting for result...'); var query = GetQuery();
$('#msgContainer').attr('class', 'alert alert-warning'); var hyrise = new HyriseConnector(GetHyriseUrl());
var endpointUrl = $('#endpointInput').val(); hyrise.executeSQL(query, function(result) {
var query = $('#queryInput').val();
// Check whether a part of the query has been selected
var selectedText = getSelectedText();
if (query.indexOf(selectedText) >= 0) {
query = selectedText;
}
var hyrise = new HyriseSQLConnector(endpointUrl);
hyrise.executeSQLQuery(query, function(result) {
// On Success
$('#msgContainer').attr('class', 'alert alert-success');
$('#resultInfo').html('Result contains ' + result.real_size + ' rows');
console.log("Query result: ", result); console.log("Query result: ", result);
updateResultTable(result); // On Success
updatePerformanceData(result); SetUiStateSuccess('Result contains ' + result.real_size + ' rows');
UpdateResultTable(result);
UpdatePerformanceData(result.performanceData);
}, function(xhr, status, error) { }, function(xhr, status, error) {
// console.log(arguments);
// On Error // On Error
console.log(arguments);
var msg = 'Error when fetching result. Possibly no connection to Hyrise.'; var msg = 'Error when fetching result. Possibly no connection to Hyrise.';
if (xhr.responseJSON) msg = xhr.responseJSON.error[0]; if (xhr.responseJSON) msg = xhr.responseJSON.error[0];
$('#resultInfo').html(msg); SetUiStateError(msg);
$('#msgContainer').attr('class', 'alert alert-danger');
$('#performanceDataTable tbody').html('');
}); });
}); });
@ -47,10 +68,42 @@ $(function() {
} }
return true; return true;
}); });
// Benchmark submit
$('#benchmarkBtn').click(function() {
SetUiStateRunning();
var query = GetQuery();
var hyrise = new HyriseConnector(GetHyriseUrl());
// TODO: hardcoded 5
var numRuns = parseInt($('#benchmarkInput').val());
hyrise.benchmarkSQL(query, numRuns, function(result) {
console.log("Benchmark result: ", result);
UpdatePerformanceData(result.performanceData);
SetUiStateSuccess('Success! See PerformanceData for results.');
});
});
// Setup Table triggers
var table = document.querySelector('#performanceDataTable');
$('#performanceDataTable thead th').click(function() {
var key = $(this).attr('data-key');
if (table._sortKey && table._sortKey == key) table._asc = !table._asc;
else table._asc = true;
var sign = (table._asc) ? 1 : -1;
if (table._data) {
table._sortKey = key;
SortTableData(table);
InsertPerformanceData(table._data);
}
});
}); });
function getSelectedText() { function GetSelectedText() {
var text = ""; var text = "";
if (window.getSelection) { if (window.getSelection) {
text = window.getSelection().toString(); text = window.getSelection().toString();
@ -61,15 +114,15 @@ function getSelectedText() {
} }
function loadSampleQueries(url) { function LoadSampleQueries(url) {
$.get(url, function(data) { $.get(url, function(data) {
var lines = data.split('\n'); var lines = data.split('\n');
var name, query = "", isBuggy = false; var name, query = "", isBuggy = false;
$.each(lines, function(i, line) { $.each(lines, function(i, line) {
if (line[0] == '#') { if (line[0] == '#') {
// Append last query // Append last query
if (name && !isBuggy) addSampleQuery(name, query); if (name && !isBuggy) AddSampleQuery(name, query);
if (name && isBuggy) addBuggyQuery(name, query); if (name && isBuggy) AddBuggyQuery(name, query);
// New query // New query
isBuggy = (line[1] == '!'); isBuggy = (line[1] == '!');
@ -80,13 +133,13 @@ function loadSampleQueries(url) {
} }
}); });
if (name && !isBuggy) addSampleQuery(name, query); if (name && !isBuggy) AddSampleQuery(name, query);
if (name && isBuggy) addBuggyQuery(name, query); if (name && isBuggy) AddBuggyQuery(name, query);
}); });
} }
function addSampleQuery(name, query) { function AddSampleQuery(name, query) {
var btn = $('<button type="button" class="btn btn-sm btn-success">' + name + '</button>'); var btn = $('<button type="button" class="btn btn-sm btn-success">' + name + '</button>');
btn.click(function(evt) { btn.click(function(evt) {
$('#queryInput').val(query); $('#queryInput').val(query);
@ -97,7 +150,7 @@ function addSampleQuery(name, query) {
$('#sampleQueries').append(btn); $('#sampleQueries').append(btn);
} }
function addBuggyQuery(name, query) { function AddBuggyQuery(name, query) {
var btn = $('<button type="button" class="btn btn-sm btn-danger">' + name + '</button>'); var btn = $('<button type="button" class="btn btn-sm btn-danger">' + name + '</button>');
btn.click(function(evt) { btn.click(function(evt) {
$('#queryInput').val(query); $('#queryInput').val(query);
@ -108,11 +161,11 @@ function addBuggyQuery(name, query) {
$('#buggyQueries').append(btn); $('#buggyQueries').append(btn);
} }
function createElement(tag, value) { function CreateElement(tag, value) {
return $('<' + tag + '>' + value + '</' + tag + '>'); return $('<' + tag + '>' + value + '</' + tag + '>');
}; };
function updateResultTable(result) { function UpdateResultTable(result) {
// Present result json in result-view // Present result json in result-view
var table = $('#resultTable'); var table = $('#resultTable');
table.html(''); table.html('');
@ -134,20 +187,42 @@ function updateResultTable(result) {
} }
}; };
function updatePerformanceData(result) { function UpdatePerformanceData(performanceData) {
var table = document.querySelector('#performanceDataTable');
$('#timeTotal').html(performanceData.totalTime.toFixed(2));
// Sort and insert into table
if (!table._sortKey) table._sortKey = 'startTime';
if (!('_asc') in table) table._asc = true;
var tableData = performanceData.operators;
table._data = tableData;
SortTableData(table);
InsertPerformanceData(tableData);
};
function InsertPerformanceData(performanceData) {
var tbody = $('#performanceDataTable tbody'); var tbody = $('#performanceDataTable tbody');
tbody.html(''); tbody.html('');
result.performanceData.sort(function(a, b) { $.each(performanceData, function(i, data) {
return a.startTime - b.startTime; if (!data.time_ms) data.time_ms = data.endTime - data.startTime;
});
$.each(result.performanceData, function(i, data) {
var tr = $('<tr>'); var tr = $('<tr>');
tr.append(createElement('td', data.id)) tr.append(CreateElement('td', data.id));
tr.append(createElement('td', data.name)) tr.append(CreateElement('td', data.name));
tr.append(createElement('td', data.duration)) tr.append(CreateElement('td', data.duration));
tr.append(createElement('td', data.startTime)) tr.append(CreateElement('td', data.time_ms.toFixed(6)));
tr.append(createElement('td', data.endTime))
tbody.append(tr); tbody.append(tr);
}); });
}; }
function SortTableData(table) {
var key = table._sortKey;
var sign = (table._asc) ? 1 : -1;
table._data.sort(function(a, b) {
if (a[key].localeCompare) return sign * a[key].localeCompare(b[key]);
return sign * (a[key] - b[key]);
});
}