]> arthur.barton.de Git - netdata.git/commitdiff
job creator in python.d.plugin
authorpaulfantom <paulfantom@gmail.com>
Fri, 17 Jun 2016 19:04:32 +0000 (21:04 +0200)
committerpaulfantom <paulfantom@gmail.com>
Fri, 17 Jun 2016 19:04:32 +0000 (21:04 +0200)
plugins.d/python.d.plugin
python.d/mysql.chart.py
python.d/python_modules/base.py

index 4131f764f9af169ba3163b963c086b7fe4893768..a5f3fc37b18a19df52963e9d06d311ab680523cf 100755 (executable)
@@ -1,11 +1,11 @@
-#!/usr/bin/python3 -u
+#!/usr/bin/env python3
 
 import os
 import sys
 import time
+import json
 try:
     assert sys.version_info >= (3,1)
-    import configparser
     import importlib.machinery
 except AssertionError:
     sys.stderr.write('Not supported python version. Needed python >= 3.1\n')
@@ -22,7 +22,6 @@ class PythonCharts(object):
                  modules_configs='../conf.d/',
                  modules_disabled=[]):
         self.first_run = True
-        self.default_priority = 90000
         # set configuration directory
         self.configs = modules_configs
 
@@ -35,37 +34,16 @@ class PythonCharts(object):
         # load configuration files
         self._load_configs()
 
+        # good economy and prosperity:
         self.jobs = self._create_jobs(self.modules)
-        
-        # set timetable dict (last execution, next execution and frequency)
-        # set priorities
-        self.timetable = {}
-        freq = 1
-        for job in self.jobs:
-            try:
-                job.priority = int(job.priority)
-            except (AttributeError,ValueError):
-                job.priority = self.default_priority
-             
-            if interval is None:
-                try:
-                    freq = int(job.update_every)
-                except (AttributeError, ValueError):
-                    freq = 1
-            
-            now = time.time()
-            self.timetable[job.__name__] = {'last' : now,
-                                            'next' : now - (now % freq) + freq,
-                                            'freq' : freq}
 
 
     def _create_jobs(self,modules):
-        # modules are a list of modules to load
-        # module store a definition of Service class
-        # module store configuration in module.config
-        # configs are list of dicts or a dict
-        # one dict is one service
-        # iterate over list of modules and inside one loop iterate over configs
+    # module store a definition of Service class
+    # module store configuration in module.config
+    # configs are list of dicts or a dict
+    # one dict is one service
+    # iterate over list of modules and inside one loop iterate over configs
         jobs = []
         for module in modules:
             if type(module.config) is dict:
@@ -78,34 +56,41 @@ class PythonCharts(object):
 
 
     def _job(self,module,conf):
-        update = 0
-        prio = 0
-        retries = 0
-        try:
-            update = int(conf['update_every'])
-        except (KeyError,ValueError):
-            update = module.update_every
-        try:
-            priority = int(conf['priority'])
-        except (KeyError,ValueError):
-            priority = module.priority
-        try:
-            retries = int(conf['retries'])
-        except (KeyError,ValueError):
-            retries = module.retries
+    # register a new job
+        # default job settings:
+        settings = {'update_every' : 11,
+                    'priority'     : 12345,
+                    'retries'      : 0}
+        # ensure job has basic variables
+        # first check in job config
+        # next check in module
+        # at last assign defaults
+        for key in settings:
+            try:
+                settings[key] = int(conf[key])
+            except (KeyError,ValueError):
+                try:
+                    settings[key] = int(getattr(module, key))
+                except (AttributeError,ValueError):
+                    pass
 
         try:
-            return module.Service(configuration=conf,
-                                  update_every=update,
-                                  priority=prio,
-                                  retries=retries)
+            job = module.Service(configuration=conf)
         except Exception as e:
-            print(e)
-            #TODO print some error
+            sys.stderr.write(module.__name__ +
+                             ": Couldn't start one of jobs " +
+                             str(e))
             return None
+        else:
+            # set execution_name (needed to plot run time graphs)
+            try:
+                job.execution_name = module.__name__ + \
+                                     "_" + \
+                                     str(conf['name'])
+            except KeyError:
+                job.execution_name = module.__name__
 
-
-    def _import_plugin(self, path, name=None):
+    def _import_module(self, path, name=None):
     # try to import module using only its path
         if name is None:
             name = path.split('/')[-1]
@@ -129,14 +114,14 @@ class PythonCharts(object):
         loaded = []
         if len(modules) > 0:
             for m in modules:
