Skip to content

Instantly share code, notes, and snippets.

@rus-kilian
Created July 7, 2021 20:14
Show Gist options
  • Save rus-kilian/996b2f1a1c1ad3f6f9e1a4847eebb278 to your computer and use it in GitHub Desktop.
Save rus-kilian/996b2f1a1c1ad3f6f9e1a4847eebb278 to your computer and use it in GitHub Desktop.
Pull platform stats (like CoPP) from various Cisco platforms
#!/usr/bin/env python3
import time
import re
import yaml
import json
import os.path
import logging
import argparse
import socket
import pprint
from netmiko import ConnectHandler
from prometheus_client import Gauge, Summary
from threading import Thread
# systemd socket activation
from prometheus_client import start_http_server
from prometheus_client.exposition import MetricsHandler
from prometheus_client.registry import REGISTRY
# Debian bullseye has _ThreadingSimpleServer renamed to ThreadingWSGIServer
try:
from prometheus_client.exposition import (
ThreadingWSGIServer as _ThreadingSimpleServer,
)
except ImportError:
from prometheus_client.exposition import _ThreadingSimpleServer
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"--debug",
"-d",
dest="debug",
action="store_true",
help="Run debug mode",
default=False,
)
parser.add_argument(
"--log",
"-l",
dest="ssh_log",
action="store_true",
help="Run in SSH debug mode with log",
default=False,
)
parser.add_argument(
"--listen-port", type=int, help="The port the exporter will listen on", default=9424
)
parser.add_argument(
"--delay",
type=int,
help="The reconnect delay the poller will wait if SSH connection died",
default=300,
)
parser.add_argument(
"--asa-delay",
type=int,
help="The refresh delay the exporter will wait between ASA runs",
default=30,
)
parser.add_argument(
"--iosxe-delay",
type=int,
help="The refresh delay the exporter will wait between IOSXE runs",
default=30,
)
parser.add_argument(
"--nxos-delay",
type=int,
help="The refresh delay the exporter will wait between NX-OS runs",
default=30,
)
args = parser.parse_args()
config = {}
if os.path.isfile("/etc/net_exporter.yaml"):
with open("/etc/net_exporter.yaml", "r") as stream:
try:
config = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(exc)
exit(1)
elif os.path.isfile(os.environ["HOME"] + "/.config.yaml"):
with open(os.environ["HOME"] + "/.config.yaml", "r") as stream:
try:
config = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(exc)
exit(1)
else:
print("No config.yaml")
exit(1)
if "login" not in config:
print("config.yaml does not contain login!")
exit(1)
if "password" not in config:
print("config.yaml does not contain password!")
exit(1)
SYSTEMD_FIRST_SOCKET_FD = 3
CONTENT_TYPE_LATEST = str("text/plain; version=0.0.1; charset=utf-8")
"""Content type of the latest text format"""
logger = logging.getLogger(__name__)
pp = pprint.PrettyPrinter(indent=4)
debug = args.debug
# create console handler and set level to debug
ch = logging.StreamHandler()
if debug:
logger.setLevel(logging.DEBUG)
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter(
"%(asctime)s - %(name)s/%(threadName)s - %(levelname)s - %(message)s"
)
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# ASA regex
asa_ip_local_pool = re.compile(r"^ip local pool\s*([a-zA-Z0-9]+)\s*")
asa_ip_local_pool_usage = re.compile(
r"^[0-9\.]+\s+[0-9\.]+\s+[0-9\.]+\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s*"
)
# IOS-XE regex
class_match = re.compile(r"^\s+Class-map:\s+([^\s]+)\s+")
by_match = re.compile(r"^\s+(\d+)\s+packets[^\d]+(\d+)\s+bytes")
conf_match = re.compile(r"^\s+conformed\s+(\d+)\s+bytes")
exc_match = re.compile(r"^\s+exceeded\s+(\d+)\s+bytes")
tcam_util_single_val = re.compile(r"^\s*([^\s].*[^\s])\s+(\d+)\s+(\d+)")
tcam_util_dual_val = re.compile(r"^\s*([^\s].*[^\s])\s+(\d+)/(\d+)\s+(\d+)/(\d+)")
ios_section_re = re.compile(r"^[=-]+$")
cpu_q_stat_re = re.compile(
r"^(\d+)\s+(\d+)\s+(\S.+\S)\s+(Yes|No)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*"
)
cpu_q_policer_stat_re = re.compile(r"^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*")
cpp_classes_re = re.compile(r"^(\d+)\s+(\S+)\s+:\s+(\S.*\S)\s*")
# NX-OS regex
service_policy_re = re.compile(r"^\s*service-policy\s+input\s+(.*)$")
class_map_re = re.compile(r"^\s+class-map\s+([A-Za-z0-9-]+)\s?.*$")
module_re = re.compile(r"^\s+module\s(\d+):$")
value_re = re.compile(r"^\s+(conformed|violated)\s+(\d+)\s+bytes")
section_re = re.compile(
r"^\s*(RMON counters|Throttle statistics|NAPI statistics|qdisc stats)[\s:]?.*"
)
pkt_counters_re = re.compile(r"^\s*(\S+)\s+packets\s+(\d+)\s+(\d+)")
throttle_re = re.compile(r"^\s*([RT]x)\spacket\s+rate\s+[^\d]+(\d+)\s+/\s+(\d+)\s+(.+)")
napi_re = re.compile(r"^\s*([RT]x)\s+([a-z]+)[\s\.]+(\d+)")
qdisc_re = re.compile(r"^\s*([a-zA-Z][a-z\ ]+[a-z])[\s\.]+(\d+)")
# ASA metrics
ASA_collect = Summary(
"asa_collect", "ASA poller details collecting IP pool stats", labelnames=["target"]
)
ASA_pool_stats = Gauge(
"asa_pool_stats",
"ASA pool usage [total usage count]",
labelnames=["target", "pool", "status"],
)
# IOSXE metrics
IOSXE_collect = Summary(
"iosxe_collect",
"IOSXE poller details collecting IP pool stats",
labelnames=["target"],
)
IOSXE_copp_stats = Gauge(
"iosxe_copp_stats", "IOSXE CoPP stats", labelnames=["target", "chain", "status"]
)
IOSXE_tcam_utilization = Gauge(
"iosxe_tcam_utilization",
"IOSXE TCAM utilization",
labelnames=["target", "asic", "table", "ref"],
)
IOSXE_CPU_queue_stats = Gauge(
"iosxe_cpu_queue_stats",
"IOSXE CPU queue stats",
labelnames=["target", "queue", "match"],
)
IOSXE_CPU_policer_queue_accept_frames = Gauge(
"iosxe_cpu_policer_queue_accept_frames",
"IOSXE CPU policer queue frames accepted",
labelnames=["target", "policer"],
)
IOSXE_CPU_policer_queue_accept_bytes = Gauge(
"iosxe_cpu_policer_queue_accept_bytes",
"IOSXE CPU policer queue bytes accepted",
labelnames=["target", "policer"],
)
IOSXE_CPU_policer_queue_drop_frames = Gauge(
"iosxe_cpu_policer_queue_drop_frames",
"IOSXE CPU policer queue frames dropped",
labelnames=["target", "policer"],
)
IOSXE_CPU_policer_queue_drop_bytes = Gauge(
"iosxe_cpu_policer_queue_drop_bytes",
"IOSXE CPU policer queue bytes dropped",
labelnames=["target", "policer"],
)
# NX-OS metrics
NXOS_collect = Summary(
"nxos_collect",
"NX-OS poller details collecting IP pool stats",
labelnames=["target"],
)
NXOS_copp_stats = Gauge(
"nxos_copp_stats",
"NX-OS CoPP stats",
labelnames=["target", "service_policy", "class_map", "module", "match"],
)
NXOS_copp_counters = Gauge(
"nxos_copp_counters",
"NX-OS CoPP counters",
labelnames=["target", "section", "direction", "match"],
)
NXOS_copp_throttle = Gauge(
"nxos_copp_throttle",
"NX-OS CoPP throttle stats",
labelnames=["target", "section", "direction", "match", "unit", "state"],
)
NXOS_napi_stats = Gauge(
"nxos_napi_stats",
"NX-OS CoPP NAPI stats",
labelnames=["target", "section", "direction", "match"],
)
NXOS_qdisc_stats = Gauge(
"nxos_qdisc_stats",
"NX-OS CoPP qdisc stats",
labelnames=["target", "section", "match"],
)
class SocketInheritingHTTPServer(_ThreadingSimpleServer):
"""A HttpServer subclass that takes over an inherited socket from systemd"""
def __init__(self, address_info, handler, fd, bind_and_activate=True):
_ThreadingSimpleServer.__init__(
self, address_info, handler, bind_and_activate=False
)
logger.debug("http server init complete - passing socket")
self.socket = socket.fromfd(fd, self.address_family, self.socket_type)
if bind_and_activate:
# NOTE: systemd provides ready-bound sockets, so we only need to activate:
logger.debug("http server activating")
self.server_activate()
else:
logger.debug("http server NOT activated")
class AKGatherer(Thread):
"""Periodically retrieve data from AK in a separate thread,
"""
def __init__(self):
Thread.__init__(self)
self.name = "AKGatherer"
self.target = None
self.login = None
self.password = ""
self.aktype = None
self.net_connect = None
# ASA pools
self.pools = {}
# IOSXE CoPP sections
self.sections = {}
def settarget(self, target, login, password, aktype):
self.name = "AKGatherer@%s" % target
logger.debug('Setting AK to "%s"' % target)
self.target = target
logger.debug('Setting AK login to "%s"' % login)
self.login = login
self.password = password
logger.debug('Setting AK type to "%s"' % aktype)
self.aktype = aktype
def check_asa(self):
if self.net_connect is None:
raise ("No SSH connection present!")
logger.debug("Fetching ASA pool definitions on %s" % self.target)
output = self.net_connect.send_command("show run ip local pool")
# connection.send("show run ipv6 \n")
for line in output.split("\n"):
if line == "":
continue
logger.debug('Got line: "%s"' % line)
line_match = asa_ip_local_pool.match(line)
if line_match:
pool = line_match.group(1)
logger.debug('Got local IP pool: "%s"' % pool)
self.pools[pool] = 0
ASA_pool_stats.labels(target=self.target, pool=pool, status="free").set(
0
)
ASA_pool_stats.labels(target=self.target, pool=pool, status="hold").set(
0
)
ASA_pool_stats.labels(
target=self.target, pool=pool, status="in_use"
).set(0)
else:
logger.debug('Failed to find pool in : "%s"' % line)
while True:
start_time = time.time()
for pool in self.pools:
logger.debug('Fetching current usage on local IP pool: "%s"' % pool)
output = self.net_connect.send_command(
"show ip local pool %s" % pool
).split("\n")
# Begin End Mask Free Held In use
# 192.0.2.1 192.0.2.254 255.255.255.0 254 0 0
# XXX: go straight for second line
line_match = asa_ip_local_pool_usage.match(output[1])
if line_match:
free = int(line_match.group(1))
hold = int(line_match.group(2))
in_use = int(line_match.group(3))
logger.debug(
'Got local IP pool "%s" usage: %d/%d/%d'
% (pool, free, hold, in_use)
)
self.pools[pool] = in_use
ASA_pool_stats.labels(
target=self.target, pool=pool, status="free"
).set(free)
ASA_pool_stats.labels(
target=self.target, pool=pool, status="hold"
).set(hold)
ASA_pool_stats.labels(
target=self.target, pool=pool, status="in_use"
).set(in_use)
else:
logger.debug('Invalid line "%s"' % output)
logger.debug("Finished collecting stats from %s" % self.target)
ASA_collect.labels(target=self.target).observe(time.time() - start_time)
logger.debug(
"Sleeping in ASA thread for %d s before pulling next stats"
% args.asa_delay
)
time.sleep(args.asa_delay)
def check_ios(self):
if self.net_connect is None:
raise ("No SSH connection present!")
while True:
start_time = time.time()
logger.debug("Fetching IOSXE CoPP stats on %s" % self.target)
data = self.net_connect.send_command("show policy-map control-plane all")
if data:
section = ""
for l in data.split("\n"):
match = class_match.match(l)
if match:
section = match.group(1)
self.sections[section] = {}
continue
# in "section"
m = by_match.match(l)
if m:
self.sections[section]["packets"] = m.group(1)
self.sections[section]["bytes"] = m.group(2)
IOSXE_copp_stats.labels(
target=self.target, chain=section, status="packets"
).set(m.group(1))
IOSXE_copp_stats.labels(
target=self.target, chain=section, status="bytes"
).set(m.group(2))
continue
m = conf_match.match(l)
if m:
self.sections[section]["conformed"] = m.group(1)
IOSXE_copp_stats.labels(
target=self.target, chain=section, status="conformed"
).set(m.group(1))
continue
m = exc_match.match(l)
if m:
self.sections[section]["exceeded"] = m.group(1)
IOSXE_copp_stats.labels(
target=self.target, chain=section, status="exceeded"
).set(m.group(1))
continue
# logger.debug('Ignoring "%s"' % l)
else:
logger.error('No data received from "%s"' % self.target)
# XXX: unfortunately no |format on IOS
data = self.net_connect.send_command(
"show platform hardware fed switch active fwd-asic resource tcam utilization"
)
if data:
asic = 0
for l in data.split("\n"):
# FIXME: in case of multi-asic, match ASIC first:
# "CAM Utilization for ASIC [0]"
m = tcam_util_single_val.match(l)
if m:
table = m.group(1)
max_val = int(m.group(2))
used_val = int(m.group(3))
logger.debug(
"Got IOSXE TCAM utilization for %s as %d/%d"
% (table, used_val, max_val)
)
IOSXE_tcam_utilization.labels(
target=self.target, asic=asic, table=table, ref="max"
).set(max_val)
IOSXE_tcam_utilization.labels(
target=self.target, asic=asic, table=table, ref="used_val"
).set(used_val)
continue
m = tcam_util_dual_val.match(l)
if m:
table = m.group(1)
max_val1 = int(m.group(2))
max_val2 = int(m.group(3))
used_val1 = int(m.group(4))
used_val2 = int(m.group(5))
logger.debug(
"Got IOSXE TCAM utilization for %s as used:%d/%d, max:%d/%d"
% (table, used_val1, used_val2, max_val1, max_val2)
)
IOSXE_tcam_utilization.labels(
target=self.target, asic=asic, table=table, ref="max1"
).set(max_val1)
IOSXE_tcam_utilization.labels(
target=self.target, asic=asic, table=table, ref="max2"
).set(max_val2)
IOSXE_tcam_utilization.labels(
target=self.target, asic=asic, table=table, ref="used_val1"
).set(used_val1)
IOSXE_tcam_utilization.labels(
target=self.target, asic=asic, table=table, ref="used_val2"
).set(used_val2)
continue
logger.debug("Fetching CPU policer stats")
data = self.net_connect.send_command(
"sh platform hardware fed switch active qos queue stats internal cpu policer"
)
section = 0
p_queue = {}
p_class = {}
if data:
for l in data.split("\n"):
logger.debug('Checking "%s"' % l)
match = ios_section_re.match(l)
if match:
section += 1
logger.debug("Found next section. Now at: %d" % section)
if section == 2:
logger.debug("Checking CPU Queue Statistics")
match = cpu_q_stat_re.match(l)
if match:
logger.debug("Found match for CPU Queue Statistics line")
qid = int(match.group(1))
# plcidx = int(match.group(2))
queue_name = match.group(3)
enabled = match.group(4)
default_rate = int(match.group(5))
set_rate = int(match.group(6))
drop_bytes = int(match.group(7))
drop_frames = int(match.group(8))
logger.debug(
"Got: QId: %d (%s), enabled: %s, rate: %d/%d, drop: %d/%d"
% (
qid,
queue_name,
enabled,
default_rate,
set_rate,
drop_bytes,
drop_frames,
)
)
if enabled == "Yes":
logger.debug("Queue is enabled. Adding to prometheus.")
IOSXE_CPU_queue_stats.labels(
target=self.target,
queue=queue_name,
match="set_rate",
).set(set_rate)
IOSXE_CPU_queue_stats.labels(
target=self.target,
queue=queue_name,
match="drop_bytes",
).set(drop_bytes)
IOSXE_CPU_queue_stats.labels(
target=self.target,
queue=queue_name,
match="drop_frames",
).set(drop_frames)
# else:
# logger.debug('Ignoring CPU Queue line!')
elif section == 4 or section == 5:
logger.debug("Checking CPU Queue Policer Statistics")
match = cpu_q_policer_stat_re.match(l)
if match:
logger.debug("Found match for CPU Queue Policer Statistics")
p_index = match.group(1)
p_accept_bytes = match.group(2)
p_accept_frames = match.group(3)
p_drop_bytes = match.group(4)
p_drop_frames = match.group(5)
p_queue[p_index] = {}
p_queue[p_index]["accept_bytes"] = p_accept_bytes
p_queue[p_index]["accept_frames"] = p_accept_frames
p_queue[p_index]["drop_bytes"] = p_drop_bytes
p_queue[p_index]["drop_frames"] = p_drop_frames
# else:
# logger.debug('Ignoring CPU Queue Policer line!')
elif section == 12:
logger.debug("Checking CPP Classes to queue map")
match = cpp_classes_re.match(l)
if match:
# logger.debug('Found match for CPP Classes to queue map')
p_index = match.group(1)
p_class[p_index] = match.group(2)
# p_description = match.group(3)
# else:
# logger.debug('Ignoring CPP Classes to queue map line!')
# else:
# logger.debug('Ignoring line in block we do not care about!')
for c in p_class:
logger.debug("Adding class %s to CPU policer Gauge" % c)
IOSXE_CPU_policer_queue_accept_bytes.labels(
target=self.target, policer=p_class[c]
).set(p_queue[c]["accept_bytes"])
IOSXE_CPU_policer_queue_accept_frames.labels(
target=self.target, policer=p_class[c]
).set(p_queue[c]["accept_frames"])
IOSXE_CPU_policer_queue_drop_bytes.labels(
target=self.target, policer=p_class[c]
).set(p_queue[c]["drop_bytes"])
IOSXE_CPU_policer_queue_drop_frames.labels(
target=self.target, policer=p_class[c]
).set(p_queue[c]["drop_frames"])
else:
logger.error('No data received from "%s"' % self.target)
logger.debug("Finished collecting stats from %s" % self.target)
IOSXE_collect.labels(target=self.target).observe(time.time() - start_time)
logger.debug(
"Sleeping in IOSXE thread for %d s before pulling next stats"
% args.iosxe_delay
)
time.sleep(args.iosxe_delay)
def check_nxos(self):
if self.net_connect is None:
raise ("No SSH connection present!")
while True:
start_time = time.time()
logger.debug("Fetching NX-OS CoPP stats on %s" % self.target)
# FIXME: looks funny on 7702 .. currently only works on 7706...
data = self.net_connect.send_command(
"sh hardware internal cpu-mac inband stats"
)
if data:
section = None
for l in data.split("\n"):
s = section_re.match(l)
if s:
section = s.group(1)
logger.debug('Found section "%s"' % section)
continue
if section == "RMON counters":
c = pkt_counters_re.match(l)
if c:
match = c.group(1)
rx = int(c.group(2))
tx = int(c.group(3))
logger.debug(
"Found %s counters in RMON: Rx:%d/Tx:%d"
% (match, rx, tx)
)
NXOS_copp_counters.labels(
target=self.target,
section=section,
match=match,
direction="rx",
).set(rx)
NXOS_copp_counters.labels(
target=self.target,
section=section,
match=match,
direction="tx",
).set(tx)
continue
if section == "Throttle statistics":
c = throttle_re.match(l)
if c:
direction = c.group(1)
cur = int(c.group(2))
max_pps = int(c.group(3))
unit = c.group(4)
logger.debug(
"Found %s counters in throttle stats: %d/%d %s"
% (direction, cur, max_pps, unit)
)
NXOS_copp_throttle.labels(
target=self.target,
section=section,
match=match,
direction=direction,
unit=unit,
state="cur",
).set(cur)
NXOS_copp_throttle.labels(
target=self.target,
section=section,
match=match,
direction=direction,
unit=unit,
state="max",
).set(max_pps)
continue
if section == "NAPI statistics":
p = napi_re.match(l)
if p:
direction = p.group(1)
pkt_match = p.group(2)
pkt = int(p.group(3))
logger.debug(
"Found %s %s in NAPI stats: %d"
% (direction, pkt_match, pkt)
)
NXOS_napi_stats.labels(
target=self.target,
section=section,
match=pkt_match,
direction=direction,
).set(pkt)
continue
if section == "qdisc stats":
p = qdisc_re.match(l)
if p:
pkt_match = p.group(1)
pkt = int(p.group(2))
logger.debug(
"Found %s in qdisc stats: %d" % (pkt_match, pkt)
)
NXOS_qdisc_stats.labels(
target=self.target, section=section, match=pkt_match
).set(pkt)
continue
# logger.debug('Ignoring "%s"' % l)
data = self.net_connect.send_command(
"show policy-map interface control-plane | json"
)
if data:
# # if we don't use the "|json" this is the regex style match
# service_policy = {}
# policy = ''
# class_map = ''
# module = 1
# for l in data.split('\n'):
# match = service_policy_re.match(l)
# if match:
# policy = match.group(1)
# logger.debug('Found service-policy block "%s"'% policy)
# service_policy[policy] = {}
# continue
#
# # in service-policy
# c = class_map_re.match(l)
# if c:
# class_map = c.group(1)
# logger.debug('Found class-map block "%s" in service-policy "%s"' % (class_map,policy))
# service_policy[policy][class_map] = {}
# continue
# # in class-map
# m = module_re.match(l)
# if m:
# module = int(m.group(1))
# logger.debug('Found module block "%d" in class-map "%s" in service-policy "%s"' % (module,class_map,policy))
# service_policy[policy][class_map][module] = {}
# continue
# # under module
# val = value_re.match(l)
# if val:
# val_type = val.group(1)
# val_value = int(val.group(2))
# logger.debug('Found %s=%d in module block "%d" in class-map "%s" in service-policy "%s"' % (val_type,val_value,module,class_map,policy))
# NXOS_copp_stats.labels(target=self.target,service_policy=policy,class_map=class_map,module=module,match=val_type).set(val_value)
# continue
#
# #logger.debug('Ignoring "%s"' % l)
j_data = json.loads(data)
# pp.pprint(j_data)
policy = j_data["pmap-name"]
for row in j_data["TABLE_cmap"]["ROW_cmap"]:
# print('Row:')
# pp.pprint(row)
# print('----------------------------------------------------------------------------')
class_map = row["cmap-name-out"]
# print('Got type: %s' % type(row['TABLE_slot']['ROW_slot']))
if type(row["TABLE_slot"]["ROW_slot"]) is dict:
# 7702 style...
module = row["TABLE_slot"]["ROW_slot"]["slot-no-out"]
for ptype in ["conform", "violate"]:
for k in row["TABLE_slot"]["ROW_slot"][
"TABLE_%s_inst" % ptype
]["ROW_%s_inst" % ptype]:
if k.endswith("-ts"):
# skip non-integer values
continue
v = int(
row["TABLE_slot"]["ROW_slot"][
"TABLE_%s_inst" % ptype
]["ROW_%s_inst" % ptype][k]
)
logger.debug(
"Found %s service_policy, class %s, module %s, match %s = %s"
% (policy, class_map, module, k, v)
)
NXOS_copp_stats.labels(
target=self.target,
service_policy=policy,
class_map=class_map,
module=module,
match=k,
).set(v)
else:
# 7706 style
# array of dict
for r in row["TABLE_slot"]["ROW_slot"]:
module = r["slot-no-out"]
for ptype in ["conform", "violate"]:
for k in r["TABLE_%s_inst" % ptype][
"ROW_%s_inst" % ptype
]:
if k.endswith("-ts"):
# skip non-integer values
continue
v = int(
r["TABLE_%s_inst" % ptype][
"ROW_%s_inst" % ptype
][k]
)
logger.debug(
"Found %s service_policy, class %s, module %s, match %s = %s"
% (policy, class_map, module, k, v)
)
NXOS_copp_stats.labels(
target=self.target,
service_policy=policy,
class_map=class_map,
module=module,
match=k,
).set(v)
# data = self.net_connect.send_command('show hardware capacity forwarding| json')
# if data:
# j_data = json.loads(data)
# module = j_data['TABLE_module']['ROW_module']['module_number']
# for m in j_data['TABLE_module']['ROW_module']['TABLE_resource_util_info']['ROW_resource_util_info']:
# res_hdr = m['resource_hdr']
# ents_use = m['ents_use']
# if 'ents_free' in m:
# ents_free = m['ents_free']
# ents_pctage = m['ents_pctage']
# logger.debug('%s = %s/%s (%s %%)' % (res_hdr,ents_use,ents_free,ents_pctage))
# else:
# logger.debug('%s = %s' % (res_hdr,ents_use))
else:
logger.error('No data received from "%s"' % self.target)
logger.debug("Finished collecting stats from %s" % self.target)
NXOS_collect.labels(target=self.target).observe(time.time() - start_time)
logger.debug(
"Sleeping in NX-OS thread for %d s before pulling next stats"
% args.nxos_delay
)
time.sleep(args.nxos_delay)
def run(self):
if self.target is None:
logger.error("No AK target set!")
return
if self.aktype is None:
logger.error("No AK type set!")
return
if self.login is None:
logger.error("No AK login set!")
return
if self.password == "":
logger.error("No AK login set!")
return
logger.debug(
"Starting AK data gather (%s) thread for %s as %s"
% (self.aktype, self.target, self.login)
)
ak_login = {
"device_type": self.aktype,
"host": self.target,
"username": self.login,
"password": self.password,
}
while True:
try:
logger.debug("Open SSH connection to %s" % self.target)
if args.ssh_log:
session_log = "/tmp/%s.log" % self.target
else:
session_log = None
self.net_connect = ConnectHandler(
**ak_login, verbose=args.ssh_log, session_log=session_log
)
# while we have a working SSH connection, poll CLI output every $delay
while True:
if self.aktype == "cisco_asa":
logger.debug("Running check_asa and produce metrics")
self.check_asa()
logger.debug("Done: Running check_asa in thread")
elif self.aktype == "cisco_ios":
logger.debug("Running check_ios and produce metrics")
self.check_ios()
logger.debug("Done: Running check_ios in thread")
elif self.aktype == "cisco_nxos":
logger.debug("Running check_nxos and produce metrics")
self.check_nxos()
logger.debug("Done: Running check_nxos in thread")
except Exception:
# Ignore failures, we will try again after refresh_interval.
# Most of them are termporary ie. connectivity problmes
logger.error("Error getting stats", exc_info=True)
logger.debug(
"Waiting before reconnect in poller thread for %d s" % args.delay
)
time.sleep(args.delay)
if __name__ == "__main__":
if "asa" in config:
logger.debug("Starting ASA gatherer thread")
asa_gatherer = {}
for ak in config["asa"]:
asa_gatherer[ak] = AKGatherer()
logger.debug("Adding Thread for %s (login: %s)" % (ak, config["login"]))
asa_gatherer[ak].settarget(
ak, config["login"], config["password"], "cisco_asa"
)
asa_gatherer[ak].start()
if "iosxe" in config:
logger.debug("Starting IOSXE gatherer thread")
iosxe_gatherer = {}
for ak in config["iosxe"]:
iosxe_gatherer[ak] = AKGatherer()
logger.debug("Adding Thread for %s (login: %s)" % (ak, config["login"]))
iosxe_gatherer[ak].settarget(
ak, config["login"], config["password"], "cisco_ios"
)
iosxe_gatherer[ak].start()
if "nxos" in config:
logger.debug("Starting NX-OS gatherer thread")
nxos_gatherer = {}
for ak in config["nxos"]:
nxos_gatherer[ak] = AKGatherer()
logger.debug("Adding Thread for %s (login: %s)" % (ak, config["login"]))
nxos_gatherer[ak].settarget(
ak, config["login"], config["password"], "cisco_nxos"
)
nxos_gatherer[ak].start()
# ...and now serve the registry contents so that we can consume it..
if os.environ.get("LISTEN_PID", None) == str(os.getpid()):
# systemd socket activation will need that httpd is waiting for socket
# to be passed - while collection still updates in the background
# inherit the socket
logger.debug("Starting systemd socket activation http server")
CustomMetricsHandler = MetricsHandler.factory(REGISTRY)
server_args = [("localhost", args.listen_port), CustomMetricsHandler]
httpd = SocketInheritingHTTPServer(*server_args, fd=SYSTEMD_FIRST_SOCKET_FD)
logging.info(
"netstatsexporter started for socket activation on fd %s"
% (SYSTEMD_FIRST_SOCKET_FD,)
)
try:
logging.info(
"netstatsexporter httpd running on socket fd %s"
% (SYSTEMD_FIRST_SOCKET_FD,)
)
httpd.serve_forever()
except KeyboardInterrupt:
httpd.socket.close()
else:
# start the server normally
# Start up the server to expose the metrics.
logger.debug("Starting http server")
start_http_server(args.listen_port)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment