summaryrefslogtreecommitdiff
path: root/overlays
diff options
context:
space:
mode:
Diffstat (limited to 'overlays')
-rw-r--r--overlays/nftables-prometheus-exporter/default.nix27
-rw-r--r--overlays/nftables-prometheus-exporter/nftables-prometheus-exporter.py151
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 @@
1final: prev:
2let
3 inpPython = final.python310;
4in {
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
3import json
4
5from os import environ
6import sys
7
8from http.server import BaseHTTPRequestHandler, HTTPServer
9
10from urllib.parse import urlparse
11
12from textwrap import dedent
13
14import subprocess
15
16
17def _format_prom_attrs(**attrs):
18 if not attrs:
19 return ''
20
21 return '{' + ','.join(map(lambda k: f'{k}="{attrs[k]}"', attrs)) + '}'
22
23def _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
36class 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
120class 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
145def main():
146 webServer = HTTPServer((str(environ.get('NFT_HOSTNAME')), int(environ.get('NFT_PORT'))), NFTMetricsServer)
147
148 webServer.serve_forever()
149
150if __name__ == "__main__":
151 sys.exit(main())