-                mod = self._import_plugin(path + m + ".chart.py")
+                mod = self._import_module(path + m + ".chart.py")
                 if mod is not None:
                     loaded.append(mod)
         else:
             # scan directory specified in path and load all modules from there
             names = os.listdir(path)
             for mod in names:
-                m = self._import_plugin(path + mod)
+                m = self._import_module(path + mod)
                 if m is not None:
                     debug("loading chart: '" + path + mod + "'")
                     loaded.append(m)
@@ -144,7 +129,6 @@ class PythonCharts(object):
 
     def _load_configs(self):
     # function modifies every loaded module in self.modules
-    # TODO ensure module has update_every, priority and retries variables
         for m in self.modules:
             configfile = self.configs + m.__name__ + ".conf"
             if os.path.isfile(configfile):
@@ -160,98 +144,97 @@ class PythonCharts(object):
                       configfile +
                       "' not found. Using defaults.")
 
-    def _disable_module(self, mod, reason=None):
-    # modifies self.modules
-        self.modules.remove(mod)
-        del self.timetable[mod.__name__]
+    def _stop(self, job, reason=None): #FIXME test if Service has __name__
+    # modifies self.jobs
+        self.jobs.remove(job)
         if reason is None:
             return
         elif reason[:3] == "no ":
             debug("chart '" +
-                  mod.__name__,
+                  job.__name__,
                   "' does not seem to have " +
                   reason[3:] +
                   "() function. Disabling it.")
         elif reason[:7] == "failed ":
             debug("chart '" +
-                  mod.__name__ + "' " +
+                  job.__name__ + "' " +
                   reason[7:] +
                   "() function reports failure.")
         elif reason[:13] == "configuration":
-            debug(mod.__name__,
+            debug(job.__name__,
                   "configuration file '" +
                   self.configs +
-                  mod.__name__ +
+                  job.__name__ +
                   ".conf' not found. Using defaults.")
         elif reason[:11] == "misbehaving":
-            debug(mod.__name__, "is "+reason)
+            debug(job.__name__, "is "+reason)
 
     def check(self):
-    # try to execute check() on every loaded module
-        for mod in self.modules:
+    # try to execute check() on every job
+        for job in self.jobs:
             try:
-                if not mod.check():
-                    self._disable_module(mod, "failed check")
+                if not job.check():
+                    self._stop(job, "failed check")
             except AttributeError:
-                self._disable_module(mod, "no check")
+                self._stop(job, "no check")
             except (UnboundLocalError, Exception) as e:
-                self._disable_module(mod, "misbehaving. Reason: " + str(e))
+                self._stop(job, "misbehaving. Reason: " + str(e))
 
     def create(self):
-    # try to execute create() on every loaded module
-        for mod in self.modules:
+    # try to execute create() on every job
+        for job in self.jobs:
             try:
-                if not mod.create():
-                    self._disable_module(mod, "failed create")
+                if not job.create():
+                    self._stop(job, "failed create")
                 else:
-                    chart = mod.__name__
+                    chart = job.execution_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.timetable[mod.__name__]['freq']) +
+                        str(job.timetable['freq']) +
                         '\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")
+                self._stop(job, "no create")
             except (UnboundLocalError, Exception) as e:
-                self._disable_module(mod, "misbehaving. Reason: " + str(e))
+                self._stop(job, "misbehaving. Reason: " + str(e))
 
-    def _update_module(self, mod):
-    # try to execute update() on every module and draw run time graph 
+    def _update_job(self, job):
+    # try to execute update() on every job and draw run time graph 
         t_start = time.time()
-        # check if it is time to execute module update() function
-        if self.timetable[mod.__name__]['next'] > t_start:
+        # check if it is time to execute job update() function
+        if job.timetable['next'] > t_start:
             return
         try:
             if self.first_run:
                 since_last = 0
             else:
-                since_last = int((t_start - self.timetable[mod.__name__]['last']) * 1000000)
-            if not mod.update(since_last):
-                self._disable_module(mod, "update failed")
+                since_last = int((t_start - job.timetable['last']) * 1000000)
+            if not job.update(since_last):
+                self._stop(job, "update failed")
                 return
         except AttributeError:
-            self._disable_module(mod, "no update")
+            self._stop(job, "no update")
             return
         except (UnboundLocalError, Exception) as e:
-            self._disable_module(mod, "misbehaving. Reason: " + str(e))
+            self._stop(job, "misbehaving. Reason: " + str(e))
             return
         t_end = time.time()
-        self.timetable[mod.__name__]['next'] = t_end - (t_end % self.timetable[mod.__name__]['freq']) + self.timetable[mod.__name__]['freq']
+        job.timetable['next'] = t_end - (t_end % job.timetable['freq']) + job.timetable['freq']
         # draw performance graph
         if self.first_run:
             dt = 0
         else:
