Report: Add CSC and VSC data
[csit.git] / resources / tools / presentation / generator_plots.py
index 004e65e..7cdcb62 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Cisco and/or its affiliates.
+# Copyright (c) 2019 Cisco and/or its affiliates.
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at:
@@ -15,6 +15,7 @@
 """
 
 
+import re
 import logging
 import pandas as pd
 import plotly.offline as ploff
@@ -24,7 +25,7 @@ from plotly.exceptions import PlotlyError
 from collections import OrderedDict
 from copy import deepcopy
 
-from utils import mean
+from utils import mean, stdev
 
 
 COLORS = ["SkyBlue", "Olive", "Purple", "Coral", "Indigo", "Pink",
@@ -195,6 +196,248 @@ def plot_performance_box(plot, input_data):
         return
 
 
+def plot_soak_bars(plot, input_data):
+    """Generate the plot(s) with algorithm: plot_soak_bars
+    specified in the specification file.
+
+    :param plot: Plot to generate.
+    :param input_data: Data to process.
+    :type plot: pandas.Series
+    :type input_data: InputData
+    """
+
+    # Transform the data
+    plot_title = plot.get("title", "")
+    logging.info("    Creating the data set for the {0} '{1}'.".
+                 format(plot.get("type", ""), plot_title))
+    data = input_data.filter_data(plot)
+    if data is None:
+        logging.error("No data.")
+        return
+
+    # Prepare the data for the plot
+    y_vals = dict()
+    y_tags = dict()
+    for job in data:
+        for build in job:
+            for test in build:
+                if y_vals.get(test["parent"], None) is None:
+                    y_tags[test["parent"]] = test.get("tags", None)
+                try:
+                    if test["type"] in ("SOAK", ):
+                        y_vals[test["parent"]] = test["throughput"]
+                    else:
+                        continue
+                except (KeyError, TypeError):
+                    y_vals[test["parent"]] = dict()
+
+    # Sort the tests
+    order = plot.get("sort", None)
+    if order and y_tags:
+        y_sorted = OrderedDict()
+        y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
+        for tag in order:
+            logging.debug(tag)
+            for suite, tags in y_tags_l.items():
+                if "not " in tag:
+                    tag = tag.split(" ")[-1]
+                    if tag.lower() in tags:
+                        continue
+                else:
+                    if tag.lower() not in tags:
+                        continue
+                try:
+                    y_sorted[suite] = y_vals.pop(suite)
+                    y_tags_l.pop(suite)
+                    logging.debug(suite)
+                except KeyError as err:
+                    logging.error("Not found: {0}".format(repr(err)))
+                finally:
+                    break
+    else:
+        y_sorted = y_vals
+
+    idx = 0
+    y_max = 0
+    traces = list()
+    for test_name, test_data in y_sorted.items():
+        idx += 1
+        name = "{nr}. {name}".\
+            format(nr=idx, name=test_name.lower().replace('-soak', ''))
+        if len(name) > 50:
+            name_lst = name.split('-')
+            name = ""
+            split_name = True
+            for segment in name_lst:
+                if (len(name) + len(segment) + 1) > 50 and split_name:
+                    name += "<br>    "
+                    split_name = False
+                name += segment + '-'
+            name = name[:-1]
+
+        y_val = test_data.get("LOWER", None)
+        if y_val:
+            y_val /= 1000000
+            if y_val > y_max:
+                y_max = y_val
+
+        time = "No Information"
+        result = "No Information"
+        hovertext = ("{name}<br>"
+                     "Packet Throughput: {val:.2f}Mpps<br>"
+                     "Final Duration: {time}<br>"
+                     "Result: {result}".format(name=name,
+                                               val=y_val,
+                                               time=time,
+                                               result=result))
+        traces.append(plgo.Bar(x=[str(idx) + '.', ],
+                               y=[y_val, ],
+                               name=name,
+                               text=hovertext,
+                               hoverinfo="text"))
+    try:
+        # Create plot
+        layout = deepcopy(plot["layout"])
+        if layout.get("title", None):
+            layout["title"] = "<b>Packet Throughput:</b> {0}". \
+                format(layout["title"])
+        if y_max:
+            layout["yaxis"]["range"] = [0, y_max + 1]
+        plpl = plgo.Figure(data=traces, layout=layout)
+        # Export Plot
+        logging.info("    Writing file '{0}{1}'.".
+                     format(plot["output-file"], plot["output-file-type"]))
+        ploff.plot(plpl, show_link=False, auto_open=False,
+                   filename='{0}{1}'.format(plot["output-file"],
+                                            plot["output-file-type"]))
+    except PlotlyError as err:
+        logging.error("   Finished with error: {}".
+                      format(repr(err).replace("\n", " ")))
+        return
+
+
+def plot_soak_boxes(plot, input_data):
+    """Generate the plot(s) with algorithm: plot_soak_boxes
+    specified in the specification file.
+
+    :param plot: Plot to generate.
+    :param input_data: Data to process.
+    :type plot: pandas.Series
+    :type input_data: InputData
+    """
+
+    # Transform the data
+    plot_title = plot.get("title", "")
+    logging.info("    Creating the data set for the {0} '{1}'.".
+                 format(plot.get("type", ""), plot_title))
+    data = input_data.filter_data(plot)
+    if data is None:
+        logging.error("No data.")
+        return
+
+    # Prepare the data for the plot
+    y_vals = dict()
+    y_tags = dict()
+    for job in data:
+        for build in job:
+            for test in build:
+                if y_vals.get(test["parent"], None) is None:
+                    y_tags[test["parent"]] = test.get("tags", None)
+                try:
+                    if test["type"] in ("SOAK", ):
+                        y_vals[test["parent"]] = test["throughput"]
+                    else:
+                        continue
+                except (KeyError, TypeError):
+                    y_vals[test["parent"]] = dict()
+
+    # Sort the tests
+    order = plot.get("sort", None)
+    if order and y_tags:
+        y_sorted = OrderedDict()
+        y_tags_l = {s: [t.lower() for t in ts] for s, ts in y_tags.items()}
+        for tag in order:
+            logging.debug(tag)
+            for suite, tags in y_tags_l.items():
+                if "not " in tag:
+                    tag = tag.split(" ")[-1]
+                    if tag.lower() in tags:
+                        continue
+                else:
+                    if tag.lower() not in tags:
+                        continue
+                try:
+                    y_sorted[suite] = y_vals.pop(suite)
+                    y_tags_l.pop(suite)
+                    logging.debug(suite)
+                except KeyError as err:
+                    logging.error("Not found: {0}".format(repr(err)))
+                finally:
+                    break
+    else:
+        y_sorted = y_vals
+
+    idx = 0
+    y_max = 0
+    traces = list()
+    for test_name, test_data in y_sorted.items():
+        idx += 1
+        name = "{nr}. {name}".\
+            format(nr=idx, name=test_name.lower().replace('-soak', ''))
+        if len(name) > 50:
+            name_lst = name.split('-')
+            name = ""
+            split_name = True
+            for segment in name_lst:
+                if (len(name) + len(segment) + 1) > 50 and split_name:
+                    name += "<br>    "
+                    split_name = False
+                name += segment + '-'
+            name = name[:-1]
+
+        y_val = test_data.get("UPPER", None)
+        if y_val:
+            y_val /= 1000000
+            if y_val > y_max:
+                y_max = y_val
+
+        y_base = test_data.get("LOWER", None)
+        if y_base:
+            y_base /= 1000000
+
+        hovertext = ("{name}<br>"
+                     "Upper bound: {upper:.2f}Mpps<br>"
+                     "Lower bound: {lower:.2f}Mpps".format(name=name,
+                                                           upper=y_val,
+                                                           lower=y_base))
+        traces.append(plgo.Bar(x=[str(idx) + '.', ],
+                               # +0.05 to see the value in case lower == upper
+                               y=[y_val - y_base + 0.05, ],
+                               base=y_base,
+                               name=name,
+                               text=hovertext,
+                               hoverinfo="text"))
+    try:
+        # Create plot
+        layout = deepcopy(plot["layout"])
+        if layout.get("title", None):
+            layout["title"] = "<b>Soak Tests:</b> {0}". \
+                format(layout["title"])
+        if y_max:
+            layout["yaxis"]["range"] = [0, y_max + 1]
+        plpl = plgo.Figure(data=traces, layout=layout)
+        # Export Plot
+        logging.info("    Writing file '{0}{1}'.".
+                     format(plot["output-file"], plot["output-file-type"]))
+        ploff.plot(plpl, show_link=False, auto_open=False,
+                   filename='{0}{1}'.format(plot["output-file"],
+                                            plot["output-file-type"]))
+    except PlotlyError as err:
+        logging.error("   Finished with error: {}".
+                      format(repr(err).replace("\n", " ")))
+        return
+
+
 def plot_latency_error_bars(plot, input_data):
     """Generate the plot(s) with algorithm: plot_latency_error_bars
     specified in the specification file.
