#!/usr/bin/python3 -u import os import sys import time import importlib.machinery import configparser class PythonCharts(object): def __init__(self,interval=1,modules=[],modules_path='../python.d/',modules_configs='../conf.d/'): self.interval = interval # check if plugin directory exists if not os.path.isdir(modules_path): debug("cannot find charts directory ",modules_path) sys.exit(1) self.configs=modules_configs self.modules = [] if len(modules) > 0: for m in modules: self.modules.append(self.import_plugin(modules_path+m+".chart.py")) else: self.load_modules(modules_path) if len(self.modules) == 0: debug("cannot find charts directory",modules_path) # sys.stdout.write("DISABLE") sys.exit(1) self.load_configs() # set last execution dict self.timetable = {} t = time.time() for m in self.modules: self.timetable[m.__name__] = t def import_plugin(self,path,name=None): if name is None: name = path.split('/')[-1] if name[-9:] != ".chart.py": return None name=name[:-9] try: return importlib.machinery.SourceFileLoader(name,path).load_module() except FileNotFoundError: return None def load_modules(self,path): names = os.listdir(path) for mod in names: m = self.import_plugin(path+mod) if m is not None: debug("loading chart: '"+path+mod+"'") self.modules.append(m) def load_configs(self): for m in self.modules: if os.path.isfile(self.configs+m.__name__+".conf"): debug("loading chart options: '"+self.configs+m.__name__+".conf'") for k,v in read_config(self.configs+m.__name__+".conf").items(): setattr(m,k,v) else: debug(m.__name__+": configuration file '"+self.configs+m.__name__+".conf' not found. Using defaults.") def disable_module(self,mod,reason=None): self.modules.remove(mod) del self.timetable[mod.__name__] if reason is None: return elif reason[:3] == "no ": debug("chart '"+ mod.__name__,"' does not seem to have "+reason[3:]+"() function. Disabling it.") elif reason[:7] == "failed ": debug("chart '"+mod.__name__+reason[3:]+"() function. reports failure.") elif reason[:13] == "configuration": debug(mod.__name__,"configuration file '"+self.configs+mod.__name__+".conf' not found. Using defaults.") elif reason[:11] == "misbehaving": debug(mod.__name__,"is misbeaving. Disabling it") #sys.stdout.write('DISABLE') def check(self): for mod in self.modules: try: if not mod.check(): self.disable_module(mod,"failed check") except AttributeError: self.disable_module(mod,"no check") except UnboundLocalError: self.disable_module(mod,"misbehaving") def create(self): for mod in self.modules: try: if not mod.create(): self.disable_module(mod,"failed create") else: chart=mod.__name__ 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') sys.stdout.write("DIMENSION run_time 'run time' absolute 1 1\n\n") sys.stdout.flush() except AttributeError: self.disable_module(mod,"no create") except UnboundLocalError: self.disable_module(mod,"misbehaving") def update_module(self,mod): t1 = time.time() try: # t=int((t1- self.timetable[mod.__name__]) * 1000) # if t == 0: t="" t = "" #FIXME if not mod.update(t): # if not mod.update(int((t1- self.timetable[mod.__name__]) * 1000)): self.disable_module(mod, "update failed") return except AttributeError: self.disable_module(mod, "no update") return except UnboundLocalError: self.disable_module(mod,"misbehaving") return t2 = time.time() # dt = int((t2 - self.timetable[mod.__name__]) * 1000) # if dt == 0: dt="" dt="" #FIXME sys.stdout.write("BEGIN netdata.plugin_pythond_"+mod.__name__+" "+str(dt)+'\n') sys.stdout.write("SET run_time = "+str(int((t2 - t1)*1000))+'\n') sys.stdout.write("END\n") sys.stdout.flush() self.timetable[mod.__name__] = t1 def update(self): while True: t_begin = time.time() for mod in self.modules: self.update_module(mod) time.sleep(self.interval - (time.time()-t_begin)) def read_config(path): #TODO make it bulletproof config = configparser.ConfigParser() config_str = "" try: with open(path, 'r', encoding="utf_8") as f: config_str = '[config]\n' + f.read() except IsADirectoryError: debug(str(path),"is a directory") return config.read_string(config_str) return dict(config.items('config')) def debug(*args): if not DEBUG_FLAG: return sys.stderr.write(PROGRAM+":") for i in args: sys.stderr.write(" "+str(i)) sys.stderr.write("\n") sys.stderr.flush() def parse_cmdline(directory,*commands): global DEBUG_FLAG, PROGRAM DEBUG_FLAG=False mods = [] for cmd in commands[1:]: if cmd == "check": pass elif cmd == "debug" or cmd == "all": DEBUG_FLAG=True # redirect stderr to stdout? elif os.path.isfile(directory+cmd+".chart.py") or os.path.isfile(directory+cmd): DEBUG_FLAG=True mods.append(cmd.replace(".chart.py","")) PROGRAM = commands[0].split('/')[-1].split('.plugin')[0] debug("started from",commands[0],"with options:",*commands[1:]) return mods #if __name__ == '__main__': def run(): # parse env variables # https://github.com/firehol/netdata/wiki/External-Plugins#environment-variables main_dir = os.getenv('NETDATA_PLUGINS_DIR',os.path.abspath(__file__).strip("python.d.plugin.py")) config_dir = os.getenv('NETDATA_CONFIG_DIR',"/etc/netdata/") # logs_dir = os.getenv('NETDATA_LOGS_DIR',None) #TODO logging? interval = int(os.getenv('NETDATA_UPDATE_EVERY',1)) # read configuration file if config_dir[-1] != '/': configfile = config_dir + '/' + "python.d.conf" else: configfile = config_dir + "python.d.conf" try: conf = read_config(configfile) try: if str(conf['enable']) == "no": debug("disabled in configuration file") sys.exit(1) except (KeyError,TypeError): pass try: modules_conf = conf['plugins_config_dir'] except (KeyError,TypeError): modules_conf = config_dir # default configuration directory try: modules_dir = conf['plugins_dir'] except (KeyError,TypeError): modules_dir = main_dir.replace("plugins.d","python.d") try: interval = int(conf['interval']) except (KeyError,TypeError): pass # default interval global DEBUG_FLAG try: DEBUG_FLAG = bool(conf['debug']) except (KeyError,TypeError): pass except FileNotFoundError: modules_conf = config_dir modules_dir = main_dir.replace("plugins.d","python.d") # directories should end with '/' if modules_dir[-1] != '/': modules_dir+="/" if modules_conf[-1] != '/': modules_conf+="/" # parse passed command line arguments modules = parse_cmdline(modules_dir,*sys.argv) # run plugins charts = PythonCharts(interval,modules,modules_dir,modules_conf) charts.check() charts.create() charts.update() if __name__ == '__main__': run()