-            dt = int((t_end - self.timetable[mod.__name__]['last']) * 1000000)
-        sys.stdout.write("BEGIN netdata.plugin_pythond_"+mod.__name__+" "+str(since_last)+'\n')
+            dt = int((t_end - job.timetable['last']) * 1000000)
+        sys.stdout.write("BEGIN netdata.plugin_pythond_"+job.execution_name+" "+str(since_last)+'\n')
         sys.stdout.write("SET run_time = " + str(int((t_end - t_start) * 1000)) + '\n')
         sys.stdout.write("END\n")
         sys.stdout.flush()
-        self.timetable[mod.__name__]['last'] = t_start
+        job.timetable['last'] = t_start
         self.first_run = False
 
     def update(self):
@@ -260,10 +243,10 @@ class PythonCharts(object):
         while True:
             t_begin = time.time()
             next_runs = []
-            for mod in self.modules:
-                self._update_module(mod)
+            for job in self.jobs:
+                self._update_job(job)
                 try:
-                    next_runs.append(self.timetable[mod.__name__]['next'])
+                    next_runs.append(job.timetable['next'])
                 except KeyError:
                     pass
             if len(next_runs) == 0:
@@ -274,20 +257,17 @@ class PythonCharts(object):
 
 
 def read_config(path):
-    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
-    try:
-        config.read_string(config_str)
-    except configparser.ParsingError as e:
-        debug("Malformed configuration file: "+str(e))
-        return
-    return dict(config.items('config'))
+    # FIXME move to JSON configurations
+#    try:
+#        with open(path, 'r') as f:
+#            config_str = '[config]\n' + f.read()
+#    except IsADirectoryError:
+#        debug(str(path), "is a directory")
+#        return
+#    try:
+#        debug("Malformed configuration file: "+str(e))
+#        return
+    return None
 
 
 def debug(*args):
index e3f17faa94b9888f91e27a2e39f0a57483cde124..f962beea2e455052e7fdaa8b1658075541ab4bca 100644 (file)
@@ -34,6 +34,11 @@ config = [
     }
 ]
 
+# default module values (can be overridden per job in `config`)
+update_every = 3
+priority = 90000
+retries = 7
+
 # query executed on MySQL server
 QUERY = "SHOW GLOBAL STATUS"
 
@@ -329,8 +334,8 @@ CHARTS = {
 
 
 class Service(BaseService):
-    def __init__(self,configuration=None,update_every=3,priority=90000,retries=2):
-        super().__init__(configuration,update_every,priority,retries)
+    def __init__(self,configuration=None):
+        super().__init__(configuration)
         self.configuration = self._parse_config(configuration)
         self.connection = None
         self.defs = {}
index c5013baef8793d75052b204b8b6ef7337d023454..906de4daf1d7e81f11c3ccc214e28c71c591dd9d 100644 (file)
@@ -1,31 +1,33 @@
 # Description: base for netdata python.d plugins
 # Author: Pawel Krupa (paulfantom)
 
+from time import time
+
+
 class BaseService(object):
-    def __init__(self,configuration=None,update_every=None,priority=None,retries=None):
-        if None in (configuration,update_every,priority,retries):
+    def __init__(self,configuration=None):
+        if configuration is None:
             # use defaults
             self.error("BaseService: no configuration parameters supplied. Cannot create Service.")
             raise RuntimeError
         else:
-            self._parse_base_config(configuration,update_every,priority,retries)
-
-    def _parse_base_config(self,config,update_every,priority,retries):
-        # parse configuration options to run this Service
-        try:
-            self.update_every = int(config['update_every'])
-        except (KeyError, ValueError):
-            self.update_every = update_every
-        try:
-            self.priority = int(config['priority'])
-        except (KeyError, ValueError):
-            self.priority = priority
-        try:
-            self.retries = int(config['retries'])
-        except (KeyError, ValueError):
-            self.retries = retries
+            self._extract_base_config(configuration)
+            self._create_timetable()
+            self.execution_name = ""
+
+    def _extract_base_config(self,config):
+        self.update_every = int(config['update_every'])
+        self.priority = int(config['priority'])
+        self.retries = int(config['retries'])
         self.retries_left = self.retries
 
+    def _create_timetable(self):
+        now = time()
+        self.timetable = {'last' : now,
+                          'next' : now - (now % self.update_every) + self.update_every,
+                          'freq' : self.update_every}
+
+
     def error(self, msg, exception=""):
         if exception != "":
             exception = " " + str(exception).replace("\n"," ")