163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
import logging
|
|
import time
|
|
|
|
import gunicorn.app.base
|
|
from prometheus_client import CONTENT_TYPE_LATEST, Summary, Counter, generate_latest
|
|
from werkzeug.routing import Map, Rule
|
|
from werkzeug.wrappers import Request, Response
|
|
from werkzeug.exceptions import InternalServerError
|
|
from .collector import collect_jellyseer
|
|
|
|
|
|
class JellyseerExporterApplication:
|
|
"""
|
|
Jellyseer prometheus collector HTTP handler.
|
|
"""
|
|
|
|
# pylint: disable=no-self-use
|
|
|
|
def __init__(self, config, duration, errors, collectors):
|
|
self._config = config
|
|
self._duration = duration
|
|
self._errors = errors
|
|
self._collectors = collectors
|
|
|
|
self._log = logging.getLogger(__name__)
|
|
|
|
def on_jellyseer(self, module='default', target='localhost'):
|
|
"""
|
|
Request handler for /jellyseer route
|
|
"""
|
|
|
|
if module in self._config:
|
|
start = time.time()
|
|
output = collect_jellyseer(
|
|
self._config[module],
|
|
target,
|
|
self._collectors
|
|
)
|
|
response = Response(output)
|
|
response.headers['content-type'] = CONTENT_TYPE_LATEST
|
|
self._duration.labels(module).observe(time.time() - start)
|
|
else:
|
|
response = Response("Module '{module}' not found in config")
|
|
response.status_code = 400
|
|
|
|
return response
|
|
|
|
def on_metrics(self):
|
|
"""
|
|
Request handler for /metrics route
|
|
"""
|
|
|
|
response = Response(generate_latest())
|
|
response.headers['content-type'] = CONTENT_TYPE_LATEST
|
|
|
|
return response
|
|
|
|
def on_index(self):
|
|
"""
|
|
Request handler for index route (/).
|
|
"""
|
|
|
|
response = Response(
|
|
"""<html>
|
|
<head><title>Jellyseer Exporter</title></head>
|
|
<body>
|
|
<h1>Jellyseer Exporter</h1>
|
|
<p>Visit <code>/jellyseer?target=1.2.3.4</code> to use.</p>
|
|
</body>
|
|
</html>"""
|
|
)
|
|
response.headers['content-type'] = 'text/html'
|
|
|
|
return response
|
|
|
|
def view(self, endpoint, values, args):
|
|
"""
|
|
Werkzeug views mapping method.
|
|
"""
|
|
|
|
allowed_args = {
|
|
'jellyseer': ['module', 'target']
|
|
}
|
|
|
|
view_registry = {
|
|
'index': self.on_index,
|
|
'metrics': self.on_metrics,
|
|
'jellyseer': self.on_jellyseer,
|
|
}
|
|
|
|
params = dict(values)
|
|
if endpoint in allowed_args:
|
|
params.update({key: args[key] for key in allowed_args[endpoint] if key in args})
|
|
|
|
try:
|
|
return view_registry[endpoint](**params)
|
|
except Exception as error: # pylint: disable=broad-except
|
|
self._log.exception("Exception thrown while rendering view")
|
|
self._errors.labels(args.get('module', 'default')).inc()
|
|
raise InternalServerError from error
|
|
|
|
@Request.application
|
|
def __call__(self, request):
|
|
url_map = Map([
|
|
Rule('/', endpoint='index'),
|
|
Rule('/metrics', endpoint='metrics'),
|
|
Rule('/jellyseer', endpoint='jellyseer'),
|
|
])
|
|
|
|
urls = url_map.bind_to_environ(request.environ)
|
|
view_func = lambda endpoint, values: self.view(endpoint, values, request.args)
|
|
return urls.dispatch(view_func, catch_http_exceptions=True)
|
|
|
|
|
|
class StandaloneGunicornApplication(gunicorn.app.base.BaseApplication):
|
|
"""
|
|
Copy-paste from https://docs.gunicorn.org/en/stable/custom.html
|
|
"""
|
|
|
|
# 'init' and 'load' methods are implemented by WSGIApplication.
|
|
# pylint: disable=abstract-method
|
|
|
|
def __init__(self, app, options=None):
|
|
self.options = options or {}
|
|
self.application = app
|
|
super().__init__()
|
|
|
|
def load_config(self):
|
|
config = {key: value for key, value in self.options.items()
|
|
if key in self.cfg.settings and value is not None}
|
|
for key, value in config.items():
|
|
self.cfg.set(key.lower(), value)
|
|
|
|
def load(self):
|
|
return self.application
|
|
|
|
|
|
def start_http_server(config, gunicorn_options, collectors):
|
|
"""
|
|
Start a HTTP API server for Jellyseer prometheus collector.
|
|
"""
|
|
|
|
duration = Summary(
|
|
'jellyseer_collection_duration_seconds',
|
|
'Duration of collections by the Jellyseer exporter',
|
|
['module'],
|
|
)
|
|
errors = Counter(
|
|
'jellyseer_request_errors_total',
|
|
'Errors in requests to Jellyseer exporter',
|
|
['module'],
|
|
)
|
|
|
|
# Initialize metrics.
|
|
for module in config.keys():
|
|
# pylint: disable=no-member
|
|
errors.labels(module)
|
|
# pylint: disable=no-member
|
|
duration.labels(module)
|
|
|
|
app = JellyseerExporterApplication(config, duration, errors, collectors)
|
|
StandaloneGunicornApplication(app, gunicorn_options).run()
|