shaker/shaker/resources/report_templates/interactive.html

740 lines
23 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
<head profile="http://gmpg.org/xfn/11">
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Shaker | The distributed data-plane testing tool for OpenStack</title>
<!-- LIBS -->
<script type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<script type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.js"></script>
<script type="text/javascript"
src="https://cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script>
<script type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.2.7/js-yaml.min.js"></script>
<script type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script type="application/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.js"></script>
<!-- STYLES -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.4/simplex/bootstrap.min.css">
<link rel=stylesheet type=text/css
href="https://cdn.datatables.net/1.10.2/css/jquery.dataTables.min.css">
<link rel=stylesheet type=text/css
href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.css">
<link rel='stylesheet' type='text/css'
href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic&subset=latin,cyrillic'>
<script type="application/javascript">
var report = [[[ report | json]]];
</script>
<script type="text/javascript">
"use strict";
$(function () {
var fields = ["scenario", "test", "concurrency", "node", "agent"];
var state = {
scenario: "",
test: "",
concurrency: "",
node: "",
agent: ""
};
var tablePageSize = 10;
// compiled templates
var scenario_source_template = Handlebars.compile($("#scenario_source_template").html());
var test_specification_template = Handlebars.compile($("#test_specification_template").html());
var agents_table_template = Handlebars.compile($("#agents_table_template").html());
var records_table_template = Handlebars.compile($("#records_table_template").html());
var executor_template = Handlebars.compile($("#executor_template").html());
var concurrency_summary_template = Handlebars.compile($("#concurrency_summary_template").html());
var test_summary_template = Handlebars.compile($("#test_summary_template").html());
var sla_table_template = Handlebars.compile($("#sla_table_template").html());
var errors_template = Handlebars.compile($("#errors_template").html());
function concatenate(strings) {
// returns strings concatenated via comma (like join() but omits empty strings)
var f = false;
var result = "";
$.each(strings, function (_i, string) {
if (string.length > 0) {
if (f) result += ", ";
result += string;
f = true;
}
});
return result;
}
function initSingleSelector(name, values) {
var selectorId = "#" + name + "_selector";
var selector_inst = $(selectorId);
selector_inst.empty();
selector_inst.append($("<option>", {
style: "font-style: italic; text-decoration: underline;",
value: "",
text: selector_inst.data('placeholder')
}));
$.each(values, function (key, item) {
selector_inst.append($('<option>', {
value: item, //(key + 1),
text: item
}));
});
selector_inst.val(state[name]);
selector_inst.
on("change", function (e) {
state[name] = this.value;
// convert state -> url
var tokens = [];
$.each(fields.sort(), function (i, field) {
if (state[field] != "") {
tokens.push(field + "=" + state[field]);
}
});
window.location.href = "#" + tokens.join(":");
});
}
function getFieldValues(field, records) {
var values_set = {};
$.each(records, function (j, record) {
if (record[field]) {
values_set["" + record[field]] = true;
}
});
return $.map(values_set, function (_i, value) {
return value;
}).sort();
}
function updateSelectors() {
$.each(fields, function (i, field) {
var values = getFieldValues(field, filterRecords(report.records, null, field));
console.log(values);
initSingleSelector(field, values);
});
}
function filterRecords(records, type, except_field) {
var res = [];
$.each(records, function(_i, record){
if (!type || typeof type === 'undefined' || record.type == type) {
var flag = true;
$.each(state, function (key, value) {
if (key != except_field && value) {
flag &= record[key] == value;
}
});
if (flag) {
res.push(record);
}
}
});
return res;
}
function getAgentsByIds(agent_ids) {
var agents = [];
$.each(agent_ids, function(agent_id){
agents.push(report.agents[agent_id]);
});
return agents;
}
/* Template Rendering Funcs */
function showScenarioSource(contentArea, scenario) {
contentArea.append(scenario_source_template(
{data: jsyaml.safeDump(scenario)}));
}
function showTestSpecification(contentArea, test) {
contentArea.append(test_specification_template(
{data: jsyaml.safeDump(test)}
))
}
function showAgentRecord(contentArea, record) {
var chart_id = "chart_" + Math.round(Math.random() * 10000);
var ext_options = {
command_yaml: jsyaml.safeDump(record["command"]),
chart_id: chart_id};
if (record["stats"]) {
ext_options["stats_yaml"] = jsyaml.safeDump(record["stats"]);
}
if ((record["start"]) && (record["finish"])) {
ext_options["time_info"] = jsyaml.safeDump({
"start": new Date(record["start"] * 1000).toISOString(),
"finish": new Date(record["finish"] * 1000).toISOString()
});
}
contentArea.append(executor_template(
{record: $.extend({}, record, ext_options)}
));
if (record["samples"]) {
var meta = record["meta"];
var axes = [];
axes[meta[1][0]] = 'y';
var axis = {
x: {label: concatenate(meta[0])},
y: {label: concatenate(meta[1]), min: 0}
};
if (meta.length > 2) {
axes[meta[2][0]] = 'y2';
axis['y2'] = {label: concatenate(meta[2]), min: 0, show: true}
}
var chart = [];
$.each(meta, function(_i, meta_item){ // row titles
chart.push([meta_item[0]]);
});
var samples = record["samples"];
for (var i=0; i < samples.length; i++) {
var point = samples[i];
for (var j=0; j < point.length; j++) {
chart[j].push(point[j]);
}
}
c3.generate({
bindto: "#" + chart_id,
data: {
x: 'time',
columns: chart,
axes: axes
},
axis: axis
});
}
}
function showConcurrencySummary(contentArea, record) {
var chart_id = "chart_" + Math.round(Math.random() * 10000);
var node_chart_id = "node_chart_" + Math.round(Math.random() * 10000);
var ext_options = { chart_id: chart_id, node_chart_id: node_chart_id };
if (record["stats"]) {
ext_options["stats_yaml"] = jsyaml.safeDump(record["stats"]);
}
console.log(record);
contentArea.append(concurrency_summary_template(
{record: $.extend({}, record, ext_options)}
));
if (record["node_chart"]) {
c3.generate({
bindto: "#" + node_chart_id,
data: {
x: 'x',
columns: record["node_chart"],
type: 'step',
order: null
},
axis: {
x: { type: 'category' }
}
});
}
}
function showTestSummary(contentArea, record) {
console.log(record);
var chart_id = "chart_" + Math.round(Math.random() * 10000);
var ext_options = { chart_id: chart_id };
if (record["stats"]) {
ext_options["stats_yaml"] = jsyaml.safeDump(record["stats"]);
}
contentArea.append(test_summary_template(
{record: $.extend({}, record, ext_options)}
));
if (record["chart"]) {
var meta = record["meta"];
var axes = [];
axes[meta[1][0]] = 'y';
var axis = {
x: {label: concatenate(meta[0])},
y: {label: concatenate(meta[1]), min: 0}
};
if (meta.length > 2) {
axes[meta[2][0]] = 'y2';
axis['y2'] = {label: concatenate(meta[2]), min: 0, show: true}
}
c3.generate({
bindto: "#" + chart_id,
data: {
x: 'concurrency',
columns: record["chart"],
axes: axes
},
axis: axis
});
}
}
function showAgentsTable(contentArea, filteredRecords) {
var agent_ids = {};
$.each(filteredRecords, function(_i, record){
if (record.agent) {
agent_ids[record.agent] = true;
}
});
var agents = getAgentsByIds(agent_ids);
if (agents.length == 0) return;
// add slave agents
var slave_agent_ids = {};
$.each(agents, function(_i, agent){
if (agent.slave_id) {
slave_agent_ids[agent.slave_id] = true;
}
});
agents = $.merge(agents, getAgentsByIds(slave_agent_ids));
contentArea.append(agents_table_template({
agents: agents,
table_id: "agents_table"
}));
$("#agents_table").dataTable({
"autoWidth": true,
"paging": false,
"ordering": true,
"info": false
});
}
function makeURL(record, s) {
var extra = {};
extra[s] = record[s];
return "#" + $.map($.extend({}, state, extra), function (val, index) {
return val? (index + "=" + val) : null;
}).join(":")
}
function showRecordsTable(contentArea, filteredRecords) {
$.each(filteredRecords, function(_i, record) {
$.each(fields, function(_j, field){
if (record[field]) {
record[field + "_url"] = makeURL(record, field);
}
});
});
contentArea.append(records_table_template({
records: filteredRecords,
table_id: "records_table"
}));
$("#records_table").dataTable({
autoWidth: true,
paging: filteredRecords.length > tablePageSize,
ordering: true,
info: false,
searching: filteredRecords.length > tablePageSize
});
}
function showSlaTable(contentArea, records) {
if (records.length == 0) return;
contentArea.append(sla_table_template({
records: records,
table_id: "sla_table"
}));
$("#records_table").dataTable({
autoWidth: true,
paging: records.length > tablePageSize,
ordering: true,
info: false,
searching: records.length > tablePageSize
});
}
function showErrors(contentArea, records) {
if (records.length == 0) return;
contentArea.append(errors_template({
records: records,
table_id: "errors_list"
}));
}
/* Update cycle */
function update() {
var filteredRecords = filterRecords(report.records);
console.log(filteredRecords);
var slaRecords = [];
var errorRecords = [];
$.each(filterRecords(report.records), function(_i, record) {
if (record["sla_info"]) {
slaRecords.push(record);
}
if (record["is_status_error"]) {
errorRecords.push(record);
}
});
var contentArea = $("#content_area");
contentArea.empty();
updateSelectors();
showRecordsTable(contentArea, filteredRecords);
showSlaTable(contentArea, slaRecords);
showErrors(contentArea, errorRecords);
if (state["scenario"] != "" && state["test"] != "" &&
state["concurrency"] != "" &&
state["agent"] != "") {
showAgentRecord(contentArea, filteredRecords[0]);
}
if (state["scenario"] != "" && state["test"] != "" &&
state["concurrency"] != "" && state["node"] == "" &&
state["agent"] == "") {
var agg_recs = filterRecords(report.records, "concurrency");
if (agg_recs.length > 0) {
showConcurrencySummary(contentArea, agg_recs[0]);
}
}
if (state["scenario"] != "" && state["test"] != "" &&
state["concurrency"] == "" && state["node"] == "" &&
state["agent"] == "") {
var test_recs = filterRecords(report.records, "test");
if (test_recs.length > 0) {
showTestSummary(contentArea, test_recs[0]);
}
}
if (state["test"] != "") {
showTestSpecification(contentArea, report.tests[state["test"]]);
}
if (state["agent"] == "") {
showAgentsTable(contentArea, filteredRecords);
}
if (state["scenario"] != "") {
showScenarioSource(contentArea, report.scenarios[state["scenario"]]);
}
}
function parseUrlToState() {
var hashUrl = window.location.hash.substr(1);
$.each(hashUrl.split(":"), function (_i, pair) {
var kv = pair.split("=");
if (kv.length == 2) {
state[kv[0]] = decodeURIComponent(kv[1]);
}
});
}
// entry-point
parseUrlToState();
console.log(state);
// change the status of tests that have sla failures
// to "fail", only if they were previously marked "ok"
$.each(report.sla, function(_i, sla) {
if (sla.state == "FAIL" && report.records[sla.record].status == "ok") {
report.records[sla.record].status = "fail";
}
});
// pre-process records
$.each(report.records, function(_i, record) {
if (record.status) {
record["is_status_ok"] = record.status == "ok";
record["is_status_lost"] = record.status == "lost";
record["is_status_interrupted"] = record.status == "interrupted";
record["is_status_error"] = record.status == "error";
record["is_status_fail"] = record.status == "fail";
}
});
// auto-select filters with 1 option
$.each(fields, function(_i, field) {
var values = getFieldValues(field, report.records);
if (values.length == 1) {
state[field] = values[0];
}
});
update();
$(window).on('hashchange', function() {
parseUrlToState();
update()
});
$('[data-toggle="tooltip"]').tooltip();
});
</script>
<style type="text/css">
</style>
</head>
<body>
<div class="container" id="container">
<div class="row text-center">
<h1>Shaker | The Report</h1>
</div>
<hr>
<div class="row">
<div class="col-md-2">
<label for="scenario_selector" title="Scenario name">Scenario</label>
<select id="scenario_selector" class="form-control" data-placeholder="- Any scenario -"></select>
</div>
<div class="col-md-2">
<label for="test_selector" title="Test name">Test Case</label>
<select id="test_selector" class="form-control" data-placeholder="- Any test case -"></select>
</div>
<div class="col-md-2">
<label for="concurrency_selector" title="Concurrency">Concurrency</label>
<select id="concurrency_selector" class="form-control" data-placeholder="- Any concurrency -"></select>
</div>
<div class="col-md-2">
<label for="node_selector" title="Host / Compute node">Host</label>
<select id="node_selector" class="form-control" data-placeholder="- Any node -"></select>
</div>
<div class="col-md-2">
<label for="agent_selector" title="Agent">Agent</label>
<select id="agent_selector" class="form-control" data-placeholder="- Any agent -"></select>
</div>
</div>
<div id="content_area" class="row">
<!-- Container for the screen content -->
</div>
<div id="footer" class="row">&nbsp;</div>
</div>
<!-- TEMPLATES --------------------------------------------------------------->
<!-- Scenario File -->
<script id="scenario_source_template" type="text/x-handlebars-template">
<h3>Scenario</h3>
<pre>{{ data }}</pre>
</script>
<!-- Test Specification -->
<script id="test_specification_template" type="text/x-handlebars-template">
<h3>Test Specification</h3>
<pre>{{ data }}</pre>
</script>
<!-- Executor Result -->
<script id="executor_template" type="text/x-handlebars-template">
<h3>Execution Summary</h3>
{{#with record}}
<div id="{{ chart_id }}"></div>
{{#if is_status_ok}}<span class="label label-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span>success</span>{{/if}}
{{#if is_status_lost}}<span class="label label-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span>lost</span>{{/if}}
{{#if is_status_interrupted}}<span class="label label-warning"><span class="glyphicon glyphicon-stop" aria-hidden="true"></span>interrupted</span>{{/if}}
{{#if is_status_error}}<span class="label label-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span>error</span>{{/if}}
{{#if is_status_fail}}<span class="label label-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span>sla fail</span>{{/if}}
{{#if stats_yaml}}
<h4>Stats</h4>
<pre>{{ stats_yaml }}</pre>
{{/if}}
{{#if time_info}}
<h4>Time Info</h4>
<pre>{{ time_info }}</pre>
{{/if}}
<h4>Command</h4>
<pre>{{ command_yaml }}</pre>
{{#if stdout}}
<h4>Stdout</h4>
<pre>{{ stdout }}</pre>
{{/if}}
{{#if verbose}}
<h4>Verbose Info</h4>
<pre>{{ verbose }}</pre>
{{/if}}
{{#if stderr}}
<h4>Stderr</h4>
<pre>{{ stderr }}</pre>
{{/if}}
{{/with}}
</script>
<!-- Concurrency summary -->
<script id="concurrency_summary_template" type="text/x-handlebars-template">
{{#with record}}
<div id="{{ chart_id }}"></div>
<h3>Per-node chart</h3>
<div id="{{ node_chart_id }}"></div>
<h3>Stats</h3>
<pre>{{ stats_yaml }}</pre>
{{/with}}
</script>
<!-- Test summary -->
<script id="test_summary_template" type="text/x-handlebars-template">
{{#with record}}
<div id="{{ chart_id }}"></div>
{{#if stats_yaml}}
<h3>Stats</h3>
<pre>{{ stats_yaml }}</pre>
{{/if}}
{{/with}}
</script>
<!-- Records Table -->
<script id="records_table_template" type="text/x-handlebars-template">
<h3>Records</h3>
<table class="table table-striped" id="{{ table_id }}">
<thead>
<tr>
<th>Scenario</th>
<th>Test Case</th>
<th>Concurrency</th>
<th>Host / Compute Node</th>
<th>Agent</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{{#records}}
<tr>
<td><a href="{{ scenario_url }}">{{ scenario }}</a></td>
<td><a href="{{ test_url }}">{{ test }}</a></td>
<td><a href="{{ concurrency_url }}">{{ concurrency }}</a></td>
<td><a href="{{ node_url }}">{{ node }}</a></td>
<td><a href="{{ agent_url }}">{{ agent }}</a></td>
<td>
{{#if is_status_ok }}
<span class="label label-success">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
success
</span>
{{/if}}
{{#if is_status_lost }}
<span class="label label-danger">
<span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
lost
</span>
{{/if}}
{{#if is_status_interrupted }}
<span class="label label-warning">
<span class="glyphicon glyphicon-stop" aria-hidden="true"></span>
interrupted
</span>
{{/if}}
{{#if is_status_error }}
<span class="label label-danger">
<span class="glyphicon glyphicon-stop" aria-hidden="true"></span>
error
</span>
{{/if}}
{{#if is_status_fail }}
<span class="label label-danger">
<span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
sla fail
</span>
{{/if}}
</td>
</tr>
{{/records}}
</tbody>
</table>
</script>
<!-- Agents List -->
<script id="agents_table_template" type="text/x-handlebars-template">
<h3>Agents</h3>
<table class="table table-striped" id="{{ table_id }}">
<thead>
<tr>
<th>Agent</th>
<th>Mode</th>
<th>IP</th>
<th>Node</th>
<th>AZ</th>
</tr>
</thead>
<tbody>
{{#agents}}
<tr>
<td>{{ id }}</td>
<td>{{ mode }}</td>
<td>{{ ip }}</td>
<td>{{ node }}</td>
<td>{{ zone }}</td>
</tr>
{{/agents}}
</tbody>
</table>
</script>
<!-- SLA List -->
<script id="sla_table_template" type="text/x-handlebars-template">
<h3>Agents</h3>
<table class="table table-striped" id="{{ table_id }}">
<thead>
<tr>
<th>Condition</th>
<th>Location</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{{#records}}
<tr>
<td>{{ sla }}</td>
<td>{{ location }}</td>
<td>
{{#if is_status_ok }}
<span class="text-success">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
{{ status }}
</span>
{{else}}
<span class="text-danger">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
{{ status }}
</span>
{{/if}}
</td>
</tr>
{{/records}}
</tbody>
</table>
</script>
<!-- Errors -->
<script id="errors_template" type="text/x-handlebars-template">
<h3>Errors</h3>
<div class="text-danger">
<ul>
{{#records}}
<li>{{ info }}<br/>{{ stderr }}{{ traceback }}</li>
{{/records}}
</ul>
</div>
</script>
</body>
</html>