master
in 870 lines 25.1 KB
Raw
1 #!/usr/bin/env bash
2 '''':;
3 pybinary=$(which python3 || which python || which python2)
4 filtered=()
5 for arg in "$@"
6 do
7 case $arg in
8 -p*) pybinary=${arg:2}
9 shift 1 ;;
10 *) filtered+=("$arg") ;;
11 esac
12 done
13 if [ "$pybinary" = "" ]
14 then
15 echo 1>&2 "python.d ERROR: python is not installed on this system"
16 echo "EXIT"
17 exit 1
18 fi
19 exec "$pybinary" "$0" "${filtered[@]}" # '''
20
21 # -*- coding: utf-8 -*-
22 # SPDX-License-Identifier: GPL-3.0-or-later
23
24 import collections
25 import copy
26 import gc
27 import json
28 import os
29 import pprint
30 import re
31 import sys
32 import threading
33 import time
34 import types
35
36 try:
37 from queue import Queue
38 except ImportError:
39 from Queue import Queue
40
41 PY_VERSION = sys.version_info[:2] # (major=3, minor=7, micro=3, releaselevel='final', serial=0)
42
43 if PY_VERSION > (3, 1):
44 from importlib.machinery import SourceFileLoader
45 else:
46 from imp import load_source as SourceFileLoader
47
48 ENV_NETDATA_USER_CONFIG_DIR = 'NETDATA_USER_CONFIG_DIR'
49 ENV_NETDATA_STOCK_CONFIG_DIR = 'NETDATA_STOCK_CONFIG_DIR'
50 ENV_NETDATA_PLUGINS_DIR = 'NETDATA_PLUGINS_DIR'
51 ENV_NETDATA_USER_PLUGINS_DIRS = 'NETDATA_USER_PLUGINS_DIRS'
52 ENV_NETDATA_LIB_DIR = 'NETDATA_LIB_DIR'
53 ENV_NETDATA_UPDATE_EVERY = 'NETDATA_UPDATE_EVERY'
54
55
56 def add_pythond_packages():
57 pluginsd = os.getenv(ENV_NETDATA_PLUGINS_DIR, os.path.dirname(__file__))
58 pythond = os.path.abspath(pluginsd + '/../python.d')
59 packages = os.path.join(pythond, 'python_modules')
60 sys.path.append(packages)
61
62
63 add_pythond_packages()
64
65 from bases.collection import safe_print
66 from bases.loggers import PythonDLogger
67 from bases.loaders import load_config
68
69 try:
70 from collections import OrderedDict
71 except ImportError:
72 from third_party.ordereddict import OrderedDict
73
74
75 def dirs():
76 var_lib = os.getenv(
77 ENV_NETDATA_LIB_DIR,
78 '@varlibdir_POST@',
79 )
80 plugin_user_config = os.getenv(
81 ENV_NETDATA_USER_CONFIG_DIR,
82 '@configdir_POST@',
83 )
84 plugin_stock_config = os.getenv(
85 ENV_NETDATA_STOCK_CONFIG_DIR,
86 '@libconfigdir_POST@',
87 )
88 pluginsd = os.getenv(
89 ENV_NETDATA_PLUGINS_DIR,
90 os.path.dirname(__file__),
91 )
92
93 modules_user_config = os.path.join(plugin_user_config, 'python.d')
94 modules_stock_config = os.path.join(plugin_stock_config, 'python.d')
95 modules = os.path.abspath(pluginsd + '/../python.d')
96 user_modules = [os.path.join(p, 'python.d') for p in
97 os.getenv(ENV_NETDATA_USER_PLUGINS_DIRS, "").split(" ") if
98 p]
99
100 Dirs = collections.namedtuple(
101 'Dirs',
102 [
103 'plugin_user_config',
104 'plugin_stock_config',
105 'modules_user_config',
106 'modules_stock_config',
107 'modules',
108 'user_modules',
109 'var_lib',
110 ]
111 )
112 return Dirs(
113 plugin_user_config,
114 plugin_stock_config,
115 modules_user_config,
116 modules_stock_config,
117 modules,
118 user_modules,
119 var_lib,
120 )
121
122
123 DIRS = dirs()
124
125 IS_ATTY = sys.stdout.isatty() or sys.stderr.isatty()
126
127 MODULE_SUFFIX = '.chart.py'
128
129
130 def find_available_modules(*directories):
131 AvailableModule = collections.namedtuple(
132 'AvailableModule',
133 [
134 'filepath',
135 'name',
136 ]
137 )
138 available = list()
139 for d in directories:
140 try:
141 if not os.path.isdir(d):
142 continue
143 files = sorted(os.listdir(d))
144 except OSError:
145 continue
146 modules = [m for m in files if m.endswith(MODULE_SUFFIX)]
147 available.extend([AvailableModule(os.path.join(d, m), m[:-len(MODULE_SUFFIX)]) for m in modules])
148
149 return available
150
151
152 def available_modules():
153 obsolete = (
154 'apache_cache', # replaced by web_log
155 'cpuidle', # rewritten in C
156 'cpufreq', # rewritten in C
157 'gunicorn_log', # replaced by web_log
158 'linux_power_supply', # rewritten in C
159 'nginx_log', # replaced by web_log
160 'mdstat', # rewritten in C
161 'sslcheck', # rewritten in Go, memory leak bug https://github.com/netdata/netdata/issues/5624
162 'unbound', # rewritten in Go
163 )
164
165 stock = [m for m in find_available_modules(DIRS.modules) if m.name not in obsolete]
166 user = find_available_modules(*DIRS.user_modules)
167
168 available, seen = list(), set()
169 for m in user + stock:
170 if m.name in seen:
171 continue
172 seen.add(m.name)
173 available.append(m)
174
175 return available
176
177
178 AVAILABLE_MODULES = available_modules()
179
180 JOB_BASE_CONF = {
181 'update_every': int(os.getenv(ENV_NETDATA_UPDATE_EVERY, 1)),
182 'priority': 60000,
183 'autodetection_retry': 0,
184 'chart_cleanup': 10,
185 'penalty': True,
186 'name': str(),
187 }
188
189 PLUGIN_BASE_CONF = {
190 'enabled': True,
191 'default_run': True,
192 'gc_run': True,
193 'gc_interval': 300,
194 }
195
196
197 def multi_path_find(name, *paths):
198 for path in paths:
199 abs_name = os.path.join(path, name)
200 if os.path.isfile(abs_name):
201 return abs_name
202 return str()
203
204
205 def load_module(name, filepath):
206 module = SourceFileLoader('pythond_' + name, filepath)
207 if isinstance(module, types.ModuleType):
208 return module
209 return module.load_module()
210
211
212 class ModuleConfig:
213 def __init__(self, name, config=None):
214 self.name = name
215 self.config = config or OrderedDict()
216 self.is_stock = False
217
218 def load(self, abs_path):
219 if not IS_ATTY:
220 self.is_stock = abs_path.startswith(DIRS.modules_stock_config)
221 self.config.update(load_config(abs_path) or dict())
222
223 def defaults(self):
224 keys = (
225 'update_every',
226 'priority',
227 'autodetection_retry',
228 'chart_cleanup',
229 'penalty',
230 )
231 return dict((k, self.config[k]) for k in keys if k in self.config)
232
233 def create_job(self, job_name, job_config=None):
234 job_config = job_config or dict()
235
236 config = OrderedDict()
237 config.update(job_config)
238 config['job_name'] = job_name
239 config['__is_stock'] = self.is_stock
240 for k, v in self.defaults().items():
241 config.setdefault(k, v)
242
243 return config
244
245 def job_names(self):
246 return [v for v in self.config if isinstance(self.config.get(v), dict)]
247
248 def single_job(self):
249 return [self.create_job(self.name, self.config)]
250
251 def multi_job(self):
252 return [self.create_job(n, self.config[n]) for n in self.job_names()]
253
254 def create_jobs(self):
255 return self.multi_job() or self.single_job()
256
257
258 class JobsConfigsBuilder:
259 def __init__(self, config_dirs):
260 self.config_dirs = config_dirs
261 self.log = PythonDLogger()
262 self.job_defaults = None
263 self.module_defaults = None
264 self.min_update_every = None
265
266 def load_module_config(self, module_name):
267 name = '{0}.conf'.format(module_name)
268 self.log.debug("[{0}] looking for '{1}' in {2}".format(module_name, name, self.config_dirs))
269 config = ModuleConfig(module_name)
270
271 abs_path = multi_path_find(name, *self.config_dirs)
272 if not abs_path:
273 self.log.warning("[{0}] '{1}' was not found".format(module_name, name))
274 return config
275
276 self.log.debug("[{0}] loading '{1}'".format(module_name, abs_path))
277 try:
278 config.load(abs_path)
279 except Exception as error:
280 self.log.error("[{0}] error on loading '{1}' : {2}".format(module_name, abs_path, repr(error)))
281 return None
282
283 self.log.debug("[{0}] '{1}' is loaded".format(module_name, abs_path))
284 return config
285
286 @staticmethod
287 def apply_defaults(jobs, defaults):
288 if defaults is None:
289 return
290 for k, v in defaults.items():
291 for job in jobs:
292 job.setdefault(k, v)
293
294 def set_min_update_every(self, jobs, min_update_every):
295 if min_update_every is None:
296 return
297 for job in jobs:
298 if 'update_every' in job and job['update_every'] < self.min_update_every:
299 job['update_every'] = self.min_update_every
300
301 def build(self, module_name):
302 config = self.load_module_config(module_name)
303 if config is None:
304 return None
305
306 configs = config.create_jobs()
307 if not config.is_stock:
308 self.log.info("[{0}] built {1} job(s) configs".format(module_name, len(configs)))
309
310 self.apply_defaults(configs, self.module_defaults)
311 self.apply_defaults(configs, self.job_defaults)
312 self.set_min_update_every(configs, self.min_update_every)
313
314 return configs
315
316
317 JOB_STATUS_ACTIVE = 'active'
318 JOB_STATUS_RECOVERING = 'recovering'
319 JOB_STATUS_DROPPED = 'dropped'
320 JOB_STATUS_INIT = 'initial'
321
322
323 class Job(threading.Thread):
324 inf = -1
325
326 def __init__(self, service, module_name, config):
327 threading.Thread.__init__(self)
328 self.daemon = True
329 self.service = service
330 self.module_name = module_name
331 self.config = config
332 self.real_name = config['job_name']
333 self.actual_name = config['override_name'] or self.real_name
334 self.autodetection_retry = config['autodetection_retry']
335 self.checks = self.inf
336 self.job = None
337 self.is_stock = config.get('__is_stock', False)
338 self.status = JOB_STATUS_INIT
339
340 def is_inited(self):
341 return self.job is not None
342
343 def init(self):
344 self.job = self.service(configuration=copy.deepcopy(self.config))
345
346 def full_name(self):
347 return self.job.name
348
349 def check(self):
350 if self.is_stock:
351 self.job.logger.mute()
352
353 ok = self.job.check()
354
355 self.job.logger.unmute()
356 self.checks -= self.checks != self.inf and not ok
357
358 return ok
359
360 def create(self):
361 self.job.create()
362
363 def need_to_recheck(self):
364 return self.autodetection_retry != 0 and self.checks != 0
365
366 def run(self):
367 self.job.run()
368
369
370 class ModuleSrc:
371 def __init__(self, m):
372 self.name = m.name
373 self.filepath = m.filepath
374 self.src = None
375
376 def load(self):
377 self.src = load_module(self.name, self.filepath)
378
379 def get(self, key):
380 return getattr(self.src, key, None)
381
382 def service(self):
383 return self.get('Service')
384
385 def defaults(self):
386 keys = (
387 'update_every',
388 'priority',
389 'autodetection_retry',
390 'chart_cleanup',
391 'penalty',
392 )
393 return dict((k, self.get(k)) for k in keys if self.get(k) is not None)
394
395 def is_disabled_by_default(self):
396 return bool(self.get('disabled_by_default'))
397
398
399 class JobsStatuses:
400 def __init__(self):
401 self.items = OrderedDict()
402
403 def dump(self):
404 return json.dumps(self.items, indent=2)
405
406 def get(self, module_name, job_name):
407 if module_name not in self.items:
408 return None
409 return self.items[module_name].get(job_name)
410
411 def has(self, module_name, job_name):
412 return self.get(module_name, job_name) is not None
413
414 def from_file(self, path):
415 with open(path) as f:
416 data = json.load(f)
417 return self.from_json(data)
418
419 @staticmethod
420 def from_json(items):
421 if not isinstance(items, dict):
422 raise Exception('items obj has wrong type : {0}'.format(type(items)))
423 if not items:
424 return JobsStatuses()
425
426 v = OrderedDict()
427 for mod_name in sorted(items):
428 if not items[mod_name]:
429 continue
430 v[mod_name] = OrderedDict()
431 for job_name in sorted(items[mod_name]):
432 v[mod_name][job_name] = items[mod_name][job_name]
433
434 rv = JobsStatuses()
435 rv.items = v
436 return rv
437
438 @staticmethod
439 def from_jobs(jobs):
440 v = OrderedDict()
441 for job in jobs:
442 status = job.status
443 if status not in (JOB_STATUS_ACTIVE, JOB_STATUS_RECOVERING):
444 continue
445 if job.module_name not in v:
446 v[job.module_name] = OrderedDict()
447 v[job.module_name][job.real_name] = status
448
449 rv = JobsStatuses()
450 rv.items = v
451 return rv
452
453
454 class StdoutSaver:
455 @staticmethod
456 def save(dump):
457 print(dump)
458
459
460 class CachedFileSaver:
461 def __init__(self, path):
462 self.last_save_success = False
463 self.last_saved_dump = str()
464 self.path = path
465
466 def save(self, dump):
467 if self.last_save_success and self.last_saved_dump == dump:
468 return
469 try:
470 with open(self.path, 'w') as out:
471 out.write(dump)
472 except Exception:
473 self.last_save_success = False
474 raise
475 self.last_saved_dump = dump
476 self.last_save_success = True
477
478
479 class PluginConfig(dict):
480 def __init__(self, *args):
481 dict.__init__(self, *args)
482
483 def is_module_explicitly_enabled(self, module_name):
484 return self._is_module_enabled(module_name, True)
485
486 def is_module_enabled(self, module_name):
487 return self._is_module_enabled(module_name, False)
488
489 def _is_module_enabled(self, module_name, explicit):
490 if module_name in self:
491 return self[module_name]
492 if explicit:
493 return False
494 return self['default_run']
495
496
497 class Plugin:
498 config_name = 'python.d.conf'
499 jobs_status_dump_name = 'pythond-jobs-statuses.json'
500
501 def __init__(self, modules_to_run, min_update_every):
502 self.modules_to_run = modules_to_run
503 self.min_update_every = min_update_every
504 self.config = PluginConfig(PLUGIN_BASE_CONF)
505 self.log = PythonDLogger()
506 self.started_jobs = collections.defaultdict(dict)
507 self.jobs = list()
508 self.saver = None
509 self.runs = 0
510
511 def load_config_file(self, filepath, expected):
512 self.log.debug("looking for '{0}'".format(filepath))
513 if not os.path.isfile(filepath):
514 log = self.log.info if not expected else self.log.error
515 log("'{0}' was not found".format(filepath))
516 return dict()
517 try:
518 config = load_config(filepath)
519 except Exception as error:
520 self.log.error("error on loading '{0}' : {1}".format(filepath, repr(error)))
521 return dict()
522 self.log.debug("'{0}' is loaded".format(filepath))
523 return config
524
525 def load_config(self):
526 user_config = self.load_config_file(
527 filepath=os.path.join(DIRS.plugin_user_config, self.config_name),
528 expected=False,
529 )
530 stock_config = self.load_config_file(
531 filepath=os.path.join(DIRS.plugin_stock_config, self.config_name),
532 expected=True,
533 )
534 self.config.update(stock_config)
535 self.config.update(user_config)
536
537 def load_job_statuses(self):
538 self.log.debug("looking for '{0}' in {1}".format(self.jobs_status_dump_name, DIRS.var_lib))
539 abs_path = multi_path_find(self.jobs_status_dump_name, DIRS.var_lib)
540 if not abs_path:
541 self.log.warning("'{0}' was not found".format(self.jobs_status_dump_name))
542 return
543
544 self.log.debug("loading '{0}'".format(abs_path))
545 try:
546 statuses = JobsStatuses().from_file(abs_path)
547 except Exception as error:
548 self.log.error("'{0}' invalid JSON format: {1}".format(
549 abs_path, ' '.join([v.strip() for v in str(error).split('\n')])))
550 return None
551 self.log.debug("'{0}' is loaded".format(abs_path))
552 return statuses
553
554 def create_jobs(self, job_statuses=None):
555 paths = [
556 DIRS.modules_user_config,
557 DIRS.modules_stock_config,
558 ]
559
560 builder = JobsConfigsBuilder(paths)
561 builder.job_defaults = JOB_BASE_CONF
562 builder.min_update_every = self.min_update_every
563
564 jobs = list()
565 for m in self.modules_to_run:
566 if not self.config.is_module_enabled(m.name):
567 self.log.info("[{0}] is disabled in the configuration file, skipping it".format(m.name))
568 continue
569
570 src = ModuleSrc(m)
571 try:
572 src.load()
573 except Exception as error:
574 self.log.warning("[{0}] error on loading source : {1}, skipping it".format(m.name, repr(error)))
575 continue
576 self.log.debug("[{0}] loaded module source : '{1}'".format(m.name, m.filepath))
577
578 if not (src.service() and callable(src.service())):
579 self.log.warning("[{0}] has no callable Service object, skipping it".format(m.name))
580 continue
581
582 if src.is_disabled_by_default() and not self.config.is_module_explicitly_enabled(m.name):
583 self.log.info("[{0}] is disabled by default, skipping it".format(m.name))
584 continue
585
586 builder.module_defaults = src.defaults()
587 configs = builder.build(m.name)
588 if not configs:
589 self.log.info("[{0}] has no job configs, skipping it".format(m.name))
590 continue
591
592 for config in configs:
593 config['job_name'] = re.sub(r'\s+', '_', config['job_name'])
594 config['override_name'] = re.sub(r'\s+', '_', config.pop('name'))
595
596 job = Job(src.service(), m.name, config)
597
598 was_previously_active = job_statuses and job_statuses.has(job.module_name, job.real_name)
599 if was_previously_active and job.autodetection_retry == 0:
600 self.log.debug('{0}[{1}] was previously active, applying recovering settings'.format(
601 job.module_name, job.real_name))
602 job.checks = 11
603 job.autodetection_retry = 30
604
605 jobs.append(job)
606
607 return jobs
608
609 def setup(self):
610 self.load_config()
611
612 if not self.config['enabled']:
613 self.log.info('disabled in the configuration file')
614 return False
615
616 statuses = self.load_job_statuses()
617
618 self.jobs = self.create_jobs(statuses)
619 if not self.jobs:
620 self.log.info('no jobs to run')
621 return False
622
623 if not IS_ATTY:
624 abs_path = os.path.join(DIRS.var_lib, self.jobs_status_dump_name)
625 self.saver = CachedFileSaver(abs_path)
626 return True
627
628 def start_jobs(self, *jobs):
629 for job in jobs:
630 if job.status not in (JOB_STATUS_INIT, JOB_STATUS_RECOVERING):
631 continue
632
633 if job.actual_name in self.started_jobs[job.module_name]:
634 self.log.info('{0}[{1}] : already served by another job, skipping it'.format(
635 job.module_name, job.real_name))
636 job.status = JOB_STATUS_DROPPED
637 continue
638
639 if not job.is_inited():
640 try:
641 job.init()
642 except Exception as error:
643 self.log.warning("{0}[{1}] : unhandled exception on init : {2}, skipping the job".format(
644 job.module_name, job.real_name, repr(error)))
645 job.status = JOB_STATUS_DROPPED
646 continue
647
648 try:
649 ok = job.check()
650 except Exception as error:
651 if not job.is_stock:
652 self.log.warning("{0}[{1}] : unhandled exception on check : {2}, skipping the job".format(
653 job.module_name, job.real_name, repr(error)))
654 job.status = JOB_STATUS_DROPPED
655 continue
656 if not ok:
657 if not job.is_stock:
658 self.log.info('{0}[{1}] : check failed'.format(job.module_name, job.real_name))
659 job.status = JOB_STATUS_RECOVERING if job.need_to_recheck() else JOB_STATUS_DROPPED
660 continue
661 self.log.info('{0}[{1}] : check success'.format(job.module_name, job.real_name))
662
663 try:
664 job.create()
665 except Exception as error:
666 self.log.warning("{0}[{1}] : unhandled exception on create : {2}, skipping the job".format(
667 job.module_name, job.real_name, repr(error)))
668 job.status = JOB_STATUS_DROPPED
669 continue
670
671 self.started_jobs[job.module_name] = job.actual_name
672 job.status = JOB_STATUS_ACTIVE
673 job.start()
674
675 @staticmethod
676 def keep_alive():
677 if not IS_ATTY:
678 safe_print('\n')
679
680 def garbage_collection(self):
681 if self.config['gc_run'] and self.runs % self.config['gc_interval'] == 0:
682 v = gc.collect()
683 self.log.debug('GC collection run result: {0}'.format(v))
684
685 def restart_recovering_jobs(self):
686 for job in self.jobs:
687 if job.status != JOB_STATUS_RECOVERING:
688 continue
689 if self.runs % job.autodetection_retry != 0:
690 continue
691 self.start_jobs(job)
692
693 def cleanup_jobs(self):
694 self.jobs = [j for j in self.jobs if j.status != JOB_STATUS_DROPPED]
695
696 def have_alive_jobs(self):
697 return next(
698 (True for job in self.jobs if job.status in (JOB_STATUS_RECOVERING, JOB_STATUS_ACTIVE)),
699 False,
700 )
701
702 def save_job_statuses(self):
703 if self.saver is None:
704 return
705 if self.runs % 10 != 0:
706 return
707 dump = JobsStatuses().from_jobs(self.jobs).dump()
708 try:
709 self.saver.save(dump)
710 except Exception as error:
711 self.log.error("error on saving jobs statuses dump : {0}".format(repr(error)))
712
713 def serve_once(self):
714 if not self.have_alive_jobs():
715 self.log.info('no jobs to serve')
716 return False
717
718 time.sleep(1)
719 self.runs += 1
720
721 self.keep_alive()
722 self.garbage_collection()
723 self.cleanup_jobs()
724 self.restart_recovering_jobs()
725 self.save_job_statuses()
726 return True
727
728 def serve(self):
729 while self.serve_once():
730 pass
731
732 def run(self):
733 self.start_jobs(*self.jobs)
734 self.serve()
735
736
737 def parse_command_line():
738 opts = sys.argv[:][1:]
739
740 debug = False
741 trace = False
742 update_every = 1
743 modules_to_run = list()
744
745 def find_first_positive_int(values):
746 return next((v for v in values if v.isdigit() and int(v) >= 1), None)
747
748 u = find_first_positive_int(opts)
749 if u is not None:
750 update_every = int(u)
751 opts.remove(u)
752 if 'debug' in opts:
753 debug = True
754 opts.remove('debug')
755 if 'trace' in opts:
756 trace = True
757 opts.remove('trace')
758 if opts:
759 modules_to_run = list(opts)
760
761 cmd = collections.namedtuple(
762 'CMD',
763 [
764 'update_every',
765 'debug',
766 'trace',
767 'modules_to_run',
768 ])
769 return cmd(
770 update_every,
771 debug,
772 trace,
773 modules_to_run,
774 )
775
776
777 def guess_module(modules, *names):
778 def guess(n):
779 found = None
780 for i, _ in enumerate(n):
781 cur = [x for x in modules if x.startswith(name[:i + 1])]
782 if not cur:
783 return found
784 found = cur
785 return found
786
787 guessed = list()
788 for name in names:
789 name = name.lower()
790 m = guess(name)
791 if m:
792 guessed.extend(m)
793 return sorted(set(guessed))
794
795
796 def disable():
797 if not IS_ATTY:
798 safe_print('DISABLE')
799 exit(0)
800
801
802 def get_modules_to_run(cmd):
803 if not cmd.modules_to_run:
804 return AVAILABLE_MODULES
805
806 modules_to_run, seen = list(), set()
807 for m in AVAILABLE_MODULES:
808 if m.name not in cmd.modules_to_run or m.name in seen:
809 continue
810 seen.add(m.name)
811 modules_to_run.append(m)
812
813 return modules_to_run
814
815
816 def main():
817 cmd = parse_command_line()
818 log = PythonDLogger()
819
820 level = os.getenv('NETDATA_LOG_LEVEL') or str()
821 level = level.lower()
822 if level == 'debug':
823 log.logger.severity = 'DEBUG'
824 elif level == 'info':
825 log.logger.severity = 'INFO'
826 elif level == 'warn' or level == 'warning' or level == 'notice':
827 log.logger.severity = 'WARNING'
828 elif level == 'err' or level == 'error':
829 log.logger.severity = 'ERROR'
830 elif level == 'emergency' or level == 'alert' or level == 'critical':
831 log.logger.severity = 'DISABLE'
832
833 if cmd.debug:
834 log.logger.severity = 'DEBUG'
835 if cmd.trace:
836 log.log_traceback = True
837
838 log.info('using python v{0}'.format(PY_VERSION[0]))
839
840 unique_avail_module_names = set([m.name for m in AVAILABLE_MODULES])
841 unknown = set(cmd.modules_to_run) - unique_avail_module_names
842 if unknown:
843 log.error('unknown modules : {0}'.format(sorted(list(unknown))))
844 guessed = guess_module(unique_avail_module_names, *cmd.modules_to_run)
845 if guessed:
846 log.info('probably you meant : \n{0}'.format(pprint.pformat(guessed, width=1)))
847 return
848
849 p = Plugin(
850 get_modules_to_run(cmd),
851 cmd.update_every,
852 )
853
854 # cheap attempt to reduce chance of python.d job running before go.d
855 # TODO: better implementation needed
856 if not IS_ATTY:
857 time.sleep(1.5)
858
859 try:
860 if not p.setup():
861 return
862 p.run()
863 except KeyboardInterrupt:
864 pass
865 log.info('exiting from main...')
866
867
868 if __name__ == "__main__":
869 main()
870 disable()