root / plugins / router / arris-sb6183 @ 13e4b4aa
Historique | Voir | Annoter | Télécharger (8,01 ko)
| 1 |
#!/usr/bin/python |
|---|---|
| 2 |
|
| 3 |
# modem: |
| 4 |
# |
| 5 |
# * upstream and downstream power levels |
| 6 |
# * downstream signal to noise ratio |
| 7 |
# * downstream error counts |
| 8 |
# |
| 9 |
# The values are retrieved from the cable modem's status web pages at |
| 10 |
# 192.168.100.1. So, this plugin must be installed on a munin node |
| 11 |
# which can access those pages. |
| 12 |
# |
| 13 |
# To install, place this plugin in the node's plugins directory, |
| 14 |
# /etc/munin/plugins and restart munin-node. |
| 15 |
# |
| 16 |
# Developed and tested with: |
| 17 |
# firmware: D30CM-OSPREY-2.4.0.1-GA-02-NOSH |
| 18 |
# hardware version: 1 |
| 19 |
# |
| 20 |
# Copyright 2020 Nathaniel Clark <nathaniel.clark@misrule.us> |
| 21 |
|
| 22 |
""" |
| 23 |
=head1 NAME |
| 24 |
|
| 25 |
arris-sb6183 - Health monitoring plugin for Arris SB6183 Cable Modem |
| 26 |
|
| 27 |
=head1 CONFIGURATION |
| 28 |
|
| 29 |
Make sure 192.168.100.1 is accessible through your firewall. |
| 30 |
|
| 31 |
To have this register with munin as it's own host set the "env.hostname" in config. |
| 32 |
Also ensure that the hostname set is listed in munin.conf. |
| 33 |
|
| 34 |
[arris*] |
| 35 |
env.hostname modem |
| 36 |
|
| 37 |
=head1 VERSION |
| 38 |
|
| 39 |
0.0.1 |
| 40 |
|
| 41 |
=head1 AUTHOR |
| 42 |
|
| 43 |
Nathaniel Clark <nathaniel.clark@misrule.us> |
| 44 |
|
| 45 |
=head1 LICENSE |
| 46 |
|
| 47 |
GPLv2 |
| 48 |
|
| 49 |
=head1 MAGIC MARKERS |
| 50 |
|
| 51 |
#%# family=contrib |
| 52 |
#%# capabilities=autoconf |
| 53 |
|
| 54 |
=cut |
| 55 |
""" |
| 56 |
|
| 57 |
import re |
| 58 |
import os |
| 59 |
import sys |
| 60 |
import requests |
| 61 |
from lxml import html |
| 62 |
|
| 63 |
|
| 64 |
URL = os.getenv("url", "http://192.168.100.1/RgConnect.asp")
|
| 65 |
HOSTNAME = os.getenv("hostname", None)
|
| 66 |
UPCOUNT = int(os.getenv("up", 4))
|
| 67 |
DOWNCOUNT = int(os.getenv("down", 16))
|
| 68 |
|
| 69 |
if len(sys.argv) == 2: |
| 70 |
if sys.argv[1] == "config": |
| 71 |
if HOSTNAME: |
| 72 |
print("host_name {0}\n".format(HOSTNAME))
|
| 73 |
|
| 74 |
# POWER |
| 75 |
print("multigraph arris_power")
|
| 76 |
print("graph_title Arris Power (dBmV)")
|
| 77 |
print("graph_vlabel Power (dBmV)")
|
| 78 |
print("graph_category network")
|
| 79 |
for i in range(1, DOWNCOUNT + 1): |
| 80 |
print("down_{0}.label Down Ch {1}".format(i, i))
|
| 81 |
print("down_{0}.type GAUGE".format(i))
|
| 82 |
print("down_{0}.draw LINE1".format(i))
|
| 83 |
for i in range(1, UPCOUNT + 1): |
| 84 |
print("up_{0}.label Up Ch {1}".format(i, i))
|
| 85 |
print("up_{0}.type GAUGE".format(i))
|
| 86 |
# print("up_{0}.draw LINE1".format(i))
|
| 87 |
|
| 88 |
for i in range(1, DOWNCOUNT + 1): |
| 89 |
name = "down_{0}".format(i)
|
| 90 |
print("\nmultigraph arris_power.{0}".format(name))
|
| 91 |
print("graph_title Downstream Power for Channel {0} (dBmV)".format(i))
|
| 92 |
print("graph_category network")
|
| 93 |
print("power.label dBmV")
|
| 94 |
print("power.type GAUGE")
|
| 95 |
print("power.draw LINE1")
|
| 96 |
for i in range(1, UPCOUNT + 1): |
| 97 |
name = "up_{0}".format(i)
|
| 98 |
print("\nmultigraph arris_power.{0}".format(name))
|
| 99 |
print("graph_title Upstream Power for Channel {0} (dBmV)".format(i))
|
| 100 |
print("graph_category network")
|
| 101 |
print("power.label dBmV")
|
| 102 |
print("power.type GAUGE")
|
| 103 |
print("power.draw LINE1")
|
| 104 |
|
| 105 |
# SNR |
| 106 |
print("\nmultigraph arris_snr")
|
| 107 |
print("graph_title Arris Signal-to-Noise Ratio (dB)")
|
| 108 |
print("graph_vlabel SNR (dB)")
|
| 109 |
print("graph_category network")
|
| 110 |
for i in range(1, DOWNCOUNT + 1): |
| 111 |
print("down_{0}.label Ch {1}".format(i, i))
|
| 112 |
print("down_{0}.type GAUGE".format(i))
|
| 113 |
print("down_{0}.draw LINE1".format(i))
|
| 114 |
|
| 115 |
for i in range(1, DOWNCOUNT + 1): |
| 116 |
name = "down_{0}".format(i)
|
| 117 |
print("\nmultigraph arris_snr.{0}".format(name))
|
| 118 |
print("graph_title SNR on Channel {0} (dB)".format(i))
|
| 119 |
print("graph_vlabel SNR (dB)")
|
| 120 |
print("graph_category network")
|
| 121 |
print("snr.label dB")
|
| 122 |
print("snr.type GAUGE")
|
| 123 |
print("snr.draw LINE1")
|
| 124 |
|
| 125 |
# ERRORS |
| 126 |
print("\nmultigraph arris_error")
|
| 127 |
print("graph_title Arris Channel Errors")
|
| 128 |
print("graph_category network")
|
| 129 |
print("graph_args --base 1000")
|
| 130 |
print("graph_vlabel errors/sec")
|
| 131 |
print("graph_category network")
|
| 132 |
print("corr.label Corrected")
|
| 133 |
print("corr.type DERIVE")
|
| 134 |
print("corr.min 0")
|
| 135 |
print("uncr.label Uncorrectable")
|
| 136 |
print("uncr.type DERIVE")
|
| 137 |
print("uncr.min 0")
|
| 138 |
print("uncr.warning 1")
|
| 139 |
|
| 140 |
for i in range(1, DOWNCOUNT + 1): |
| 141 |
name = "down_{0}".format(i)
|
| 142 |
print("\nmultigraph arris_error.{0}".format(name))
|
| 143 |
print("graph_title Channel {0} Errors".format(i))
|
| 144 |
print("graph_args --base 1000")
|
| 145 |
print("graph_vlabel errors/sec")
|
| 146 |
print("graph_category network")
|
| 147 |
print("corr.label Correctable")
|
| 148 |
print("corr.type DERIVE")
|
| 149 |
print("corr.min 0")
|
| 150 |
print("uncr.label Uncorrectable")
|
| 151 |
print("uncr.type DERIVE")
|
| 152 |
print("uncr.min 0")
|
| 153 |
print("uncr.warning 1")
|
| 154 |
|
| 155 |
sys.exit(0) |
| 156 |
|
| 157 |
if sys.argv[1] == "autoconfig": |
| 158 |
try: |
| 159 |
page = requests.get(URL) |
| 160 |
except: |
| 161 |
print("no (no router)")
|
| 162 |
else: |
| 163 |
if page.status_code == 200: |
| 164 |
print("yes")
|
| 165 |
else: |
| 166 |
print("no (Bad status code: %d)" % page.status_code)
|
| 167 |
sys.exit(0) |
| 168 |
|
| 169 |
rxblank = re.compile(r"[\x00\n\r\t ]+", re.MULTILINE) |
| 170 |
rxcomment = re.compile(r"<!--.*?-->") |
| 171 |
rxscript = re.compile(r"<script.*?</script>", re.MULTILINE) |
| 172 |
|
| 173 |
page = requests.get(URL) |
| 174 |
data = rxscript.sub("", rxcomment.sub("", rxblank.sub(" ", page.text)))
|
| 175 |
dom = html.fromstring(data) |
| 176 |
|
| 177 |
arr = dom.xpath('//table[contains(@class, "simpleTable")]')
|
| 178 |
downstream = arr[1] |
| 179 |
upstream = arr[2] |
| 180 |
|
| 181 |
trs = downstream.findall("tr")
|
| 182 |
# drop title |
| 183 |
trs.pop(0) |
| 184 |
|
| 185 |
headings = ["".join(x.itertext()).strip() for x in trs.pop(0).findall("td")]
|
| 186 |
# ['Channel', 'Lock Status', 'Modulation', 'Channel ID', 'Frequency', 'Power', 'SNR', 'Corrected', 'Uncorrectables'] |
| 187 |
|
| 188 |
mapper = lambda x, y: (x, y) |
| 189 |
|
| 190 |
# Summation Graphs |
| 191 |
correct = 0 |
| 192 |
uncorr = 0 |
| 193 |
power = {"up": ["U"] * UPCOUNT, "down": ["U"] * DOWNCOUNT}
|
| 194 |
snr = ["U"] * DOWNCOUNT |
| 195 |
for row in trs: |
| 196 |
data = dict( |
| 197 |
map( |
| 198 |
mapper, headings, ["".join(x.itertext()).strip() for x in row.findall("td")]
|
| 199 |
) |
| 200 |
) |
| 201 |
uncorr += int(data["Uncorrectables"]) |
| 202 |
correct += int(data["Corrected"]) |
| 203 |
|
| 204 |
channel = int(data["Channel"]) |
| 205 |
|
| 206 |
print("multigraph arris_power.down_{0}".format(channel))
|
| 207 |
value = data["Power"].split(" ")[0]
|
| 208 |
print("power.value {0}".format(value))
|
| 209 |
power["down"][channel - 1] = value |
| 210 |
|
| 211 |
print("multigraph arris_snr.down_{0}".format(channel))
|
| 212 |
value = data["SNR"].split(" ")[0]
|
| 213 |
print("snr.value {0}".format(value))
|
| 214 |
snr[channel - 1] = value |
| 215 |
|
| 216 |
print("multigraph arris_error.down_{0}".format(channel))
|
| 217 |
print("corr.value {0}".format(data["Corrected"]))
|
| 218 |
print("uncr.value {0}".format(data["Uncorrectables"]))
|
| 219 |
|
| 220 |
# Fill missing |
| 221 |
for i in range(len(trs), DOWNCOUNT): |
| 222 |
print("multigraph arris_power.down_{0}".format(i + 1))
|
| 223 |
print("power.value U")
|
| 224 |
|
| 225 |
print("multigraph arris_snr.down_{0}".format(i + 1))
|
| 226 |
print("snr.value U")
|
| 227 |
|
| 228 |
print("multigraph arris_error.down_{0}".format(i + 1))
|
| 229 |
print("corr.value U")
|
| 230 |
print("uncr.value U")
|
| 231 |
|
| 232 |
print("multigraph arris_error")
|
| 233 |
print("corr.value {0}".format(correct))
|
| 234 |
print("uncr.value {0}".format(uncorr))
|
| 235 |
|
| 236 |
print("multigraph arris_snr")
|
| 237 |
for i in range(0, DOWNCOUNT): |
| 238 |
print("down_{0}.value {1}".format(i + 1, snr[i]))
|
| 239 |
|
| 240 |
trs = upstream.findall("tr")
|
| 241 |
# drop title |
| 242 |
trs.pop(0) |
| 243 |
|
| 244 |
headings = ["".join(x.itertext()).strip() for x in trs.pop(0).findall("td")]
|
| 245 |
# ['Channel', 'Lock Status', 'US Channel Type', 'Channel ID', 'Symbol Rate', 'Frequency', 'Power'] |
| 246 |
for row in trs: |
| 247 |
data = dict( |
| 248 |
map( |
| 249 |
mapper, headings, ["".join(x.itertext()).strip() for x in row.findall("td")]
|
| 250 |
) |
| 251 |
) |
| 252 |
channel = int(data["Channel"]) |
| 253 |
print("multigraph arris_power.up_{0}".format(channel))
|
| 254 |
value = data["Power"].split(" ")[0]
|
| 255 |
print("power.value {0}".format(value))
|
| 256 |
power["up"][channel - 1] = value |
| 257 |
|
| 258 |
# Fill missing |
| 259 |
for i in range(len(trs), UPCOUNT): |
| 260 |
print("multigraph arris_power.up_{0}".format(i + 1))
|
| 261 |
print("power.value U")
|
| 262 |
|
| 263 |
print("multigraph arris_power")
|
| 264 |
for i in range(0, DOWNCOUNT): |
| 265 |
print("down_{0}.value {1}".format(i + 1, power["down"][i]))
|
| 266 |
for i in range(0, UPCOUNT): |
| 267 |
print("up_{0}.value {1}".format(i + 1, power["up"][i]))
|
