Skip to content

Commit bd5d405

Browse files
First pass at using Forking HTTP server
1 parent 09baba0 commit bd5d405

File tree

2 files changed

+72
-26
lines changed

2 files changed

+72
-26
lines changed

p4exporter.py

+71-24
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import os
44
import yaml
55
from argparse import ArgumentParser
6-
from prometheus_client import make_wsgi_app
7-
from wsgiref.simple_server import make_server, WSGIServer
8-
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
6+
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily
7+
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST
98
import time
109
import logging
11-
import threading
1210
from P4 import P4
1311
from collections import defaultdict
14-
from socketserver import ThreadingMixIn
12+
import urllib.parse as urlparse
13+
from http.server import BaseHTTPRequestHandler, HTTPServer
14+
from socketserver import ForkingMixIn
1515

1616

1717
COMMAND_STATES = {
@@ -26,13 +26,14 @@
2626

2727
class P4Collector(object):
2828

29-
def __init__(self, config):
30-
self.config = config
31-
self.p4_pool = defaultdict(dict)
29+
def __init__(self):
30+
self.p4_pool = {}
31+
self.params = {}
32+
self.config = {}
3233

3334
def connection(self, p4port):
3435
tid = threading.current_thread().ident
35-
if p4port not in self.p4_pool or tid not in self.p4_pool[p4port]:
36+
if p4port not in self.p4_pool:
3637
p4 = P4(exception_level=1, prog='prometheus-p4-metrics')
3738
hostname, port = p4port.split(':')
3839
credentials = self.config.get('credentials', {}).get(p4port, None)
@@ -53,13 +54,13 @@ def connection(self, p4port):
5354
except Exception as e:
5455
logging.error('Failed to log in to %s: %s', p4port, e)
5556
credentials = None
56-
self.p4_pool[p4port][tid] = (p4, credentials is not None)
57+
self.p4_pool[p4port] = (p4, credentials is not None)
5758
except Exception as e:
5859
logging.error('Failed to connect to %s: %s', p4port, e)
5960
return None, False
6061
else:
6162
logging.debug('Using cached connection for %s/%s', p4port, tid)
62-
return self.p4_pool[p4port][tid]
63+
return self.p4_pool[p4port]
6364

6465
def name(self, name):
6566
return 'p4_' + name
@@ -157,7 +158,7 @@ def depot_guages(self, p4):
157158
created_guage.add_metric([depot_name, depot_type], int(depot['time']))
158159
return size_guage, count_guage, created_guage
159160

160-
def collect(self, params):
161+
def collect(self):
161162
if not params:
162163
return
163164
p4port = params['target'][0]
@@ -205,26 +206,72 @@ def collect(self, params):
205206
yield created_guage
206207

207208

208-
class ThreadedWSGIServer(ThreadingMixIn, WSGIServer):
209+
class ForkingHTTPServer(ForkingMixIn, HTTPServer):
209210
pass
210211

211212

213+
class P4ExporterHandler(BaseHTTPRequestHandler):
214+
def __init__(self, config_path, *args, **kwargs):
215+
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
216+
self._config_path = config_path
217+
self._collector = P4Collector()
218+
logging.info('New collector created')
219+
220+
def collect(self, config, params):
221+
with open(self._config_path) as f:
222+
config = yaml.safe_load(f)
223+
self._collector.config = config
224+
self._collector.params = params
225+
registry = CollectorRegistry()
226+
registry.register(self._collector)
227+
return generate_latest(registry)
228+
229+
def do_GET(self):
230+
logging.verbose('Got request...')
231+
url = urlparse.urlparse(self.path)
232+
if url.path == '/metrics':
233+
params = urlparse.parse_qs(url.query)
234+
if 'target' not in params:
235+
self.send_response(400)
236+
self.end_headers()
237+
self.wfile.write("Missing 'target' from parameters")
238+
return
239+
240+
try:
241+
output = self.collect()
242+
self.send_response(200)
243+
self.send_header('Content-Type', CONTENT_TYPE_LATEST)
244+
self.end_headers()
245+
self.wfile.write(output)
246+
except Exception as e:
247+
logging.error('Internal error: %s', e)
248+
self.send_response(500)
249+
self.end_headers()
250+
self.wfile.write(traceback.format_exc())
251+
252+
elif url.path == '/':
253+
self.send_response(200)
254+
self.end_headers()
255+
self.wfile.write("""<html>
256+
<head><title>SNMP Exporter</title></head>
257+
<body>
258+
<h1>SNMP Exporter</h1>
259+
<p>Visit <code>/metrics?address=1.2.3.4</code> to use.</p>
260+
</body>
261+
</html>""")
262+
else:
263+
self.send_response(404)
264+
self.end_headers()
265+
266+
212267
if __name__ == '__main__':
213268
parser = ArgumentParser()
214269
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=True, help='Enable verbose logging')
215270
parser.add_argument('-p', '--port', dest='port', type=int, default=9666, help='The port to expose metrics on, default: 9666')
216271
parser.add_argument('-c', '--config', dest='config', default='/etc/p4_exporter/conf.yml', help='Path to the configuration file')
217272
options = parser.parse_args()
218273
logging.basicConfig(level=logging.DEBUG if options.verbose else logging.INFO, format='[%(levelname)s] %(message)s')
219-
logging.info('Creating collector...')
220-
if os.path.isfile(options.config):
221-
config = yaml.load(open(options.config, 'r'))
222-
else:
223-
logging.warning("Config file %s does not exist, no credentials loaded.", options.config)
224-
config = {}
225-
REGISTRY.register(P4Collector(config))
226-
logging.info('Loaded %d credentials', len(config.get('credentials', [])))
274+
handler = lambda *args, **kwargs: P4ExporterHandler(options.config, *args, **kwargs)
227275
logging.info('Listening on port :%d...', options.port)
228-
app = make_wsgi_app()
229-
httpd = make_server('', options.port, app, server_class=ThreadedWSGIServer)
230-
httpd.serve_forever()
276+
server = ForkingHTTPServer(('', options.port), handler)
277+
server.serve_forever()

requirements.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
p4python
22
pyaml
3-
uwsgi
4-
-e git+https://github.com/seanhoughton/client_python.git@feature/params#egg=prometheus_client
3+
prometheus_client

0 commit comments

Comments
 (0)