]> arthur.barton.de Git - netdata.git/blob - plugins.d/python.d.plugin
fix wrongly calculated run time
[netdata.git] / plugins.d / python.d.plugin
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__]) * 1000)
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()