2
0

prometheus stats cli tool

This commit is contained in:
Stefan Bühler 2022-04-11 18:17:00 +02:00
parent c753f490ac
commit f1b36bd171
5 changed files with 115 additions and 0 deletions

View File

@ -34,3 +34,4 @@ where = src
[options.entry_points]
console_scripts =
capport-control = capport.control.run:main
capport-stats = capport.stats:main

61
src/capport/stats.py Normal file
View File

@ -0,0 +1,61 @@
from __future__ import annotations
import ipaddress
import sys
import typing
import time
import trio
import capport.utils.ipneigh
import capport.utils.nft_set
from . import cptypes
def print_metric(name: str, mtype: str, value, *, now: typing.Optional[int]=None, help: typing.Optional[str]=None):
# no labels in our names for now, always print help and type
if help:
print(f'# HELP {name} {help}')
print(f'# TYPE {name} {mtype}')
if now:
print(f'{name} {value} {now}')
else:
print(f'{name} {value}')
async def amain(client_ifname: str):
ns = capport.utils.nft_set.NftSet()
captive_allowed_entries: typing.Set[cptypes.MacAddress] = {
entry['mac']
for entry in ns.list()
}
seen_allowed_entries: typing.Set[cptypes.MacAddress] = set()
total_ipv4 = 0
total_ipv6 = 0
unique_clients = set()
unique_ipv4 = set()
unique_ipv6 = set()
async with capport.utils.ipneigh.connect() as ipn:
ipn.ip.strict_check = True
async for (mac, addr) in ipn.dump_neighbors(client_ifname):
if mac in captive_allowed_entries:
seen_allowed_entries.add(mac)
unique_clients.add(mac)
if isinstance(addr, ipaddress.IPv4Address):
total_ipv4 += 1
unique_ipv4.add(mac)
else:
total_ipv6 += 1
unique_ipv6.add(mac)
print_metric('capport_allowed_macs', 'gauge', len(captive_allowed_entries), help='Number of allowed client mac addresses')
print_metric('capport_allowed_neigh_macs', 'gauge', len(seen_allowed_entries), help='Number of allowed client mac addresses seen in neighbor cache')
print_metric('capport_unique', 'gauge', len(unique_clients), help='Number of clients (mac addresses) in client network seen in neighbor cache')
print_metric('capport_unique_ipv4', 'gauge', len(unique_ipv4), help='Number of IPv4 clients (unique per mac) in client network seen in neighbor cache')
print_metric('capport_unique_ipv6', 'gauge', len(unique_ipv6), help='Number of IPv4 clients (unique per mac) in client network seen in neighbor cache')
print_metric('capport_total_ipv4', 'gauge', total_ipv4, help='Number of IPv4 addresses seen in neighbor cache')
print_metric('capport_total_ipv6', 'gauge', total_ipv6, help='Number of IPv6 addresses seen in neighbor cache')
def main():
assert len(sys.argv) == 2, "Need name of client interface as argument"
trio.run(amain, sys.argv[1])

View File

@ -2,10 +2,14 @@ from __future__ import annotations
import contextlib
import errno
import ipaddress
import socket
import typing
import pr2modules.iproute.linux
import pr2modules.netlink.exceptions
import pr2modules.netlink.rtnl
import pr2modules.netlink.rtnl.ndmsg
from capport import cptypes
@ -18,6 +22,7 @@ async def connect():
class NeighborController:
def __init__(self):
self.ip = pr2modules.iproute.linux.IPRoute()
self.ip.bind()
async def get_neighbor(
self,
@ -61,3 +66,19 @@ class NeighborController:
if e.code == errno.ENOENT:
return None
raise
async def dump_neighbors(self, interface: str) -> typing.Generator[typing.Tuple[cptypes.MacAddress, cptypes.IPAddress]]:
ifindex = socket.if_nametoindex(interface)
unicast_num = pr2modules.netlink.rtnl.rt_type['unicast']
# ip.neigh doesn't support AF_UNSPEC (as it is 0 and evaluates to `False` and gets forced to AF_INET)
for family in (socket.AF_INET, socket.AF_INET6):
for neigh in self.ip.neigh('dump', ifindex=ifindex, family=family):
if neigh['ndm_type'] != unicast_num:
continue
mac = neigh.get_attr(neigh.name2nla('lladdr'))
if not mac:
continue
dst = ipaddress.ip_address(neigh.get_attr(neigh.name2nla('dst')))
if dst.is_link_local:
continue
yield (cptypes.MacAddress.parse(mac), dst)

View File

@ -0,0 +1,24 @@
#!/bin/sh
set -e
base=$(dirname "$(readlink -f "$0")")
cd "${base}"
instance=$1
ifname=$2
if [ -z "${instance}" -o -z "${ifname}" ]; then
echo >&2 "Syntax: $0 instancename clientifname"
exit 1
fi
targetname="/var/lib/prometheus/node-exporter/capport-${instance}.prom"
tmpname="${targetname}.$$"
if ./stats.sh "${ifname}" > "${tmpname}"; then
mv "${tmpname}" "${targetname}"
else
rm "${tmpname}"
exit 1
fi

8
stats.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
base=$(dirname "$(readlink -f "$0")")
cd "${base}"
exec ./venv/bin/capport-stats "$@"