234 lines
6.8 KiB
Python
Executable File
234 lines
6.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
=pod
|
|
|
|
=encoding utf8
|
|
|
|
=head1 NAME
|
|
|
|
technicolor_tc8715d - Munin plugin for graphing statistics from
|
|
Technicolor TC8715D cable modems.
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
The following values are graphed:
|
|
|
|
=over
|
|
|
|
=item *
|
|
|
|
upstream and downstream power levels
|
|
|
|
=item *
|
|
|
|
downstream signal to noise ratio
|
|
|
|
=item *
|
|
|
|
downstream signal statistics (codeword counts)
|
|
|
|
=back
|
|
|
|
=head1 USAGE
|
|
|
|
Install as you would with any Munin plugin. No configuration is
|
|
needed. Requires the multigraph and dirtyconfig capabilities available
|
|
in munin 2.0 and newer.
|
|
|
|
=head1 NOTES
|
|
|
|
Developed and tested with Python 3.7.3, Technicolor TC8715D cable
|
|
modem hardware revision 1.1, software image name
|
|
TC8715D-01.EF.04.38.00-180405-S-FF9-D.img, advanced services
|
|
2.6.30-1.0.11mp1-g24a0ad5-dirty.
|
|
|
|
=head1 DEVELOPMENT
|
|
|
|
The latest version of this plugin can be found in the munin contrib
|
|
repository at https://github.com/munin-monitoring/contrib. Issues
|
|
with this plugin may be reported there. Patches accepted through the
|
|
normal github process of forking the repository and submitting a
|
|
pull request with your commits.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Copyright © 2019 Kenyon Ralph <kenyon@kenyonralph.com>
|
|
|
|
=head1 LICENSE
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
=cut
|
|
|
|
"""
|
|
|
|
import html.parser
|
|
import urllib.request
|
|
import sys
|
|
|
|
|
|
class TechnicolorHTMLParser(html.parser.HTMLParser):
|
|
def __init__(self):
|
|
html.parser.HTMLParser.__init__(self)
|
|
self.signaldatapage = list()
|
|
|
|
# Number of downstream channels.
|
|
self.downstream_channels = 0
|
|
|
|
# Number of upstream channels.
|
|
self.upstream_channels = 0
|
|
|
|
self.downstream_SNRs = list()
|
|
self.downstream_powers = list()
|
|
self.upstream_powers = list()
|
|
self.unerrored_codewords = list()
|
|
self.correctable_codewords = list()
|
|
self.uncorrectable_codewords = list()
|
|
|
|
def handle_data(self, data):
|
|
data = data.strip()
|
|
if data != "":
|
|
self.signaldatapage.append(data)
|
|
|
|
def process(self):
|
|
# Delete the junk before the statistics start. This list
|
|
# should start with 'Downstream' after this deletion.
|
|
del self.signaldatapage[0:119]
|
|
|
|
index_positions = [i for i, x in enumerate(self.signaldatapage) if x == "Index"]
|
|
lock_status_positions = [
|
|
i for i, x in enumerate(self.signaldatapage) if x == "Lock Status"
|
|
]
|
|
self.downstream_channels = lock_status_positions[0] - index_positions[0] - 1
|
|
self.upstream_channels = lock_status_positions[1] - index_positions[1] - 1
|
|
|
|
self.downstream_SNRs = self.signaldatapage[
|
|
6 + 3 * self.downstream_channels : 22 + 3 * self.downstream_channels
|
|
]
|
|
self.downstream_SNRs = [x.split()[0] for x in self.downstream_SNRs]
|
|
|
|
self.downstream_powers = self.signaldatapage[
|
|
7 + 4 * self.downstream_channels : 23 + 4 * self.downstream_channels
|
|
]
|
|
self.downstream_powers = [x.split()[0] for x in self.downstream_powers]
|
|
|
|
self.upstream_powers = self.signaldatapage[
|
|
15
|
|
+ 6 * self.downstream_channels
|
|
+ 4 * self.upstream_channels : 15
|
|
+ 6 * self.downstream_channels
|
|
+ 5 * self.upstream_channels
|
|
]
|
|
self.upstream_powers = [x.split()[0] for x in self.upstream_powers]
|
|
|
|
self.unerrored_codewords = self.signaldatapage[
|
|
19
|
|
+ 6 * self.downstream_channels
|
|
+ 7 * self.upstream_channels : 19
|
|
+ 7 * self.downstream_channels
|
|
+ 7 * self.upstream_channels
|
|
]
|
|
|
|
self.correctable_codewords = self.signaldatapage[
|
|
20
|
|
+ 7 * self.downstream_channels
|
|
+ 7 * self.upstream_channels : 20
|
|
+ 8 * self.downstream_channels
|
|
+ 7 * self.upstream_channels
|
|
]
|
|
|
|
self.uncorrectable_codewords = self.signaldatapage[
|
|
21
|
|
+ 8 * self.downstream_channels
|
|
+ 7 * self.upstream_channels : 21
|
|
+ 9 * self.downstream_channels
|
|
+ 7 * self.upstream_channels
|
|
]
|
|
|
|
|
|
if len(sys.argv) != 2 or sys.argv[1] != "config":
|
|
print(
|
|
"Error: plugin designed for the dirtyconfig protocol, must be run with the config argument"
|
|
)
|
|
sys.exit(1)
|
|
|
|
parser = TechnicolorHTMLParser()
|
|
|
|
for line in urllib.request.urlopen("http://192.168.100.1/vendor_network.asp"):
|
|
parser.feed(line.decode())
|
|
|
|
parser.process()
|
|
|
|
print(
|
|
"""multigraph technicolor_tc8715d_power
|
|
graph_title Technicolor TC8715D Cable Modem Power
|
|
graph_vlabel Signal Strength (dBmV)
|
|
graph_info This graph shows the channel power values reported by a Technicolor TC8715D cable modem.
|
|
graph_category network"""
|
|
)
|
|
|
|
for i in range(parser.downstream_channels):
|
|
print(
|
|
f"""ds_power_{i+1}.label Channel {i+1} Downstream Power
|
|
ds_power_{i+1}.type GAUGE
|
|
ds_power_{i+1}.value {parser.downstream_powers[i]}"""
|
|
)
|
|
|
|
for i in range(parser.upstream_channels):
|
|
print(
|
|
f"""us_power_{i+1}.label Channel {i+1} Upstream Power
|
|
us_power_{i+1}.type GAUGE
|
|
us_power_{i+1}.value {parser.upstream_powers[i]}"""
|
|
)
|
|
|
|
print(
|
|
"""multigraph technicolor_tc8715d_snr
|
|
graph_title Technicolor TC8715D Cable Modem SNR
|
|
graph_vlabel Signal-to-Noise Ratio (dB)
|
|
graph_info Downstream signal-to-noise ratio reported by a Technicolor TC8715D cable modem.
|
|
graph_category network"""
|
|
)
|
|
|
|
for i in range(parser.downstream_channels):
|
|
print(
|
|
f"""snr_{i+1}.label Channel {i+1} SNR
|
|
snr_{i+1}.type GAUGE
|
|
snr_{i+1}.value {parser.downstream_SNRs[i]}"""
|
|
)
|
|
|
|
print(
|
|
"""multigraph technicolor_tc8715d_codewords
|
|
graph_title Technicolor TC8715D Cable Modem Codewords
|
|
graph_vlabel Codewords/${graph_period}
|
|
graph_info Downstream codeword rates reported by a Technicolor TC8715D cable modem.
|
|
graph_category network"""
|
|
)
|
|
|
|
for i in range(parser.downstream_channels):
|
|
print(
|
|
f"""unerr_{i+1}.label Channel {i+1} Unerrored Codewords
|
|
unerr_{i+1}.type DERIVE
|
|
unerr_{i+1}.min 0
|
|
unerr_{i+1}.value {parser.unerrored_codewords[i]}
|
|
corr_{i+1}.label Channel {i+1} Correctable Codewords
|
|
corr_{i+1}.type DERIVE
|
|
corr_{i+1}.min 0
|
|
corr_{i+1}.value {parser.correctable_codewords[i]}
|
|
uncorr_{i+1}.label Channel {i+1} Uncorrectable Codewords
|
|
uncorr_{i+1}.type DERIVE
|
|
uncorr_{i+1}.min 0
|
|
uncorr_{i+1}.value {parser.uncorrectable_codewords[i]}"""
|
|
)
|