Merge "[Reports] Improve HTML report and add dummy_output scenario"

This commit is contained in:
Jenkins 2016-01-08 12:54:42 +00:00 committed by Gerrit Code Review
commit eeb0b7ea2c
8 changed files with 467 additions and 270 deletions

View File

@ -589,6 +589,16 @@
min: 20 min: 20
max: 80 max: 80
Dummy.dummy_output:
-
runner:
type: "constant"
times: 20
concurrency: 10
sla:
failure_rate:
max: 0
Dummy.dummy_with_scenario_output: Dummy.dummy_with_scenario_output:
- -
runner: runner:

View File

@ -80,6 +80,63 @@ class Dummy(scenario.Scenario):
% exception_probability % exception_probability
) )
@scenario.configure()
def dummy_output(self, random_range=25):
"""Generate dummy output.
This scenario generates example of output data.
:param random_range: max int limit for generated random values
"""
rand = lambda n: [n, random.randint(1, random_range)]
desc = "This is a description text for %s"
self.add_output(additive={"title": "Additive Stat Table",
"description": desc % "Additive Stat Table",
"chart": "OutputStatsTable",
"items": [rand("foo stat"), rand("bar stat"),
rand("spam stat")]})
self.add_output(additive={"title": "Additive Foo StackedArea",
"description": (
desc % "Additive Foo StackedArea"),
"chart": "OutputStackedAreaChart",
"items": [rand("foo 1"), rand("foo 2")]})
self.add_output(additive={"title": ("Additive Bar StackedArea "
"(no description)"),
"description": "",
"chart": "OutputStackedAreaChart",
"items": [rand("bar %d" % i)
for i in range(1, 7)]})
self.add_output(additive={"title": "Additive Spam Pie",
"description": desc % "Additive Spam Pie",
"chart": "OutputAvgChart",
"items": [rand("spam %d" % i)
for i in range(1, 4)]},
complete={"title": "Complete StackedArea",
"description": desc % "Complete StackedArea",
"widget": "StackedArea",
"data": [
[name, [rand(i) for i in range(30)]]
for name in ("alpha", "beta", "gamma")]})
self.add_output(
complete={"title": "Complete Pie (no description)",
"description": "",
"widget": "Pie",
"data": [rand("delta"), rand("epsilon"), rand("zeta"),
rand("theta"), rand("lambda"), rand("omega")]})
data = {"cols": ["mu column", "xi column", "pi column",
"tau column", "chi column"],
"rows": [([name + " row"] + [rand(i)[1] for i in range(4)])
for name in ("iota", "nu", "rho", "phi", "psi")]}
self.add_output(complete={"title": "Complete Table",
"description": desc % "Complete Table",
"widget": "Table",
"data": data})
@scenario.configure() @scenario.configure()
def dummy_with_scenario_output(self): def dummy_with_scenario_output(self):
"""Return a dummy scenario output. """Return a dummy scenario output.

View File

