Merge "[Reports] Use timestamps on X axis in trends report"

This commit is contained in:
Jenkins 2016-09-13 14:39:37 +00:00 committed by Gerrit Code Review
commit bea20ca6ec
4 changed files with 257 additions and 200 deletions

View File

@ -167,15 +167,15 @@ def trends(tasks_results):
class Trends(object): class Trends(object):
"""Process tasks results and make trends data. """Process workloads results and make trends data.
Group tasks results by their input configuration, Group workloads results by their input configuration,
calculate statistics for these groups and prepare it calculate statistics for these groups and prepare it
for displaying in trends HTML report. for displaying in trends HTML report.
""" """
def __init__(self): def __init__(self):
self._tasks = {} self._data = {}
def _to_str(self, obj): def _to_str(self, obj):
"""Convert object into string.""" """Convert object into string."""
@ -197,29 +197,39 @@ class Trends(object):
def add_result(self, result): def add_result(self, result):
key = self._make_hash(result["key"]["kw"]) key = self._make_hash(result["key"]["kw"])
if key not in self._tasks: if key not in self._data:
name = result["key"]["name"] self._data[key] = {
self._tasks[key] = {"seq": 1, "actions": {},
"name": name, "sla_failures": 0,
"cls": name.split(".")[0], "name": result["key"]["name"],
"met": name.split(".")[1], "config": json.dumps(result["key"]["kw"], indent=2)}
"data": {},
"total": None,
"atomic": [],
"stat": {},
"sla_failures": 0,
"config": json.dumps(result["key"]["kw"],
indent=2)}
else:
self._tasks[key]["seq"] += 1
for sla in result["sla"]: for sla in result["sla"]:
self._tasks[key]["sla_failures"] += not sla["success"] self._data[key]["sla_failures"] += not sla["success"]
task = {row[0]: dict(zip(result["info"]["stat"]["cols"], row)) stat = {row[0]: dict(zip(result["info"]["stat"]["cols"], row))
for row in result["info"]["stat"]["rows"]} for row in result["info"]["stat"]["rows"]}
ts = int(result["info"]["tstamp_start"] * 1000)
for action in stat:
# NOTE(amaretskiy): some atomic actions can be missed due to
# failures. We can ignore that because we use NVD3 lineChart()
# for displaying trends, which is safe for missed points
if action not in self._data[key]["actions"]:
self._data[key]["actions"][action] = {
"durations": {"min": [], "median": [], "90%ile": [],
"95%ile": [], "max": [], "avg": []},
"success": []}
try:
success = float(stat[action]["Success"].rstrip("%"))
except ValueError:
# Got "n/a" for some reason
success = 0
self._data[key]["actions"][action]["success"].append(
(ts, success))
for k in task:
for tgt, src in (("min", "Min (sec)"), for tgt, src in (("min", "Min (sec)"),
("median", "Median (sec)"), ("median", "Median (sec)"),
("90%ile", "90%ile (sec)"), ("90%ile", "90%ile (sec)"),
@ -227,49 +237,44 @@ class Trends(object):
("max", "Max (sec)"), ("max", "Max (sec)"),
("avg", "Avg (sec)")): ("avg", "Avg (sec)")):
# NOTE(amaretskiy): some atomic actions can be self._data[key]["actions"][action]["durations"][tgt].append(
# missed due to failures. We can ignore that (ts, stat[action][src]))
# because we use NVD3 lineChart() for displaying
# trends, which is safe for missed points
if k not in self._tasks[key]["data"]:
self._tasks[key]["data"][k] = {"min": [],
"median": [],
"90%ile": [],
"95%ile": [],
"max": [],
"avg": [],
"success": []}
self._tasks[key]["data"][k][tgt].append(
(self._tasks[key]["seq"], task[k][src]))
try:
success = float(task[k]["Success"].rstrip("%"))
except ValueError:
# Got "n/a" for some reason
success = 0
self._tasks[key]["data"][k]["success"].append(
(self._tasks[key]["seq"], success))
def get_data(self): def get_data(self):
for key, value in self._tasks.items(): trends = []
total = None
for k, v in value["data"].items(): for wload in self._data.values():
success = [("success", v.pop("success"))] trend = {"stat": {},
if k == "total": "name": wload["name"],
total = {"values": v, "success": success} "cls": wload["name"].split(".")[0],
"met": wload["name"].split(".")[1],
"sla_failures": wload["sla_failures"],
"config": wload["config"],
"actions": []}
for action, data in wload["actions"].items():
action_durs = [(k, sorted(v))
for k, v in data["durations"].items()]
if action == "total":
trend.update(
{"length": len(data["success"]),
"durations": action_durs,
"success": [("success", sorted(data["success"]))]})
else: else:
self._tasks[key]["atomic"].append( trend["actions"].append(
{"name": k, "values": list(v.items()), {"name": action,
"success": success}) "durations": action_durs,
"success": [("success", sorted(data["success"]))]})
for stat, comp in (("min", charts.streaming.MinComputation()), for stat, comp in (("min", charts.streaming.MinComputation()),
("max", charts.streaming.MaxComputation()), ("max", charts.streaming.MaxComputation()),
("avg", charts.streaming.MeanComputation())): ("avg", charts.streaming.MeanComputation())):
for k, v in total["values"][stat]: for k, v in trend["durations"]:
if isinstance(v, (float,) + six.integer_types): for i in v:
comp.add(v) if isinstance(i[1], (float,) + six.integer_types):
self._tasks[key]["stat"][stat] = comp.result() comp.add(i[1])
del self._tasks[key]["data"] trend["stat"][stat] = comp.result()
total["values"] = list(total["values"].items())
self._tasks[key]["total"] = total trends.append(trend)
self._tasks[key]["single"] = self._tasks[key]["seq"] < 2
return sorted(self._tasks.values(), key=lambda s: s["name"]) return sorted(trends, key=lambda i: i["name"])

