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