Skip to content

Commit

Permalink
Collect dhcp-stats lacking "vlan" in the location
Browse files Browse the repository at this point in the history
  • Loading branch information
hmpf committed Mar 7, 2024
1 parent f92920c commit 30c5359
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 18 deletions.
16 changes: 13 additions & 3 deletions contrib/scripts/isc_dhpcd_graphite/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,19 @@ is "vlan1" the resulting graphite path is ``nav.bloviate.vlan1``.
Assumptions about Location
--------------------------

The script assumes that the value of the ``location``-key contains a vlan-name
of the form "vlanNUMBER", regex ``vlan\d+``. If this is not the case, a warning
is issued on stderr and that row of results is not passed on to graphite.
The script will escape the location-string to make a valid graphite key, that
is: replace any signs not in [-A-Za-z0-9_] with an underscore. A "location" of
the form "10.0.0.1/24" will be transformed to "10_0_0_1_24". If there are no
valid letters left after the escape, a warning is issued on stderr and that
row of results is not passed on to graphite.

Extracting a vlan from locations
--------------------------------

If the flag '--extract-vlan' is used, it will be assumed that the value of the
``location``-key contains a vlan-name of the form "vlanNUMBER", regex
``vlan\d+``. If this is not the case, a warning is issued on stderr and that
row of results is not passed on to graphite.

The location-value is normalized so that a value of "Student vlan2 new" is sent
to graphite as "vlan2".
66 changes: 51 additions & 15 deletions contrib/scripts/isc_dhpcd_graphite/isc_dhpcd_graphite.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import sys
from time import time

from nav.metrics.names import escape_metric_name


DEFAULT_PREFIX = "nav.dhcp"
DEFAULT_CONFIG_FILE = "/etc/dhcpd/dhcpd.conf"
DEFAULT_CMD_PATH = pathlib.Path("/usr/bin/dhcpd-pools")
DEFAULT_PORT = "2004"
DEFAULT_PROTOCOL = 'text' # MB doesn't trust pickle so we go with text
DEFAULT_PROTOCOL = "text" # MB doesn't trust pickle so we go with text

# graphite likes pickle protocol 2. Python 3: 3, Python 3.8+: 4
PICKLE_PROTOCOL = range(0, pickle.HIGHEST_PROTOCOL + 1)
Expand All @@ -33,6 +36,7 @@
"touched": "touch",
"free": "free",
}
VERSION="0.2"


Metric = namedtuple("Metric", ["path", "value", "timestamp"])
Expand Down Expand Up @@ -77,11 +81,27 @@ def parse_args():
"--location",
help=(
"Location, if any, to append to the metric prefix to build the path."
' If the vlan is named "vlan1" and the location is "building1.cellar"'
' If the location is named "vlan1" and the location is "building1.cellar"'
" the resulting metric path would be PREFIX.building1.cellar.vlan1"
),
type=str,
)
parser.add_argument(
"--extract-vlan",
help=(
"Try to extract the name of a vlan from the location."
' If the vlan is named "vlan1_baluba" '
" the resulting metric path would be PREFIX.vlan1"
),
action="store_true",
default=False,
)
parser.add_argument(
"--version",
help="Show version of script and exit",
action="store_true",
default=False,
)
protocol_choices = ("text",) + tuple(str(p) for p in PICKLE_PROTOCOL)
parser.add_argument(
"-P",
Expand Down Expand Up @@ -115,6 +135,16 @@ def parse_args():
return args


def get_config_from_args(args):
config = None
if getattr(args, "extract_vlan", False):
class Config:
pass
config = Config()
config.extract_vlan = True
return config


# run command and store json output
def exec_dhcpd_pools(config_file, cmd_path=DEFAULT_CMD_PATH):
flags = f"-c {config_file} {FLAGS}".split()
Expand All @@ -126,24 +156,24 @@ def exec_dhcpd_pools(config_file, cmd_path=DEFAULT_CMD_PATH):


# reformat the data
def render(jsonblob, prefix, protocol=DEFAULT_PROTOCOL):
def render(jsonblob, prefix, protocol=DEFAULT_PROTOCOL, config=None):
if isinstance(protocol, int):
return _render_pickle(jsonblob, prefix, protocol)
return _render_text(jsonblob, prefix)
return _render_pickle(jsonblob, prefix, protocol, config)
return _render_text(jsonblob, prefix, config)


def _render_text(jsonblob, prefix):
def _render_text(jsonblob, prefix, config=None):
template = "{metric.path} {metric.value} {metric.timestamp}\n"
input = _tuplify(jsonblob, prefix)
input = _tuplify(jsonblob, prefix, config)
output = []
for metric in input:
line = template.format(metric=metric)
output.append(line)
return "".join(output).encode("ascii")


def _render_pickle(jsonblob, prefix, protocol):
input = _tuplify(jsonblob, prefix)
def _render_pickle(jsonblob, prefix, protocol,config=None):
input = _tuplify(jsonblob, prefix, config)
output = []
for metric in input:
output.append((metric.path, (metric.timestamp, metric.value)))
Expand All @@ -153,12 +183,14 @@ def _render_pickle(jsonblob, prefix, protocol):
return message


def _tuplify(jsonblob, prefix):
def _tuplify(jsonblob, prefix, config=None):
timestamp = trunc(time())
data = jsonblob["shared-networks"]
output = list()
for vlan_stat in data:
vlan = _clean_vlan(vlan_stat["location"])
vlan = escape_metric_name(vlan_stat["location"])
if config:
vlan = _extract_vlan(vlan)
if not vlan:
continue
for key, metric in METRIC_MAPPER.items():
Expand All @@ -168,7 +200,7 @@ def _tuplify(jsonblob, prefix):
return output


def _clean_vlan(location):
def _extract_vlan(location):
regex = re.search("vlan\d+", location)
if regex:
return regex.group()
Expand All @@ -193,13 +225,17 @@ def send_to_graphite(metrics_blob, server, port):

def main():
args = parse_args()
if args.version:
print(f"version: {VERSION}")
sys.exit(0)
config = get_config_from_args(args)
jsonblob = exec_dhcpd_pools(args.config_file, args.command)
output = render(jsonblob, args.actual_prefix, args.protocol)
output = render(jsonblob, args.actual_prefix, args.protocol, config)
if args.noop:
if args.protocol == "text":
print(output.decode('ascii'))
print(output.decode("ascii"))
else:
print(hexlify(output).decode('ascii'))
print(hexlify(output).decode("ascii"))
else:
send_to_graphite(output, args.server, args.port)

Expand Down

0 comments on commit 30c5359

Please sign in to comment.