1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
"""Plumd main entry point.""" |
3
|
|
|
import os |
4
|
|
|
import sys |
5
|
|
|
import time |
6
|
|
|
import signal |
7
|
|
|
import random |
8
|
|
|
import logging |
9
|
|
|
import argparse |
10
|
|
|
|
11
|
|
|
import plumd |
12
|
|
|
import plumd.util |
13
|
|
|
import plumd.config |
14
|
|
|
|
15
|
|
|
PY3 = sys.version_info > (3,) |
16
|
|
|
|
17
|
|
|
__author__ = 'Kenny Freeman' |
18
|
|
|
__email__ = '[email protected]' |
19
|
|
|
__license__ = "ISCL" |
20
|
|
|
__docformat__ = 'reStructuredText' |
21
|
|
|
|
22
|
|
|
|
23
|
|
|
def get_config(): |
24
|
|
|
"""Return a conf object - either from the default file or cmd line arg. |
25
|
|
|
|
26
|
|
|
:rtype: plumd.config.conf |
27
|
|
|
""" |
28
|
|
|
descr = """plumd: measures system and service metrics.""" |
29
|
|
|
# get cli options - currently just the path to this instances config file |
30
|
|
|
argp = argparse.ArgumentParser(description=descr) |
31
|
|
|
argp.add_argument('--config', metavar='config', type=str, |
32
|
|
|
nargs='?', default=plumd.DEFAULT_CONFIG_FILE, |
33
|
|
|
help='main configuration file in yaml format') |
34
|
|
|
args = argp.parse_args() |
35
|
|
|
|
36
|
|
|
# get a simple configuration helper object |
37
|
|
|
config = None |
38
|
|
|
try: |
39
|
|
|
config = plumd.config.Conf(args.config).defaults(plumd.DEFAULT_CONFIG) |
40
|
|
|
except plumd.ConfigError as exc: |
41
|
|
|
sys.stderr.write("configuration failed to load: {0}\n".format(exc)) |
42
|
|
|
sys.exit(1) |
43
|
|
|
|
44
|
|
|
# ensure log level is known |
45
|
|
|
llevel = config.get('log.level') |
46
|
|
|
if llevel not in plumd.LOG_LEVELS: |
47
|
|
|
llevels = " ".join(list(plumd.LOG_LEVELS.keys())) |
48
|
|
|
sys.stderr.write("unkonwn log level: {0}\n".format(llevel)) |
49
|
|
|
sys.stderr.write("valid log levels are: {0}\n".format(llevels)) |
50
|
|
|
sys.exit(1) |
51
|
|
|
|
52
|
|
|
# all done, return our config |
53
|
|
|
return config |
54
|
|
|
|
55
|
|
|
|
56
|
|
|
def initialize(config): |
57
|
|
|
"""Do various initilization things. |
58
|
|
|
|
59
|
|
|
:param config: a :class:`plumd.config.conf` instance |
60
|
|
|
:type config: plumd.config.conf |
61
|
|
|
""" |
62
|
|
|
# set the configured timezone, from: |
63
|
|
|
# http://stackoverflow.com/questions/6321160/python-logging-how-to-set-time-to-gmt |
64
|
|
|
timezzone = config.get('tz') |
65
|
|
|
if timezzone: |
66
|
|
|
os.environ['TZ'] = timezzone |
67
|
|
|
time.tzset() |
68
|
|
|
|
69
|
|
|
# add hostname metadata |
70
|
|
|
if config.get('meta.hostname'): |
71
|
|
|
meta = config.get('meta') |
72
|
|
|
# meta is a list of single item {'key': 'value'} dicts |
73
|
|
|
for entry in meta: |
74
|
|
|
for key, val in entry.items(): |
75
|
|
|
if key == 'hostname': |
76
|
|
|
err = "meta.hostname: hostname in meta already: {0} : {1}\n" |
77
|
|
|
sys.stderr.write(err.format(val, config.path)) |
78
|
|
|
sys.exit(1) |
79
|
|
|
meta.append({'hostname': plumd.util.get_hostname()}) |
80
|
|
|
config.set_conf('meta', meta) |
81
|
|
|
|
82
|
|
|
return config |
83
|
|
|
|
84
|
|
|
|
85
|
|
|
def get_log(config): |
86
|
|
|
"""Return a configured logger. |
87
|
|
|
|
88
|
|
|
:param config: a :class:`plumd.config.conf` instance |
89
|
|
|
:type config: plumd.config.conf |
90
|
|
|
:param src: all log entries generated are tagged with src=<src> |
91
|
|
|
:type src: str |
92
|
|
|
|
93
|
|
|
:rtype: structlog.logger |
94
|
|
|
""" |
95
|
|
|
# create logger |
96
|
|
|
handler = logging.StreamHandler(sys.stdout) |
97
|
|
|
formatter = logging.Formatter(config.get('log.format')) |
98
|
|
|
handler.setFormatter(formatter) |
99
|
|
|
stdlog = logging.getLogger() |
100
|
|
|
stdlog.addHandler(handler) |
101
|
|
|
stdlog.setLevel(plumd.LOG_LEVELS[config.get('log.level')]) |
102
|
|
|
return stdlog |
103
|
|
|
|
104
|
|
|
|
105
|
|
|
def get_plugins(config, log): |
106
|
|
|
"""Return a plugin loader object with plugins already started. |
107
|
|
|
|
108
|
|
|
:param config: a :class:`plumd.config.conf` instance |
109
|
|
|
:type config: plumd.config.conf |
110
|
|
|
:param log: a structlog logging instance |
111
|
|
|
:type log: structlog |
112
|
|
|
|
113
|
|
|
:rtype: plumd.load.PluginLoader |
114
|
|
|
""" |
115
|
|
|
# load reader and writer plugins and start |
116
|
|
|
log.info("loading plugins") |
117
|
|
|
psys = None |
118
|
|
|
# several things can go wrong loading plugins |
119
|
|
|
try: |
120
|
|
|
psys = plumd.PluginLoader(log, config) |
121
|
|
|
except plumd.PluginLoadError as exc: |
122
|
|
|
log.critical("plugin load failed: {0}".format(exc)) |
123
|
|
|
sys.exit(1) |
124
|
|
|
except plumd.ConfigError as exc: |
125
|
|
|
log.critical("invalid plugin config: {0}".format(exc)) |
126
|
|
|
sys.exit(1) |
127
|
|
|
except plumd.DuplicatePlugin as exc: |
128
|
|
|
log.critical("duplicate plugin: {0}".format(exc)) |
129
|
|
|
sys.exit(1) |
130
|
|
|
else: |
131
|
|
|
log.info("plugins loaded") |
132
|
|
|
time.sleep(random.randrange(int(config.get('delay.startup')))) |
133
|
|
|
psys.start() |
134
|
|
|
log.debug("main: plugins: {0}".format(psys)) |
135
|
|
|
|
136
|
|
|
# all done, return the PluginLoader object |
137
|
|
|
return psys |
138
|
|
|
|
139
|
|
|
|
140
|
|
|
def main(): |
141
|
|
|
"""Main entry point for plumd.""" |
142
|
|
|
# setup signal handling |
143
|
|
|
sig_wait = [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT] |
144
|
|
|
sig_ignore = [signal.SIGUSR1, signal.SIGUSR2, signal.SIGHUP] |
145
|
|
|
swait = plumd.util.SignalWaiter(sig_wait=sig_wait, sig_ignore=sig_ignore) |
146
|
|
|
|
147
|
|
|
# get a configuration helper object |
148
|
|
|
config = get_config() |
149
|
|
|
|
150
|
|
|
# initialize it |
151
|
|
|
config = initialize(config) |
152
|
|
|
|
153
|
|
|
# create a logger |
154
|
|
|
log = get_log(config=config) |
155
|
|
|
vinfo = sys.version_info |
156
|
|
|
ver_str = "{0}.{1}.{2}".format(vinfo[0], vinfo[1], vinfo[2]) |
157
|
|
|
msg = "starting - running on python {0}".format(ver_str) |
158
|
|
|
log.info(msg) |
159
|
|
|
|
160
|
|
|
# get the plugin loader with all plugins started |
161
|
|
|
psys = get_plugins(log=log, config=config) |
162
|
|
|
|
163
|
|
|
# check to ensure we've loaded a sane configuration |
164
|
|
|
# is at least one reader/writer plugin loaded? |
165
|
|
|
if psys.nplugins < 1: |
166
|
|
|
log.error("exiting: no plugins loaded") |
167
|
|
|
sys.exit(1) |
168
|
|
|
# writer plugin? |
169
|
|
|
elif psys.nwriters < 1: |
170
|
|
|
log.error("exiting: no writers loaded") |
171
|
|
|
sys.exit(1) |
172
|
|
|
# reader plugin? |
173
|
|
|
elif psys.nreaders < 1: |
174
|
|
|
log.error("exiting: no readers loaded") |
175
|
|
|
sys.exit(1) |
176
|
|
|
|
177
|
|
|
# all set, now wait for the exit signal |
178
|
|
|
log.info("running") |
179
|
|
|
swait.wait() |
180
|
|
|
|
181
|
|
|
# stop readers/writers/bridge |
182
|
|
|
log.info("stopping") |
183
|
|
|
psys.stop() |
184
|
|
|
|
185
|
|
|
# all done, exit |
186
|
|
|
log.info("exiting") |
187
|
|
|
sys.exit(0) |
188
|
|
|
|
189
|
|
|
|
190
|
|
|
if __name__ == "__main__": |
191
|
|
|
main() |
192
|
|
|
|