Commit | Line | Data |
---|---|---|
2f7279c1 KS |
1 | #!/usr/bin/python3 |
2 | # | |
3 | # SPDX-FileCopyrightText: 2024 Kienan Stewart <kstewart@efficios.com> | |
4 | # SPDX-License-Identifier: GPL-2.0-or-later | |
5 | # | |
6 | # Prometheus exporter for rasdaemon | |
7 | # | |
8 | # Based on https://github.com/openstreetmap/prometheus-exporters/blob/main/exporters/rasdaemon/rasdaemon_exporter | |
9 | # | |
10 | ||
11 | import argparse | |
12 | import http.server | |
13 | import sqlite3 | |
14 | import urllib.parse | |
15 | ||
16 | METRICS = { | |
17 | 'rasdaemon_mc_events_total': { | |
18 | 'help': 'Memory controller errors', | |
19 | 'type': 'counter', | |
20 | 'query': 'SELECT mc, top_layer, middle_layer, lower_layer, err_type, SUM(err_count) as err_count FROM mc_event GROUP BY mc, top_layer, middle_layer, lower_layer, err_type', | |
21 | }, | |
22 | } | |
23 | ||
24 | class ExporterServer(http.server.ThreadingHTTPServer): | |
25 | ||
26 | def __init__(self, server_address, RequestHandlerClass, db_file='/var/lib/rasdaemon/ras-mc_event.db'): | |
27 | super().__init__(server_address, RequestHandlerClass) | |
28 | self.db_file = db_file | |
29 | ||
30 | ||
31 | class ExporterHandler(http.server.BaseHTTPRequestHandler): | |
32 | ||
33 | def do_GET(self): | |
34 | url = urllib.parse.urlparse(self.path) | |
35 | if url.path != '/metrics': | |
36 | self.send_response(404) | |
37 | self.end_headers() | |
38 | return | |
39 | ||
40 | metrics_details = {key: dict() for (key, _) in METRICS.items()} | |
41 | with sqlite3.connect('file:{}?mode=ro'.format(self.server.db_file), uri=True) as con: | |
42 | cursor = con.cursor() | |
43 | cursor.row_factory = sqlite3.Row | |
44 | for key, _ in METRICS.items(): | |
45 | result = cursor.execute(METRICS[key]['query']) | |
46 | metrics_details[key] = result.fetchall() | |
47 | ||
48 | print(metrics_details) | |
49 | self.send_response(200) | |
50 | self.end_headers() | |
51 | for key, value in METRICS.items(): | |
52 | self.wfile.write("# HELP {} {}\n".format(key, value['help']).encode()) | |
53 | self.wfile.write("# TYPE {} {}\n".format(key, value['type']).encode()) | |
54 | if not metrics_details[key]: | |
55 | self.wfile.write("{} 0\n".format(key).encode()) | |
56 | for entry in metrics_details[key]: | |
57 | labels = ','.join(['{}="{}"'.format(key, entry[key]) for key in entry.keys() if key != 'err_count']) | |
58 | if labels: | |
59 | self.wfile.write("{}{{{}}} {}\n".format(key, labels, entry['err_count']).encode()) | |
60 | else: | |
61 | self.wfile.write("{} {}\n".format(key, entry['err_count']).encode()) | |
62 | ||
63 | ||
64 | def _get_argument_parser(): | |
65 | parser = argparse.ArgumentParser( | |
66 | prog='rasdaemon-exporter', | |
67 | description='Exporters rasdaemon metrics' | |
68 | ) | |
69 | parser.add_argument( | |
70 | '-p', '--port', type=int, default=9797, | |
71 | help='The port to listen on' | |
72 | ) | |
73 | parser.add_argument( | |
74 | '-l', '--listen-address', type=str, default='', | |
75 | help='The address to listen on' | |
76 | ) | |
77 | parser.add_argument( | |
78 | '-f', '--rasdaemon-db-file', type=str, #type=argparse.FileType('r'), | |
79 | default='/var/lib/rasdaemon/ras-mc_event.db', | |
80 | help='The path to the rasdaemon sqlite3 database' | |
81 | ) | |
82 | return parser | |
83 | ||
84 | ||
85 | def serve(listen_address, listen_port, db_file): | |
86 | with ExporterServer((listen_address, listen_port), ExporterHandler, db_file) as httpd: | |
87 | httpd.serve_forever() | |
88 | print('done') | |
89 | ||
90 | ||
91 | if __name__ == '__main__': | |
92 | parser = _get_argument_parser() | |
93 | args = parser.parse_args() | |
94 | serve(args.listen_address, args.port, args.rasdaemon_db_file) |