From ad746e3fea62cd3a083298e51975ea1809a9f4ed Mon Sep 17 00:00:00 2001 From: Albert Stefanov Date: Wed, 25 Oct 2023 11:19:28 +0300 Subject: [PATCH] Monitor AP Wi-Fi Status CheckMK plugin to monitor APs Closes #12 --- tools/cmk-plugins/wifi_intefaces/.gitignore | 5 ++ tools/cmk-plugins/wifi_intefaces/README.md | 29 +++++++ .../agent_based/wifi_interfaces.py | 53 +++++++++++++ .../agents/linux/plugins/wifi_interfaces | 37 +++++++++ tools/cmk-plugins/wifi_intefaces/build.py | 13 ++++ .../wifi_intefaces/requirements.txt | 1 + .../web/plugins/metrics/wifi_interfaces.py | 75 +++++++++++++++++++ .../web/plugins/perfometer/wifi_interfaces.py | 22 ++++++ 8 files changed, 235 insertions(+) create mode 100644 tools/cmk-plugins/wifi_intefaces/.gitignore create mode 100644 tools/cmk-plugins/wifi_intefaces/README.md create mode 100644 tools/cmk-plugins/wifi_intefaces/agent_based/wifi_interfaces.py create mode 100755 tools/cmk-plugins/wifi_intefaces/agents/linux/plugins/wifi_interfaces create mode 100755 tools/cmk-plugins/wifi_intefaces/build.py create mode 100644 tools/cmk-plugins/wifi_intefaces/requirements.txt create mode 100644 tools/cmk-plugins/wifi_intefaces/web/plugins/metrics/wifi_interfaces.py create mode 100644 tools/cmk-plugins/wifi_intefaces/web/plugins/perfometer/wifi_interfaces.py diff --git a/tools/cmk-plugins/wifi_intefaces/.gitignore b/tools/cmk-plugins/wifi_intefaces/.gitignore new file mode 100644 index 0000000..5510cbe --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/.gitignore @@ -0,0 +1,5 @@ +*.mkp +.coverage +__pycache__ +*.log +dist/* \ No newline at end of file diff --git a/tools/cmk-plugins/wifi_intefaces/README.md b/tools/cmk-plugins/wifi_intefaces/README.md new file mode 100644 index 0000000..ddfa580 --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/README.md @@ -0,0 +1,29 @@ +# CheckMK extension for monitoring the Wi-Fi interface status @openfest.org + +## Description + +Monitors several 802.11 interface parameters: + +- Associated station count +- Noise Floor +- Channel Usage (channel busy time / total channel time) + +## Building + +We use a simple library to pack the extension into a `.mkp` file without setting up a full CheckMK development server. + +```bash +pip install -r requirements.txt +./build.py +``` + +The build artifacts are at `dist/`. + +## Deployment + +Copy the `mkp` file to the monitoring server and execute: + +```bash +mkp install [output_file].mkp +mkp enable [plugin_name] [version] +``` diff --git a/tools/cmk-plugins/wifi_intefaces/agent_based/wifi_interfaces.py b/tools/cmk-plugins/wifi_intefaces/agent_based/wifi_interfaces.py new file mode 100644 index 0000000..8477d59 --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/agent_based/wifi_interfaces.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +from .agent_based_api.v1 import * + +def check_wifi_status(item, section): + print(item) + print(section) + + for interface in section: + if interface['name'] == item: + usage = interface['delta_ch_time_busy'] / interface['delta_ch_time'] * 100 + yield Metric("delta_ch_time", interface['delta_ch_time_busy']) + yield Metric("delta_ch_time_busy", interface['delta_ch_time_busy']) + yield Metric("channel_usage", usage, levels=(0,100)) + yield Metric("noise_floor", interface['noise'], levels=(-120,0)) + yield Metric("client_count", interface['client_count'], levels=(0,None)) + + if usage < 30: + yield Result(state = State.OK, summary = f"Clients: {interface['client_count']}, Channel usage: {usage:.02f}%") + elif usage < 60: + yield Result(state = State.WARN, summary = f"Clients: {interface['client_count']}, Channel usage: {usage:.02f}%") + else: + yield Result(state = State.CRIT, summary = f"Clients: {interface['client_count']}, Channel usage: {usage:.02f}%") + +def discover_wifi_status(section): + print(section) + for interface in section: + yield Service(item=interface['name']) + +def parse_wifi_interfaces(string_table): + # format: "$interface;$ch_time;$ch_time_busy;$noise;$delta_ch_time;$delta_ch_time_busy,$client_count" + return [{ + 'name': row[0], + 'ch_time' : int(row[1]), + 'ch_time_busy' : int(row[2]), + 'noise' : int(row[3]), + 'delta_ch_time' : int(row[4]), + 'delta_ch_time_busy' : int(row[5]), + 'client_count' : int(row[6]), + } for row in string_table] + +register.agent_section( + name = "wifi_interfaces", + parse_function = parse_wifi_interfaces, +) + +register.check_plugin( + name="wifi_interface_status", + service_name="Wi-Fi Interface %s", + discovery_function=discover_wifi_status, + sections=['wifi_interfaces'], + check_function=check_wifi_status +) \ No newline at end of file diff --git a/tools/cmk-plugins/wifi_intefaces/agents/linux/plugins/wifi_interfaces b/tools/cmk-plugins/wifi_intefaces/agents/linux/plugins/wifi_interfaces new file mode 100755 index 0000000..f214719 --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/agents/linux/plugins/wifi_interfaces @@ -0,0 +1,37 @@ +#!/bin/sh + +CACHE_FILE=/usr/lib/check_mk_agent/plugins/wifi_interfaces.cache + +echo "<<>>" # 59 = ascii semi-colon (;) + +interfaces=$(ls /sys/class/net | grep -iE 'phy.+') + +# Create empty file if it does not exist +# First plugin run may produce garbage data or not run at all, which is OK +# Cache file format: $interface,$time,$busy +touch $CACHE_FILE +cached_output="$(cat "$CACHE_FILE")" +echo -n "" > "$CACHE_FILE" + +for interface in $interfaces +do + ch_time_old="$(echo "$cached_output" | awk -v interface="$interface" -F';' '$1 ~ interface { print $2 }')" + ch_time_busy_old="$(echo "$cached_output" | awk -v interface="$interface" -F';' '$1 ~ interface { print $3 }')" + + output="$(ethtool -S "$interface")" + + ch_time="$(echo "$output" | awk -F ': ' '/ch_time:/{ print $2 }')" + ch_time_busy="$(echo "$output" | awk -F ': ' '/ch_time_busy:/{ print $2 }')" + echo "$interface;$ch_time;$ch_time_busy" >> "$CACHE_FILE" + + # The noise is represented as an unsigned byte, we need a signed one. Thus, we subtract 2**7. + noise="$(expr $(echo "$output" | awk -F ': ' '/noise:/{ print $2 }') - 256)" + client_count="$(iw dev $interface station dump | wc -l)" + + # We calculate the deltas to use for alarms locally; fields are u64 + delta_ch_time=$(expr $(expr $ch_time - $ch_time_old) % 18446744073709551616) + delta_ch_time_busy=$(expr $(expr $ch_time_busy - $ch_time_busy_old) % 18446744073709551616) + + + echo "$interface;$ch_time;$ch_time_busy;$noise;$delta_ch_time;$delta_ch_time_busy;$client_count" +done diff --git a/tools/cmk-plugins/wifi_intefaces/build.py b/tools/cmk-plugins/wifi_intefaces/build.py new file mode 100755 index 0000000..a23374e --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/build.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import mkp + +mkp.dist({'author': 'Albert Stefanov ', + 'description': 'Agent-based plugin for checking OpenWRT Wi-Fi interfaces status, for use @openfest.org', + 'name': 'wifi_interfaces', + 'title': 'OpenWRT Wi-Fi Interfaces', + 'download_url': 'https://github.com/openfest/openfest-network-2023', + 'version': '0.0.1', + 'version.min_required': '2.0.0', +}, +path='.') diff --git a/tools/cmk-plugins/wifi_intefaces/requirements.txt b/tools/cmk-plugins/wifi_intefaces/requirements.txt new file mode 100644 index 0000000..bb63bd6 --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/requirements.txt @@ -0,0 +1 @@ +mkp @ git+https://github.com/inettgmbh/python-mkp@directories diff --git a/tools/cmk-plugins/wifi_intefaces/web/plugins/metrics/wifi_interfaces.py b/tools/cmk-plugins/wifi_intefaces/web/plugins/metrics/wifi_interfaces.py new file mode 100644 index 0000000..9a81172 --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/web/plugins/metrics/wifi_interfaces.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import cmk.utils.render + +from cmk.gui.i18n import _ +from cmk.gui.plugins.metrics.utils import ( + graph_info, + indexed_color, + metric_info, + parse_color_into_hexrgb, +) + + +metric_info["channel_usage"] = { + "title": _("Channel Usage"), + "unit": "%", + "color": "11/a", +} + +metric_info["noise_floor"] = { + "title": _("Noise Floor"), + "unit": "db", + "color": "11/b", +} + +metric_info["delta_ch_time"] = { + "title": _("Channel Time delta"), + "unit": "", + "color": "33/a", +} + +metric_info["delta_ch_time_busy"] = { + "title": _("Channel Busy Time delta"), + "unit": "", + "color": "13/a", +} + +metric_info["client_count"] = { + "title": _("Client Count"), + "unit": "count", + "color": "13/b", +} + + +graph_info["channel_usage"] = { + "title": _("Channel Usage"), + "metrics": [ + ("channel_usage", "line") + ], + "range": (0, 100), +} + +graph_info["client_count"] = { + "title": _("Client Count"), + "metrics": [ + ("client_count", "line") + ], +} + +graph_info["noise_floor"] = { + "title": _("Noise Floor"), + "metrics": [ + ("noise_floor", "line") + ], + "range": (-120, 0), +} + +graph_info["channel_times"] = { + "title": _("Channel Times"), + "metrics": [ + ("delta_ch_time", "stack"), + ("delta_ch_time_busy", "stack") + ], +} diff --git a/tools/cmk-plugins/wifi_intefaces/web/plugins/perfometer/wifi_interfaces.py b/tools/cmk-plugins/wifi_intefaces/web/plugins/perfometer/wifi_interfaces.py new file mode 100644 index 0000000..42103d2 --- /dev/null +++ b/tools/cmk-plugins/wifi_intefaces/web/plugins/perfometer/wifi_interfaces.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from cmk.gui.plugins.metrics import ( + perfometer_info +) + +perfometer_info.append({ + 'type': 'dual', + 'perfometers': [ + { + 'type': 'linear', + 'segments': ['client_count'], + 'total': 1000, + }, + { + 'type': 'linear', + 'segments': ['channel_usage'], + 'total': 100, + }, + ], +})