Scrape OpenVPN metrics from OpenVPN management interface
This commit is contained in:
		
							
								
								
									
										68
									
								
								exporter.py
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								exporter.py
									
									
									
									
									
								
							| @@ -1,6 +1,8 @@ | |||||||
| #!/usr/bin/env python | #!/usr/bin/env python | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
| import os | import os | ||||||
|  | from collections import Counter | ||||||
| from motor.motor_asyncio import AsyncIOMotorClient | from motor.motor_asyncio import AsyncIOMotorClient | ||||||
| from sanic import Sanic, response | from sanic import Sanic, response | ||||||
|  |  | ||||||
| @@ -8,19 +10,71 @@ app = Sanic("exporter") | |||||||
|  |  | ||||||
| MONGO_URI = os.getenv("MONGO_URI", "mongodb://127.0.0.1:27017/default") | MONGO_URI = os.getenv("MONGO_URI", "mongodb://127.0.0.1:27017/default") | ||||||
|  |  | ||||||
| @app.listener('before_server_start') |  | ||||||
|  | @app.listener("before_server_start") | ||||||
| async def setup_db(app, loop): | async def setup_db(app, loop): | ||||||
|     app.ctx.db = AsyncIOMotorClient(MONGO_URI).get_default_database() |     app.ctx.db = AsyncIOMotorClient(MONGO_URI).get_default_database() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def wrap(i, prefix="pinecrypt_gateway_"): | ||||||
|  |     metrics_seen = set() | ||||||
|  |     async for name, tp, value, labels in i: | ||||||
|  |         if name not in metrics_seen: | ||||||
|  |             yield "# TYPE %s %s" % (name, tp) | ||||||
|  |             metrics_seen.add(name) | ||||||
|  |         yield "%s%s %d" % ( | ||||||
|  |             prefix + name, | ||||||
|  |             ("{%s}" % ",".join(["%s=\"%s\"" % j for j in labels.items()]) if labels else ""), | ||||||
|  |             value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def exporter_stats(): | ||||||
|  |     # Export number of open file handles | ||||||
|  |     yield "exporter_file_descriptors", "gauge", len(os.listdir("/proc/self/fd")), {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def openvpn_stats(port, service): | ||||||
|  |     last_seen = Counter() | ||||||
|  |  | ||||||
|  |     # Export OpenVPN status | ||||||
|  |     reader, writer = await asyncio.open_connection("127.0.0.1", port) | ||||||
|  |     writer.write(b"status 2\n") | ||||||
|  |     await writer.drain() | ||||||
|  |     while True: | ||||||
|  |         line = await reader.readline() | ||||||
|  |         values = line.decode("ascii").strip().split(",") | ||||||
|  |         header = values[0] | ||||||
|  |  | ||||||
|  |         if header == "END": | ||||||
|  |             break | ||||||
|  |         elif header == "CLIENT_LIST": | ||||||
|  |             _, cn, remote, _, _, rxbytes, txbytes, _, since, _, cid, pid, cipher = values | ||||||
|  |             labels = {"cn": cn, "service": service} | ||||||
|  |             yield "client_connected_since", "gauge", int(since), labels | ||||||
|  |  | ||||||
|  |             labels["cipher"] = cipher | ||||||
|  |             yield "client_rx_bytes", "counter", int(rxbytes), labels | ||||||
|  |             yield "client_tx_bytes", "counter", int(txbytes), labels | ||||||
|  |         elif header == "ROUTING_TABLE": | ||||||
|  |             _, addr, cn, remote, _, last_ref = values | ||||||
|  |             last_seen[cn] = max(last_seen[cn], int(last_ref)) | ||||||
|  |  | ||||||
|  |     writer.close() | ||||||
|  |     await writer.wait_closed() | ||||||
|  |  | ||||||
|  |     for key, value in last_seen.items(): | ||||||
|  |         yield "client_last_seen", "gauge", value, {"cn": key, "service": service} | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/metrics") | @app.route("/metrics") | ||||||
| async def view_export(request): | async def view_export(request): | ||||||
|     coll = app.ctx.db["certidude_certificates"] |     coll = app.ctx.db["certidude_certificates"] | ||||||
|  |  | ||||||
|     async def streaming_fn(response): |     async def streaming_fn(response): | ||||||
|         await response.write("# HELP pinecrypt_remote_last_seen Remote client last seen\n") |         for i in exporter_stats(), openvpn_stats(7505, "openvpn-udp"), openvpn_stats(7506, "openvpn-tcp"): | ||||||
|         await response.write("# TYPE pinecrypt_remote_last_seen gauge\n") |             async for line in wrap(i): | ||||||
|         async for doc in coll.find({"status":"signed", "last_seen":{"$exists":True}}, {"common_name":1, "last_seen":1}): |                 await response.write(line + "\n") | ||||||
|             await response.write("pinecrypt_remote_last_seen{cn=\"%s\"} %d\n" % ( |  | ||||||
|                 doc["common_name"], doc["last_seen"].timestamp())) |     return response.stream(streaming_fn, content_type="text/plain") | ||||||
|     return response.stream(streaming_fn, content_type='text/plain') |  | ||||||
|  |  | ||||||
| app.run(port=3001) | app.run(port=3001) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user