View File

@ -73,9 +73,9 @@ var widgetDirective = function($compile) {
.showControls(opts.controls) .showControls(opts.controls)
.clipEdge(true); .clipEdge(true);
chart.xAxis chart.xAxis
.tickFormat(d3.format(opts.xformat || "d")) .axisLabel(opts.xname)
.axisLabel(opts.xname || "") .tickFormat(opts.xformat)
.showMaxMin(false); .showMaxMin(opts.showmaxmin);
chart.yAxis chart.yAxis
.orient("left") .orient("left")
.tickFormat(d3.format(opts.yformat || ",.3f")); .tickFormat(d3.format(opts.yformat || ",.3f"));
@ -92,9 +92,10 @@ var widgetDirective = function($compile) {
.useInteractiveGuideline(opts.guide) .useInteractiveGuideline(opts.guide)
.clipEdge(true); .clipEdge(true);
chart.xAxis chart.xAxis
.tickFormat(d3.format(opts.xformat || "d")) .axisLabel(opts.xname)
.axisLabel(opts.xname || "") .tickFormat(opts.xformat)
.showMaxMin(false); .rotateLabels(opts.xrotate)
.showMaxMin(opts.showmaxmin);
chart.yAxis chart.yAxis
.orient("left") .orient("left")
.tickFormat(d3.format(opts.yformat || ",.3f")); .tickFormat(d3.format(opts.yformat || ",.3f"));
@ -184,14 +185,20 @@ var widgetDirective = function($compile) {
} }
} }
var options = { var opts = {
xname: attrs.nameX || "", xname: attrs.nameX || "",
xformat: attrs.formatX || "d", xrotate: attrs.rotateX || 0,
yformat: attrs.formatY || ",.3f", yformat: attrs.formatY || ",.3f",
controls: attrs.controls === "true", controls: attrs.controls === "true",
guide: attrs.guide === "true" guide: attrs.guide === "true",
showmaxmin: attrs.showmaxmin === "true"
}; };
Chart.get_chart(attrs.widget)(el, data, options, do_after); if (attrs.formatDateX) {
opts.xformat = function(d) { return d3.time.format(attrs.formatDateX)(new Date(d)) }
} else {
opts.xformat = d3.format(attrs.formatX || "d")
}
Chart.get_chart(attrs.widget)(el, data, opts, do_after);
} }
if (attrs.nameY) { if (attrs.nameY) {

View File

@ -97,9 +97,9 @@
name: "Total", name: "Total",
visible: function(){ return true } visible: function(){ return true }
}, { }, {
id: "atomic", id: "actions",
name: "Atomic actions", name: "Atomic actions",
visible: function(){ return (! $scope.wload.single) && $scope.wload.atomic.length } visible: function(){ return ($scope.wload.length !== 1) && $scope.wload.actions.length }
}, { }, {
id: "config", id: "config",
name: "Configuration", name: "Configuration",
@ -134,6 +134,8 @@
} }
} }
/* Other helpers */
$scope.showError = function(message) { $scope.showError = function(message) {
return (function (e) { return (function (e) {
e.style.display = "block"; e.style.display = "block";
@ -173,8 +175,7 @@
w.order_idx = itr > 1 ? " ["+itr+"]" : "" w.order_idx = itr > 1 ? " ["+itr+"]" : ""
$scope.wload_map[w.ref] = w; $scope.wload_map[w.ref] = w;
$scope.nav_map[w.ref] = cls_idx; $scope.nav_map[w.ref] = cls_idx;
met.push({name:w.met, itr:itr, idx:idx, order_idx:w.order_idx, met.push({name:w.met, itr:itr, idx:idx, order_idx:w.order_idx, ref:w.ref});
ref:w.ref, single:w.single});
prev_met = w.met; prev_met = w.met;
itr += 1; itr += 1;
} }
@ -271,7 +272,7 @@
<div class="navmet" <div class="navmet"
title="{{m.name}}{{m.order_idx}}" title="{{m.name}}{{m.order_idx}}"
ng-show="n.idx==nav_idx" ng-show="n.idx==nav_idx"
ng-class="{active:wload && m.ref==wload.ref, single:m.single}" ng-class="{active:wload && m.ref==wload.ref, single:m.length === 1}"
ng-click="location.path(m.ref)" ng-click="location.path(m.ref)"
ng-repeat="m in n.met track by $index" ng-repeat="m in n.met track by $index"
ng-repeat-end>{{m.name}}{{m.order_idx}}</div> ng-repeat-end>{{m.name}}{{m.order_idx}}</div>
@ -336,18 +337,18 @@
<tbody> <tbody>
<tr ng-repeat="w in data | orderBy:ov_srt:ov_dir" <tr ng-repeat="w in data | orderBy:ov_srt:ov_dir"
ng-click="location.path(w.ref)" ng-click="location.path(w.ref)"
ng-class="{single:w.single}"> ng-class="{single:w.length === 1}">
<td>{{w.ref}} <td>{{w.ref}}
<td>{{w.seq}} <td>{{w.length}}
<td> <td>
<span ng-if="w.single">-</span> <span ng-if="w.length === 1">-</span>
<span ng-if="!w.single">{{w.stat.min | number:4}}</span> <span ng-if="w.length !== 1">{{w.stat.min | number:4}}</span>
<td> <td>
<span ng-if="w.single">-</span> <span ng-if="w.length === 1">-</span>
<span ng-if="!w.single">{{w.stat.max | number:4}}</span> <span ng-if="w.length !== 1">{{w.stat.max | number:4}}</span>
<td> <td>
<span ng-if="w.single">-</span> <span ng-if="w.length === 1">-</span>
<span ng-if="!w.single">{{w.stat.avg | number:4}}</span> <span ng-if="w.length !== 1">{{w.stat.avg | number:4}}</span>
<td title="{{w.sla_failures ? 'Failures: ' + w.sla_failures : 'No failures'}}"> <td title="{{w.sla_failures ? 'Failures: ' + w.sla_failures : 'No failures'}}">
<span ng-hide="w.sla_failures" class="status-pass">&#x2714;</span> <span ng-hide="w.sla_failures" class="status-pass">&#x2714;</span>
<span ng-show="w.sla_failures" class="status-fail">&#x2716;</span> <span ng-show="w.sla_failures" class="status-fail">&#x2716;</span>
@ -371,46 +372,58 @@
<div ng-include="tab"></div> <div ng-include="tab"></div>
<script type="text/ng-template" id="total"> <script type="text/ng-template" id="total">
<div ng-if="wload.single"> <div ng-if="wload.length === 1">
<div style="margin:30px 0 10px; font-size:14px; color:#ff6622"> <div style="margin:30px 0 10px; font-size:14px; color:#ff6622">
This workload has single run so trends can not be displayed.<br> This workload has single run so trends can not be displayed.<br>
There should be at least two workload results with the same configuration There should be at least two workload results with the same configuration
</div> </div>
</div> </div>
<div ng-if="!wload.single"> <div ng-if="wload.length !== 1">
<h2>Total durations</h2> <h2>Total durations</h2>
<div widget="Lines" <div widget="Lines"
data="wload.total.values" data="wload.durations"
name-x="Task run sequence number"
controls="true" controls="true"
guide="true"> guide="true"
showmaxmin="true"
rotate-x="-70"
format-date-x="%Y-%m-%d %H:%M:%S"
style="height:370px">
</div> </div>
<h2>Total success rate</h2> <h2>Total success rate</h2>
<div widget="Lines" <div widget="Lines"
data="wload.total.success" data="wload.success"
name-x="Task run sequence number"
controls="true" controls="true"
guide="true"> guide="true"
showmaxmin="true"
rotate-x="-70"
format-date-x="%Y-%m-%d %H:%M:%S"
style="height:370px">
</div> </div>
</div> </div>
</script> </script>
<script type="text/ng-template" id="atomic"> <script type="text/ng-template" id="actions">
<h2>Atomic actions durations / success rate</h2> <h2>Atomic actions durations / success rate</h2>
<div ng-repeat="chart in wload.atomic track by $index"> <div ng-repeat="chart in wload.actions track by $index">
<span id="{{chart.name}}"></span> <span id="{{chart.name}}"></span>
<div class="chart-title">{{chart.name}}</div> <div class="chart-title">{{chart.name}}</div>
<div widget="Lines" <div widget="Lines"
data="chart.values" data="chart.durations"
name-x="Task run sequence number"
controls="true" controls="true"
guide="true"> guide="true"
showmaxmin="true"
rotate-x="-70"
format-date-x="%Y-%m-%d %H:%M:%S"
style="height:370px">
</div> </div>
<div widget="Lines" <div widget="Lines"
data="chart.success" data="chart.success"
name-x="Task run sequence number"
controls="true" controls="true"
guide="true"> guide="true"
showmaxmin="true"
rotate-x="-70"
format-date-x="%Y-%m-%d %H:%M:%S"
style="height:400px">
</div> </div>
</div> </div>
</script> </script>

View File

@ -178,7 +178,7 @@ class TrendsTestCase(test.TestCase):
def test___init__(self): def test___init__(self):
trends = plot.Trends() trends = plot.Trends()
self.assertEqual({}, trends._tasks) self.assertEqual({}, trends._data)
self.assertRaises(TypeError, plot.Trends, 42) self.assertRaises(TypeError, plot.Trends, 42)
@ddt.data({"args": [None], "result": "None"}, @ddt.data({"args": [None], "result": "None"},
@ -227,11 +227,12 @@ class TrendsTestCase(test.TestCase):
atomic = {"a": 123, "b": 456} atomic = {"a": 123, "b": 456}
stat_rows = [["a", 0.7, 0.85, 0.9, 0.87, 1.25, 0.67, "100.0%", 4], stat_rows = [["a", 0.7, 0.85, 0.9, 0.87, 1.25, 0.67, "100.0%", 4],
["b", 0.5, 0.75, 0.85, 0.9, 1.1, 0.58, "100.0%", 4], ["b", 0.5, 0.75, 0.85, 0.9, 1.1, 0.58, "100.0%", 4],
["total", 1.2, 1.55, 1.7, 1.9, 1.5, 1.6, "100.0%", 4]] ["total", 1.2, 1.55, 1.7, 1.8, 1.5, 0.8, "100.0%", 4]]
return { return {
"key": {"kw": salt + "_kw", "name": "Scenario.name_%s" % salt}, "key": {"kw": "kw_%d" % salt, "name": "Scenario.name_%d" % salt},
"sla": [{"success": sla_success}], "sla": [{"success": sla_success}],
"info": {"iterations_count": 4, "atomic": atomic, "info": {"iterations_count": 4, "atomic": atomic,
"tstamp_start": 123456.789 + salt,
"stat": {"rows": stat_rows, "stat": {"rows": stat_rows,
"cols": ["Action", "Min (sec)", "Median (sec)", "cols": ["Action", "Min (sec)", "Median (sec)",
"90%ile (sec)", "95%ile (sec)", "90%ile (sec)", "95%ile (sec)",
@ -240,120 +241,151 @@ class TrendsTestCase(test.TestCase):
"iterations": ["<iter-0>", "<iter-1>", "<iter-2>", "<iter-3>"]} "iterations": ["<iter-0>", "<iter-1>", "<iter-2>", "<iter-3>"]}
def _sort_trends(self, trends_result): def _sort_trends(self, trends_result):
for r_idx, res in enumerate(trends_result): for idx in range(len(trends_result)):
trends_result[r_idx]["total"]["values"].sort() trends_result[idx]["durations"].sort()
for a_idx, dummy in enumerate(res["atomic"]): for a_idx in range(len(trends_result[idx]["actions"])):
trends_result[r_idx]["atomic"][a_idx]["values"].sort() trends_result[idx]["actions"][a_idx]["durations"].sort()
return trends_result return trends_result
def test_add_result_and_get_data(self): def test_add_result_and_get_data(self):
trends = plot.Trends() trends = plot.Trends()
for i in 0, 1: for i in 0, 1:
trends.add_result(self._make_result(str(i))) trends.add_result(self._make_result(i))
expected = [ expected = [
{"atomic": [ {"actions": [{"durations": [("90%ile", [(123456789, 0.9)]),
{"name": "a", ("95%ile", [(123456789, 0.87)]),
"success": [("success", [(1, 100.0)])], ("avg", [(123456789, 0.67)]),
"values": [("90%ile", [(1, 0.9)]), ("95%ile", [(1, 0.87)]), ("max", [(123456789, 1.25)]),
("avg", [(1, 0.67)]), ("max", [(1, 1.25)]), ("median", [(123456789, 0.85)]),
("median", [(1, 0.85)]), ("min", [(1, 0.7)])]}, ("min", [(123456789, 0.7)])],
{"name": "b", "name": "a",
"success": [("success", [(1, 100.0)])], "success": [("success", [(123456789, 100.0)])]},
"values": [("90%ile", [(1, 0.85)]), ("95%ile", [(1, 0.9)]), {"durations": [("90%ile", [(123456789, 0.85)]),
("avg", [(1, 0.58)]), ("max", [(1, 1.1)]), ("95%ile", [(123456789, 0.9)]),
("median", [(1, 0.75)]), ("min", [(1, 0.5)])]}], ("avg", [(123456789, 0.58)]),
"cls": "Scenario", "config": "\"0_kw\"", "met": "name_0", ("max", [(123456789, 1.1)]),
"name": "Scenario.name_0", "seq": 1, "single": True, ("median", [(123456789, 0.75)]),
"sla_failures": 0, "stat": {"avg": 1.6, "max": 1.5, "min": 1.2}, ("min", [(123456789, 0.5)])],
"total": {"success": [("success", [(1, 100.0)])], "name": "b",
"values": [("90%ile", [(1, 1.7)]), "success": [("success", [(123456789, 100.0)])]}],
("95%ile", [(1, 1.9)]), "cls": "Scenario",
("avg", [(1, 1.6)]), "config": "\"kw_0\"",
("max", [(1, 1.5)]), "durations": [("90%ile", [(123456789, 1.7)]),
("median", [(1, 1.55)]), ("95%ile", [(123456789, 1.8)]),
("min", [(1, 1.2)])]}}, ("avg", [(123456789, 0.8)]),
{"atomic": [ ("max", [(123456789, 1.5)]),
{"name": "a", ("median", [(123456789, 1.55)]),
"success": [("success", [(1, 100.0)])], ("min", [(123456789, 1.2)])],
"values": [("90%ile", [(1, 0.9)]), ("95%ile", [(1, 0.87)]), "length": 1,
("avg", [(1, 0.67)]), ("max", [(1, 1.25)]), "met": "name_0",
("median", [(1, 0.85)]), ("min", [(1, 0.7)])]}, "name": "Scenario.name_0",
{"name": "b", "sla_failures": 0,
"success": [("success", [(1, 100.0)])], "stat": {"avg": 1.425, "max": 1.8, "min": 0.8},
"values": [("90%ile", [(1, 0.85)]), ("95%ile", [(1, 0.9)]), "success": [("success", [(123456789, 100.0)])]},
("avg", [(1, 0.58)]), ("max", [(1, 1.1)]), {"actions": [{"durations": [("90%ile", [(123457789, 0.9)]),
("median", [(1, 0.75)]), ("min", [(1, 0.5)])]}], ("95%ile", [(123457789, 0.87)]),
"cls": "Scenario", "config": "\"1_kw\"", "met": "name_1", ("avg", [(123457789, 0.67)]),
"name": "Scenario.name_1", "seq": 1, "single": True, ("max", [(123457789, 1.25)]),
"sla_failures": 0, "stat": {"avg": 1.6, "max": 1.5, "min": 1.2}, ("median", [(123457789, 0.85)]),
"total": {"success": [("success", [(1, 100.0)])], ("min", [(123457789, 0.7)])],
"values": [("90%ile", [(1, 1.7)]), "name": "a",
("95%ile", [(1, 1.9)]), "success": [("success", [(123457789, 100.0)])]},
("avg", [(1, 1.6)]), {"durations": [("90%ile", [(123457789, 0.85)]),
("max", [(1, 1.5)]), ("95%ile", [(123457789, 0.9)]),
("median", [(1, 1.55)]), ("avg", [(123457789, 0.58)]),
("min", [(1, 1.2)])]}}] ("max", [(123457789, 1.1)]),
("median", [(123457789, 0.75)]),
("min", [(123457789, 0.5)])],
"name": "b",
"success": [("success", [(123457789, 100.0)])]}],
"cls": "Scenario",
"config": "\"kw_1\"",
"durations": [("90%ile", [(123457789, 1.7)]),
("95%ile", [(123457789, 1.8)]),
("avg", [(123457789, 0.8)]),
("max", [(123457789, 1.5)]),
("median", [(123457789, 1.55)]),
("min", [(123457789, 1.2)])],
"length": 1,
"met": "name_1",
"name": "Scenario.name_1",
"sla_failures": 0,
"stat": {"avg": 1.425, "max": 1.8, "min": 0.8},
"success": [("success", [(123457789, 100.0)])]}]
self.assertEqual(expected, self._sort_trends(trends.get_data())) self.assertEqual(expected, self._sort_trends(trends.get_data()))
def test_add_result_once_and_get_data(self): def test_add_result_once_and_get_data(self):
trends = plot.Trends() trends = plot.Trends()
trends.add_result(self._make_result("foo", sla_success=False)) trends.add_result(self._make_result(42, sla_success=False))
expected = [ expected = [
{"atomic": [ {"actions": [{"durations": [("90%ile", [(123498789, 0.9)]),
{"name": "a", ("95%ile", [(123498789, 0.87)]),
"success": [("success", [(1, 100.0)])], ("avg", [(123498789, 0.67)]),
"values": [("90%ile", [(1, 0.9)]), ("95%ile", [(1, 0.87)]), ("max", [(123498789, 1.25)]),
("avg", [(1, 0.67)]), ("max", [(1, 1.25)]), ("median", [(123498789, 0.85)]),
("median", [(1, 0.85)]), ("min", [(1, 0.7)])]}, ("min", [(123498789, 0.7)])],
{"name": "b", "name": "a",
"success": [("success", [(1, 100.0)])], "success": [("success", [(123498789, 100.0)])]},
"values": [("90%ile", [(1, 0.85)]), ("95%ile", [(1, 0.9)]), {"durations": [("90%ile", [(123498789, 0.85)]),
("avg", [(1, 0.58)]), ("max", [(1, 1.1)]), ("95%ile", [(123498789, 0.9)]),
("median", [(1, 0.75)]), ("min", [(1, 0.5)])]}], ("avg", [(123498789, 0.58)]),
"cls": "Scenario", "config": "\"foo_kw\"", "met": "name_foo", ("max", [(123498789, 1.1)]),
"name": "Scenario.name_foo", "seq": 1, "single": True, ("median", [(123498789, 0.75)]),
"sla_failures": 1, "stat": {"avg": 1.6, "max": 1.5, "min": 1.2}, ("min", [(123498789, 0.5)])],
"total": {"success": [("success", [(1, 100.0)])], "name": "b",
"values": [("90%ile", [(1, 1.7)]), "success": [("success", [(123498789, 100.0)])]}],
("95%ile", [(1, 1.9)]), "cls": "Scenario",
("avg", [(1, 1.6)]), "config": "\"kw_42\"",
("max", [(1, 1.5)]), "durations": [("90%ile", [(123498789, 1.7)]),
("median", [(1, 1.55)]), ("95%ile", [(123498789, 1.8)]),
("min", [(1, 1.2)])]}}] ("avg", [(123498789, 0.8)]),
("max", [(123498789, 1.5)]),
("median", [(123498789, 1.55)]),
("min", [(123498789, 1.2)])],
"length": 1,
"met": "name_42",
"name": "Scenario.name_42",
"sla_failures": 1,
"stat": {"avg": 1.425, "max": 1.8, "min": 0.8},
"success": [("success", [(123498789, 100.0)])]}]
self.assertEqual(expected, self._sort_trends(trends.get_data())) self.assertEqual(expected, self._sort_trends(trends.get_data()))
def test_add_result_with_na_and_get_data(self): def test_add_result_with_na_and_get_data(self):
trends = plot.Trends() trends = plot.Trends()
trends.add_result(self._make_result("foo", trends.add_result(
sla_success=False, with_na=True)) self._make_result(42, sla_success=False, with_na=True))
expected = [ expected = [
{"atomic": [{"name": "a", {"actions": [{"durations": [("90%ile", [(123498789, "n/a")]),
"success": [("success", [(1, 0)])], ("95%ile", [(123498789, "n/a")]),
"values": [("90%ile", [(1, "n/a")]), ("avg", [(123498789, "n/a")]),
("95%ile", [(1, "n/a")]), ("max", [(123498789, "n/a")]),
("avg", [(1, "n/a")]), ("median", [(123498789, "n/a")]),
("max", [(1, "n/a")]), ("min", [(123498789, "n/a")])],
("median", [(1, "n/a")]), "name": "a",
("min", [(1, "n/a")])]}, "success": [("success", [(123498789, 0)])]},
{"name": "b", {"durations": [("90%ile", [(123498789, "n/a")]),
"success": [("success", [(1, 0)])], ("95%ile", [(123498789, "n/a")]),
"values": [("90%ile", [(1, "n/a")]), ("avg", [(123498789, "n/a")]),
("95%ile", [(1, "n/a")]), ("max", [(123498789, "n/a")]),
("avg", [(1, "n/a")]), ("median", [(123498789, "n/a")]),
("max", [(1, "n/a")]), ("min", [(123498789, "n/a")])],
("median", [(1, "n/a")]), "name": "b",
("min", [(1, "n/a")])]}], "success": [("success", [(123498789, 0)])]}],
"cls": "Scenario", "config": "\"foo_kw\"", "met": "name_foo", "cls": "Scenario",
"name": "Scenario.name_foo", "seq": 1, "single": True, "config": "\"kw_42\"",
"sla_failures": 1, "stat": {"avg": None, "max": None, "durations": [("90%ile", [(123498789, "n/a")]),
"min": None}, ("95%ile", [(123498789, "n/a")]),
"total": {"success": [("success", [(1, 0)])], ("avg", [(123498789, "n/a")]),
"values": [("90%ile", [(1, "n/a")]), ("max", [(123498789, "n/a")]),
("95%ile", [(1, "n/a")]), ("median", [(123498789, "n/a")]),
("avg", [(1, "n/a")]), ("min", [(123498789, "n/a")])],
("max", [(1, "n/a")]), "length": 1,
("median", [(1, "n/a")]), "met": "name_42",
("min", [(1, "n/a")])]}}] "name": "Scenario.name_42",
"sla_failures": 1,
"stat": {"avg": None, "max": None, "min": None},
"success": [("success", [(123498789, 0)])]}]
self.assertEqual(expected, self._sort_trends(trends.get_data())) self.assertEqual(expected, self._sort_trends(trends.get_data()))
def test_get_data_no_results_added(self): def test_get_data_no_results_added(self):