Projet

Général

Profil

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

root / plugins / router / arris-sb6183 @ 77e53059

Historique | Voir | Annoter | Télécharger (10,2 ko)

1
#!/usr/bin/env python3
2

    
3
# Copyright 2020 Nathaniel Clark <nathaniel.clark@misrule.us>
4
#
5
# 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
#
9
# 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
#
14
# 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
#
18

    
19
"""
20
=head1 NAME
21

    
22
arris-sb6183 - Health monitoring plugin for Arris SB6183 Cable Modem
23

    
24
=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
* uptime
31

    
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
=head1 CONFIGURATION
37

    
38
Make sure 192.168.100.1 is accessible through your firewall.
39

    
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
=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
=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
from urllib import request
76

    
77
HOSTNAME = os.getenv("hostname", None)
78
STATUS_URL = "http://192.168.100.1/RgConnect.asp"
79
INFO_URL = "http://192.168.100.1/RgSwInfo.asp"
80
UPCOUNT = 4
81
DOWNCOUNT = 16
82

    
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
        # 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
        # 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
            print("up_{0}.draw LINE1".format(i))
117

    
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
            from lxml import html
190

    
191
            resp = request.urlopen(STATUS_URL)
192
        except ImportError:
193
            print("no (missing lxml module)")
194
        except OSError:
195
            print("no (no router)")
196
        else:
197
            if resp.status == 200:
198
                print("yes")
199
            else:
200
                print("no (Bad status code: %d)" % page.status_code)
201
        sys.exit(0)
202

    
203
from lxml import html
204

    
205
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

    
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
        "",
227
        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

    
246
    date = "".join(trs[0].findall("td")[1].itertext()).strip()
247

    
248
    arr = date.split(" ")
249
    rx = re.compile(r"[hms]")
250
    days = int(arr[0])
251
    hms = rx.sub("", arr[2]).split(":")
252

    
253
    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

    
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
        zip(headings, ["".join(x.itertext()).strip() for x in row.findall("td")])
282
    )
283
    uncorr += int(data["Uncorrectables"])
284
    correct += int(data["Corrected"])
285

    
286
    channel = int(data["Channel"])
287

    
288
    print("\nmultigraph arris_power.down_{0}".format(channel))
289
    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
    print("\nmultigraph arris_power.down_{0}".format(i + 1))
305
    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
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

    
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
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

    
334
for row in trs:
335
    data = dict(
336
        zip(headings, ["".join(x.itertext()).strip() for x in row.findall("td")])
337
    )
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]))