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] [options.entry_points]
console_scripts = console_scripts =
capport-control = capport.control.run:main 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 contextlib
import errno import errno
import ipaddress
import socket
import typing import typing
import pr2modules.iproute.linux import pr2modules.iproute.linux
import pr2modules.netlink.exceptions import pr2modules.netlink.exceptions
import pr2modules.netlink.rtnl
import pr2modules.netlink.rtnl.ndmsg
from capport import cptypes from capport import cptypes
@ -18,6 +22,7 @@ async def connect():
class NeighborController: class NeighborController:
def __init__(self): def __init__(self):
self.ip = pr2modules.iproute.linux.IPRoute() self.ip = pr2modules.iproute.linux.IPRoute()
self.ip.bind()
async def get_neighbor( async def get_neighbor(
self, self,
@ -61,3 +66,19 @@ class NeighborController:
if e.code == errno.ENOENT: if e.code == errno.ENOENT:
return None return None
raise 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 "$@"