diff options
| author | Gregor Kleen <gkleen@yggdrasil.li> | 2022-01-01 16:51:10 +0100 |
|---|---|---|
| committer | Gregor Kleen <gkleen@yggdrasil.li> | 2022-01-01 16:51:10 +0100 |
| commit | a806adad2017413071d20d519d9a5d9b6b937474 (patch) | |
| tree | d6a23660977c0e78e770783058965d92de243dbd /overlays/nftables-prometheus-exporter | |
| parent | c389674935494e1246d156515e25ead60551e705 (diff) | |
| download | nixos-a806adad2017413071d20d519d9a5d9b6b937474.tar nixos-a806adad2017413071d20d519d9a5d9b6b937474.tar.gz nixos-a806adad2017413071d20d519d9a5d9b6b937474.tar.bz2 nixos-a806adad2017413071d20d519d9a5d9b6b937474.tar.xz nixos-a806adad2017413071d20d519d9a5d9b6b937474.zip | |
vidhar: prometheus: nftables
Diffstat (limited to 'overlays/nftables-prometheus-exporter')
| -rw-r--r-- | overlays/nftables-prometheus-exporter/default.nix | 27 | ||||
| -rw-r--r-- | overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py | 151 |
2 files changed, 178 insertions, 0 deletions
diff --git a/overlays/nftables-prometheus-exporter/default.nix b/overlays/nftables-prometheus-exporter/default.nix new file mode 100644 index 00000000..452f160f --- /dev/null +++ b/overlays/nftables-prometheus-exporter/default.nix | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | final: prev: | ||
| 2 | let | ||
| 3 | inpPython = final.python310; | ||
| 4 | in { | ||
| 5 | nftables-prometheus-exporter = prev.stdenv.mkDerivation rec { | ||
| 6 | name = "nftables-prometheus-exporter"; | ||
| 7 | src = ./nftables-prometheus-exporter.py; | ||
| 8 | |||
| 9 | phases = [ "buildPhase" "checkPhase" "installPhase" ]; | ||
| 10 | |||
| 11 | python = inpPython.withPackages (ps: with ps; []); | ||
| 12 | |||
| 13 | buildPhase = '' | ||
| 14 | substituteAll $src nftables-prometheus-exporter | ||
| 15 | ''; | ||
| 16 | |||
| 17 | doCheck = true; | ||
| 18 | checkPhase = '' | ||
| 19 | ${python}/bin/python -m py_compile nftables-prometheus-exporter | ||
| 20 | ''; | ||
| 21 | |||
| 22 | installPhase = '' | ||
| 23 | install -m 0755 -D -t $out/bin \ | ||
| 24 | nftables-prometheus-exporter | ||
| 25 | ''; | ||
| 26 | }; | ||
| 27 | } | ||
diff --git a/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py b/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py new file mode 100644 index 00000000..c5c5139d --- /dev/null +++ b/overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | #!@python@/bin/python | ||
| 2 | |||
| 3 | import json | ||
| 4 | |||
| 5 | from os import environ | ||
| 6 | import sys | ||
| 7 | |||
| 8 | from http.server import BaseHTTPRequestHandler, HTTPServer | ||
| 9 | |||
| 10 | from urllib.parse import urlparse | ||
| 11 | |||
| 12 | from textwrap import dedent | ||
| 13 | |||
| 14 | import subprocess | ||
| 15 | |||
| 16 | |||
| 17 | def _format_prom_attrs(**attrs): | ||
| 18 | if not attrs: | ||
| 19 | return '' | ||
| 20 | |||
| 21 | return '{' + ','.join(map(lambda k: f'{k}="{attrs[k]}"', attrs)) + '}' | ||
| 22 | |||
| 23 | def _format_prom_metrics(metricName, metricType, metrics, metricHelp=''): | ||
| 24 | metricStr = dedent(f''' | ||
| 25 | # HELP {metricName} {metricHelp} | ||
| 26 | # TYPE {metricName} {metricType} | ||
| 27 | ''').lstrip() | ||
| 28 | for (attrs, val) in metrics: | ||
| 29 | attrs_str = _format_prom_attrs(**attrs) | ||
| 30 | metricStr += dedent(f''' | ||
| 31 | {metricName}{attrs_str} {val} | ||
| 32 | ''').lstrip() | ||
| 33 | return metricStr | ||
| 34 | |||
| 35 | |||
| 36 | class NFTMetrics: | ||
| 37 | _instance = None | ||
| 38 | |||
| 39 | @classmethod | ||
| 40 | def instance(cls): | ||
| 41 | if cls._instance is None: | ||
| 42 | cls._instance = cls.__new__(cls) | ||
| 43 | cls._instance.attrs = None | ||
| 44 | return cls._instance | ||
| 45 | |||
| 46 | |||
| 47 | def __init__(self): | ||
| 48 | raise RuntimeError('Call instance() instead') | ||
| 49 | |||
| 50 | def update(self): | ||
| 51 | attrs = dict() | ||
| 52 | queries = dict() | ||
| 53 | |||
| 54 | for query_name in ['ruleset', 'counters', 'maps', 'meters', 'sets']: | ||
| 55 | process = subprocess.run( | ||
| 56 | ('nft', '--json', 'list', query_name), | ||
| 57 | capture_output = True, check = True, text = True | ||
| 58 | ) | ||
| 59 | data = json.loads(process.stdout) | ||
| 60 | version = data['nftables'][0]['metainfo']['json_schema_version'] | ||
| 61 | if version != 1: | ||
| 62 | raise RuntimeError(f'nftables json schema v{version} is not supported') | ||
| 63 | queries[query_name] = data['nftables'][1:] | ||
| 64 | |||
| 65 | |||
| 66 | def extract_query(query_name, type_name): | ||
| 67 | return [ | ||
| 68 | item[type_name] | ||
| 69 | for item in queries[query_name] | ||
| 70 | if type_name in item | ||
| 71 | ] | ||
| 72 | |||
| 73 | attrs['rules_count'] = len(extract_query('ruleset', 'rule')) | ||
| 74 | attrs['chain_count'] = len(extract_query('ruleset', 'chain')) | ||
| 75 | attrs['counters'] = extract_query('counters', 'counter') | ||
| 76 | attrs['maps'] = extract_query('maps', 'map') | ||
| 77 | attrs['meters'] = extract_query('meters', 'meter') | ||
| 78 | attrs['sets'] = extract_query('sets', 'set') | ||
| 79 | |||
| 80 | self.attrs = attrs | ||
| 81 | |||
| 82 | def json_text(self): | ||
| 83 | return json.dumps(self.attrs) | ||
| 84 | |||
| 85 | def prometheus(self): | ||
| 86 | metrics = '' | ||
| 87 | |||
| 88 | metrics += _format_prom_metrics('nftables_rules_count', 'gauge', [({}, self.attrs['rules_count'])], 'Number of nftables rules') | ||
| 89 | metrics += _format_prom_metrics('nftables_chains_count', 'gauge', [({}, self.attrs['chain_count'])], 'Number of nftables chains') | ||
| 90 | |||
| 91 | counter_bytes = [] | ||
| 92 | counter_packets = [] | ||
| 93 | for counter in self.attrs['counters']: | ||
| 94 | labels = { k: v for k, v in counter.items() if k not in set(['bytes', 'packets']) } | ||
| 95 | counter_bytes += [(labels, counter['bytes'])] | ||
| 96 | counter_packets += [(labels, counter['packets'])] | ||
| 97 | metrics += _format_prom_metrics('nftables_counter_bytes', 'counter', counter_bytes) | ||
| 98 | metrics += _format_prom_metrics('nftables_counter_packets_count', 'counter', counter_packets) | ||
| 99 | |||
| 100 | map_counts = [] | ||
| 101 | for meter in self.attrs['maps']: | ||
| 102 | labels = { k: v for k, v in counter.items() if k not in set(['elem']) } | ||
| 103 | map_counts += [(labels, len(meter['elem']))] | ||
| 104 | metrics += _format_prom_metrics('nftables_map_elem_count', 'gauge', map_counts) | ||
| 105 | |||
| 106 | meter_counts = [] | ||
| 107 | for meter in self.attrs['meters']: | ||
| 108 | labels = { k: v for k, v in counter.items() if k not in set(['elem']) } | ||
| 109 | meter_counts += [(labels, len(meter['elem']))] | ||
| 110 | metrics += _format_prom_metrics('nftables_meter_elem_count', 'gauge', meter_counts) | ||
| 111 | |||
| 112 | set_counts = [] | ||
| 113 | for meter in self.attrs['sets']: | ||
| 114 | labels = { k: v for k, v in counter.items() if k not in set(['elem']) } | ||
| 115 | set_counts += [(labels, len(meter['elem']))] | ||
| 116 | metrics += _format_prom_metrics('nftables_set_elem_count', 'gauge', set_counts) | ||
| 117 | |||
| 118 | return metrics.encode('utf-8') | ||
| 119 | |||
| 120 | class NFTMetricsServer(BaseHTTPRequestHandler): | ||
| 121 | def do_GET(self): | ||
| 122 | zte_metrics = NFTMetrics.instance() | ||
| 123 | zte_metrics.update() | ||
| 124 | |||
| 125 | url = urlparse(self.path) | ||
| 126 | |||
| 127 | match url.path: | ||
| 128 | case '/metrics.json': | ||
| 129 | self.send_response(200) | ||
| 130 | self.send_header("Content-type", "application/json") | ||
| 131 | self.end_headers() | ||
| 132 | |||
| 133 | self.wfile.write(zte_metrics.json_text().encode('utf-8')) | ||
| 134 | case '/metrics': | ||
| 135 | self.send_response(200) | ||
| 136 | self.send_header("Content-type", "text/plain") | ||
| 137 | self.end_headers() | ||
| 138 | |||
| 139 | self.wfile.write(zte_metrics.prometheus()) | ||
| 140 | case _: | ||
| 141 | self.send_response(404) | ||
| 142 | self.end_headers() | ||
| 143 | |||
| 144 | |||
| 145 | def main(): | ||
| 146 | webServer = HTTPServer((str(environ.get('NFT_HOSTNAME')), int(environ.get('NFT_PORT'))), NFTMetricsServer) | ||
| 147 | |||
| 148 | webServer.serve_forever() | ||
| 149 | |||
| 150 | if __name__ == "__main__": | ||
| 151 | sys.exit(main()) | ||
