1 # SPDX-License-Identifier: EPL-1.0
2 ##############################################################################
3 # Copyright (c) 2018 The Linux Foundation and others.
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 ##############################################################################
11 from copy import deepcopy as dc
19 print(json.dumps(x, indent=4, sort_keys=True))
30 "grid": {"categoryLines": False, "style": {"color": "#eee"}},
36 "legendPosition": "right",
38 "addTimeMarker": False,
43 def create(self, config):
45 temp["title"] = config["title"]
46 temp["type"] = temp["params"]["type"] = config["type"]
49 temp["params"]["categoryAxes"] = [
50 dc(cat.create()) for i in range(config["num_cat_axes"])
54 temp["params"]["valueAxes"] = [
55 dc(val.create(position=i["position"], title=i["title"]))
56 for _, i in config["value_axes"].items()
65 field=config["aggs"][i]["field"],
66 custom_label=config["aggs"][i]["custom_label"],
67 schema=config["aggs"][i]["schema"],
70 for i in range(1, len(config["aggs"]) + 1)
73 temp["params"]["seriesParams"] = [
75 i["data_type"], i["mode"], i["label"], i["agg_id"], i["value_axis"]
77 for _, i in config["seriesParams"].items()
91 "scale": {"type": "linear"},
92 "labels": {"show": True, "truncate": 100},
97 # Category axes are named as CategoryAxis-i
100 temp = dc(self.content)
101 temp["id"] = "CategoryAxis-{}".format(self.counter)
114 "scale": {"type": "linear", "mode": "normal"},
115 "labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
116 "title": {"text": None},
120 def create(self, position="left", title="Value"):
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)
129 # raise ValueError('Not one of left or right')
131 temp["name"] = "LeftAxis-{}".format(self.counter)
133 temp["title"]["text"] = title
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)
144 def __init__(self, data_type, mode, label, agg_id, value_axis):
151 "id": str(agg_id), # the id of the aggregation they point to
153 "valueAxis": "ValueAxis-{}".format(value_axis),
154 "drawLinesBetweenPoints": True,
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.
166 # Example, if your value is in {
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
185 "params": {"field": None, "customLabel": None},
189 def create(self, id, field, custom_label, schema):
190 temp = dc(self.content)
192 temp["params"]["field"] = field
193 temp["params"]["customLabel"] = custom_label
194 temp["schema"] = schema
195 if schema == "metric":
198 elif schema == "segment":
199 temp["type"] = "terms"
200 temp["params"]["size"] = 20 # default
201 temp["params"]["order"] = "asc"
202 temp["params"]["orderBy"] = "_term"
206 # 'series' actually combines and simplifies both 'seriesParams' and 'aggs'
207 # Both 'seriesParams' and 'aggs' support 'default' to set default values
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.
217 def generate(dash_config, viz_config):
223 "index_pattern": None,
228 "num_cat_axes": None,
231 value_axes_format = {"index": {"position": None, "title": None}}
233 seriesParams_format = {
243 aggs_format = {"index": {"custom_label": None, "field": None, "schema": None}}
245 # all general description must be present in either of the config files
246 for config in [viz_config, dash_config]:
255 for i in general_fields:
257 format[i] = config[i]
258 except KeyError as e:
261 # setting any default values if available
263 "value_axes": value_axes_format,
264 "seriesParams": seriesParams_format,
267 for index, container in mappings.items():
269 default_values = viz_config[index]["default"]
270 for i in default_values:
271 container["index"][i] = default_values[i]
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"]:
280 temp = dc(value_axes_format)
281 temp[str(value_axes_counter)] = temp["index"]
282 for i in ["position", "title"]:
284 temp[str(value_axes_counter)][i] = viz_config["value_axes"][m][i]
287 format["value_axes"].update(temp)
288 value_axes_counter += 1
290 seriesParams_fields = ["value_axis", "data_type", "mode", "label", "agg_id"]
292 for m in viz_config["seriesParams"]:
294 temp = dc(seriesParams_format)
295 temp[m] = temp["index"]
296 for i in seriesParams_fields:
298 temp[m][i] = viz_config["seriesParams"][m][i]
301 format["seriesParams"].update(temp)
307 for m in viz_config["aggs"]:
309 temp = dc(aggs_format)
310 temp[m] = temp["index"]
311 for i in ["field", "custom_label", "schema"]:
313 temp[m][i] = viz_config["aggs"][m][i]
316 format["aggs"].update(temp)
319 ####################################################################
321 # collect 'series' from both the configs
325 configs.append(viz_config)
330 dash_config["y-axis"]["series"]
331 configs.append(dash_config["y-axis"])
335 ########################################################################
336 # Extract 'series' from either of the configs
337 for config in configs:
339 value_axes_counter = 1
340 for key in config["value_axes"]:
342 value_axes_temp = dc(value_axes_format)
343 value_axes_temp[str(value_axes_counter)] = value_axes_temp["index"]
345 for index in ["position", "title"]:
347 value_axes_temp[str(value_axes_counter)][index] = config[
350 except KeyError as e:
352 format["value_axes"].update(value_axes_temp)
353 value_axes_counter += 1
355 except KeyError as e:
359 for key in config["series"]:
361 # check if this key is present or not
362 config["series"][key]["not_in_seriesParams"]
364 seriesParams_temp = dc(seriesParams_format)
365 seriesParams_temp[key] = seriesParams_temp["index"]
366 for index in ["value_axis", "data_type", "mode", "label"]:
368 seriesParams_temp[key][index] = config["series"][key][index]
369 except KeyError as e:
371 seriesParams_temp[key]["agg_id"] = key
372 format["seriesParams"].update(seriesParams_temp)
374 agg_temp = dc(aggs_format)
375 agg_temp[key] = agg_temp["index"]
376 for index in ["field", "schema"]:
378 agg_temp[key][index] = config["series"][key][index]
379 except KeyError as e:
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!")
386 ##########################################################################
388 # to remove the default template index
389 for i in ["value_axes", "seriesParams", "aggs"]:
391 format[i].pop("index")
393 # print("No default index found")
396 missing = config_validator(format)
398 raise ValueError("Missing required field values :-", *missing)
403 generated_visState = vis.create(format)
405 # checking incase there are None values
406 # in the format indicating missing fields
408 missing = config_validator(generated_visState)
410 raise ValueError("required fields are missing values! ", *missing)
411 return format, generated_visState
414 # Check the generated format if it contains any key with None
415 # as it's value which indicates incomplete information
418 def config_validator(val, missing=[]):
419 for key, value in val.items():
420 if isinstance(value, dict):
421 config_validator(value)
427 if __name__ == "__main__":
428 with open("viz_config.yaml", "r") as f:
429 viz_config = yaml.safe_load(f)
431 with open("dash_config.yaml", "r") as f:
432 dash_config = yaml.safe_load(f)
435 dash_config["dashboard"]["viz"][2], viz_config["opendaylight-test-performance"]
437 # generate(dash_config['dashboard']['viz'][3],viz_config['opendaylight-test-performance'])
438 # generate(dash_config['dashboard']['viz'][1],viz_config['opendaylight-test-feature'])