Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / router / arris-sb6183 @ 1671e356

Historique | Voir | Annoter | Télécharger (8,01 ko)

1 1671e356 Nathaniel Clark
#!/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 accessable 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]))