| 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) |