@ -12,8 +12,8 @@
p { margin:0; padding:5px 0 } p { margin:0; padding:5px 0 }
p.thesis { padding:10px 0 } p.thesis { padding:10px 0 }
h1 { color:#666; margin:0 0 20px; font-size:30px; font-weight:normal } h1 { color:#666; margin:0 0 20px; font-size:30px; font-weight:normal }
h2 { color:#777; margin:20px 0 10px; font-size:25px; font-weight:normal } h2, .h2 { color:#666; margin:24px 0 6px; font-size:25px; font-weight:normal }
h3 { color:#666; margin:13px 0 4px; font-size:18px; font-weight:normal } h3, .h3 { color:#777; margin:12px 0 4px; font-size:18px; font-weight:normal }
table { border-collapse:collapse; border-spacing:0; width:100%; font-size:12px; margin:0 0 10px } table { border-collapse:collapse; border-spacing:0; width:100%; font-size:12px; margin:0 0 10px }
table th { text-align:left; padding:8px; color:#000; border:2px solid #ddd; border-width:0 0 2px 0 } table th { text-align:left; padding:8px; color:#000; border:2px solid #ddd; border-width:0 0 2px 0 }
table th.sortable { cursor:pointer } table th.sortable { cursor:pointer }
@ -21,7 +21,6 @@
table.compact td { padding:4px 8px } table.compact td { padding:4px 8px }
table.striped tr:nth-child(odd) td { background:#f9f9f9 } table.striped tr:nth-child(odd) td { background:#f9f9f9 }
table.linked tbody tr:hover { background:#f9f9f9; cursor:pointer } table.linked tbody tr:hover { background:#f9f9f9; cursor:pointer }
.richcolor td { color:#036; font-weight:bold }
.rich, .rich td { font-weight:bold } .rich, .rich td { font-weight:bold }
.code { padding:10px; font-size:13px; color:#333; background:#f6f6f6; border:1px solid #e5e5e5; border-radius:4px } .code { padding:10px; font-size:13px; color:#333; background:#f6f6f6; border:1px solid #e5e5e5; border-radius:4px }

View File

@ -23,46 +23,41 @@
{% endblock %} {% endblock %}
{% block js_before %} {% block js_before %}
{% raw %}
"use strict"; "use strict";
if (typeof angular === "object") { angular.module("TaskApp", []).controller( if (typeof angular === "object") { angular.module("TaskApp", []).controller(
"TaskController", ["$scope", "$location", function($scope, $location) { "TaskController", ["$scope", "$location", function($scope, $location) {
{% endraw %}
$scope.source = {{ source }};
$scope.scenarios = {{ data }};
{% raw %}
$scope.location = { $scope.location = {
/* This is a junior brother of angular's $location, that allows non-`#' /* #/path/hash/sub/div */
symbol in uri, like `#/path/hash' instead of `#/path#hash' */
_splitter: "/",
normalize: function(str) { normalize: function(str) {
/* Remove unwanted characters from string */ /* Remove unwanted characters from string */
if (typeof str !== "string") { return "" } if (typeof str !== "string") { return "" }
return str.replace(/[^\w\-\.]/g, "") return str.replace(/[^\w\-\.]/g, "")
}, },
_parseUri: function(uriStr) {
/* :returns: {path:string, hash:string} */
var self = this;
var obj = {path: "", hash: ""};
angular.forEach(uriStr.split(self._splitter), function(v){
var s = self.normalize(v);
if (! s) { return }
if (! this.path) { this.path = s } else if (! this.hash) { this.hash = s }
}, obj)
return obj
},
uri: function(obj) { uri: function(obj) {
/* Getter/Setter */ /* Getter/Setter */
if (! obj) { return this._parseUri($location.url()) } if (! obj) {
if (obj.path && obj.hash) { var uri = {path: "", hash: "", sub: "", div: ""};
$location.url(obj.path + this._splitter + obj.hash) var arr = ["div", "sub", "hash", "path"];
} else if (obj.path) { angular.forEach($location.url().split("/"), function(value){
$location.url(obj.path) var v = $scope.location.normalize(value);
} else { if (v) { var k = arr.pop(); if (k) { this[k] = v }}
$location.url("/") }, uri);
return uri
} }
var arr = [obj.path, obj.hash, obj.sub, obj.div], res = [];
for (var i in arr) { if (! arr[i]) { break }; res.push(arr[i]) }
return $location.url("/" + res.join("/"))
}, },
path: function(path, hash) { path: function(path, hash) {
/* Getter/Setter */ /* Getter/Setter */
var uri = this.uri();
if (path === "") { return this.uri({}) } if (path === "") { return this.uri({}) }
path = this.normalize(path); path = this.normalize(path);
var uri = this.uri();
if (! path) { return uri.path } if (! path) { return uri.path }
uri.path = path; uri.path = path;
var _hash = this.normalize(hash); var _hash = this.normalize(hash);
@ -71,23 +66,27 @@
}, },
hash: function(hash) { hash: function(hash) {
/* Getter/Setter */ /* Getter/Setter */
var uri = this.uri(); if (hash) { this.uri({path:this.uri().path, hash:hash}) }
if (! hash) { return uri.hash } return this.uri().hash
return this.uri({path:uri.path, hash:hash})
} }
} }
/* Dispatch */ /* Dispatch */
$scope.route = function(uri) { $scope.route = function(uri) {
if (! ($scope.scenarios && $scope.scenarios.length)) { if (! $scope.scenarios_map) { return }
return
}
if (uri.path in $scope.scenarios_map) { if (uri.path in $scope.scenarios_map) {
$scope.view = {is_scenario:true}; $scope.view = {is_scenario:true};
$scope.scenario = $scope.scenarios_map[uri.path]; $scope.scenario = $scope.scenarios_map[uri.path];
$scope.nav_idx = $scope.nav_map[uri.path]; $scope.nav_idx = $scope.nav_map[uri.path];
$scope.showTab(uri.hash); if ($scope.scenario.iterations.histogram.views.length) {
$scope.mainHistogram = $scope.scenario.iterations.histogram.views[0]
}
if ($scope.scenario.atomic.histogram.views.length) {
$scope.atomicHistogram = $scope.scenario.atomic.histogram.views[0]
}
$scope.outputIteration = 0;
$scope.showTab(uri);
} else { } else {
$scope.scenario = null; $scope.scenario = null;
if (uri.path === "source") { if (uri.path === "source") {
@ -102,11 +101,7 @@
$scope.route($scope.location.uri()) $scope.route($scope.location.uri())
}); });
/* Navigation */ $scope.showNav = function(nav_idx) { $scope.nav_idx = nav_idx }
$scope.showNav = function(nav_idx) {
$scope.nav_idx = nav_idx
}
/* Tabs */ /* Tabs */
@ -121,8 +116,8 @@
visible: function(){ return !! $scope.scenario.atomic.pie.length } visible: function(){ return !! $scope.scenario.atomic.pie.length }
},{ },{
id: "output", id: "output",
name: "Output", name: "Scenario Data",
visible: function(){ return !! $scope.scenario.additive_output.length } visible: function(){ return $scope.scenario.output.length }
},{ },{
id: "failures", id: "failures",
name: "Failures", name: "Failures",
@ -137,19 +132,33 @@
angular.forEach($scope.tabs, angular.forEach($scope.tabs,
function(tab){ this[tab.id] = tab }, $scope.tabs_map); function(tab){ this[tab.id] = tab }, $scope.tabs_map);
$scope.showTab = function(tab_id) { $scope.showTab = function(uri) {
$scope.tab = tab_id in $scope.tabs_map ? tab_id : "overview" $scope.tab = uri.hash in $scope.tabs_map ? uri.hash : "overview";
if (! $scope.scenario.output) {
var has_additive = !! $scope.scenario.additive_output.length;
var has_complete = !! ($scope.scenario.complete_output.length
&& $scope.scenario.complete_output[0].length);
$scope.scenario.output = {
has_additive: has_additive,
has_complete: has_complete,
length: has_additive + has_complete,
active: has_additive ? "additive" : (has_complete ? "complete" : "")
}
}
if (uri.hash === "output") {
if (uri.sub && $scope.scenario.output["has_" + uri.sub]) {
$scope.scenario.output.active = uri.sub
}
}
} }
for (var i in $scope.tabs) { for (var i in $scope.tabs) {
if ($scope.tabs[i].id === $scope.location.hash()) { if ($scope.tabs[i].id === $scope.location.hash()) {
$scope.tab = $scope.tabs[i].id $scope.tab = $scope.tabs[i].id
} }
$scope.tabs[i].isVisible = function(){ $scope.tabs[i].isVisible = function() {
if ($scope.scenario) { if ($scope.scenario) {
if (this.visible()) { if (this.visible()) { return true }
return true
}
/* If tab should be hidden but is selected - show another one */ /* If tab should be hidden but is selected - show another one */
if (this.id === $scope.location.hash()) { if (this.id === $scope.location.hash()) {
for (var i in $scope.tabs) { for (var i in $scope.tabs) {
@ -165,149 +174,6 @@
} }
} }
/* Charts */
var Charts = {
_render: function(selector, data, chart){
nv.addGraph(function() {
d3.select(selector)
.datum(data)
.transition()
.duration(0)
.call(chart);
nv.utils.windowResize(chart.update)
})
},
/* NOTE(amaretskiy): this is actually a result of
d3.scale.category20().range(), excluding red color (#d62728)
which is reserved for errors */
_colors: ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c",
"#98df8a", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b",
"#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7",
"#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
pie: function(selector, data){
var chart = nv.models.pieChart()
.x(function(d) { return d.key })
.y(function(d) { return d.values })
.showLabels(true)
.labelType("percent")
.donut(true)
.donutRatio(0.25)
.donutLabelsOutside(true)
.color(function(d){
if (d.data && d.data.color) {
return d.data.color
}
});
var data_ = [],
colors = [],
colors_map = {errors: "#d62728"};
for (var i in data) {
var key = data[i][0];
if (! (key in colors_map)) {
if (! colors.length) { colors = this._colors.slice() }
colors_map[key] = colors.shift()
}
data_.push({key:key, values:data[i][1], color:colors_map[key]})
}
this._render(selector, data_, chart)
},
stack: function(selector, data, conf){
var chart = nv.models.stackedAreaChart()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] })
.clipEdge(true)
.showControls(conf.controls)
.useInteractiveGuideline(conf.guide);
chart.xAxis
.axisLabel(conf.xLabel || "")
.showMaxMin(false)
.tickFormat(d3.format(conf.xFormat || "d"));
chart.yAxis
.axisLabel(conf.yLabel || "")
.tickFormat(d3.format(conf.yFormat || ",.3f"));
var data_ = [];
for (var i in data) {
var d = {key:data[i][0], values:data[i][1]};
if (d.key === "failed_duration") {
d.color = "#d62728"
}
data_.push(d)
}
this._render(selector, data_, chart)
},
histogram: function(selector, data){
var chart = nv.models.multiBarChart()
.reduceXTicks(true)
.showControls(false)
.transitionDuration(0)
.groupSpacing(0.05);
chart.legend
.radioButtonMode(true)
chart.xAxis
.axisLabel("Duration (seconds)")
.tickFormat(d3.format(",.2f"));
chart.yAxis
.axisLabel("Iterations (frequency)")
.tickFormat(d3.format("d"));
this._render(selector, data, chart)
}
};
$scope.renderTotal = function() {
if (! $scope.scenario) {
return
}
Charts.stack(
"#total-stack", $scope.scenario.iterations.iter,
{xLabel: "Iteration sequence number",
controls: true,
guide: true});
if ($scope.scenario.load_profile.length) {
Charts.stack(
"#load-profile-stack",
$scope.scenario.load_profile,
{xLabel: "Timeline (seconds)",
xFormat: ",.2f", yFormat: "d"})
}
Charts.pie("#total-pie", $scope.scenario.iterations.pie);
if ($scope.scenario.iterations.histogram.data.length) {
var idx = this.totalHistogramModel.id;
Charts.histogram("#total-histogram",
$scope.scenario.iterations.histogram.data[idx])
}
}
$scope.renderDetails = function() {
if (! $scope.scenario) {
return
}
Charts.stack(
"#atomic-stack",
$scope.scenario.atomic.iter,
{xLabel: "Iteration sequence number",
controls: true,
guide: true});
if ($scope.scenario.atomic.pie) {
Charts.pie("#atomic-pie", $scope.scenario.atomic.pie)
}
if ($scope.scenario.atomic.histogram.data.length) {
var idx = this.atomicHistogramModel.id;
Charts.histogram("#atomic-histogram", $scope.scenario.atomic.histogram.data[idx])
}
}
$scope.renderOutput = function() {
if ($scope.scenario && $scope.scenario.additive_output.length) {
Charts.stack("#output-stack", $scope.scenario.additive_output[0].data, {})
}
}
$scope.showError = function(message) { $scope.showError = function(message) {
return (function (e) { return (function (e) {
e.style.display = "block"; e.style.display = "block";
@ -318,23 +184,16 @@
/* Initialization */ /* Initialization */
angular.element(document).ready(function(){ angular.element(document).ready(function(){
$scope.source = {{ source }};
$scope.scenarios = {{ data }};
if (! $scope.scenarios.length) { if (! $scope.scenarios.length) {
return $scope.showError("No data...") return $scope.showError("No data...")
} }
$scope.totalHistogramModel = {label:'', id:0};
$scope.atomicHistogramModel = {label:'', id:0};
/* Compose data mapping */ /* Compose data mapping */
$scope.nav = []; $scope.nav = [];
$scope.nav_map = {}; $scope.nav_map = {};
$scope.scenarios_map = {}; $scope.scenarios_map = {};
var scenario_ref = $scope.location.path(); var met = [], itr = 0, cls_idx = 0;
var met = [];
var itr = 0;
var cls_idx = 0;
var prev_cls, prev_met; var prev_cls, prev_met;
for (var idx in $scope.scenarios) { for (var idx in $scope.scenarios) {
@ -350,21 +209,13 @@
cls_idx += 1 cls_idx += 1
} }
if (prev_met !== sc.met) { if (prev_met !== sc.met) { itr = 1 };
itr = 1
}
sc.ref = $scope.location.normalize(sc.cls+"."+sc.met+(itr > 1 ? "-"+itr : "")); sc.ref = $scope.location.normalize(sc.cls+"."+sc.met+(itr > 1 ? "-"+itr : ""));
$scope.scenarios_map[sc.ref] = sc; $scope.scenarios_map[sc.ref] = sc;
$scope.nav_map[sc.ref] = cls_idx; $scope.nav_map[sc.ref] = cls_idx;
var current_ref = $scope.location.path();
if (sc.ref === current_ref) {
scenario_ref = sc.ref
}
met.push({name:sc.name, itr:itr, idx:idx, ref:sc.ref}); met.push({name:sc.name, itr:itr, idx:idx, ref:sc.ref});
prev_met = sc.met; prev_met = sc.met;
itr += 1 itr += 1;
} }
if (met.length) { if (met.length) {
@ -374,11 +225,161 @@
/* Start */ /* Start */
var uri = $scope.location.uri(); var uri = $scope.location.uri();
uri.path = scenario_ref; uri.path = $scope.location.path();
$scope.route(uri); $scope.route(uri);
$scope.$digest() $scope.$digest()
}); });
}])} }])
.directive("widget", function($compile) {
var Chart = {
_render: function(node, data, chart){
nv.addGraph(function() {
d3.select(node).datum(data).transition().duration(0).call(chart);
nv.utils.windowResize(chart.update)
})
},
/* NOTE(amaretskiy): this is actually a result of
d3.scale.category20().range(), excluding red color (#d62728)
which is reserved for errors */
_colors: ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c",
"#98df8a", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b",
"#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7",
"#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
_widgets: {
Pie: "pie",
StackedArea: "stack",
Histogram: "histogram"
},
get_chart: function(widget) {
if (widget in this._widgets) {
var name = this._widgets[widget];
return Chart[name]
}
return function() { console.log("Error: unexpected widget:", widget) }
},
pie: function(node, data) {
var chart = nv.models.pieChart()
.x(function(d) { return d.key })
.y(function(d) { return d.values })
.showLabels(true)
.labelType("percent")
.donut(true)
.donutRatio(0.25)
.donutLabelsOutside(true)
.color(function(d){
if (d.data && d.data.color) { return d.data.color }
});
var data_ = [], colors = [], colors_map = {errors: "#d62728"};
for (var i in data) {
var key = data[i][0];
if (! (key in colors_map)) {
if (! colors.length) { colors = Chart._colors.slice() }
colors_map[key] = colors.shift()
}
data_.push({key:key, values:data[i][1], color:colors_map[key]})
}
Chart._render(node, data_, chart)
},
stack: function(node, data, opts) {
var chart = nv.models.stackedAreaChart()
.x(function(d) { return d[0] })
.y(function(d) { return d[1] })
.useInteractiveGuideline(opts.guide)
.showControls(opts.controls)
.clipEdge(true);
chart.xAxis
.tickFormat(d3.format(opts.xformat || "d"))
.axisLabel(opts.xname || "")
.showMaxMin(false);
chart.yAxis
.tickFormat(d3.format(opts.yformat || ",.3f"))
.axisLabel(opts.yname || "");
var data_ = [];
for (var i in data) {
var d = {key:data[i][0], values:data[i][1]};
if (d.key === "failed_duration") {
d.color = "#d62728"
}
data_.push(d)
}
Chart._render(node, data_, chart)
},
histogram: function(node, data) {
var chart = nv.models.multiBarChart()
.reduceXTicks(true)
.showControls(false)
.transitionDuration(0)
.groupSpacing(0.05);
chart
.legend.radioButtonMode(true);
chart.xAxis
.axisLabel("Duration (seconds)")
.tickFormat(d3.format(",.2f"));
chart.yAxis
.axisLabel("Iterations (frequency)")
.tickFormat(d3.format("d"));
Chart._render(node, data, chart)
}
};
return {
restrict: "A",
scope: { data: "=" },
link: function(scope, element, attrs) {
scope.$watch("data", function(data) {
if (! data) { return console.log("Chart has no data to render!") }
if (attrs.widget === "Table") {
var ng_class = attrs.lastrowClass ? " ng-class='{"+attrs.lastrowClass+":$last}'" : "";
var template = "<table class='striped'><thead>" +
"<tr><th ng-repeat='i in data.cols track by $index'>{{i}}<tr>" +
"</thead><tbody>" +
"<tr" + ng_class + " ng-repeat='row in data.rows track by $index'>" +
"<td ng-repeat='i in row track by $index'>{{i}}" +
"<tr>" +
"</tbody></table>";
var el = element.empty().append($compile(template)(scope)).children()[0]
} else {
/* Hide widget if not enough data */
if (attrs.widget === "StackedArea") {
if ((! data.length) || (data[0].length < 1) || (data[0][1].length < 2)) {
return element.empty().css({display:"none"})
}
}
else if (attrs.widget === "Pie") {
if (! data.length) {
return element.empty().css({display:"none"})
}
}
var options = {
xname: attrs.nameX || "",
yname: attrs.nameY || "",
xformat: attrs.formatX || "d",
yformat: attrs.formatY || ",.3f",
controls: attrs.controls === "true",
guide: attrs.guide === "true"
};
var el = element.addClass("chart").css({display:"block"}).html("<svg></svg>").children()[0];
Chart.get_chart(attrs.widget)(el, data, options)
}
if (attrs.description) {
var desc_el = angular.element("<div>").addClass(attrs.descriptionClass || "h3").text(attrs.description);
angular.element(el).parent().prepend(desc_el)
}
if (attrs.title) {
var title_el = angular.element("<div>").addClass(attrs.titleClass || "h2").text(attrs.title);
angular.element(el).parent().prepend(title_el)
}
});
}
}
})
}
{% endraw %}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
@ -402,14 +403,15 @@
.failure-mesg { color:#900 } .failure-mesg { color:#900 }
.failure-trace { color:#333; white-space:pre; overflow:auto } .failure-trace { color:#333; white-space:pre; overflow:auto }
.chart { height:300px } .link { color:#428BCA; padding:5px 15px 5px 5px; text-decoration:underline; cursor:pointer }
.chart .chart-dropdown { float:right; margin:0 35px 0 } .link.active { color:#333; text-decoration:none }
.chart.lesser { padding:0; margin:0; float:left; width:40% }
.chart.larger { padding:0; margin:0; float:left; width:59% } .chart { padding:0; margin:0 }
.chart svg { height:300px; padding:0; margin:0 }
.chart.lower svg { height:180px }
.expandable { cursor:pointer } .expandable { cursor:pointer }
.clearfix { clear:both } .clearfix { clear:both }
.top-margin { margin-top:40px !important }
.sortable > .arrow { display:inline-block; width:12px; height:inherit; color:#c90 } .sortable > .arrow { display:inline-block; width:12px; height:inherit; color:#c90 }
.content-main { margin:0 5px; display:block; float:left } .content-main { margin:0 5px; display:block; float:left }
{% endblock %} {% endblock %}
@ -488,32 +490,28 @@
<b ng-show="ov_srt=='full_duration' && !ov_dir">&#x25b4;</b> <b ng-show="ov_srt=='full_duration' && !ov_dir">&#x25b4;</b>
<b ng-show="ov_srt=='full_duration' && ov_dir">&#x25be;</b> <b ng-show="ov_srt=='full_duration' && ov_dir">&#x25be;</b>
</span> </span>
<th class="sortable" <th class="sortable" title="Number of iterations"
title="Number of iterations"
ng-click="ov_srt='iterations_count'; ov_dir=!ov_dir"> ng-click="ov_srt='iterations_count'; ov_dir=!ov_dir">
Iterations Iterations
<span class="arrow"> <span class="arrow">
<b ng-show="ov_srt=='iterations_count' && !ov_dir">&#x25b4;</b> <b ng-show="ov_srt=='iterations_count' && !ov_dir">&#x25b4;</b>
<b ng-show="ov_srt=='iterations_count' && ov_dir">&#x25be;</b> <b ng-show="ov_srt=='iterations_count' && ov_dir">&#x25be;</b>
</span> </span>
<th class="sortable" <th class="sortable" title="Scenario runner type"
title="Scenario runner type"
ng-click="ov_srt='runner'; ov_dir=!ov_dir"> ng-click="ov_srt='runner'; ov_dir=!ov_dir">
Runner Runner
<span class="arrow"> <span class="arrow">
<b ng-show="ov_srt=='runner' && !ov_dir">&#x25b4;</b> <b ng-show="ov_srt=='runner' && !ov_dir">&#x25b4;</b>
<b ng-show="ov_srt=='runner' && ov_dir">&#x25be;</b> <b ng-show="ov_srt=='runner' && ov_dir">&#x25be;</b>
</span> </span>
<th class="sortable" <th class="sortable" title="Number of errors occurred"
title="Number of errors occurred"
ng-click="ov_srt='errors.length'; ov_dir=!ov_dir"> ng-click="ov_srt='errors.length'; ov_dir=!ov_dir">
Errors Errors
<span class="arrow"> <span class="arrow">
<b ng-show="ov_srt=='errors.length' && !ov_dir">&#x25b4;</b> <b ng-show="ov_srt=='errors.length' && !ov_dir">&#x25b4;</b>
<b ng-show="ov_srt=='errors.length' && ov_dir">&#x25be;</b> <b ng-show="ov_srt=='errors.length' && ov_dir">&#x25be;</b>
</span> </span>
<th class="sortable" <th class="sortable" title="Whether SLA check is successful"
title="Whether SLA check is successful"
ng-click="ov_srt='sla_success'; ov_dir=!ov_dir"> ng-click="ov_srt='sla_success'; ov_dir=!ov_dir">
Success (SLA) Success (SLA)
<span class="arrow"> <span class="arrow">
@ -558,8 +556,6 @@
<div ng-include="tab"></div> <div ng-include="tab"></div>
<script type="text/ng-template" id="overview"> <script type="text/ng-template" id="overview">
{{renderTotal()}}
<p class="thesis"> <p class="thesis">
Load duration: <b>{{scenario.load_duration | number:3}} s</b> &nbsp; Load duration: <b>{{scenario.load_duration | number:3}} s</b> &nbsp;
Full duration: <b>{{scenario.full_duration | number:3}} s</b> &nbsp; Full duration: <b>{{scenario.full_duration | number:3}} s</b> &nbsp;
@ -589,72 +585,119 @@
</table> </table>
</div> </div>
<h2>Total durations</h2> <div widget="Table"
<table class="striped"> data="scenario.table"
<thead> lastrow-class="rich"
<tr> title="Total durations">
<th ng-repeat="i in scenario.table.cols track by $index">{{i}}
<tr>
</thead>
<tbody>
<tr ng-class="{richcolor:$last}"
ng-repeat="row in scenario.table.rows track by $index">
<td ng-repeat="i in row track by $index">{{i}}
<tr>
</tbody>
</table>
<div class="chart">
<svg id="total-stack"></svg>
</div> </div>
<h3>Load Profile</h3> <div widget="StackedArea"
<div class="chart" style="height:180px" ng-show="scenario.load_profile[0][1].length"> data="scenario.iterations.iter"
<svg id="load-profile-stack"></svg> name-x="Iteration sequence number"
controls="true"
guide="true">
</div> </div>
<h3>Distribution</h3> <div widget="StackedArea"
<div class="chart lesser top-margin"> data="scenario.load_profile"
<svg id="total-pie"></svg> title="Load Profile"
title-class="h3"
name-x="Timeline (seconds)"
format-x=",.2f"
class="lower">
</div> </div>
<div class="chart larger top-margin" <div widget="Pie"
ng-show="scenario.iterations.histogram.data.length"> data="scenario.iterations.pie"
<svg id="total-histogram"></svg> title="Distribution"
<select class="chart-dropdown" title-class="h3"
ng-model="totalHistogramModel" style="float:left; width:40%; margin-top:15px">
ng-options="i.name for i in scenario.iterations.histogram.views track by i.id"></select>
</div> </div>
<div widget="Histogram"
ng-if="scenario.iterations.histogram.data.length"
data="scenario.iterations.histogram.data[mainHistogram.id]"
style="float:left; width:59%; margin-top:15px; position:relative; top:40px">
</div>
<select ng-model="mainHistogram"
ng-show="scenario.iterations.histogram.data.length"
ng-options="i.name for i in scenario.iterations.histogram.views track by i.id"
style="float:right; margin:45px 35px 0">
</select>
<div class="clearfix"></div>
</script> </script>
<script type="text/ng-template" id="details"> <script type="text/ng-template" id="details">
{{renderDetails()}}
<h2>Atomic Action Durations</h2> <div widget="StackedArea"
<div class="chart"> data="scenario.atomic.iter"
<svg id="atomic-stack"></svg> title="Atomic Action Durations"
name-x="Iteration sequence number"
controls="true"
guide="true">
</div> </div>
<h3>Distribution</h3> <div widget="Pie"
<div class="chart lesser top-margin"> data="scenario.atomic.pie"
<svg id="atomic-pie"></svg> title="Distribution"
title-class="h3"
style="float:left; width:40%; margin-top:15px">
</div> </div>
<div class="chart larger top-margin" <div widget="Histogram" data="scenario.atomic.histogram.data[atomicHistogram.id]"
ng-show="scenario.atomic.histogram.data.length"> ng-if="scenario.atomic.histogram.data.length"
<svg id="atomic-histogram"></svg> style="float:left; width:59%; margin-top:15px; position:relative; top:40px">
<select class="chart-dropdown"
ng-model="atomicHistogramModel"
ng-options="i.name for i in scenario.atomic.histogram.views track by i.id"></select>
</div> </div>
<select ng-show="scenario.atomic.histogram.data.length"
ng-model="atomicHistogram"
ng-options="i.name for i in scenario.atomic.histogram.views track by i.id"
style="float:right; margin:45px 35px 0">
</select>
<div class="clearfix"></div>
</script> </script>
<script type="text/ng-template" id="output"> <script type="text/ng-template" id="output">
{{renderOutput()}}
<h2>Scenario output</h2> <div style="padding:10px 0 0">
<div class="chart"> <span class="link"
<svg id="output-stack"></svg> ng-click="location.hash('output/additive')"
ng-class="{active:scenario.output.active === 'additive'}"
ng-if="scenario.output.has_additive">Aggregated</span>
<span class="link"
ng-click="location.hash('output/complete')"
ng-class="{active:scenario.output.active === 'complete'}"
ng-if="scenario.output.has_complete">Per iteration</span>
</div>
<div ng-repeat="chart in scenario.additive_output"
ng-if="scenario.output.active === 'additive'">
<div widget="{{chart.widget}}"
title="{{chart.title}}"
description="{{chart.description}}"
data="chart.data">
</div>
</div>
<div ng-if="scenario.output.active === 'complete'" style="padding:10px 0 0">
<select ng-model="outputIteration">
<option ng-repeat="i in scenario.complete_output track by $index"
value="{{$index}}">
Iteration {{$index}}
</select>
<div ng-repeat="chart in scenario.complete_output[outputIteration]">
<div widget="{{chart.widget}}"
title="{{chart.title}}"
description="{{chart.description}}"
data="chart.data">
</div>
</div>
</div> </div>
</script> </script>

View File

@ -0,0 +1,20 @@
{
"Dummy.dummy_output": [
{
"args": {
"random_range": 25
},
"runner": {
"type": "constant",
"times": 50,
"concurrency": 5
},
"context": {
"users": {
"tenants": 1,
"users_per_tenant": 1
}
}
}
]
}

View File

@ -0,0 +1,13 @@
---
Dummy.dummy_output:
-
args:
random_range: 25
runner:
type: "constant"
times: 50
concurrency: 5
context:
users:
tenants: 1
users_per_tenant: 1

View File

@ -51,6 +51,58 @@ class DummyTestCase(test.TestCase):
scenario.dummy_exception_probability, scenario.dummy_exception_probability,
exception_probability=1) exception_probability=1)
@mock.patch("rally.plugins.common.scenarios.dummy.dummy.random")
def test_dummy_output(self, mock_random):
mock_random.randint.side_effect = lambda min_, max_: max_
desc = "This is a description text for %s"
for random_range, exp in (None, 25), (1, 1), (42, 42):
scenario = dummy.Dummy(test.get_test_context())
if random_range is None:
scenario.dummy_output()
else:
scenario.dummy_output(random_range=random_range)
expected = {
"additive": [
{"chart": "OutputStatsTable",
"items": [[s + " stat", exp]
for s in ("foo", "bar", "spam")],
"title": "Additive Stat Table",
"description": desc % "Additive Stat Table"},
{"chart": "OutputStackedAreaChart",
"items": [["foo 1", exp], ["foo 2", exp]],
"title": "Additive Foo StackedArea",
"description": desc % "Additive Foo StackedArea"},
{"chart": "OutputStackedAreaChart",
"items": [["bar %d" % i, exp] for i in range(1, 7)],
"title": "Additive Bar StackedArea (no description)",
"description": ""},
{"chart": "OutputAvgChart",
"items": [["spam %d" % i, exp] for i in range(1, 4)],
"title": "Additive Spam Pie",
"description": desc % "Additive Spam Pie"}],
"complete": [
{"data": [["alpha", [[i, exp] for i in range(30)]],
["beta", [[i, exp] for i in range(30)]],
["gamma", [[i, exp] for i in range(30)]]],
"title": "Complete StackedArea",
"description": desc % "Complete StackedArea",
"widget": "StackedArea"},
{"data": [["delta", exp], ["epsilon", exp], ["zeta", exp],
["theta", exp], ["lambda", exp], ["omega", exp]],
"title": "Complete Pie (no description)",
"description": "",
"widget": "Pie"},
{"data": {
"cols": ["mu column", "xi column", "pi column",
"tau column", "chi column"],
"rows": [
[r + " row", exp, exp, exp, exp]
for r in ("iota", "nu", "rho", "phi", "psi")]},
"title": "Complete Table",
"description": desc % "Complete Table",
"widget": "Table"}]}
self.assertEqual(expected, scenario._output)
def test_dummy_dummy_with_scenario_output(self): def test_dummy_dummy_with_scenario_output(self):
scenario = dummy.Dummy(test.get_test_context()) scenario = dummy.Dummy(test.get_test_context())
result = scenario.dummy_with_scenario_output() result = scenario.dummy_with_scenario_output()

View File

@ -223,6 +223,9 @@ class ScenarioTestCase(test.TestCase):
def test_add_output(self): def test_add_output(self):
scenario_inst = scenario.Scenario() scenario_inst = scenario.Scenario()
self.assertEqual({"additive": [], "complete": []},
scenario_inst._output)
additive1 = {"title": "Additive 1", "chart": "FooChart", additive1 = {"title": "Additive 1", "chart": "FooChart",
"description": "Foo description", "description": "Foo description",
"items": [["foo", 1], ["bar", 2]]} "items": [["foo", 1], ["bar", 2]]}