@cryptotaxi247 / netdata-1 / commits / dd1b39caf

python plugin fixes for better tracing debugging

Costa Tsaousis committed Jun 21, 2016 at 11:28 UTC dd1b39cafcdbfafd1b720fca7a5b01bf764047cc
3 files changed +126 -103
plugins.d/python.d.plugin
+111 -91
@@ -7,21 +7,79 @@ import os
7 import sys
8 import time
9
10 +
11 +MODULE_EXTENSION = ".chart.py"
12 +BASE_CONFIG = {'update_every': 1,
13 + 'priority': 90000,
14 + 'retries': 10}
15 +
16 +
17 +# -----------------------------------------------------------------------------
18 +# logging functions
19 +
20 +PROGRAM = os.path.basename(__file__).replace(".plugin", "")
21 +DEBUG_FLAG = False
22 +
23 +def debug(*args):
24 + """
25 + Print message on stderr.
26 + """
27 + if not DEBUG_FLAG:
28 + return
29 + sys.stderr.write(PROGRAM + " DEBUG :")
30 + for i in args:
31 + sys.stderr.write(" " + str(i))
32 + sys.stderr.write("\n")
33 + sys.stderr.flush()
34 +
35 +def error(*args):
36 + """
37 + Print message on stderr.
38 + """
39 + sys.stderr.write(PROGRAM + " ERROR :")
40 + for i in args:
41 + sys.stderr.write(" " + str(i))
42 + sys.stderr.write("\n")
43 + sys.stderr.flush()
44 +
45 +def info(*args):
46 + """
47 + Print message on stderr.
48 + """
49 + sys.stderr.write(PROGRAM + " INFO :")
50 + for i in args:
51 + sys.stderr.write(" " + str(i))
52 + sys.stderr.write("\n")
53 + sys.stderr.flush()
54 +
55 +def fatal(*args):
56 + """
57 + Print message on stderr and exit.
58 + """
59 + sys.stderr.write(PROGRAM + " FATAL :")
60 + for i in args:
61 + sys.stderr.write(" " + str(i))
62 + sys.stderr.write("\n")
63 + sys.stderr.flush()
64 + sys.stdout.write('DISABLE\n')
65 + sys.exit(1)
66 +
67 +
68 +
69 +# -----------------------------------------------------------------------------
70 +# globals & python modules management
71 +
72 # setup environment
73 # https://github.com/firehol/netdata/wiki/External-Plugins#environment-variables
12 -MODULES_DIR = os.getenv('NETDATA_PLUGINS_DIR',
13 - os.path.abspath(__file__).strip("python.d.plugin.py").replace("plugins.d", ""))
74 +MODULES_DIR = os.path.abspath(os.getenv('NETDATA_PLUGINS_DIR',
75 + os.path.dirname(__file__)) + "/../python.d") + "/"
76 CONFIG_DIR = os.getenv('NETDATA_CONFIG_DIR', "/etc/netdata/")
15 -INTERVAL = os.getenv('NETDATA_UPDATE_EVERY', None)
77 +UPDATE_EVERY = os.getenv('NETDATA_UPDATE_EVERY', 1)
78 # directories should end with '/'
17 -if MODULES_DIR[-1] != "/":
18 - MODULES_DIR += "/"
19 -MODULES_DIR += "python.d/"
79 if CONFIG_DIR[-1] != "/":
80 CONFIG_DIR += "/"
81 sys.path.append(MODULES_DIR + "python_modules")
82
24 -
83 try:
84 assert sys.version_info >= (3, 1)
85 import importlib.machinery
@@ -30,7 +88,7 @@ try:
88 # import builtins
89 # builtins.PY_VERSION = 3
90 PY_VERSION = 3
33 - sys.stderr.write('python.d.plugin: Using python 3\n')
91 + info('Using python v3')
92 except (AssertionError, ImportError):
93 try:
94 import imp
@@ -39,38 +97,26 @@ except (AssertionError, ImportError):
97 # import __builtin__
98 # __builtin__.PY_VERSION = 2
99 PY_VERSION = 2
42 - sys.stderr.write('python.d.plugin: Using python 2\n')
100 + info('Using python v2')
101 except ImportError:
44 - sys.stderr.write('python.d.plugin: Cannot start. No importlib.machinery on python3 or lack of imp on python2\n')
45 - sys.stdout.write('DISABLE\n')
46 - sys.exit(1)
102 + fatal('Cannot start. No importlib.machinery on python3 or lack of imp on python2')
103 try:
104 import yaml
105 except ImportError:
50 - sys.stderr.write('python.d.plugin: Cannot find yaml library\n')
51 - sys.stdout.write('DISABLE\n')
52 - sys.exit(1)
53 -
54 -DEBUG_FLAG = False
55 -PROGRAM = "python.d.plugin"
56 -MODULE_EXTENSION = ".chart.py"
57 -BASE_CONFIG = {'update_every': 10,
58 - 'priority': 12345,
59 - 'retries': 0}
60 -
106 + fatal('Cannot find yaml library')
107
108 class PythonCharts(object):
109 """
110 Main class used to control every python module.
111 """
112 def __init__(self,
67 - interval=None,
113 + update_every=None,
114 modules=None,
115 modules_path='../python.d/',
116 modules_configs='../conf.d/',
117 modules_disabled=None):
118 """
73 - :param interval: int
119 + :param update_every: int
120 :param modules: list
121 :param modules_path: str
122 :param modules_configs: str
@@ -94,9 +140,9 @@ class PythonCharts(object):
140
141 # good economy and prosperity:
142 self.jobs = self._create_jobs(configured_modules) # type: list
97 - if DEBUG_FLAG and interval is not None:
143 + if DEBUG_FLAG and update_every is not None:
144 for job in self.jobs:
99 - job.create_timetable(interval)
145 + job.create_timetable(update_every)
146
147 @staticmethod
148 def _import_module(path, name=None):
@@ -132,9 +178,7 @@ class PythonCharts(object):
178
179 # check if plugin directory exists
180 if not os.path.isdir(path):
135 - debug("cannot find charts directory ", path)
136 - sys.stdout.write("DISABLE\n")
137 - sys.exit(1)
181 + fatal("cannot find charts directory ", path)
182
183 # load modules
184 loaded = []
@@ -146,19 +190,17 @@ class PythonCharts(object):
190 if mod is not None:
191 loaded.append(mod)
192 else: # exit if plugin is not found
149 - sys.stdout.write("DISABLE")
150 - sys.stdout.flush()
151 - sys.exit(1)
193 + fatal('no modules found.')
194 else:
195 # scan directory specified in path and load all modules from there
196 names = os.listdir(path)
197 for mod in names:
198 if mod.strip(MODULE_EXTENSION) in disabled:
157 - debug("disabling:", mod.strip(MODULE_EXTENSION))
199 + error(mod + ": disabled module ", mod.strip(MODULE_EXTENSION))
200 continue
201 m = self._import_module(path + mod)
202 if m is not None:
161 - debug("loading module: '" + path + mod + "'")
203 + debug(mod + ": loading module '" + path + mod + "'")
204 loaded.append(m)
205 return loaded
206
@@ -173,18 +215,15 @@ class PythonCharts(object):
215 for mod in modules:
216 configfile = self.configs + mod.__name__ + ".conf"
217 if os.path.isfile(configfile):
176 - debug("loading module configuration: '" + configfile + "'")
218 + debug(mod.__name__ + ": loading module configuration: '" + configfile + "'")
219 try:
220 setattr(mod,
221 'config',
222 self._parse_config(mod, read_config(configfile)))
223 except Exception as e:
182 - debug("something went wrong while loading configuration", e)
224 + error(mod.__name__ + ": cannot parse configuration file '" + configfile + "':", str(e))
225 else:
184 - debug(mod.__name__ +
185 - ": configuration file '" +
186 - configfile +
187 - "' not found. Using defaults.")
226 + error(mod.__name__ + ": configuration file '" + configfile + "' not found. Using defaults.")
227 # set config if not found
228 if not hasattr(mod, 'config'):
229 mod.config = {None: {}}
@@ -259,10 +298,9 @@ class PythonCharts(object):
298 try:
299 job = module.Service(configuration=conf, name=name)
300 except Exception as e:
262 - debug(module.__name__ +
263 - ": Couldn't start job named " +
264 - str(name) +
265 - ": " +
301 + error(module.__name__ +
302 + ("/" + str(name) if name is not None else "") +
303 + ": cannot start job: '" +
304 str(e))
305 return None
306 else:
@@ -271,6 +309,7 @@ class PythonCharts(object):
309 if name is not None:
310 job.chart_name += "_" + name
311 jobs.append(job)
312 + debug(module.__name__ + ("/" + str(name) if name is not None else "") + ": job added")
313
314 return [j for j in jobs if j is not None]
315
@@ -281,31 +320,31 @@ class PythonCharts(object):
320 :param job: object
321 :param reason: str
322 """
284 - prefix = ""
323 + prefix = job.__module__
324 if job.name is not None:
286 - prefix = "'" + job.name + "' in "
325 + prefix += "/" + job.name
326 + prefix += ": "
327
288 - prefix += "'" + job.__module__ + MODULE_EXTENSION + "' "
328 self.jobs.remove(job)
329 if reason is None:
330 return
331 elif reason[:3] == "no ":
293 - debug(prefix +
332 + error(prefix +
333 "does not seem to have " +
334 reason[3:] +
335 "() function. Disabling it.")
336 elif reason[:7] == "failed ":
298 - debug(prefix +
337 + error(prefix +
338 reason[7:] +
339 "() function reports failure.")
340 elif reason[:13] == "configuration":
302 - debug(prefix +
341 + error(prefix +
342 "configuration file '" +
343 self.configs +
344 job.__module__ +
345 ".conf' not found. Using defaults.")
346 elif reason[:11] == "misbehaving":
308 - debug(prefix + "is " + reason)
347 + error(prefix + "is " + reason)
348
349 def check(self):
350 """
@@ -350,7 +389,7 @@ class PythonCharts(object):
389 str(job.timetable['freq']) +
390 '\n')
391 sys.stdout.write("DIMENSION run_time 'run time' absolute 1 1\n\n")
353 - sys.stdout.flush()
392 + # sys.stdout.flush()
393 i += 1
394 except AttributeError:
395 self._stop(job, "no create")
@@ -397,7 +436,7 @@ class PythonCharts(object):
436 sys.stdout.write("BEGIN netdata.plugin_pythond_" + job.chart_name + " " + str(since_last) + '\n')
437 sys.stdout.write("SET run_time = " + str(int((t_end - t_start) * 1000)) + '\n')
438 sys.stdout.write("END\n")
400 - sys.stdout.flush()
439 + # sys.stdout.flush()
440 job.timetable['last'] = t_start
441 job.retries_left = job.retries
442 self.first_run = False
@@ -421,9 +460,7 @@ class PythonCharts(object):
460 pass
461 i += 1
462 if len(next_runs) == 0:
424 - debug("No plugins loaded")
425 - sys.stdout.write("DISABLE\n")
426 - sys.exit(1)
463 + fatal('no python.d modules loaded.')
464 time.sleep(min(next_runs) - time.time())
465
466
@@ -437,27 +474,14 @@ def read_config(path):
474 with open(path, 'r') as stream:
475 config = yaml.load(stream)
476 except (OSError, IOError):
440 - debug(str(path), "is not a valid configuration file")
477 + error(str(path), "is not a valid configuration file")
478 return None
479 except yaml.YAMLError as e:
443 - debug(str(path), "is malformed:", e)
480 + error(str(path), "is malformed:", e)
481 return None
482 return config
483
484
448 -def debug(*args):
449 - """
450 - Print message on stderr.
451 - """
452 - if not DEBUG_FLAG:
453 - return
454 - sys.stderr.write(PROGRAM + ":")
455 - for i in args:
456 - sys.stderr.write(" " + str(i))
457 - sys.stderr.write("\n")
458 - sys.stderr.flush()
459 -
460 -
485 def parse_cmdline(directory, *commands):
486 """
487 Parse parameters from command line.
@@ -466,7 +490,8 @@ def parse_cmdline(directory, *commands):
490 :return: dict
491 """
492 global DEBUG_FLAG
469 - interval = None
493 + global UPDATE_EVERY
494 + update_every = UPDATE_EVERY
495
496 mods = []
497 for cmd in commands[1:]:
@@ -479,18 +504,15 @@ def parse_cmdline(directory, *commands):
504 DEBUG_FLAG = True
505 mods.append(cmd.replace(".chart.py", ""))
506 else:
482 - DEBUG_FLAG = False
507 try:
484 - interval = int(cmd)
508 + update_every = int(cmd)
509 except ValueError:
486 - pass
510 + update_every = UPDATE_EVERY
511
512 debug("started from", commands[0], "with options:", *commands[1:])
489 - if len(mods) == 0 and DEBUG_FLAG is False:
490 - interval = None
513
492 - return {'interval': interval,
493 - 'modules': mods}
514 + UPDATE_EVERY = update_every
515 + return {'modules': mods}
516
517
518 # if __name__ == '__main__':
@@ -505,26 +527,25 @@ def run():
527 disabled = []
528 configfile = CONFIG_DIR + "python.d.conf"
529
508 - interval = INTERVAL
530 + update_every = UPDATE_EVERY
531 conf = read_config(configfile)
532 if conf is not None:
533 try:
534 + # FIXME: is this right? exit the whole plugin?
535 if str(conf['enable']) is False:
513 - debug("disabled in configuration file")
514 - sys.stdout.write("DISABLE\n")
515 - sys.exit(1)
536 + fatal('disabled in configuration file.\n')
537 except (KeyError, TypeError):
538 pass
539 try:
519 - interval = conf['interval']
540 + update_every = conf['update_every']
541 except (KeyError, TypeError):
521 - pass # use default interval from NETDATA_UPDATE_EVERY
542 + pass # use default update_every from NETDATA_UPDATE_EVERY
543 try:
544 DEBUG_FLAG = conf['debug']
545 except (KeyError, TypeError):
546 pass
547 for k, v in conf.items():
527 - if k in ("interval", "debug", "enable"):
548 + if k in ("update_every", "debug", "enable"):
549 continue
550 if v is False:
551 disabled.append(k)
@@ -532,15 +553,14 @@ def run():
553 # parse passed command line arguments
554 out = parse_cmdline(MODULES_DIR, *sys.argv)
555 modules = out['modules']
535 - if out['interval'] is not None:
536 - interval = out['interval']
556 + info("MODULES_DIR='" + MODULES_DIR + "', CONFIG_DIR='" + CONFIG_DIR + "', UPDATE_EVERY=" + str(UPDATE_EVERY) + ", ONLY_MODULES=" + str(out['modules']))
557
558 # run plugins
539 - charts = PythonCharts(interval, modules, MODULES_DIR, CONFIG_DIR + "python.d/", disabled)
559 + charts = PythonCharts(update_every, modules, MODULES_DIR, CONFIG_DIR + "python.d/", disabled)
560 charts.check()
561 charts.create()
562 charts.update()
543 - sys.stdout.write("DISABLE")
563 + fatal("finished")
564
565
566 if __name__ == '__main__':
python.d/example.chart.py
+5 -3
@@ -1,16 +1,18 @@
1 # Description: example netdata python.d plugin
2 # Author: Pawel Krupa (paulfantom)
3
4 +import os
5 +import sys
6 import random
7 from base import BaseService
8
7 -NAME = "example.chart.py"
9 +NAME = os.path.basename(__file__).replace(".chart.py", "")
10 +
11 # default module values
9 -update_every = 3
12 +update_every = 4
13 priority = 90000
14 retries = 7
15
13 -
16 class Service(BaseService):
17 def __init__(self, configuration=None, name=None):
18 super(self.__class__,self).__init__(configuration=configuration, name=name)
python.d/mysql.chart.py
+10 -9
@@ -1,9 +1,10 @@
1 # Description: MySQL netdata python.d plugin
2 # Author: Pawel Krupa (paulfantom)
3
4 +import os
5 import sys
6
6 -NAME = "mysql.chart.py"
7 +NAME = os.path.basename(__file__).replace(".chart.py", "")
8
9 # import 3rd party library to handle MySQL communication
10 try:
@@ -23,23 +24,23 @@ except ImportError:
24
25 from base import BaseService
26
27 +# default module values (can be overridden per job in `config`)
28 +update_every = 3
29 +priority = 90000
30 +retries = 7
31 +
32 # default configuration (overridden by python.d.plugin)
33 config = {
34 'local': {
35 'user': 'root',
36 'password': '',
37 'socket': '/var/run/mysqld/mysqld.sock',
32 - 'update_every': 3,
33 - 'retries': 4,
34 - 'priority': 100
38 + 'update_every': update_every,
39 + 'retries': retries,
40 + 'priority': priority
41 }
42 }
43
38 -# default module values (can be overridden per job in `config`)
39 -update_every = 3
40 -priority = 90000
41 -retries = 7
42 -
44 # query executed on MySQL server
45 QUERY = "SHOW GLOBAL STATUS"
46