@@ -546,6 +789,8 @@ def plot_throughput_speedup_analysis(plot, input_data):
             limit = plot["limits"]["nic"]["xxv710"]
         elif "xl710" in test_name:
             limit = plot["limits"]["nic"]["xl710"]
+        elif "x553" in test_name:
+            limit = plot["limits"]["nic"]["x553"]
         else:
             limit = 0
         if limit > nic_limit:
@@ -839,3 +1084,309 @@ def plot_http_server_performance_box(plot, input_data):
         logging.error("   Finished with error: {}".
                       format(str(err).replace("\n", " ")))
         return
+
+
+def plot_service_density_heatmap(plot, input_data):
+    """Generate the plot(s) with algorithm: plot_service_density_heatmap
+    specified in the specification file.
+
+    :param plot: Plot to generate.
+    :param input_data: Data to process.
+    :type plot: pandas.Series
+    :type input_data: InputData
+    """
+
+    REGEX_CN = re.compile(r'^(\d*)R(\d*)C$')
+
+    txt_chains = list()
+    txt_nodes = list()
+    vals = dict()
+
+    # Transform the data
+    logging.info("    Creating the data set for the {0} '{1}'.".
+                 format(plot.get("type", ""), plot.get("title", "")))
+    data = input_data.filter_data(plot, continue_on_error=True)
+    if data is None:
+        logging.error("No data.")
+        return
+
+    for job in data:
+        for build in job:
+            for test in build:
+                for tag in test['tags']:
+                    groups = re.search(REGEX_CN, tag)
+                    if groups:
+                        c = str(groups.group(1))
+                        n = str(groups.group(2))
+                        break
+                else:
+                    continue
+                if vals.get(c, None) is None:
+                    vals[c] = dict()
+                if vals[c].get(n, None) is None:
+                    vals[c][n] = dict(name=test["name"],
+                                      vals=list(),
+                                      nr=None,
+                                      mean=None,
+                                      stdev=None)
+                if plot["include-tests"] == "MRR":
+                    result = test["result"]["receive-rate"].avg
+                elif plot["include-tests"] == "PDR":
+                    result = test["throughput"]["PDR"]["LOWER"]
+                elif plot["include-tests"] == "NDR":
+                    result = test["throughput"]["NDR"]["LOWER"]
+                else:
+                    result = None
+
+                if result:
+                    vals[c][n]["vals"].append(result)
+
+    for key_c in vals.keys():
+        txt_chains.append(key_c)
+        for key_n in vals[key_c].keys():
+            txt_nodes.append(key_n)
+            if vals[key_c][key_n]["vals"]:
+                vals[key_c][key_n]["nr"] = len(vals[key_c][key_n]["vals"])
+                vals[key_c][key_n]["mean"] = \
+                    round(mean(vals[key_c][key_n]["vals"]) / 1000000, 2)
+                vals[key_c][key_n]["stdev"] = \
+                    round(stdev(vals[key_c][key_n]["vals"]) / 1000000, 2)
+    txt_nodes = list(set(txt_nodes))
+
+    txt_chains = sorted(txt_chains, key=lambda chain: int(chain))
+    txt_nodes = sorted(txt_nodes, key=lambda node: int(node))
+
+    chains = [i + 1 for i in range(len(txt_chains))]
+    nodes = [i + 1 for i in range(len(txt_nodes))]
+
+    data = [list() for _ in range(len(chains))]
+    for c in chains:
+        for n in nodes:
+            try:
+                val = vals[txt_chains[c - 1]][txt_nodes[n - 1]]["mean"]
+            except (KeyError, IndexError):
+                val = None
+            data[c - 1].append(val)
+
+    hovertext = list()
+    annotations = list()
+
+    text = ("{name}<br>"
+            "No. of Samples: {nr}<br>"
+            "Throughput: {val}<br>"
+            "Stdev: {stdev}")
+
+    for c in range(len(txt_chains)):
+        hover_line = list()
+        for n in range(len(txt_nodes)):
+            if data[c][n] is not None:
+                annotations.append(dict(
+                    x=n+1,
+                    y=c+1,
+                    xref="x",
+                    yref="y",
+                    xanchor="center",
+                    yanchor="middle",
+                    text=str(data[c][n]),
+                    font=dict(
+                        size=14,
+                    ),
+                    align="center",
+                    showarrow=False
+                ))
+                hover_line.append(text.format(
+                    name=vals[txt_chains[c]][txt_nodes[n]]["name"],
+                    nr=vals[txt_chains[c]][txt_nodes[n]]["nr"],
+                    val=data[c][n],
+                    stdev=vals[txt_chains[c]][txt_nodes[n]]["stdev"]))
+        hovertext.append(hover_line)
+
+    traces = [
+        plgo.Heatmap(x=nodes,
+                     y=chains,
+                     z=data,
+                     colorbar=dict(
+                         title="Packet Throughput [Mpps]",
+                         titleside="right",
+                         titlefont=dict(
+                            size=14
+                         ),
+                     ),
+                     showscale=True,
+                     colorscale="Reds",
+                     text=hovertext,
+                     hoverinfo="text")
+    ]
+
+    for idx, item in enumerate(txt_nodes):
+        annotations.append(dict(
+            x=idx+1,
+            y=0,
+            xref="x",
+            yref="y",
+            xanchor="center",
+            yanchor="top",
+            text=item,
+            font=dict(
+                size=16,
+            ),
+            align="center",
+            showarrow=False
+        ))
+    for idx, item in enumerate(txt_chains):
+        annotations.append(dict(
+            x=0.3,
+            y=idx+1,
+            xref="x",
+            yref="y",
+            xanchor="right",
+            yanchor="middle",
+            text=item,
+            font=dict(
+                size=16,
+            ),
+            align="center",
+            showarrow=False
+        ))
+    # X-axis:
+    annotations.append(dict(
+        x=0.55,
+        y=1.05,
+        xref="paper",
+        yref="paper",
+        xanchor="center",
+        yanchor="middle",
+        text="<b>No. of Network Functions per Service Instance</b>",
+        font=dict(
+            size=16,
+        ),
+        align="center",
+        showarrow=False
+    ))
+    # Y-axis:
+    annotations.append(dict(
+        x=-0.04,
+        y=0.5,
+        xref="paper",
+        yref="paper",
+        xanchor="center",
+        yanchor="middle",
+        text="<b>No. of Service Instances</b>",
+        font=dict(
+            size=16,
+        ),
+        align="center",
+        textangle=270,
+        showarrow=False
+    ))
+    updatemenus = list([
+        dict(
+            x=1.0,
+            y=0.0,
+            xanchor='right',
+            yanchor='bottom',
+            direction='up',
+            buttons=list([
+                dict(
+                    args=[{"colorscale": "Reds", "reversescale": False}],
+                    label="Red",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Blues", "reversescale": True}],
+                    label="Blue",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Greys", "reversescale": True}],
+                    label="Grey",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Greens", "reversescale": True}],
+                    label="Green",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "RdBu", "reversescale": False}],
+                    label="RedBlue",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Picnic", "reversescale": False}],
+                    label="Picnic",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Rainbow", "reversescale": False}],
+                    label="Rainbow",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Portland", "reversescale": False}],
+                    label="Portland",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Jet", "reversescale": False}],
+                    label="Jet",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Hot", "reversescale": True}],
+                    label="Hot",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Blackbody", "reversescale": True}],
+                    label="Blackbody",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Earth", "reversescale": True}],
+                    label="Earth",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Electric", "reversescale": True}],
+                    label="Electric",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Viridis", "reversescale": True}],
+                    label="Viridis",
+                    method="update"
+                ),
+                dict(
+                    args=[{"colorscale": "Cividis", "reversescale": True}],
+                    label="Cividis",
+                    method="update"
+                ),
+            ])
+        )
+    ])
+
+    try:
+        layout = deepcopy(plot["layout"])
+    except KeyError as err:
+        logging.error("Finished with error: No layout defined")
+        logging.error(repr(err))
+        return
+
+    layout["annotations"] = annotations
+    layout['updatemenus'] = updatemenus
+
+    try:
+        # Create plot
+        plpl = plgo.Figure(data=traces, layout=layout)
+
+        # Export Plot
+        logging.info("    Writing file '{0}{1}'.".
+                     format(plot["output-file"], plot["output-file-type"]))
+        ploff.plot(plpl, show_link=False, auto_open=False,
+                   filename='{0}{1}'.format(plot["output-file"],
+                                            plot["output-file-type"]))
+    except PlotlyError as err:
+        logging.error("   Finished with error: {}".
+                      format(str(err).replace("\n", " ")))
+        return