Auto-generated patch by python-black
[integration/test.git] / csit / scripts / generate_visState.py
1 # SPDX-License-Identifier: EPL-1.0
2 ##############################################################################
3 # Copyright (c) 2018 The Linux Foundation and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Eclipse Public License v1.0
7 # which accompanies this distribution, and is available at
8 # http://www.eclipse.org/legal/epl-v10.html
9 ##############################################################################
10 import yaml
11 from copy import deepcopy as dc
12
13 import json
14
15 # Pretty Printer
16
17
18 def p(x):
19     print(json.dumps(x, indent=4, sort_keys=True))
20
21
22 class visState:
23     # viState template
24     def __init__(self):
25         self.content = {
26             "title": None,
27             "type": None,
28             "params": {
29                 "type": None,
30                 "grid": {"categoryLines": False, "style": {"color": "#eee"}},
31                 "categoryAxes": None,
32                 "valueAxes": None,
33                 "seriesParams": None,
34                 "addTooltip": True,
35                 "addLegend": True,
36                 "legendPosition": "right",
37                 "times": [],
38                 "addTimeMarker": False,
39             },
40             "aggs": None,
41         }
42
43     def create(self, config):
44         temp = self.content
45         temp["title"] = config["title"]
46         temp["type"] = temp["params"]["type"] = config["type"]
47
48         cat = categoryAxes()
49         temp["params"]["categoryAxes"] = [
50             dc(cat.create()) for i in range(config["num_cat_axes"])
51         ]
52
53         val = ValueAxes()
54         temp["params"]["valueAxes"] = [
55             dc(val.create(position=i["position"], title=i["title"]))
56             for _, i in config["value_axes"].items()
57         ]
58
59         agg = aggs()
60
61         temp["aggs"] = [
62             dc(
63                 agg.create(
64                     id=i,
65                     field=config["aggs"][i]["field"],
66                     custom_label=config["aggs"][i]["custom_label"],
67                     schema=config["aggs"][i]["schema"],
68                 )
69             )
70             for i in range(1, len(config["aggs"]) + 1)
71         ]
72
73         temp["params"]["seriesParams"] = [
74             seriesParams(
75                 i["data_type"], i["mode"], i["label"], i["agg_id"], i["value_axis"]
76             ).create()
77             for _, i in config["seriesParams"].items()
78         ]
79
80         return temp
81
82
83 class categoryAxes:
84     def __init__(self):
85         self.content = {
86             "id": None,
87             "type": "category",
88             "position": "bottom",
89             "show": True,
90             "style": {},
91             "scale": {"type": "linear"},
92             "labels": {"show": True, "truncate": 100},
93             "title": {},
94         }
95         self.counter = 0
96
97     # Category axes are named as CategoryAxis-i
98     def create(self):
99         self.counter += 1
100         temp = dc(self.content)
101         temp["id"] = "CategoryAxis-{}".format(self.counter)
102         return temp
103
104
105 class ValueAxes:
106     def __init__(self):
107         self.content = {
108             "id": None,
109             "name": None,
110             "type": "value",
111             "position": "left",
112             "show": True,
113             "style": {},
114             "scale": {"type": "linear", "mode": "normal"},
115             "labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
116             "title": {"text": None},
117         }
118         self.counter = 0
119
120     def create(self, position="left", title="Value"):
121         self.counter += 1
122         temp = dc(self.content)
123         temp["id"] = "ValueAxis-{}".format(self.counter)
124         if position == "left":
125             temp["name"] = "LeftAxis-{}".format(self.counter)
126         elif position == "right":
127             temp["name"] = "RightAxis-{}".format(self.counter)
128         else:
129             # raise ValueError('Not one of left or right')
130             # assuming default
131             temp["name"] = "LeftAxis-{}".format(self.counter)
132
133         temp["title"]["text"] = title
134
135         return temp
136
137
138 # 'seriesParams' are the ones that actually show up in the plots.
139 # They point to a data source a.k.a 'aggs' (short for aggregation)
140 # to get their data.
141
142
143 class seriesParams:
144     def __init__(self, data_type, mode, label, agg_id, value_axis):
145         self.content = {
146             "show": True,
147             "type": data_type,
148             "mode": mode,
149             "data": {
150                 "label": label,
151                 "id": str(agg_id),  # the id of the aggregation they point to
152             },
153             "valueAxis": "ValueAxis-{}".format(value_axis),
154             "drawLinesBetweenPoints": True,
155             "showCircles": True,
156         }
157
158     def create(self):
159         return self.content
160
161
162 # 'aggs' or aggregation refers to collection of values. They are the data
163 # source which are used by seriesParams. and as expected they take 'field'
164 # as the nested name of the key.
165 #
166 # Example, if your value is in {
167 #  'perfomance': {
168 #  'plots': {
169 #         'rate': myval,
170 #          ...
171 #        }
172 #   },
173 #   then I would have to use, 'performance.plots.rate' as the 'field' for aggs
174 # the 'schema' of an agg is 'metric' which are to be
175 # plotted in the Y-axis and 'segment' for the ones in X-axis
176
177
178 class aggs:
179     def __init__(self):
180         self.content = {
181             "id": None,
182             "enabled": True,
183             "type": None,
184             "schema": None,
185             "params": {"field": None, "customLabel": None},
186         }
187         self.counter = 0
188
189     def create(self, id, field, custom_label, schema):
190         temp = dc(self.content)
191         temp["id"] = id
192         temp["params"]["field"] = field
193         temp["params"]["customLabel"] = custom_label
194         temp["schema"] = schema
195         if schema == "metric":
196             temp["type"] = "max"
197             return temp
198         elif schema == "segment":
199             temp["type"] = "terms"
200             temp["params"]["size"] = 20  # default
201             temp["params"]["order"] = "asc"
202             temp["params"]["orderBy"] = "_term"
203         return temp
204
205
206 # 'series' actually combines and simplifies both 'seriesParams' and 'aggs'
207 # Both 'seriesParams' and 'aggs' support 'default' to set default values
208
209 # generate takes both the template config and project specific config and
210 # parses and organizes as much info available from that and
211 # generates an intermediate format first which
212 # contains all necessary info to deterministically create the visState to
213 # be sent to Kibana. Hence, any error occuring in the visualizaton side
214 # must first be checked by looking at the intermediate format.
215
216
217 def generate(dash_config, viz_config):
218
219     format = {
220         "type": None,
221         "value_axes": {},
222         "seriesParams": {},
223         "index_pattern": None,
224         "desc": None,
225         "id": None,
226         "aggs": {},
227         "title": None,
228         "num_cat_axes": None,
229     }
230
231     value_axes_format = {"index": {"position": None, "title": None}}
232
233     seriesParams_format = {
234         "index": {
235             "value_axis": None,
236             "data_type": None,
237             "mode": None,
238             "label": None,
239             "agg_id": None,
240         }
241     }
242
243     aggs_format = {"index": {"custom_label": None, "field": None, "schema": None}}
244
245     # all general description must be present in either of the config files
246     for config in [viz_config, dash_config]:
247         general_fields = [
248             "type",
249             "index_pattern",
250             "num_cat_axes",
251             "title",
252             "desc",
253             "id",
254         ]
255         for i in general_fields:
256             try:
257                 format[i] = config[i]
258             except KeyError as e:
259                 pass
260
261     # setting any default values if available
262     mappings = {
263         "value_axes": value_axes_format,
264         "seriesParams": seriesParams_format,
265         "aggs": aggs_format,
266     }
267     for index, container in mappings.items():
268         try:
269             default_values = viz_config[index]["default"]
270             for i in default_values:
271                 container["index"][i] = default_values[i]
272         except Exception:
273             pass
274
275     ####################################################################
276     # Extract 'value_axes', 'seriesParams' or 'aggs' if present in viz_config
277     value_axes_counter = 1
278     for m in viz_config["value_axes"]:
279         if m != "default":
280             temp = dc(value_axes_format)
281             temp[str(value_axes_counter)] = temp["index"]
282             for i in ["position", "title"]:
283                 try:
284                     temp[str(value_axes_counter)][i] = viz_config["value_axes"][m][i]
285                 except KeyError:
286                     pass
287             format["value_axes"].update(temp)
288             value_axes_counter += 1
289
290     seriesParams_fields = ["value_axis", "data_type", "mode", "label", "agg_id"]
291     try:
292         for m in viz_config["seriesParams"]:
293             if m != "default":
294                 temp = dc(seriesParams_format)
295                 temp[m] = temp["index"]
296                 for i in seriesParams_fields:
297                     try:
298                         temp[m][i] = viz_config["seriesParams"][m][i]
299                     except KeyError:
300                         pass
301                 format["seriesParams"].update(temp)
302     except KeyError:
303         pass
304
305     agg_counter = 1
306     try:
307         for m in viz_config["aggs"]:
308             if m != "default":
309                 temp = dc(aggs_format)
310                 temp[m] = temp["index"]
311                 for i in ["field", "custom_label", "schema"]:
312                     try:
313                         temp[m][i] = viz_config["aggs"][m][i]
314                     except KeyError:
315                         pass
316                 format["aggs"].update(temp)
317     except KeyError:
318         pass
319     ####################################################################
320
321     # collect 'series' from both the configs
322     configs = []
323     try:
324         viz_config["series"]
325         configs.append(viz_config)
326     except KeyError:
327         pass
328
329     try:
330         dash_config["y-axis"]["series"]
331         configs.append(dash_config["y-axis"])
332     except KeyError:
333         pass
334
335     ########################################################################
336     # Extract 'series' from either of the configs
337     for config in configs:
338         try:
339             value_axes_counter = 1
340             for key in config["value_axes"]:
341
342                 value_axes_temp = dc(value_axes_format)
343                 value_axes_temp[str(value_axes_counter)] = value_axes_temp["index"]
344
345                 for index in ["position", "title"]:
346                     try:
347                         value_axes_temp[str(value_axes_counter)][index] = config[
348                             "value_axes"
349                         ][key][index]
350                     except KeyError as e:
351                         pass
352                 format["value_axes"].update(value_axes_temp)
353                 value_axes_counter += 1
354
355         except KeyError as e:
356             pass
357
358         try:
359             for key in config["series"]:
360                 try:
361                     # check if this key is present or not
362                     config["series"][key]["not_in_seriesParams"]
363                 except KeyError:
364                     seriesParams_temp = dc(seriesParams_format)
365                     seriesParams_temp[key] = seriesParams_temp["index"]
366                     for index in ["value_axis", "data_type", "mode", "label"]:
367                         try:
368                             seriesParams_temp[key][index] = config["series"][key][index]
369                         except KeyError as e:
370                             pass
371                     seriesParams_temp[key]["agg_id"] = key
372                     format["seriesParams"].update(seriesParams_temp)
373                 finally:
374                     agg_temp = dc(aggs_format)
375                     agg_temp[key] = agg_temp["index"]
376                     for index in ["field", "schema"]:
377                         try:
378                             agg_temp[key][index] = config["series"][key][index]
379                         except KeyError as e:
380                             pass
381                     agg_temp[key]["custom_label"] = config["series"][key]["label"]
382                     format["aggs"].update(agg_temp)
383         except KeyError as e:
384             print("required fields are empty!")
385
386     ##########################################################################
387
388     # to remove the default template index
389     for i in ["value_axes", "seriesParams", "aggs"]:
390         try:
391             format[i].pop("index")
392         except KeyError:
393             # print("No default index found")
394             pass
395
396     missing = config_validator(format)
397     if len(missing):
398         raise ValueError("Missing required field values :-", *missing)
399
400     p(format)
401
402     vis = visState()
403     generated_visState = vis.create(format)
404
405     # checking incase there are None values
406     # in the format indicating missing fields
407
408     missing = config_validator(generated_visState)
409     if len(missing):
410         raise ValueError("required fields are missing values! ", *missing)
411     return format, generated_visState
412
413
414 # Check the generated format if it contains any key with None
415 # as it's value which indicates incomplete information
416
417
418 def config_validator(val, missing=[]):
419     for key, value in val.items():
420         if isinstance(value, dict):
421             config_validator(value)
422         if value is None:
423             missing.append(key)
424     return missing
425
426
427 if __name__ == "__main__":
428     with open("viz_config.yaml", "r") as f:
429         viz_config = yaml.safe_load(f)
430
431     with open("dash_config.yaml", "r") as f:
432         dash_config = yaml.safe_load(f)
433
434     generate(
435         dash_config["dashboard"]["viz"][2], viz_config["opendaylight-test-performance"]
436     )
437     # generate(dash_config['dashboard']['viz'][3],viz_config['opendaylight-test-performance'])
438     # generate(dash_config['dashboard']['viz'][1],viz_config['opendaylight-test-feature'])