root / plugins / router / arris-sb6183 @ 77e53059
Historique | Voir | Annoter | Télécharger (10,2 ko)
| 1 | 7b078749 | Nathaniel Clark | #!/usr/bin/env python3 |
|---|---|---|---|
| 2 | 1671e356 | Nathaniel Clark | |
| 3 | 7b078749 | Nathaniel Clark | # Copyright 2020 Nathaniel Clark <nathaniel.clark@misrule.us> |
| 4 | 1671e356 | Nathaniel Clark | # |
| 5 | 7b078749 | Nathaniel Clark | # This program is free software; you can redistribute it and/or modify |
| 6 | # it under the terms of the GNU Library General Public License as published by |
||
| 7 | # the Free Software Foundation; version 2 only |
||
| 8 | 1671e356 | Nathaniel Clark | # |
| 9 | 7b078749 | Nathaniel Clark | # This program is distributed in the hope that it will be useful, |
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 12 | # GNU Library General Public License for more details. |
||
| 13 | 1671e356 | Nathaniel Clark | # |
| 14 | 7b078749 | Nathaniel Clark | # You should have received a copy of the GNU Library General Public License |
| 15 | # along with this program; if not, write to the Free Software |
||
| 16 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
||
| 17 | 1671e356 | Nathaniel Clark | # |
| 18 | |||
| 19 | """ |
||
| 20 | =head1 NAME |
||
| 21 | |||
| 22 | arris-sb6183 - Health monitoring plugin for Arris SB6183 Cable Modem |
||
| 23 | |||
| 24 | 7b078749 | Nathaniel Clark | =head1 DESCRIPTION |
| 25 | |||
| 26 | This provides the following multigraphs: |
||
| 27 | * upstream and downstream power levels |
||
| 28 | * downstream signal to noise ratio |
||
| 29 | * downstream error counts |
||
| 30 | 77e53059 | Nathaniel Clark | * uptime |
| 31 | 7b078749 | Nathaniel Clark | |
| 32 | The values are retrieved from the cable modem's status web pages at |
||
| 33 | 192.168.100.1. So, this plugin must be installed on a munin node |
||
| 34 | which can access those pages. |
||
| 35 | |||
| 36 | 1671e356 | Nathaniel Clark | =head1 CONFIGURATION |
| 37 | |||
| 38 | 13e4b4aa | Nathaniel Clark | Make sure 192.168.100.1 is accessible through your firewall. |
| 39 | 1671e356 | Nathaniel Clark | |
| 40 | To have this register with munin as it's own host set the "env.hostname" in config. |
||
| 41 | Also ensure that the hostname set is listed in munin.conf. |
||
| 42 | |||
| 43 | [arris*] |
||
| 44 | env.hostname modem |
||
| 45 | |||
| 46 | 7b078749 | Nathaniel Clark | =head1 TESTING |
| 47 | |||
| 48 | Developed and tested with: |
||
| 49 | firmware: D30CM-OSPREY-2.4.0.1-GA-02-NOSH |
||
| 50 | hardware version: 1 |
||
| 51 | |||
| 52 | 1671e356 | Nathaniel Clark | =head1 VERSION |
| 53 | |||
| 54 | 0.0.1 |
||
| 55 | |||
| 56 | =head1 AUTHOR |
||
| 57 | |||
| 58 | Nathaniel Clark <nathaniel.clark@misrule.us> |
||
| 59 | |||
| 60 | =head1 LICENSE |
||
| 61 | |||
| 62 | GPLv2 |
||
| 63 | |||
| 64 | =head1 MAGIC MARKERS |
||
| 65 | |||
| 66 | #%# family=contrib |
||
| 67 | #%# capabilities=autoconf |
||
| 68 | |||
| 69 | =cut |
||
| 70 | """ |
||
| 71 | |||
| 72 | import re |
||
| 73 | import os |
||
| 74 | import sys |
||
| 75 | 7b078749 | Nathaniel Clark | from urllib import request |
| 76 | 1671e356 | Nathaniel Clark | |
| 77 | HOSTNAME = os.getenv("hostname", None)
|
||
| 78 | 77e53059 | Nathaniel Clark | STATUS_URL = "http://192.168.100.1/RgConnect.asp" |
| 79 | INFO_URL = "http://192.168.100.1/RgSwInfo.asp" |
||
| 80 | 7b078749 | Nathaniel Clark | UPCOUNT = 4 |
| 81 | DOWNCOUNT = 16 |
||
| 82 | 1671e356 | Nathaniel Clark | |
| 83 | if len(sys.argv) == 2: |
||
| 84 | if sys.argv[1] == "config": |
||
| 85 | if HOSTNAME: |
||
| 86 | print("host_name {0}\n".format(HOSTNAME))
|
||
| 87 | |||
| 88 | 77e53059 | Nathaniel Clark | # UPTIME |
| 89 | print( |
||
| 90 | """multigraph arris_uptime |
||
| 91 | graph_title Modem Uptime |
||
| 92 | graph_category system |
||
| 93 | graph_args --base 1000 -l 0 |
||
| 94 | graph_vlabel uptime in days |
||
| 95 | graph_scale no |
||
| 96 | graph_category system |
||
| 97 | graph_info This graph shows the number of days that the the host is up and running so far. |
||
| 98 | uptime.label uptime |
||
| 99 | uptime.info The system uptime itself in days. |
||
| 100 | uptime.draw AREA |
||
| 101 | """ |
||
| 102 | ) |
||
| 103 | |||
| 104 | 1671e356 | Nathaniel Clark | # POWER |
| 105 | print("multigraph arris_power")
|
||
| 106 | print("graph_title Arris Power (dBmV)")
|
||
| 107 | print("graph_vlabel Power (dBmV)")
|
||
| 108 | print("graph_category network")
|
||
| 109 | for i in range(1, DOWNCOUNT + 1): |
||
| 110 | print("down_{0}.label Down Ch {1}".format(i, i))
|
||
| 111 | print("down_{0}.type GAUGE".format(i))
|
||
| 112 | print("down_{0}.draw LINE1".format(i))
|
||
| 113 | for i in range(1, UPCOUNT + 1): |
||
| 114 | print("up_{0}.label Up Ch {1}".format(i, i))
|
||
| 115 | print("up_{0}.type GAUGE".format(i))
|
||
| 116 | 7b078749 | Nathaniel Clark | print("up_{0}.draw LINE1".format(i))
|
| 117 | 1671e356 | Nathaniel Clark | |
| 118 | for i in range(1, DOWNCOUNT + 1): |
||
| 119 | name = "down_{0}".format(i)
|
||
| 120 | print("\nmultigraph arris_power.{0}".format(name))
|
||
| 121 | print("graph_title Downstream Power for Channel {0} (dBmV)".format(i))
|
||
| 122 | print("graph_category network")
|
||
| 123 | print("power.label dBmV")
|
||
| 124 | print("power.type GAUGE")
|
||
| 125 | print("power.draw LINE1")
|
||
| 126 | for i in range(1, UPCOUNT + 1): |
||
| 127 | name = "up_{0}".format(i)
|
||
| 128 | print("\nmultigraph arris_power.{0}".format(name))
|
||
| 129 | print("graph_title Upstream Power for Channel {0} (dBmV)".format(i))
|
||
| 130 | print("graph_category network")
|
||
| 131 | print("power.label dBmV")
|
||
| 132 | print("power.type GAUGE")
|
||
| 133 | print("power.draw LINE1")
|
||
| 134 | |||
| 135 | # SNR |
||
| 136 | print("\nmultigraph arris_snr")
|
||
| 137 | print("graph_title Arris Signal-to-Noise Ratio (dB)")
|
||
| 138 | print("graph_vlabel SNR (dB)")
|
||
| 139 | print("graph_category network")
|
||
| 140 | for i in range(1, DOWNCOUNT + 1): |
||
| 141 | print("down_{0}.label Ch {1}".format(i, i))
|
||
| 142 | print("down_{0}.type GAUGE".format(i))
|
||
| 143 | print("down_{0}.draw LINE1".format(i))
|
||
| 144 | |||
| 145 | for i in range(1, DOWNCOUNT + 1): |
||
| 146 | name = "down_{0}".format(i)
|
||
| 147 | print("\nmultigraph arris_snr.{0}".format(name))
|
||
| 148 | print("graph_title SNR on Channel {0} (dB)".format(i))
|
||
| 149 | print("graph_vlabel SNR (dB)")
|
||
| 150 | print("graph_category network")
|
||
| 151 | print("snr.label dB")
|
||
| 152 | print("snr.type GAUGE")
|
||
| 153 | print("snr.draw LINE1")
|
||
| 154 | |||
| 155 | # ERRORS |
||
| 156 | print("\nmultigraph arris_error")
|
||
| 157 | print("graph_title Arris Channel Errors")
|
||
| 158 | print("graph_category network")
|
||
| 159 | print("graph_args --base 1000")
|
||
| 160 | print("graph_vlabel errors/sec")
|
||
| 161 | print("graph_category network")
|
||
| 162 | print("corr.label Corrected")
|
||
| 163 | print("corr.type DERIVE")
|
||
| 164 | print("corr.min 0")
|
||
| 165 | print("uncr.label Uncorrectable")
|
||
| 166 | print("uncr.type DERIVE")
|
||
| 167 | print("uncr.min 0")
|
||
| 168 | print("uncr.warning 1")
|
||
| 169 | |||
| 170 | for i in range(1, DOWNCOUNT + 1): |
||
| 171 | name = "down_{0}".format(i)
|
||
| 172 | print("\nmultigraph arris_error.{0}".format(name))
|
||
| 173 | print("graph_title Channel {0} Errors".format(i))
|
||
| 174 | print("graph_args --base 1000")
|
||
| 175 | print("graph_vlabel errors/sec")
|
||
| 176 | print("graph_category network")
|
||
| 177 | print("corr.label Correctable")
|
||
| 178 | print("corr.type DERIVE")
|
||
| 179 | print("corr.min 0")
|
||
| 180 | print("uncr.label Uncorrectable")
|
||
| 181 | print("uncr.type DERIVE")
|
||
| 182 | print("uncr.min 0")
|
||
| 183 | print("uncr.warning 1")
|
||
| 184 | |||
| 185 | sys.exit(0) |
||
| 186 | |||
| 187 | if sys.argv[1] == "autoconfig": |
||
| 188 | try: |
||
| 189 | 7b078749 | Nathaniel Clark | from lxml import html |
| 190 | |||
| 191 | 77e53059 | Nathaniel Clark | resp = request.urlopen(STATUS_URL) |
| 192 | 7b078749 | Nathaniel Clark | except ImportError: |
| 193 | print("no (missing lxml module)")
|
||
| 194 | except OSError: |
||
| 195 | 1671e356 | Nathaniel Clark | print("no (no router)")
|
| 196 | else: |
||
| 197 | 7b078749 | Nathaniel Clark | if resp.status == 200: |
| 198 | 1671e356 | Nathaniel Clark | print("yes")
|
| 199 | else: |
||
| 200 | print("no (Bad status code: %d)" % page.status_code)
|
||
| 201 | sys.exit(0) |
||
| 202 | |||
| 203 | 7b078749 | Nathaniel Clark | from lxml import html |
| 204 | |||
| 205 | 1671e356 | Nathaniel Clark | rxblank = re.compile(r"[\x00\n\r\t ]+", re.MULTILINE) |
| 206 | rxcomment = re.compile(r"<!--.*?-->") |
||
| 207 | rxscript = re.compile(r"<script.*?</script>", re.MULTILINE) |
||
| 208 | |||
| 209 | 77e53059 | Nathaniel Clark | |
| 210 | def process_url(url): |
||
| 211 | """ |
||
| 212 | Extract simpleTables from page at URL |
||
| 213 | """ |
||
| 214 | try: |
||
| 215 | resp = request.urlopen(url) |
||
| 216 | except OSError: |
||
| 217 | print("failed to contact router", file=sys.stderr)
|
||
| 218 | return [] |
||
| 219 | if resp.status != 200: |
||
| 220 | print( |
||
| 221 | "failed to get status page %d: %s" % (resp.status, resp.reason), |
||
| 222 | file=sys.stderr, |
||
| 223 | ) |
||
| 224 | return [] |
||
| 225 | data = rxscript.sub( |
||
| 226 | 7b078749 | Nathaniel Clark | "", |
| 227 | 77e53059 | Nathaniel Clark | rxcomment.sub( |
| 228 | "", |
||
| 229 | rxblank.sub( |
||
| 230 | " ", "".join(map(lambda x: x.decode("utf-8"), resp.readlines()))
|
||
| 231 | ), |
||
| 232 | ), |
||
| 233 | ) |
||
| 234 | dom = html.fromstring(data) |
||
| 235 | |||
| 236 | return dom.xpath('//table[contains(@class, "simpleTable")]')
|
||
| 237 | |||
| 238 | |||
| 239 | print("multi_graph arris_uptime")
|
||
| 240 | arr = process_url(INFO_URL) |
||
| 241 | if arr: |
||
| 242 | trs = arr[1].findall("tr")
|
||
| 243 | # drop title |
||
| 244 | trs.pop(0) |
||
| 245 | 1671e356 | Nathaniel Clark | |
| 246 | 77e53059 | Nathaniel Clark | date = "".join(trs[0].findall("td")[1].itertext()).strip()
|
| 247 | 1671e356 | Nathaniel Clark | |
| 248 | 77e53059 | Nathaniel Clark | arr = date.split(" ")
|
| 249 | rx = re.compile(r"[hms]") |
||
| 250 | days = int(arr[0]) |
||
| 251 | hms = rx.sub("", arr[2]).split(":")
|
||
| 252 | 1671e356 | Nathaniel Clark | |
| 253 | 77e53059 | Nathaniel Clark | seconds = ((days * 24 + int(hms[0])) * 60 + int(hms[1])) * 60 + int(hms[2]) |
| 254 | print("uptime.value {0}".format(seconds / 86400.0))
|
||
| 255 | else: |
||
| 256 | print("uptime.value U")
|
||
| 257 | |||
| 258 | |||
| 259 | arr = process_url(STATUS_URL) |
||
| 260 | if arr: |
||
| 261 | downstream = arr[1] |
||
| 262 | upstream = arr[2] |
||
| 263 | |||
| 264 | trs = downstream.findall("tr")
|
||
| 265 | # drop title |
||
| 266 | trs.pop(0) |
||
| 267 | |||
| 268 | headings = ["".join(x.itertext()).strip() for x in trs.pop(0).findall("td")]
|
||
| 269 | # ['Channel', 'Lock Status', 'Modulation', 'Channel ID', 'Frequency', 'Power', 'SNR', 'Corrected', 'Uncorrectables'] |
||
| 270 | else: |
||
| 271 | trs = [] |
||
| 272 | headings = [] |
||
| 273 | 1671e356 | Nathaniel Clark | |
| 274 | # Summation Graphs |
||
| 275 | correct = 0 |
||
| 276 | uncorr = 0 |
||
| 277 | power = {"up": ["U"] * UPCOUNT, "down": ["U"] * DOWNCOUNT}
|
||
| 278 | snr = ["U"] * DOWNCOUNT |
||
| 279 | for row in trs: |
||
| 280 | data = dict( |
||
| 281 | 7b078749 | Nathaniel Clark | zip(headings, ["".join(x.itertext()).strip() for x in row.findall("td")])
|
| 282 | 1671e356 | Nathaniel Clark | ) |
| 283 | uncorr += int(data["Uncorrectables"]) |
||
| 284 | correct += int(data["Corrected"]) |
||
| 285 | |||
| 286 | channel = int(data["Channel"]) |
||
| 287 | |||
| 288 | 77e53059 | Nathaniel Clark | print("\nmultigraph arris_power.down_{0}".format(channel))
|
| 289 | 1671e356 | Nathaniel Clark | value = data["Power"].split(" ")[0]
|
| 290 | print("power.value {0}".format(value))
|
||
| 291 | power["down"][channel - 1] = value |
||
| 292 | |||
| 293 | print("multigraph arris_snr.down_{0}".format(channel))
|
||
| 294 | value = data["SNR"].split(" ")[0]
|
||
| 295 | print("snr.value {0}".format(value))
|
||
| 296 | snr[channel - 1] = value |
||
| 297 | |||
| 298 | print("multigraph arris_error.down_{0}".format(channel))
|
||
| 299 | print("corr.value {0}".format(data["Corrected"]))
|
||
| 300 | print("uncr.value {0}".format(data["Uncorrectables"]))
|
||
| 301 | |||
| 302 | # Fill missing |
||
| 303 | for i in range(len(trs), DOWNCOUNT): |
||
| 304 | 77e53059 | Nathaniel Clark | print("\nmultigraph arris_power.down_{0}".format(i + 1))
|
| 305 | 1671e356 | Nathaniel Clark | print("power.value U")
|
| 306 | |||
| 307 | print("multigraph arris_snr.down_{0}".format(i + 1))
|
||
| 308 | print("snr.value U")
|
||
| 309 | |||
| 310 | print("multigraph arris_error.down_{0}".format(i + 1))
|
||
| 311 | print("corr.value U")
|
||
| 312 | print("uncr.value U")
|
||
| 313 | |||
| 314 | print("multigraph arris_error")
|
||
| 315 | 77e53059 | Nathaniel Clark | if arr: |
| 316 | print("corr.value {0}".format(correct))
|
||
| 317 | print("uncr.value {0}".format(uncorr))
|
||
| 318 | else: |
||
| 319 | print("corr.value U")
|
||
| 320 | print("uncr.value U")
|
||
| 321 | 1671e356 | Nathaniel Clark | |
| 322 | print("multigraph arris_snr")
|
||
| 323 | for i in range(0, DOWNCOUNT): |
||
| 324 | print("down_{0}.value {1}".format(i + 1, snr[i]))
|
||
| 325 | |||
| 326 | 77e53059 | Nathaniel Clark | if arr: |
| 327 | trs = upstream.findall("tr")
|
||
| 328 | # drop title |
||
| 329 | trs.pop(0) |
||
| 330 | |||
| 331 | headings = ["".join(x.itertext()).strip() for x in trs.pop(0).findall("td")]
|
||
| 332 | # ['Channel', 'Lock Status', 'US Channel Type', 'Channel ID', 'Symbol Rate', 'Frequency', 'Power'] |
||
| 333 | 1671e356 | Nathaniel Clark | |
| 334 | for row in trs: |
||
| 335 | data = dict( |
||
| 336 | 7b078749 | Nathaniel Clark | zip(headings, ["".join(x.itertext()).strip() for x in row.findall("td")])
|
| 337 | 1671e356 | Nathaniel Clark | ) |
| 338 | channel = int(data["Channel"]) |
||
| 339 | print("multigraph arris_power.up_{0}".format(channel))
|
||
| 340 | value = data["Power"].split(" ")[0]
|
||
| 341 | print("power.value {0}".format(value))
|
||
| 342 | power["up"][channel - 1] = value |
||
| 343 | |||
| 344 | # Fill missing |
||
| 345 | for i in range(len(trs), UPCOUNT): |
||
| 346 | print("multigraph arris_power.up_{0}".format(i + 1))
|
||
| 347 | print("power.value U")
|
||
| 348 | |||
| 349 | print("multigraph arris_power")
|
||
| 350 | for i in range(0, DOWNCOUNT): |
||
| 351 | print("down_{0}.value {1}".format(i + 1, power["down"][i]))
|
||
| 352 | for i in range(0, UPCOUNT): |
||
| 353 | print("up_{0}.value {1}".format(i + 1, power["up"][i])) |
