]> arthur.barton.de Git - netdata.git/blob - python.d/python_modules/base.py
comments
[netdata.git] / python.d / python_modules / base.py
1 # -*- coding: utf-8 -*-
2 # Description: prototypes for netdata python.d modules
3 # Author: Pawel Krupa (paulfantom)
4
5 import time
6 import sys
7 try:
8     from urllib.request import urlopen
9 except ImportError:
10     from urllib2 import urlopen
11
12 import threading
13 import msg
14
15
16 class BaseService(threading.Thread):
17     """
18     Prototype of Service class.
19     Implemented basic functionality to run jobs by `python.d.plugin`
20     """
21     debugging = False
22
23     def __init__(self, configuration=None, name=None):
24         """
25         This needs to be initialized in child classes
26         :param configuration: dict
27         :param name: str
28         """
29         threading.Thread.__init__(self)
30         self.daemon = True
31         self.retries = 0
32         self.retries_left = 0
33         self.priority = 140000
34         self.update_every = 1
35         self.name = name
36         if configuration is None:
37             self.error("BaseService: no configuration parameters supplied. Cannot create Service.")
38             raise RuntimeError
39         else:
40             self._extract_base_config(configuration)
41             self.timetable = {}
42             self.create_timetable()
43             self.chart_name = ""
44
45     def _extract_base_config(self, config):
46         """
47         Get basic parameters to run service
48         Minimum config:
49             config = {'update_every':1,
50                       'priority':100000,
51                       'retries':0}
52         :param config: dict
53         """
54         self.update_every = int(config.pop('update_every'))
55         self.priority = int(config.pop('priority'))
56         self.retries = int(config.pop('retries'))
57         self.retries_left = self.retries
58         self.configuration = config
59
60     def create_timetable(self, freq=None):
61         """
62         Create service timetable.
63         `freq` is optional
64         Example:
65             timetable = {'last': 1466370091.3767564,
66                          'next': 1466370092,
67                          'freq': 1}
68         :param freq: int
69         """
70         if freq is None:
71             freq = self.update_every
72         now = time.time()
73         self.timetable = {'last': now,
74                           'next': now - (now % freq) + freq,
75                           'freq': freq}
76
77     def _run_once(self):
78         """
79         Executes self.update(interval) and draws run time chart.
80         Return value presents exit status of update()
81         :return: boolean
82         """
83         t_start = time.time()
84         # check if it is time to execute job update() function
85         if self.timetable['next'] > t_start:
86             msg.debug(self.chart_name + " will be run in " +
87                       str(int((self.timetable['next'] - t_start) * 1000)) + " ms")
88             return True
89
90         since_last = int((t_start - self.timetable['last']) * 1000000)
91         msg.debug(self.chart_name +
92                   " ready to run, after " + str(int((t_start - self.timetable['last']) * 1000)) +
93                   " ms (update_every: " + str(self.timetable['freq'] * 1000) +
94                   " ms, latency: " + str(int((t_start - self.timetable['next']) * 1000)) + " ms)")
95         if not self.update(since_last):
96             return False
97         t_end = time.time()
98         self.timetable['next'] = t_end - (t_end % self.timetable['freq']) + self.timetable['freq']
99
100         # draw performance graph
101         run_time = str(int((t_end - t_start) * 1000))
102         run_time_chart = "BEGIN netdata.plugin_pythond_" + self.chart_name + " " + str(since_last) + '\n'
103         run_time_chart += "SET run_time = " + run_time + '\n'
104         run_time_chart += "END\n"
105         sys.stdout.write(run_time_chart)
106         msg.debug(self.chart_name + " updated in " + str(run_time) + " ms")
107         self.timetable['last'] = t_start
108         return True
109
110     def run(self):
111         """
112         Runs job in thread. Handles retries.
113         Exits when job failed or timed out.
114         :return: None
115         """
116         self.timetable['last'] = time.time()
117         while True:
118             try:
119                 status = self._run_once()
120             except Exception as e:
121                 msg.error("Something wrong: " + str(e))
122                 return
123             if status:
124                 time.sleep(self.timetable['next'] - time.time())
125                 self.retries_left = self.retries
126             else:
127                 self.retries -= 1
128                 if self.retries_left <= 0:
129                     msg.error("no more retries. Exiting")
130                     return
131                 else:
132                     time.sleep(self.timetable['freq'])
133
134     def check(self):
135         """
136         check() prototype
137         :return: boolean
138         """
139         msg.error("Service " + str(self.__module__) + "doesn't implement check() function")
140         return False
141
142     def create(self):
143         """
144         create() prototype
145         :return: boolean
146         """
147         msg.error("Service " + str(self.__module__) + "doesn't implement create() function?")
148         return False
149
150     def update(self, interval):
151         """
152         update() prototype
153         :param interval: int
154         :return: boolean
155         """
156         msg.error("Service " + str(self.__module__) + "doesn't implement update() function")
157         return False
158
159
160 class UrlService(BaseService):
161     def __init__(self, configuration=None, name=None):
162         self.charts = {}
163         # charts definitions in format:
164         # charts = {
165         #    'chart_name_in_netdata': {
166         #        'options': "parameters defining chart (passed to CHART statement)",
167         #        'lines': [
168         #           { 'name': 'dimension_name',
169         #             'options': 'dimension parameters (passed to DIMENSION statement)"
170         #           }
171         #        ]}
172         #    }
173         self.order = []
174         self.definitions = {}
175         # definitions are created dynamically in create() method based on 'charts' dictionary. format:
176         # definitions = {
177         #     'chart_name_in_netdata' : [ charts['chart_name_in_netdata']['lines']['name'] ]
178         # }
179         self.url = ""
180         BaseService.__init__(self, configuration=configuration, name=name)
181
182     def _get_data(self):
183         """
184         Get raw data from http request
185         :return: str
186         """
187         raw = None
188         try:
189             f = urlopen(self.url, timeout=self.update_every)
190             raw = f.read().decode('utf-8')
191         except Exception as e:
192             msg.error(self.__module__, str(e))
193         finally:
194             try:
195                 f.close()
196             except:
197                 pass
198         return raw
199
200     def _formatted_data(self):
201         """
202         Format data received from http request
203         :return: dict
204         """
205         return {}
206
207     def check(self):
208         """
209         Format configuration data and try to connect to server
210         :return: boolean
211         """
212         if self.name is None or self.name == str(None):
213             self.name = 'local'
214         else:
215             self.name = str(self.name)
216         try:
217             self.url = str(self.configuration['url'])
218         except (KeyError, TypeError):
219             pass
220
221         if self._formatted_data() is not None:
222             return True
223         else:
224             return False
225
226     def create(self):
227         """
228         Create charts
229         :return: boolean
230         """
231         for name in self.order:
232             if name not in self.charts:
233                 continue
234             self.definitions[name] = []
235             for line in self.charts[name]['lines']:
236                 self.definitions[name].append(line['name'])
237
238         idx = 0
239         data = self._formatted_data()
240         if data is None:
241             return False
242         data_stream = ""
243         for name in self.order:
244             header = "CHART " + \
245                      self.__module__ + "_" + \
246                      self.name + "." + \
247                      name + " " + \
248                      self.charts[name]['options'] + " " + \
249                      str(self.priority + idx) + " " + \
250                      str(self.update_every)
251             content = ""
252             # check if server has this datapoint
253             for line in self.charts[name]['lines']:
254                 if line['name'] in data:
255                     content += "\nDIMENSION " + line['name'] + " " + line['options']
256
257             if len(content) > 0:
258                 data_stream += header + content + "\n"
259                 idx += 1
260
261         print(data_stream)
262
263         if idx == 0:
264             return False
265         return True
266
267     def update(self, interval):
268         """
269         Update charts
270         :param interval: int
271         :return: boolean
272         """
273         data = self._formatted_data()
274         if data is None:
275             return False
276
277         data_stream = ""
278         for chart, dimensions in self.definitions.items():
279             header = "BEGIN " + self.__module__ + "_" + str(self.name) + "." + chart + " " + str(interval)
280             c = ""
281             for dim in dimensions:
282                 try:
283                     c += "\nSET " + dim + " = " + str(data[dim])
284                 except KeyError:
285                     pass
286             if len(c) != 0:
287                 data_stream += header + c + "\nEND\n"
288         print(data_stream)
289
290         return True