#!/bin/sh # weird shebang? See below: "interpreter selection" # # Collect information related to ath9k wireless events and states. # * rate control statistics ("rc_stats") # * events (dropped, transmitted, beacon loss, ...) # * traffic (packets, bytes) # # All data is collected for each separate station (in case of multiple # connected peers). Combined graphs are provided as a summary. # # # This plugin works with the following python interpreters: # * Python 3 # * micropython # # # Copyright (C) 2015 Lars Kruse # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Magic markers #%# capabilities=autoconf suggest #%# family=auto """true" # ****************** Interpreter Selection *************** # This unbelievable dirty hack allows to find a suitable python interpreter. # This is specifically useful for OpenWRT where typically only micropython is available. # # This "execution hack" works as follows: # * the script is executed by busybox ash or another shell # * the above line (three quotes before and one quote after 'true') evaluates differently for shell and python: # * shell: run "true" (i.e. nothing happens) # * python: ignore everything up to the next three consecutive quotes # Thus we may place shell code here that will take care for selecting an interpreter. # prefer micropython if it is available - otherwise fall back to any python (2 or 3) if which micropython >/dev/null; then /usr/bin/micropython "$0" "$@" else python3 "$0" "$@" fi exit $? # For shell: ignore everything starting from here until the last line of this file. # This is necessary for syntax checkers that try to complain about invalid shell syntax below. true < IP """ arp_cache = {} # example content: # IP address HW type Flags HW address Mask Device # 192.168.2.70 0x1 0x0 00:00:00:00:00:00 * eth0.10 # 192.168.12.76 0x1 0x2 24:a4:3c:fd:76:98 * eth1.10 for line in open("/proc/net/arp", "r").read().split("\n"): # skip empty lines if not line: continue tokens = line.split() ip, mac = tokens[0], tokens[3] # the header line can be ignored - all other should have well-formed MACs if not ":" in mac: continue # ignore remote peers outside of the broadcast domain if mac == "00:00:00:00:00:00": continue arp_cache[mac] = ip return arp_cache def _parse_stations(self): stations_base = os.path.join(self._path, "stations") arp_cache = self._parse_arp_cache() for item in os.listdir(stations_base): peer_mac = item # use the IP or fall back to the MAC without separators (":") if peer_mac in arp_cache: label = arp_cache[peer_mac] key = peer_mac.replace(":", "") else: label = peer_mac key = "host_" + peer_mac.replace(":", "").replace(".", "") yield Station(label, key, os.path.join(stations_base, item)) def get_config(self, scope): yield from Station.get_summary_config(scope, self.stations, self._graph_base) for station in self.stations: yield from station.get_config(scope, self._graph_base) yield "" def get_values(self, scope): yield from Station.get_summary_values(scope, self.stations, self._graph_base) for station in self.stations: yield from station.get_values(scope, self._graph_base) yield "" class Ath9kDriver: def __init__(self, path, graph_base): self._path = path self._graph_base = graph_base self.interfaces = tuple(self._parse_interfaces()) def _parse_interfaces(self): for phy in os.listdir(self._path): phy_path = os.path.join(self._path, phy) for item in os.listdir(phy_path): if item.startswith("netdev:"): wifi = item.split(":", 1)[1] label = "{phy}/{interface}".format(phy=phy, interface=wifi) wifi_path = os.path.join(phy_path, item) graph_base = "{base}_{phy}_{interface}".format(base=self._graph_base, phy=phy, interface=wifi) yield WifiInterface(label, wifi_path, graph_base) def get_config(self, scope): for interface in self.interfaces: yield from interface.get_config(scope) def get_values(self, scope): for interface in self.interfaces: yield from interface.get_values(scope) def _get_up_down_pair(unit, key_up, key_down, factor=None, divider=None, use_negative=True): """ return all required statements for a munin-specific up/down value pair "factor" or "divider" can be given for unit conversions """ for key in (key_up, key_down): if use_negative: yield "{key}.label {unit}".format(key=key, unit=unit) else: yield "{key}.label {key} {unit}".format(key=key, unit=unit) yield "{key}.type COUNTER".format(key=key) if factor: yield "{key}.cdef {key},{factor},*".format(key=key, factor=factor) if divider: yield "{key}.cdef {key},{divider},/".format(key=key, divider=divider) if use_negative: yield "{key_down}.graph no".format(key_down=key_down) yield "{key_up}.negative {key_down}".format(key_up=key_up, key_down=key_down) def get_scope(): called_name = os.path.basename(sys.argv[0]) name_prefix = "ath9k_" if called_name.startswith(name_prefix): scope = called_name[len(name_prefix):] if not scope in PLUGIN_SCOPES: print_error("Invalid scope requested: {0} (expected: {1})".format(scope, PLUGIN_SCOPES)) sys.exit(2) else: print_error("Invalid filename - failed to discover plugin scope") sys.exit(2) return scope def print_error(message): # necessary fallback for micropython linesep = getattr(os, "linesep", "\n") sys.stderr.write(message + linesep) if __name__ == "__main__": ath9k = Ath9kDriver(SYS_BASE_DIR, GRAPH_BASE_NAME) # parse arguments if len(sys.argv) > 1: if sys.argv[1]=="config": for item in ath9k.get_config(get_scope()): print(item) sys.exit(0) elif sys.argv[1] == "autoconf": if os.path.exists(SYS_BASE_PATH): print('yes') else: print('no') sys.exit(0) elif sys.argv[1] == "suggest": for scope in PLUGIN_SCOPES: print(scope) sys.exit(0) elif sys.argv[1] == "version": print_error('olsrd Munin plugin, version %s' % plugin_version) sys.exit(0) elif sys.argv[1] == "": # ignore pass else: # unknown argument print_error("Unknown argument") sys.exit(1) # output values for item in ath9k.get_values(get_scope()): print(item) # final marker for shell / python hybrid script (see "Interpreter Selection") EOF = True EOF