diff --git a/config/prometheus.yml b/config/prometheus.yml index 4cad0e2..6cde359 100644 --- a/config/prometheus.yml +++ b/config/prometheus.yml @@ -5,10 +5,25 @@ global: scrape_configs: - job_name: mikrotik-exporter - bearer_token: ZwRZm7Qe6J - dns_sd_configs: - - names: - - '_mikrotik._tcp.mgmt.k-space.ee' + metrics_path: /probe + params: + module: + - full + basic_auth: + username: netpoller + password: ... + static_configs: + - targets: + - 172.23.0.1 + - 172.23.0.100 + - 100.102.1.111 + - 100.102.1.112 + - 100.102.1.114 + - 100.102.1.115 + - 100.102.1.121 + - 100.102.1.131 + - 100.102.1.141 + - 100.102.1.151 relabel_configs: - source_labels: [ __address__ ] target_label: __param_target diff --git a/mikrotik.py b/mikrotik.py index 94be736..88faa20 100755 --- a/mikrotik.py +++ b/mikrotik.py @@ -15,9 +15,10 @@ PREFIX = os.getenv("PROMETHEUS_PREFIX", "mikrotik_") -async def wrap(i): +async def wrap(i, **extra_labels): metrics_seen = set() async for name, tp, value, labels in i: + labels.update(extra_labels) if name not in metrics_seen: yield "# TYPE %s %s" % (PREFIX + name, tp) metrics_seen.add(name) @@ -26,7 +27,7 @@ async def wrap(i): ("{%s}" % ",".join(["%s=\"%s\"" % j for j in labels.items()]) if labels else ""), value) -async def scrape_mikrotik(mk): +async def scrape_mikrotik(mk, module_full=False): async for obj in mk.query("/interface/print"): labels = {"port": obj["name"]} @@ -124,7 +125,7 @@ async def scrape_mikrotik(mk): for key in ("version", "cpu", "cpu-count", "board-name", "architecture-name"): labels[key.replace("-", "_")] = obj[key] - yield "system_version", "gauge", 1, labels + yield "system_version_info", "gauge", 1, labels async for obj in mk.query("/system/health/print"): # Normalize ROS6 vs ROS7 difference @@ -166,6 +167,10 @@ async def scrape_mikrotik(mk): else: raise NotImplementedError("Don't know how to handle system health record %s" % repr(key)) + if not module_full: + # Specify `module: full` in Probe CRD to pull in extra metrics below + return + async for obj in mk.query("/interface/bridge/host/print"): try: vendor = await oui.lookup(obj["mac-address"]) @@ -181,7 +186,7 @@ async def scrape_mikrotik(mk): yield "bridge_host_info", "gauge", 1, labels async for obj in mk.query("/ip/arp/print"): - if obj["status"] in ("failed", "incomplete", ""): + if obj.get("status", "") in ("failed", "incomplete", ""): continue labels = { "address": obj["address"], @@ -209,6 +214,10 @@ async def scrape_mikrotik(mk): } yield "neighbor_host_info", "gauge", 1, labels +class ServiceUnavailableError(exceptions.ServerError): + status_code = 503 + + @app.route("/probe", stream=True) async def view_export(request): authorization = request.headers.get("Authorization") @@ -244,12 +253,18 @@ async def view_export(request): try: await mk.connect() await mk.login() - async for line in wrap(scrape_mikrotik(mk)): + global_labels = {} + async for obj in mk.query("/system/identity/print"): + global_labels["identity_name"] = obj["name"] + + async for line in wrap(scrape_mikrotik(mk, module_full=request.args.get("module") == "full"), **global_labels): await response.send(line + "\n") except OSError as e: - # Host unreachable pool.pop(handle) - raise exceptions.ServerError(e.strerror) + if e.errno == 113: # Host unreachable translates to gateway timeout + raise ServiceUnavailableError(e.strerror) + else: + raise exceptions.ServerError(e.strerror) except RuntimeError as e: # Handle TCPTransport closed exception pool.pop(handle)