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