diff --git a/dashboards/grafanalib_backend.py b/dashboards/grafanalib_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..dab1b30eb2064d1d4476e96f325d8379d19c9e7a --- /dev/null +++ b/dashboards/grafanalib_backend.py @@ -0,0 +1,27 @@ +import grafanalib.core +from grafanalib.influxdb import InfluxDBTarget + +from .panels import get_arguments + + +def get_target_class_name(name, *, prefix="Wrapped"): + if not name.startswith(prefix): + raise ValueError(f"{name} does not start with {prefix}") + return name.replace(prefix, "") + + +def get_grafanalib_object(wrapped_object): + cls_name = get_target_class_name(wrapped_object.__class__.__name__) + if cls_name == "InfluxDBTarget": + target_class = InfluxDBTarget + else: + target_class = getattr(grafanalib.core, cls_name) + kwargs = get_arguments(wrapped_object) + for kwarg, value in kwargs.items(): + if value.__class__.__name__.startswith("Wrapped"): + kwargs[kwarg] = get_grafanalib_object(value) + elif isinstance(value, list): + if value and value[0].__class__.__name__.startswith("Wrapped"): + kwargs[kwarg] = [get_grafanalib_object(o) for o in value] + + return target_class(**kwargs) diff --git a/dashboards/panels.py b/dashboards/panels.py new file mode 100644 index 0000000000000000000000000000000000000000..ef988fb8419c83c74aaeeaa70b4adda8b02efe78 --- /dev/null +++ b/dashboards/panels.py @@ -0,0 +1,105 @@ +from dataclasses import dataclass, field, fields +from typing import List + + +@dataclass +class WrappedTarget: + query: str + alias: str = None + + +class WrappedInfluxDBTarget(WrappedTarget): + pass + + +@dataclass +class WrappedRepeat: + direction: str + variable: str + maxPerRow: int = None + + +@dataclass +class WrappedPanel: + title: str + dataSource: str + targets: List[WrappedTarget] = field(default_factory=list) + thresholdType: str = 'absolute' + thresholds: List = field(default_factory=list) + extra_kwargs: dict = field(default_factory=dict) + + +@dataclass +class WrappedTimeSeries(WrappedPanel): + unit: str = 'none' + axisLabel: str = '' + pointSize: int = 1 + repeat: WrappedRepeat = None + + +@dataclass +class WrappedStat(WrappedPanel): + alignment: str = 'center' + colorMode: str = 'value' + graphMode: str = 'area' + reduceCalc: str = 'last' + orientation: str = 'auto' + transparent: bool = True + + +@dataclass +class WrappedRowPanel: + title: str + collapsed: bool = True + panels: List[WrappedPanel] = field(default_factory=list) + + +@dataclass +class WrappedDashboardList(WrappedPanel): + searchTags: List[str] = field(default_factory=list) + showHeadings: bool = False + showSearch: bool = True + showStarred: bool = False + + +@dataclass +class WrappedTime: + start: str + end: str + + +@dataclass +class WrappedDashboard: + title: str + description: str + tags: List[str] = field(default_factory=list) + timezone: str = "browser" + rows: List = field(default_factory=list) + panels: List = field(default_factory=list) + templating = None + annotation = None + time: WrappedTime = field(default_factory=lambda: WrappedTime(start='now-30d', end='now')) + + def __post_init__(self): + if len(self.rows) != 0 and len(self.panels) != 0: + raise ValueError("Either rows or panels") + + +@dataclass +class WrappedTemplate: + name: str + query: str + type: str = "query" + includeAll: bool = True + multi: bool = True + regex: str = None + + +def get_arguments(wrapped) -> dict: + ret = dict() + for cur_field in fields(wrapped): + if cur_field.type == dict: + ret.update(getattr(wrapped, cur_field.name)) + elif value := getattr(wrapped, cur_field.name): + ret.setdefault(cur_field.name, value) + return ret diff --git a/setup.py b/setup.py index 653668b2cb101cbf0866935f7fa57dd132e1fed6..fc236d3cd9e2967840cc565b23c2e9f273cd5d00 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ setup(name="cb-util", author_email="Christoph.alt@fau.de", packages=find_packages(include=["cbutil", "cbutil.postprocessing", - "dashboards"]), + "dashboards", + "common"]), install_requires=[ "python-dotenv", "influxdb", diff --git a/tests/common/test_queries.py b/tests/common/test_queries.py new file mode 100644 index 0000000000000000000000000000000000000000..054f67e7262842dde141e4b7e535b0265179cab1 --- /dev/null +++ b/tests/common/test_queries.py @@ -0,0 +1,52 @@ +from common.queries import Query, ShowTagQuery + + +def test_showtagquery_from(): + q = ShowTagQuery('host', 'UniformGridCPU') + assert str(q) == 'SHOW TAG VALUES FROM \"UniformGridCPU\" WITH key = \"host\"' + + +def test_showtagquery_without_from(): + q = ShowTagQuery('host') + assert str(q) == 'SHOW TAG VALUES WITH key = \"host\"' + + +def test_big_query(): + q = Query( + _select='mlupsPerProcess', + _from='UniformGridGPU', + _where='"host" =~ /^$host$/ AND "collisionSetup" =~ /^$collisionSetup$/', + _group_by=[ + "blocks_0", + "blocks_1", + "blocks_2", + "cellsPerBlock_0", + "cellsPerBlock_1", + "cellsPerBlock_2", + "gpuBlockSize_0", + "gpuBlockSize_1", + "gpuBlockSize_2", + "collisionSetup", + "stencil", + "streamingPattern" + ] + ) + + q1 = ('SELECT "mlupsPerProcess" ' + 'FROM "UniformGridGPU" ' + 'WHERE ("host" =~ /^$host$/ AND "collisionSetup" =~ /^$collisionSetup$/) AND $timeFilter ' + 'GROUP BY "blocks_0", "blocks_1", "blocks_2", ' + '"cellsPerBlock_0", "cellsPerBlock_1", "cellsPerBlock_2", ' + '"gpuBlockSize_0", "gpuBlockSize_1", "gpuBlockSize_2", ' + '"collisionSetup", "stencil", "streamingPattern"') + assert q1 == str(q) + + +def test_min_query(): + q = Query("UniformGridGPU") + assert str(q) == 'SELECT "*" FROM "UniformGridGPU" WHERE $timeFilter' + + +def test_query_with_select(): + q = Query('UniformGridGPU', 'mlupsPerProcess') + assert str(q) == 'SELECT "mlupsPerProcess" FROM "UniformGridGPU" WHERE $timeFilter' diff --git a/tests/test_grafanalib_backend.py b/tests/test_grafanalib_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..72509fcfe58e6fcd7dc40b5522090e121fe900e8 --- /dev/null +++ b/tests/test_grafanalib_backend.py @@ -0,0 +1,56 @@ +from unittest.mock import patch + +from dashboards.grafanalib_backend import (get_grafanalib_object, + get_target_class_name) +from dashboards.panels import (WrappedDashboard, WrappedInfluxDBTarget, + WrappedPanel, WrappedTime, WrappedTimeSeries, + get_arguments) + + +def test_get_target_class_Panel(): + assert get_target_class_name("WrappedPanel") == "Panel" + assert get_target_class_name("PrefixPanel", prefix="Prefix") == "Panel" + panel = WrappedPanel("test", "InfluxDB-1") + assert get_target_class_name(panel.__class__.__name__) + + +@patch('dashboards.grafanalib_backend.grafanalib.core') +def test_get_grafanalib_panel(mock_grafanalib): + panel = WrappedPanel("test", "InfluxDB-1") + kwargs = get_arguments(panel) + + get_grafanalib_object(panel) + mock_grafanalib.Panel.assert_called_with(**kwargs) + + +@patch('grafanalib.core') +def test_get_grafanalib_ts(mock_grafanalib): + panel = WrappedTimeSeries("test", "InfluxDB-1") + kwargs = get_arguments(panel) + get_grafanalib_object(panel) + mock_grafanalib.TimeSeries.assert_called_with(**kwargs) + + +@patch('dashboards.grafanalib_backend.InfluxDBTarget') +@patch('grafanalib.core') +def test_get_grafanalib_ts_with_target(mock_grafanalib_core, mock_grafanalib_influx): + target = WrappedInfluxDBTarget('SELECT "mlupsperprocess" FROM "UniformGridCPU"') + panel = WrappedTimeSeries("test", "InfluxDB-1", targets=[target]) + get_grafanalib_object(panel) + mock_grafanalib_core.TimeSeries.assert_called() + mock_grafanalib_influx.assert_called_with(**get_arguments(target)) + + +@patch('grafanalib.core') +def test_time(mock_grafanalib): + time = WrappedTime('now-30d', 'now') + get_grafanalib_object(time) + mock_grafanalib.Time.assert_called_with(start='now-30d', end='now') + + +@patch('grafanalib.core') +def test_dashboard(mock_grafanalib): + dashboard = WrappedDashboard("title", "description") + get_grafanalib_object(dashboard) + mock_grafanalib.Time.assert_called() + mock_grafanalib.Dashboard.assert_called() diff --git a/tests/test_panels.py b/tests/test_panels.py new file mode 100644 index 0000000000000000000000000000000000000000..f09e45c2aa0da1b55727857872f68253dfd7449c --- /dev/null +++ b/tests/test_panels.py @@ -0,0 +1,24 @@ +from dashboards.panels import WrappedPanel, WrappedTimeSeries, get_arguments + + +def test_get_arguments_panel(): + panel = WrappedPanel("test", "InfluxDB-1") + kwargs = get_arguments(panel) + assert kwargs['title'] == "test" + assert kwargs['dataSource'] == "InfluxDB-1" + + +def test_get_arguments_ts(): + panel = WrappedTimeSeries("test", "InfluxDB-1", unit='W') + kwargs = get_arguments(panel) + assert kwargs['title'] == "test" + assert kwargs['dataSource'] == "InfluxDB-1" + assert kwargs['unit'] == 'W' + + +def test_extra_kwargs(): + panel = WrappedPanel("test", "InfluxDB-1", extra_kwargs={'color': 'red'}) + kwargs = get_arguments(panel) + assert kwargs['title'] == "test" + assert kwargs['dataSource'] == "InfluxDB-1" + assert kwargs['color'] == 'red'