]> arthur.barton.de Git - netdata.git/blob - python.d/python_modules/base.py
03754da629a9dfc9c2b41be3e83d841733237a62
[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         t_start = time.time()
79         # check if it is time to execute job update() function
80         if self.timetable['next'] > t_start:
81             msg.debug(self.chart_name + " will be run in " +
82                       str(int((self.timetable['next'] - t_start) * 1000)) + " ms")
83             return True
84
85         since_last = int((t_start - self.timetable['last']) * 1000000)
86         msg.debug(self.chart_name +
87                   " ready to run, after " + str(int((t_start - self.timetable['last']) * 1000)) +
88                   " ms (update_every: " + str(self.timetable['freq'] * 1000) +
89                   " ms, latency: " + str(int((t_start - self.timetable['next']) * 1000)) + " ms)")
90         if not self.update(since_last):
91             return False
92         t_end = time.time()
93         self.timetable['next'] = t_end - (t_end % self.timetable['freq']) + self.timetable['freq']
94
95         # draw performance graph
96         run_time = str(int((t_end - t_start) * 1000))
97         run_time_chart = "BEGIN netdata.plugin_pythond_" + self.chart_name + " " + str(since_last) + '\n'
98         run_time_chart += "SET run_time = " + run_time + '\n'
99         run_time_chart += "END\n"
100         sys.stdout.write(run_time_chart)
101         msg.debug(self.chart_name + " updated in " + str(run_time) + " ms")
102         self.timetable['last'] = t_start
103         return True
104
105     def run(self):
106         self.timetable['last'] = time.time()
107         while True:
108             try:
109                 status = self._run_once()
110             except Exception as e:
111                 msg.error("Something wrong: " + str(e))
112                 return
113             if status:
114                 time.sleep(self.timetable['next'] - time.time())
115                 self.retries_left = self.retries
116             else:
117                 self.retries -= 1
118                 if self.retries_left <= 0:
119                     msg.error("no more retries. Exiting")
120                     return
121                 else:
122                     time.sleep(self.timetable['freq'])
123
124     def check(self):
125         """
126         check() prototype
127         :return: boolean
128         """
129         msg.error("Service " + str(self.__module__) + "doesn't implement check() function")
130         return False
131
132     def create(self):
133         """
134         create() prototype
135         :return: boolean
136         """
137         msg.error("Service " + str(self.__module__) + "doesn't implement create() function?")
138         return False
139
140     def update(self, interval):
141         """
142         update() prototype
143         :param interval: int
144         :return: boolean
145         """
146         msg.error("Service " + str(self.__module__) + "doesn't implement update() function")
147         return False
148
149
150 class UrlService(BaseService):
151     def __init__(self, configuration=None, name=None):
152         self.charts = {}
153         # charts definitions in format:
154         # charts = {
155         #    'chart_name_in_netdata': {
156         #        'options': "parameters defining chart (passed to CHART statement)",
157         #        'lines': [
158         #           { 'name': 'dimension_name',
159         #             'options': 'dimension parameters (passed to DIMENSION statement)"
160         #           }
161         #        ]}
162         #    }
163         self.order = []
164         self.definitions = {}
165         # definitions are created dynamically in create() method based on 'charts' dictionary. format:
166         # definitions = {
167         #     'chart_name_in_netdata' : [ charts['chart_name_in_netdata']['lines']['name'] ]
168         # }
169         self.url = ""
170         BaseService.__init__(self, configuration=configuration, name=name)
171
172     def _get_data(self):
173         """
174         Get raw data from http request
175         :return: str
176         """
177         raw = None
178         try:
179             f = urlopen(self.url, timeout=self.update_every)
180             raw = f.read().decode('utf-8')
181         except Exception as e:
182             msg.error(self.__module__, str(e))
183         finally:
184             try:
185                 f.close()
186             except:
187                 pass
188         return raw
189
190     def _formatted_data(self):
191         """
192         Format data received from http request
193         :return: dict
194         """
195         return {}
196
197     def check(self):
198         """
199         Format configuration data and try to connect to server
200         :return: boolean
201         """
202         if self.name is None or self.name == str(None):
203             self.name = 'local'
204         else:
205             self.name = str(self.name)
206         try:
207             self.url = str(self.configuration['url'])
208         except (KeyError, TypeError):
209             pass
210
211         if self._formatted_data() is not None:
212             return True
213         else:
214             return False
215
216     def create(self):
217         """
218         Create charts
219         :return: boolean
220         """
221         for name in self.order:
222             if name not in self.charts:
223                 continue
224             self.definitions[name] = []
225             for line in self.charts[name]['lines']:
226                 self.definitions[name].append(line['name'])
227
228         idx = 0
229         data = self._formatted_data()
230         if data is None:
231             return False
232         data_stream = ""
233         for name in self.order:
234             header = "CHART " + \
235                      self.__module__ + "_" + \
236                      self.name + "." + \
237                      name + " " + \
238                      self.charts[name]['options'] + " " + \
239                      str(self.priority + idx) + " " + \
240                      str(self.update_every)
241             content = ""
242             # check if server has this datapoint
243             for line in self.charts[name]['lines']:
244                 if line['name'] in data:
245                     content += "\nDIMENSION " + line['name'] + " " + line['options']
246
247             if len(content) > 0:
248                 data_stream += header + content + "\n"
249                 idx += 1
250
251         print(data_stream)
252
253         if idx == 0:
254             return False
255         return True
256
257     def update(self, interval):
258         """
259         Update charts
260         :param interval: int
261         :return: boolean
262         """
263         data = self._formatted_data()
264         if data is None:
265             return False
266
267         data_stream = ""
268         for chart, dimensions in self.definitions.items():
269             header = "BEGIN " + self.__module__ + "_" + str(self.name) + "." + chart + " " + str(interval)
270             c = ""
271             for dim in dimensions:
272                 try:
273                     c += "\nSET " + dim + " = " + str(data[dim])
274                 except KeyError:
275                     pass
276             if len(c) != 0:
277                 data_stream += header + c + "\nEND\n"
278         print(data_stream)
279
280         return True