| 1 | # -*- coding: utf-8 -*- |
| 2 | # Description: pandas netdata python.d module |
| 3 | # Author: Andrew Maguire (andrewm4894) |
| 4 | # SPDX-License-Identifier: GPL-3.0-or-later |
| 5 | |
| 6 | import os |
| 7 | import pandas as pd |
| 8 | |
| 9 | try: |
| 10 | import requests |
| 11 | HAS_REQUESTS = True |
| 12 | except ImportError: |
| 13 | HAS_REQUESTS = False |
| 14 | |
| 15 | try: |
| 16 | from sqlalchemy import create_engine |
| 17 | HAS_SQLALCHEMY = True |
| 18 | except ImportError: |
| 19 | HAS_SQLALCHEMY = False |
| 20 | |
| 21 | from bases.FrameworkServices.SimpleService import SimpleService |
| 22 | |
| 23 | ORDER = [] |
| 24 | |
| 25 | CHARTS = {} |
| 26 | |
| 27 | |
| 28 | class Service(SimpleService): |
| 29 | def __init__(self, configuration=None, name=None): |
| 30 | SimpleService.__init__(self, configuration=configuration, name=name) |
| 31 | self.order = ORDER |
| 32 | self.definitions = CHARTS |
| 33 | self.chart_configs = self.configuration.get('chart_configs', None) |
| 34 | self.line_sep = self.configuration.get('line_sep', ';') |
| 35 | |
| 36 | def run_code(self, df_steps): |
| 37 | """eval() each line of code and ensure the result is a pandas dataframe""" |
| 38 | |
| 39 | # process each line of code |
| 40 | lines = df_steps.split(self.line_sep) |
| 41 | for line in lines: |
| 42 | line_clean = line.strip('\n').strip(' ') |
| 43 | if line_clean != '' and line_clean[0] != '#': |
| 44 | df = eval(line_clean) |
| 45 | assert isinstance(df, pd.DataFrame), 'The result of each evaluated line of `df_steps` must be of type `pd.DataFrame`' |
| 46 | |
| 47 | # take top row of final df as data to be collected by netdata |
| 48 | data = df.to_dict(orient='records')[0] |
| 49 | |
| 50 | return data |
| 51 | |
| 52 | def check(self): |
| 53 | """ensure charts and dims all configured and that we can get data""" |
| 54 | |
| 55 | if not HAS_REQUESTS: |
| 56 | self.warning('requests library could not be imported') |
| 57 | |
| 58 | if not HAS_SQLALCHEMY: |
| 59 | self.warning('sqlalchemy library could not be imported') |
| 60 | |
| 61 | if not self.chart_configs: |
| 62 | self.error('chart_configs must be defined') |
| 63 | |
| 64 | data = dict() |
| 65 | |
| 66 | # add each chart as defined by the config |
| 67 | for chart_config in self.chart_configs: |
| 68 | if chart_config['name'] not in self.charts: |
| 69 | chart_template = { |
| 70 | 'options': [ |
| 71 | chart_config['name'], |
| 72 | chart_config['title'], |
| 73 | chart_config['units'], |
| 74 | chart_config['family'], |
| 75 | chart_config['context'], |
| 76 | chart_config['type'] |
| 77 | ], |
| 78 | 'lines': [] |
| 79 | } |
| 80 | self.charts.add_chart([chart_config['name']] + chart_template['options']) |
| 81 | |
| 82 | data_tmp = self.run_code(chart_config['df_steps']) |
| 83 | data.update(data_tmp) |
| 84 | |
| 85 | for dim in data_tmp: |
| 86 | self.charts[chart_config['name']].add_dimension([dim, dim, 'absolute', 1, 1]) |
| 87 | |
| 88 | return True |
| 89 | |
| 90 | def get_data(self): |
| 91 | """get data for each chart config""" |
| 92 | |
| 93 | data = dict() |
| 94 | |
| 95 | for chart_config in self.chart_configs: |
| 96 | data_tmp = self.run_code(chart_config['df_steps']) |
| 97 | data.update(data_tmp) |
| 98 | |
| 99 | return data |