Projet

Général

Profil

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

root / plugins / router / arris-sb6183 @ 7b078749

Historique | Voir | Annoter | Télécharger (8,66 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

    
31
The values are retrieved from the cable modem's status web pages at
32
192.168.100.1. So, this plugin must be installed on a munin node
33
which can access those pages.
34

    
35
=head1 CONFIGURATION
36

    
37
Make sure 192.168.100.1 is accessible through your firewall.
38

    
39
To have this register with munin as it's own host set the "env.hostname" in config.
40
Also ensure that the hostname set is listed in munin.conf.
41

    
42
[arris*]
43
env.hostname modem
44

    
45
=head1 TESTING
46

    
47
Developed and tested with:
48
firmware:         D30CM-OSPREY-2.4.0.1-GA-02-NOSH
49
hardware version: 1
50

    
51
=head1 VERSION
52

    
53
0.0.1
54

    
55
=head1 AUTHOR
56

    
57
Nathaniel Clark <nathaniel.clark@misrule.us>
58

    
59
=head1 LICENSE
60

    
61
GPLv2
62

    
63
=head1 MAGIC MARKERS
64

    
65
 #%# family=contrib
66
 #%# capabilities=autoconf
67

    
68
=cut
69
"""
70

    
71
import re
72
import os
73
import sys
74
from urllib import request
75

    
76
HOSTNAME = os.getenv("hostname", None)
77
URL = "http://192.168.100.1/RgConnect.asp"
78
UPCOUNT = 4
79
DOWNCOUNT = 16
80

    
81
if len(sys.argv) == 2:
82
    if sys.argv[1] == "config":
83
        if HOSTNAME:
84
            print("host_name {0}\n".format(HOSTNAME))
85

    
86
        # POWER
87
        print("multigraph arris_power")
88
        print("graph_title Arris Power (dBmV)")
89
        print("graph_vlabel Power (dBmV)")
90
        print("graph_category network")
91
        for i in range(1, DOWNCOUNT + 1):
92
            print("down_{0}.label Down Ch {1}".format(i, i))
93
            print("down_{0}.type GAUGE".format(i))
94
            print("down_{0}.draw LINE1".format(i))
95
        for i in range(1, UPCOUNT + 1):
96
            print("up_{0}.label Up Ch {1}".format(i, i))
97
            print("up_{0}.type GAUGE".format(i))
98
            print("up_{0}.draw LINE1".format(i))
99

    
100
        for i in range(1, DOWNCOUNT + 1):
101
            name = "down_{0}".format(i)
102
            print("\nmultigraph arris_power.{0}".format(name))
103
            print("graph_title Downstream Power for Channel {0} (dBmV)".format(i))
104
            print("graph_category network")
105
            print("power.label dBmV")
106
            print("power.type GAUGE")
107
            print("power.draw LINE1")
108
        for i in range(1, UPCOUNT + 1):
109
            name = "up_{0}".format(i)
110
            print("\nmultigraph arris_power.{0}".format(name))
111
            print("graph_title Upstream Power for Channel {0} (dBmV)".format(i))
112
            print("graph_category network")
113
            print("power.label dBmV")
114
            print("power.type GAUGE")
115
            print("power.draw LINE1")
116

    
117
        # SNR
118
        print("\nmultigraph arris_snr")
119
        print("graph_title Arris Signal-to-Noise Ratio (dB)")
120
        print("graph_vlabel SNR (dB)")
121
        print("graph_category network")
122
        for i in range(1, DOWNCOUNT + 1):
123
            print("down_{0}.label Ch {1}".format(i, i))
124
            print("down_{0}.type GAUGE".format(i))
125
            print("down_{0}.draw LINE1".format(i))
126

    
127
        for i in range(1, DOWNCOUNT + 1):
128
            name = "down_{0}".format(i)
129
            print("\nmultigraph arris_snr.{0}".format(name))
130
            print("graph_title SNR on Channel {0} (dB)".format(i))
131
            print("graph_vlabel SNR (dB)")
132
            print("graph_category network")
133
            print("snr.label dB")
134
            print("snr.type GAUGE")
135
            print("snr.draw LINE1")
136

    
137
        # ERRORS
138
        print("\nmultigraph arris_error")
139
        print("graph_title Arris Channel Errors")
140
        print("graph_category network")
141
        print("graph_args --base 1000")
142
        print("graph_vlabel errors/sec")
143
        print("graph_category network")
144
        print("corr.label Corrected")
145
        print("corr.type DERIVE")
146
        print("corr.min 0")
147
        print("uncr.label Uncorrectable")
148
        print("uncr.type DERIVE")
149
        print("uncr.min 0")
150
        print("uncr.warning 1")
151

    
152
        for i in range(1, DOWNCOUNT + 1):
153
            name = "down_{0}".format(i)
154
            print("\nmultigraph arris_error.{0}".format(name))
155
            print("graph_title Channel {0} Errors".format(i))
156
            print("graph_args --base 1000")
157
            print("graph_vlabel errors/sec")
158
            print("graph_category network")
159
            print("corr.label Correctable")
160
            print("corr.type DERIVE")
161
            print("corr.min 0")
162
            print("uncr.label Uncorrectable")
163
            print("uncr.type DERIVE")
164
            print("uncr.min 0")
165
            print("uncr.warning 1")
166

    
167
        sys.exit(0)
168

    
169
    if sys.argv[1] == "autoconfig":
170
        try:
171
            from lxml import html
172

    
173
            resp = request.urlopen(URL)
174
        except ImportError:
175
            print("no (missing lxml module)")
176
        except OSError:
177
            print("no (no router)")
178
        else:
179
            if resp.status == 200:
180
                print("yes")
181
            else:
182
                print("no (Bad status code: %d)" % page.status_code)
183
        sys.exit(0)
184

    
185
from lxml import html
186

    
187
rxblank = re.compile(r"[\x00\n\r\t ]+", re.MULTILINE)
188
rxcomment = re.compile(r"<!--.*?-->")
189
rxscript = re.compile(r"<script.*?</script>", re.MULTILINE)
190

    
191
resp = request.urlopen(URL)
192
data = rxscript.sub(
193
    "",
194
    rxcomment.sub(
195
        "",
196
        rxblank.sub(" ", "".join(map(lambda x: x.decode("utf-8"), resp.readlines()))),
197
    ),
198
)
199
dom = html.fromstring(data)
200

    
201
arr = dom.xpath('//table[contains(@class, "simpleTable")]')
202
downstream = arr[1]
203
upstream = arr[2]
204

    
205
trs = downstream.findall("tr")
206
# drop title
207
trs.pop(0)
208

    
209
headings = ["".join(x.itertext()).strip() for x in trs.pop(0).findall("td")]
210
# ['Channel', 'Lock Status', 'Modulation', 'Channel ID', 'Frequency', 'Power', 'SNR', 'Corrected', 'Uncorrectables']
211

    
212
# Summation Graphs
213
correct = 0
214
uncorr = 0
215
power = {"up": ["U"] * UPCOUNT, "down": ["U"] * DOWNCOUNT}
216
snr = ["U"] * DOWNCOUNT
217
for row in trs:
218
    data = dict(
219
        zip(headings, ["".join(x.itertext()).strip() for x in row.findall("td")])
220
    )
221
    uncorr += int(data["Uncorrectables"])
222
    correct += int(data["Corrected"])
223

    
224
    channel = int(data["Channel"])
225

    
226
    print("multigraph arris_power.down_{0}".format(channel))
227
    value = data["Power"].split(" ")[0]
228
    print("power.value {0}".format(value))
229
    power["down"][channel - 1] = value
230

    
231
    print("multigraph arris_snr.down_{0}".format(channel))
232
    value = data["SNR"].split(" ")[0]
233
    print("snr.value {0}".format(value))
234
    snr[channel - 1] = value
235

    
236
    print("multigraph arris_error.down_{0}".format(channel))
237
    print("corr.value {0}".format(data["Corrected"]))
238
    print("uncr.value {0}".format(data["Uncorrectables"]))
239

    
240
# Fill missing
241
for i in range(len(trs), DOWNCOUNT):
242
    print("multigraph arris_power.down_{0}".format(i + 1))
243
    print("power.value U")
244

    
245
    print("multigraph arris_snr.down_{0}".format(i + 1))
246
    print("snr.value U")
247

    
248
    print("multigraph arris_error.down_{0}".format(i + 1))
249
    print("corr.value U")
250
    print("uncr.value U")
251

    
252
print("multigraph arris_error")
253
print("corr.value {0}".format(correct))
254
print("uncr.value {0}".format(uncorr))
255

    
256
print("multigraph arris_snr")
257
for i in range(0, DOWNCOUNT):
258
    print("down_{0}.value {1}".format(i + 1, snr[i]))
259

    
260
trs = upstream.findall("tr")
261
# drop title
262
trs.pop(0)
263

    
264
headings = ["".join(x.itertext()).strip() for x in trs.pop(0).findall("td")]
265
# ['Channel', 'Lock Status', 'US Channel Type', 'Channel ID', 'Symbol Rate', 'Frequency', 'Power']
266
for row in trs:
267
    data = dict(
268
        zip(headings, ["".join(x.itertext()).strip() for x in row.findall("td")])
269
    )
270
    channel = int(data["Channel"])
271
    print("multigraph arris_power.up_{0}".format(channel))
272
    value = data["Power"].split(" ")[0]
273
    print("power.value {0}".format(value))
274
    power["up"][channel - 1] = value
275

    
276
# Fill missing
277
for i in range(len(trs), UPCOUNT):
278
    print("multigraph arris_power.up_{0}".format(i + 1))
279
    print("power.value U")
280

    
281
print("multigraph arris_power")
282
for i in range(0, DOWNCOUNT):
283
    print("down_{0}.value {1}".format(i + 1, power["down"][i]))
284
for i in range(0, UPCOUNT):
285
    print("up_{0}.value {1}".format(i + 1, power["up"][i]))