Add report generate-nsb command
[yardstick.git] / yardstick / benchmark / core / report.py
1 ##############################################################################
2 # Copyright (c) 2017 Rajesh Kudaka <4k.rajesh@gmail.com>
3 # Copyright (c) 2018 Intel Corporation.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ##############################################################################
10
11 """ Handler for yardstick command 'report' """
12
13 import ast
14 import re
15 import uuid
16
17 import jinja2
18 from api.utils import influx
19 from oslo_utils import encodeutils
20 from oslo_utils import uuidutils
21 from yardstick.common import constants as consts
22 from yardstick.common.utils import cliargs
23
24
25 class JSTree(object):
26     """Data structure to parse data for use with the JS library jsTree"""
27     def __init__(self):
28         self._created_nodes = ['#']
29         self.jstree_data = []
30
31     def _create_node(self, _id):
32         """Helper method for format_for_jstree to create each node.
33
34         Creates the node (and any required parents) and keeps track
35         of the created nodes.
36
37         :param _id: (string) id of the node to be created
38         :return: None
39         """
40         components = _id.split(".")
41
42         if len(components) == 1:
43             text = components[0]
44             parent_id = "#"
45         else:
46             text = components[-1]
47             parent_id = ".".join(components[:-1])
48             # make sure the parent has been created
49             if not parent_id in self._created_nodes:
50                 self._create_node(parent_id)
51
52         self.jstree_data.append({"id": _id, "text": text, "parent": parent_id})
53         self._created_nodes.append(_id)
54
55     def format_for_jstree(self, data):
56         """Format the data into the required format for jsTree.
57
58         The data format expected is a list of key-value pairs which represent
59         the data and label for each metric e.g.:
60
61             [{'data': [0, ], 'label': 'tg__0.DropPackets'},
62              {'data': [548, ], 'label': 'tg__0.LatencyAvg.5'},]
63
64         This data is converted into the format required for jsTree to group and
65         display the metrics in a hierarchial fashion, including creating a
66         number of parent nodes e.g.::
67
68             [{"id": "tg__0", "text": "tg__0", "parent": "#"},
69              {"id": "tg__0.DropPackets", "text": "DropPackets", "parent": "tg__0"},
70              {"id": "tg__0.LatencyAvg", "text": "LatencyAvg", "parent": "tg__0"},
71              {"id": "tg__0.LatencyAvg.5", "text": "5", "parent": "tg__0.LatencyAvg"},]
72
73         :param data: (list) data to be converted
74         :return: list
75         """
76         self._created_nodes = ['#']
77         self.jstree_data = []
78
79         for item in data:
80             self._create_node(item["label"])
81
82         return self.jstree_data
83
84
85 class Report(object):
86     """Report commands.
87
88     Set of commands to manage reports.
89     """
90
91     def __init__(self):
92         self.Timestamp = []
93         self.yaml_name = ""
94         self.task_id = ""
95
96     def _validate(self, yaml_name, task_id):
97         if re.match(r"^[\w-]+$", yaml_name):
98             self.yaml_name = yaml_name
99         else:
100             raise ValueError("invalid yaml_name", yaml_name)
101
102         if uuidutils.is_uuid_like(task_id):
103             task_id = '{' + task_id + '}'
104             task_uuid = (uuid.UUID(task_id))
105             self.task_id = task_uuid
106         else:
107             raise ValueError("invalid task_id", task_id)
108
109     def _get_fieldkeys(self):
110         fieldkeys_cmd = "show field keys from \"%s\""
111         fieldkeys_query = fieldkeys_cmd % (self.yaml_name)
112         query_exec = influx.query(fieldkeys_query)
113         if query_exec:
114             return query_exec
115         else:
116             raise KeyError("Test case not found.")
117
118     def _get_tasks(self):
119         task_cmd = "select * from \"%s\" where task_id= '%s'"
120         task_query = task_cmd % (self.yaml_name, self.task_id)
121         query_exec = influx.query(task_query)
122         if query_exec:
123             return query_exec
124         else:
125             raise KeyError("Task ID or Test case not found.")
126
127     def _generate_common(self, args):
128         """Actions that are common to both report formats.
129
130         Create the necessary data structure for rendering
131         the report templates.
132         """
133         self._validate(args.yaml_name[0], args.task_id[0])
134
135         self.db_fieldkeys = self._get_fieldkeys()
136
137         self.db_task = self._get_tasks()
138
139         field_keys = []
140         datasets = []
141         table_vals = {}
142
143         field_keys = [encodeutils.to_utf8(field['fieldKey'])
144                       for field in self.db_fieldkeys]
145
146         for key in field_keys:
147             self.Timestamp = []
148             values = []
149             for task in self.db_task:
150                 task_time = encodeutils.to_utf8(task['time'])
151                 if not isinstance(task_time, str):
152                     task_time = str(task_time, 'utf8')
153                 if not isinstance(key, str):
154                     key = str(key, 'utf8')
155                 task_time = task_time[11:]
156                 head, _, tail = task_time.partition('.')
157                 task_time = head + "." + tail[:6]
158                 self.Timestamp.append(task_time)
159                 if task[key] is None:
160                     values.append(None)
161                 elif isinstance(task[key], (int, float)):
162                     values.append(task[key])
163                 else:
164                     values.append(ast.literal_eval(task[key]))
165             datasets.append({'label': key, 'data': values})
166             table_vals['Timestamp'] = self.Timestamp
167             table_vals[key] = values
168
169         return datasets, table_vals
170
171     @cliargs("task_id", type=str, help=" task id", nargs=1)
172     @cliargs("yaml_name", type=str, help=" Yaml file Name", nargs=1)
173     def generate(self, args):
174         """Start report generation."""
175         datasets, table_vals = self._generate_common(args)
176
177         template_dir = consts.YARDSTICK_ROOT_PATH + "yardstick/common"
178         template_environment = jinja2.Environment(
179             autoescape=False,
180             loader=jinja2.FileSystemLoader(template_dir))
181
182         context = {
183             "datasets": datasets,
184             "Timestamps": self.Timestamp,
185             "task_id": self.task_id,
186             "table": table_vals,
187         }
188
189         template_html = template_environment.get_template("report.html.j2")
190
191         with open(consts.DEFAULT_HTML_FILE, "w") as file_open:
192             file_open.write(template_html.render(context))
193
194         print("Report generated. View %s" % consts.DEFAULT_HTML_FILE)
195
196     @cliargs("task_id", type=str, help=" task id", nargs=1)
197     @cliargs("yaml_name", type=str, help=" Yaml file Name", nargs=1)
198     def generate_nsb(self, args):
199         """Start NSB report generation."""
200         datasets, table_vals = self._generate_common(args)
201         jstree_data = JSTree().format_for_jstree(datasets)
202
203         template_dir = consts.YARDSTICK_ROOT_PATH + "yardstick/common"
204         template_environment = jinja2.Environment(
205             autoescape=False,
206             loader=jinja2.FileSystemLoader(template_dir),
207             lstrip_blocks=True)
208
209         context = {
210             "Timestamps": self.Timestamp,
211             "task_id": self.task_id,
212             "table": table_vals,
213             "jstree_nodes": jstree_data,
214         }
215
216         template_html = template_environment.get_template("nsb_report.html.j2")
217
218         with open(consts.DEFAULT_HTML_FILE, "w") as file_open:
219             file_open.write(template_html.render(context))
220
221         print("Report generated. View %s" % consts.DEFAULT_HTML_FILE)