#!/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 modules directory", modules_path) # sys.stdout.write("DISABLE\n") sys.exit(1) self.load_configs() # set last execution and execution frequency dict self.timetable = {} #last_exec = time.time() last_exec = 0 for m in self.modules: try: interval = m.update_every except AttributeError: interval = self.interval self.timetable[m.__name__] = [last_exec, int(interval)] 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 Exception as e: debug(str(e)) 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: configfile = self.configs + m.__name__ + ".conf" if os.path.isfile(configfile): debug("loading chart options: '" + configfile + "'") for k, v in read_config(configfile).items(): setattr(m, k, v) else: debug( m.__name__ + ": configuration file '" + configfile + "' 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( "CHART netdata.plugin_pythond_" + chart + " '' 'Execution time for " + chart + " plugin' 'milliseconds / run' python.d netdata.plugin_python area 145000 " + str(self.timetable[mod.__name__][1]) + '\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() # check if it is time to execute module update() function if (t1 - self.timetable[mod.__name__][0]) < self.timetable[mod.__name__][1]: return try: # t=int((t1- self.timetable[mod.__name__][0]) * 1000) # if t == 0: t="" t = "" # FIXME if not mod.update(t): # if not mod.update(int((t1- self.timetable[mod.__name__][0]) * # 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__][0]) * 1000) # if dt == 0: dt="" # dt="" #FIXME #sys.stdout.write("BEGIN netdata.plugin_pythond_"+mod.__name__+" "+str(dt)+'\n') sys.stdout.write("BEGIN netdata.plugin_pythond_" + mod.__name__ + "\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__][0] = 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)) / 2) 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()