@cryptotaxi247 / netdata-1 / commits / bd546d322

Initial support for python plugins

paulfantom committed Jun 10, 2016 at 14:27 UTC bd546d32209e4cc5ccc8d91a3e70a8808ffe73e0
2 files changed +246
plugins.d/python.d.plugin new
+231
@@ -0,0 +1,231 @@
1 +#!/usr/bin/python3 -u
2 +
3 +import os
4 +import sys
5 +import time
6 +import importlib.machinery
7 +import configparser
8 +
9 +class PythonCharts(object):
10 + def __init__(self,interval=1,modules=[],modules_path='../python.d/',modules_configs='../conf.d/'):
11 + self.interval = interval
12 + # check if plugin directory exists
13 + if not os.path.isdir(modules_path):
14 + debug("cannot find charts directory ",modules_path)
15 + sys.exit(1)
16 + self.configs=modules_configs
17 +
18 + self.modules = []
19 + if len(modules) > 0:
20 + for m in modules:
21 + self.modules.append(self.import_plugin(modules_path+m+".chart.py"))
22 + else:
23 + self.load_modules(modules_path)
24 + if len(self.modules) == 0:
25 + debug("cannot find charts directory",modules_path)
26 +# sys.stdout.write("DISABLE")
27 + sys.exit(1)
28 +
29 + self.load_configs()
30 +
31 + # set last execution dict
32 + self.timetable = {}
33 + t = time.time()
34 + for m in self.modules:
35 + self.timetable[m.__name__] = t
36 +
37 + def import_plugin(self,path,name=None):
38 + if name is None:
39 + name = path.split('/')[-1]
40 + if name[-9:] != ".chart.py":
41 + return None
42 + name=name[:-9]
43 + try:
44 + return importlib.machinery.SourceFileLoader(name,path).load_module()
45 + except FileNotFoundError:
46 + return None
47 +
48 + def load_modules(self,path):
49 + names = os.listdir(path)
50 + for mod in names:
51 + m = self.import_plugin(path+mod)
52 + if m is not None:
53 + debug("loading chart: '"+path+mod+"'")
54 + self.modules.append(m)
55 +
56 + def load_configs(self):
57 + for m in self.modules:
58 + if os.path.isfile(self.configs+m.__name__+".conf"):
59 + debug("loading chart options: '"+self.configs+m.__name__+".conf'")
60 + for k,v in read_config(self.configs+m.__name__+".conf").items():
61 + setattr(m,k,v)
62 + else:
63 + debug(m.__name__+": configuration file '"+self.configs+m.__name__+".conf' not found. Using defaults.")
64 +
65 + def disable_module(self,mod,reason=None):
66 + self.modules.remove(mod)
67 + del self.timetable[mod.__name__]
68 + if reason is None:
69 + return
70 + elif reason[:3] == "no ":
71 + debug("chart '"+ mod.__name__,"' does not seem to have "+reason[3:]+"() function. Disabling it.")
72 + elif reason[:7] == "failed ":
73 + debug("chart '"+mod.__name__+reason[3:]+"() function. reports failure.")
74 + elif reason[:13] == "configuration":
75 + debug(mod.__name__,"configuration file '"+self.configs+mod.__name__+".conf' not found. Using defaults.")
76 + elif reason[:11] == "misbehaving":
77 + debug(mod.__name__,"is misbeaving. Disabling it")
78 + #sys.stdout.write('DISABLE')
79 +
80 +
81 + def check(self):
82 + for mod in self.modules:
83 + try:
84 + if not mod.check():
85 + self.disable_module(mod,"failed check")
86 + except AttributeError:
87 + self.disable_module(mod,"no check")
88 + except UnboundLocalError:
89 + self.disable_module(mod,"misbehaving")
90 +
91 + def create(self):
92 + for mod in self.modules:
93 + try:
94 + if not mod.create():
95 + self.disable_module(mod,"failed create")
96 + else:
97 + chart=mod.__name__
98 + sys.stdout.write("CHART netdata.plugin_pythond_"+chart+" '' 'Execution time for "+chart+" plugin' 'milliseconds / run' python.d netdata.plugin_python area 145000 "+str(self.interval)+'\n')
99 + sys.stdout.write("DIMENSION run_time 'run time' absolute 1 1\n\n")
100 + sys.stdout.flush()
101 + except AttributeError:
102 + self.disable_module(mod,"no create")
103 + except UnboundLocalError:
104 + self.disable_module(mod,"misbehaving")
105 +
106 + def update_module(self,mod):
107 + t1 = time.time()
108 + try:
109 +# t=int((t1- self.timetable[mod.__name__]) * 1000000)
110 +# if t == 0: t=""
111 + t = "" #FIXME
112 + if not mod.update(t):
113 +# if not mod.update(int((t1- self.timetable[mod.__name__]) * 1000)):
114 + self.disable_module(mod, "update failed")
115 + return
116 + except AttributeError:
117 + self.disable_module(mod, "no update")
118 + return
119 + except UnboundLocalError:
120 + self.disable_module(mod,"misbehaving")
121 + return
122 + t2 = time.time()
123 +# dt = int((t2 - self.timetable[mod.__name__]) * 1000)
124 +# if dt == 0: dt=""
125 + dt="" #FIXME
126 + sys.stdout.write("BEGIN netdata.plugin_pythond_"+mod.__name__+" "+str(dt)+'\n')
127 + sys.stdout.write("SET run_time = "+str(int((t2 - t1))*1000)+'\n')
128 + sys.stdout.write("END\n")
129 + sys.stdout.flush()
130 + self.timetable[mod.__name__] = t1
131 +
132 + def update(self):
133 + while True:
134 + t_begin = time.time()
135 + for mod in self.modules:
136 + self.update_module(mod)
137 + time.sleep(self.interval - (time.time()-t_begin))
138 +
139 +
140 +def read_config(path):
141 + #TODO make it bulletproof
142 + config = configparser.ConfigParser()
143 + config_str = ""
144 + try:
145 + with open(path, 'r', encoding="utf_8") as f:
146 + config_str = '[config]\n' + f.read()
147 + except IsADirectoryError:
148 + debug(str(path),"is a directory")
149 + return
150 + config.read_string(config_str)
151 + return dict(config.items('config'))
152 +
153 +def debug(*args):
154 + if not DEBUG_FLAG:
155 + return
156 + sys.stderr.write(PROGRAM+":")
157 + for i in args:
158 + sys.stderr.write(" "+str(i))
159 + sys.stderr.write("\n")
160 + sys.stderr.flush()
161 +
162 +def parse_cmdline(directory,*commands):
163 + global DEBUG_FLAG, PROGRAM
164 + DEBUG_FLAG=False
165 +
166 + mods = []
167 + for cmd in commands[1:]:
168 + if cmd == "check":
169 + pass
170 + elif cmd == "debug" or cmd == "all":
171 + DEBUG_FLAG=True
172 + # redirect stderr to stdout?
173 + elif os.path.isfile(directory+cmd+".chart.py") or os.path.isfile(directory+cmd):
174 + DEBUG_FLAG=True
175 + mods.append(cmd.replace(".chart.py",""))
176 +
177 + PROGRAM = commands[0].split('/')[-1].split('.plugin')[0]
178 + debug("started from",commands[0],"with options:",*commands[1:])
179 +
180 + return mods
181 +
182 +#if __name__ == '__main__':
183 +def run():
184 + # parse env variables
185 + # https://github.com/firehol/netdata/wiki/External-Plugins#environment-variables
186 + main_dir = os.getenv('NETDATA_PLUGINS_DIR',os.path.abspath(__file__).strip("python.d.plugin.py"))
187 + config_dir = os.getenv('NETDATA_CONFIG_DIR',"/etc/netdata/")
188 + # logs_dir = os.getenv('NETDATA_LOGS_DIR',None) #TODO logging?
189 + interval = int(os.getenv('NETDATA_UPDATE_EVERY',1))
190 +
191 + # read configuration file
192 + if config_dir[-1] != '/':
193 + configfile = config_dir + '/' + "python.d.conf"
194 + else:
195 + configfile = config_dir + "python.d.conf"
196 +
197 + try:
198 + conf = read_config(configfile)
199 + try:
200 + if str(conf['enable']) == "no":
201 + debug("disabled in configuration file")
202 + sys.exit(1)
203 + except (KeyError,TypeError): pass
204 + try: modules_conf = conf['plugins_config_dir']
205 + except (KeyError,TypeError): modules_conf = config_dir # default configuration directory
206 + try: modules_dir = conf['plugins_dir']
207 + except (KeyError,TypeError): modules_dir = main_dir.replace("plugins.d","python.d")
208 + try: interval = int(conf['interval'])
209 + except (KeyError,TypeError): pass # default interval
210 + global DEBUG_FLAG
211 + try: DEBUG_FLAG = bool(conf['debug'])
212 + except (KeyError,TypeError): pass
213 + except FileNotFoundError:
214 + modules_conf = config_dir
215 + modules_dir = main_dir.replace("plugins.d","python.d")
216 +
217 + # directories should end with '/'
218 + if modules_dir[-1] != '/': modules_dir+="/"
219 + if modules_conf[-1] != '/': modules_conf+="/"
220 +
221 + # parse passed command line arguments
222 + modules = parse_cmdline(modules_dir,*sys.argv)
223 +
224 + # run plugins
225 + charts = PythonCharts(interval,modules,modules_dir,modules_conf)
226 + charts.check()
227 + charts.create()
228 + charts.update()
229 +
230 +if __name__ == '__main__':
231 + run()
python.d/example.chart.py new
+15
@@ -0,0 +1,15 @@
1 +import random
2 +
3 +def check():
4 + return True
5 +
6 +def create():
7 + print("CHART python_example.random '' 'A random number' 'random number' random random line 90000 1")
8 + print("DIMENSION random1 '' absolute 1 1")
9 + return True
10 +
11 +def update(interval):
12 + print("BEGIN python_example.random "+str(interval))
13 + print("SET random1 = "+str(random.randint(0,100)))
14 + print("END")
15 + return True