diff options
Diffstat (limited to 'overlays')
| -rw-r--r-- | overlays/cake-prometheus-exporter/cake-prometheus-exporter.py | 124 | ||||
| -rw-r--r-- | overlays/cake-prometheus-exporter/default.nix | 29 |
2 files changed, 153 insertions, 0 deletions
diff --git a/overlays/cake-prometheus-exporter/cake-prometheus-exporter.py b/overlays/cake-prometheus-exporter/cake-prometheus-exporter.py new file mode 100644 index 00000000..07197149 --- /dev/null +++ b/overlays/cake-prometheus-exporter/cake-prometheus-exporter.py | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | #!@python@/bin/python | ||
| 2 | |||
| 3 | from os import environ | ||
| 4 | import sys | ||
| 5 | |||
| 6 | from http.server import BaseHTTPRequestHandler, HTTPServer | ||
| 7 | |||
| 8 | import subprocess | ||
| 9 | import json | ||
| 10 | |||
| 11 | from urllib.parse import urlparse | ||
| 12 | |||
| 13 | |||
| 14 | def _format_prom_attrs(**attrs): | ||
| 15 | if not attrs: | ||
| 16 | return '' | ||
| 17 | |||
| 18 | return '{' + ','.join(map(lambda k: f'{k}="{attrs[k]}"', attrs)) + '}' | ||
| 19 | |||
| 20 | def _format_prom_metrics(metricName, metricType, metrics, metricHelp=''): | ||
| 21 | metricStr = dedent(f''' | ||
| 22 | # HELP {metricName} {metricHelp} | ||
| 23 | # TYPE {metricName} {metricType} | ||
| 24 | ''').lstrip() | ||
| 25 | for (attrs, val) in metrics: | ||
| 26 | attrs_str = _format_prom_attrs(**attrs) | ||
| 27 | metricStr += dedent(f''' | ||
| 28 | {metricName}{attrs_str} {val} | ||
| 29 | ''').lstrip() | ||
| 30 | return metricStr | ||
| 31 | |||
| 32 | |||
| 33 | class CAKEMetrics: | ||
| 34 | _instance = None | ||
| 35 | |||
| 36 | @classmethod | ||
| 37 | def instance(cls): | ||
| 38 | if cls._instance is None: | ||
| 39 | cls._instance = cls.__new__(cls) | ||
| 40 | cls._instance.attrs = None | ||
| 41 | return cls._instance | ||
| 42 | |||
| 43 | def __init__(self): | ||
| 44 | raise RuntimeError('Call instance() instead') | ||
| 45 | |||
| 46 | def update(self): | ||
| 47 | attrs = dict() | ||
| 48 | |||
| 49 | tc_output = None | ||
| 50 | with subprocess.Popen(['tc', '-s', '-j', 'qdisc', 'show'], stdout=subprocess.PIPE) as proc: | ||
| 51 | tc_output = json.load(proc.stdout) | ||
| 52 | |||
| 53 | for qdisc in tc_output: | ||
| 54 | if 'kind' not in qdisc or qdisc['kind'] != 'cake': | ||
| 55 | continue | ||
| 56 | |||
| 57 | tin_names = [] | ||
| 58 | if len(qdisc['tins']) == 4: | ||
| 59 | tin_names = ['bulk', 'best-effort', 'video', 'voice'] | ||
| 60 | tins = {} | ||
| 61 | for tin_name, tin_data in zip(tin_names, qdics['tins']): | ||
| 62 | tins[tin_name] = { | ||
| 63 | 'bytes': tin_data['sent_bytes'], | ||
| 64 | 'packets': tin_data['sent_packets'] | ||
| 65 | } | ||
| 66 | |||
| 67 | attrs[qdisc['dev']] = { | ||
| 68 | 'bytes': qdisc['bytes'], | ||
| 69 | 'packets': qdisc['packets'], | ||
| 70 | 'tins': tins | ||
| 71 | } | ||
| 72 | |||
| 73 | self.attrs = attrs | ||
| 74 | |||
| 75 | def json_text(self): | ||
| 76 | return json.dumps(self.attrs) | ||
| 77 | |||
| 78 | def prometheus(self): | ||
| 79 | metrics = '' | ||
| 80 | |||
| 81 | metrics += _format_prom_metrics('cake_bytes', 'counter', [({'dev': dev}, self.attrs[dev]['bytes']) for dev in self.attrs]) | ||
| 82 | metrics += _format_prom_metrics('cake_packets', 'counter', [({'dev': dev}, self.attrs[dev]['packets']) for dev in self.attrs]) | ||
| 83 | |||
| 84 | metrics += _format_prom_metrics('cake_tin_bytes', 'counter', [({'dev': dev, 'tin': tin}, self.attrs[dev]['tins'][tin]['bytes']) for tin in self.attrs[dev]['tins'] for dev in self.attrs]) | ||
| 85 | metrics += _format_prom_metrics('cake_tin_packets', 'counter', [({'dev': dev, 'tin': tin}, self.attrs[dev]['tins'][tin]['packets']) for tin in self.attrs[dev]['tins'] for dev in self.attrs]) | ||
| 86 | |||
| 87 | return metrics.encode('utf-8') | ||
| 88 | |||
| 89 | |||
| 90 | class CAKEMetricsServer(BaseHTTPRequestHandler): | ||
| 91 | def log_message(self, format, *args): | ||
| 92 | pass | ||
| 93 | |||
| 94 | def do_GET(self): | ||
| 95 | cake_metrics = CAKEMetrics.instance() | ||
| 96 | cake_metrics.update() | ||
| 97 | |||
| 98 | url = urlparse(self.path) | ||
| 99 | |||
| 100 | match url.path: | ||
| 101 | case '/metrics.json': | ||
| 102 | self.send_response(200) | ||
| 103 | self.send_header("Content-type", "application/json") | ||
| 104 | self.end_headers() | ||
| 105 | |||
| 106 | self.wfile.write(cake_metrics.json_text().encode('utf-8')) | ||
| 107 | case '/metrics': | ||
| 108 | self.send_response(200) | ||
| 109 | self.send_header("Content-type", "text/plain") | ||
| 110 | self.end_headers() | ||
| 111 | |||
| 112 | self.wfile.write(cake_metrics.prometheus()) | ||
| 113 | case _: | ||
| 114 | self.send_response(404) | ||
| 115 | self.end_headers() | ||
| 116 | |||
| 117 | |||
| 118 | def main(): | ||
| 119 | webServer = HTTPServer((str(environ.get('CAKE_HOSTNAME')), int(environ.get('CAKE_PORT'))), CAKEMetricsServer) | ||
| 120 | |||
| 121 | webServer.serve_forever() | ||
| 122 | |||
| 123 | if __name__ == "__main__": | ||
| 124 | sys.exit(main()) | ||
diff --git a/overlays/cake-prometheus-exporter/default.nix b/overlays/cake-prometheus-exporter/default.nix new file mode 100644 index 00000000..3d0acc2d --- /dev/null +++ b/overlays/cake-prometheus-exporter/default.nix | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | { final, prev, ... }: | ||
| 2 | let | ||
| 3 | inpPython = final.python310.override {}; | ||
| 4 | in { | ||
| 5 | cake-prometheus-exporter = prev.stdenv.mkDerivation rec { | ||
| 6 | pname = "cake-prometheus-exporter"; | ||
| 7 | version = "0.0.0"; | ||
| 8 | |||
| 9 | src = ./cake-prometheus-exporter.py; | ||
| 10 | |||
| 11 | phases = [ "buildPhase" "checkPhase" "installPhase" ]; | ||
| 12 | |||
| 13 | python = inpPython.withPackages (ps: with ps; []); | ||
| 14 | |||
| 15 | buildPhase = '' | ||
| 16 | substituteAll $src cake-prometheus-exporter | ||
| 17 | ''; | ||
| 18 | |||
| 19 | doCheck = true; | ||
| 20 | checkPhase = '' | ||
| 21 | ${python}/bin/python -m py_compile cake-prometheus-exporter | ||
| 22 | ''; | ||
| 23 | |||
| 24 | installPhase = '' | ||
| 25 | install -m 0755 -D -t $out/bin \ | ||
| 26 | cake-prometheus-exporter | ||
| 27 | ''; | ||
| 28 | }; | ||
| 29 | } | ||
