Projet

Général

Profil

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

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