add plugin for Technicolor TC8715D cable modem stats

This commit is contained in:
Kenyon Ralph 2019-12-07 22:41:20 -08:00 committed by Lars Kruse
parent 1fc177ce9a
commit 6abc523a58
1 changed files with 233 additions and 0 deletions

View File

@ -0,0 +1,233 @@
#!/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 This graph shows the 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 This graph shows the 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]}"""
)