Mercurial > ~darius > hgwebdir.cgi > adslstats
annotate speedcheck.py @ 37:4f9a79f733ff
Don't explode when the link is down.
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Sat, 21 Nov 2020 10:31:42 +1030 |
parents | 815e6b61d76e |
children | 1a87c79cf103 |
rev | line source |
---|---|
35
815e6b61d76e
Decode to string to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
32
diff
changeset
|
1 #!/usr/bin/env python3 |
28 | 2 |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
3 import configparser |
28 | 4 import optparse |
5 import os | |
6 import re | |
7 import rrdtool | |
8 import subprocess | |
9 import time | |
10 | |
11 def main(): | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
12 conf = configparser.ConfigParser() |
28 | 13 |
14 conflist = [] | |
15 if ('HOME' in os.environ): | |
16 conflist.append(os.path.expanduser('~/.speedcheck.ini')) | |
17 conf.read(conflist) | |
18 | |
19 usage = '''%prog [options]''' | |
20 parser = optparse.OptionParser(usage) | |
21 parser.add_option('-v', '--verbose', action="store_true", default=False, | |
22 help="Enable debug output") | |
23 parser.add_option('-r', '--rrd', action="store", help="Path to RRD") | |
24 parser.add_option('-g', '--graphdir', action="store", help="Directory for graphs") | |
25 | |
26 (opts, args) = parser.parse_args() | |
27 if opts.rrd == None: | |
28 if conf.has_option('global', 'rrd'): | |
29 opts.rrd = conf.get('global', 'rrd') | |
30 else: | |
31 parser.error('Path to RRD must be specified in either the ini or on the command line') | |
32 | |
33 if opts.graphdir == None: | |
34 if conf.has_option('global', 'graphdir'): | |
35 opts.graphdir = conf.get('global', 'graphdir') | |
36 else: | |
37 parser.error('Graph directory must be specified in either the ini or on the command line') | |
38 | |
39 if opts.verbose: | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
40 print('Fetching stats...') |
28 | 41 stats = fetchstats(conf) |
42 if opts.verbose: | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
43 print(stats) |
28 | 44 if opts.verbose: |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
45 print('Updating RRD') |
28 | 46 updaterrd(opts.rrd, stats) |
47 if opts.verbose: | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
48 print('Updating graph') |
28 | 49 graphrrd(opts.rrd, opts.graphdir) |
50 | |
51 def fetchstats(conf): | |
52 stats = {} | |
53 if conf.has_option('global', 'neardl'): | |
54 stats['neardl'] = testdl(conf.get('global', 'neardl')) | |
55 if conf.has_option('global', 'nearul'): | |
56 stats['nearul'] = testul(conf.get('global', 'nearul')) | |
57 if conf.has_option('global', 'nearping'): | |
58 stats['nearpl'], stats['nearlat'] = testping(conf.get('global', 'nearping')) | |
59 if conf.has_option('global', 'fardl'): | |
60 stats['fardl'] = testdl(conf.get('global', 'fardl')) | |
61 if conf.has_option('global', 'farul'): | |
62 stats['farul'] = testul(conf.get('global', 'farul')) | |
63 if conf.has_option('global', 'farping'): | |
64 stats['farpl'], stats['farlat'] = testping(conf.get('global', 'farping')) | |
65 | |
66 return stats | |
67 | |
68 def testdl(url): | |
69 p = subprocess.Popen(['curl', '-w', '%{speed_download}', '-so', '/dev/null', url], stdout = subprocess.PIPE) | |
70 speed, xxx = p.communicate() | |
71 if p.returncode != 0: | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
72 print('Error %d fetching \'%s\'' % (p.returncode, url)) |
28 | 73 return None |
74 return float(speed) * 8.0 / 1024.0 # convert to kbit/sec | |
75 | |
76 def testping(host): | |
77 p = subprocess.Popen(['ping', '-c', '5', '-t', '8', '-q', host], stdout = subprocess.PIPE) | |
78 stdout, stderr = p.communicate() | |
35
815e6b61d76e
Decode to string to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
32
diff
changeset
|
79 l = stdout.decode('ascii', 'ignore').split('\n') |
28 | 80 if len(l) != 6: |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
81 print('Unable to parse ping line:', l) |
28 | 82 xx, xx, xx, plossline, latline, xx = l |
83 ploss = float(re.match('.* received, ([0-9.]+)% packet loss', plossline).groups()[0]) | |
84 latency = float(re.match('.*stddev = [0-9.]+/([0-9.]+)/.* ms', latline).groups()[0]) | |
85 return ploss, latency | |
86 | |
87 def createrrd(rrdname): | |
88 # Create RRD for upstream/downstream speed, packet loss and | |
89 # latency for near and far site | |
90 # Do a test every half and hour | |
91 # Average 2 for hourly stats (keep 168 - a weeks worth) | |
92 # Average 48 for hourly stats (keep 1825 - 5 years worth) | |
93 # Detemine minimum & maximum for an hour and keep a weeks worth. | |
94 rrdtool.create(rrdname, | |
95 '--step', '300', | |
96 'DS:neardl:GAUGE:3600:0:U', | |
97 'DS:nearul:GAUGE:3600:0:U', | |
98 'DS:nearpl:GAUGE:3600:0:100', | |
99 'DS:nearlat:GAUGE:3600:0:U', | |
100 'DS:fardl:GAUGE:3600:0:U', | |
101 'DS:farul:GAUGE:3600:0:U', | |
102 'DS:farpl:GAUGE:3600:0:100', | |
103 'DS:farlat:GAUGE:3600:0:U', | |
104 'RRA:AVERAGE:0.1:2:168', | |
105 'RRA:AVERAGE:0.1:48:1825', | |
106 'RRA:MIN:0.1:2:168', | |
107 'RRA:MAX:0.1:2:168', | |
108 ) | |
109 | |
110 def updaterrd(rrdname, stats): | |
111 try: | |
112 os.stat(rrdname) | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
113 except OSError as e: |
28 | 114 if e.errno == 2: |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
115 print('Creating RRD...') |
28 | 116 createrrd(rrdname) |
117 s = '%d:' % (int(time.time())) | |
118 for a in ['neardl', 'nearul', 'nearpl', 'nearlat', 'fardl', 'farul', 'farpl', 'farlat']: | |
119 if a in stats: | |
120 s += '%f:' % (stats[a]) | |
121 else: | |
122 s += 'U:' | |
123 s = s[0:-1] | |
124 rrdtool.update(rrdname, s) | |
125 | |
126 def graphrrd(rrdname, graphdir): | |
127 latencyargs = ( | |
128 '-a', 'SVG', | |
129 '--vertical-label', 'milliseconds', | |
130 | |
131 'DEF:nearlat=%s:nearlat:AVERAGE' % rrdname, | |
132 'DEF:nearlatmin=%s:nearlat:MIN' % rrdname, | |
133 'DEF:nearlatmax=%s:nearlat:MAX' % rrdname, | |
134 'CDEF:nearlatdif=nearlatmax,nearlatmin,-', | |
135 | |
136 'LINE0.001:nearlatmin#000000:', | |
137 'AREA:nearlatdif#00dc76::STACK', | |
138 'LINE1:nearlatmax#00ff00:Near latency', | |
139 | |
140 'DEF:farlat=%s:farlat:AVERAGE' % rrdname, | |
141 'DEF:farlatmin=%s:farlat:MIN' % rrdname, | |
142 'DEF:farlatmax=%s:farlat:MAX' % rrdname, | |
143 'CDEF:farlatdif=farlatmax,farlatmin,-', | |
144 | |
145 'LINE0.001:farlatmin#000000:', | |
146 'AREA:farlatdif#dc0076::STACK', | |
147 'LINE1:farlatmax#ff0000:Far packetloss', | |
148 | |
149 'DEF:nearpl=%s:nearpl:AVERAGE' % rrdname, | |
150 'LINE1:nearpl#0000ff:Near packet loss (%)', | |
151 | |
152 'DEF:farpl=%s:farpl:AVERAGE' % rrdname, | |
153 'LINE1:nearpl#ffff00:Far packet loss (%)', | |
154 ) | |
155 rrdtool.graph('%s/latency-hour-link.svg' % (graphdir), | |
156 '--width', '768', | |
157 '--height', '256', | |
158 '--start', 'end - 7d', | |
159 '--end', 'now', | |
160 *latencyargs) | |
161 rrdtool.graph('%s/latency-daily-link.svg' % (graphdir), | |
162 '--width', '768', | |
163 '--height', '256', | |
164 '--start', 'end - 365d', | |
165 '--end', 'now', | |
166 *latencyargs) | |
167 | |
168 bwargs = ( | |
169 '-a', 'SVG', | |
170 '-X', '0', | |
171 '--vertical-label', 'kbit/sec', | |
172 | |
173 'DEF:neardl=%s:neardl:AVERAGE' % rrdname, | |
174 'DEF:neardlmin=%s:neardl:MIN' % rrdname, | |
175 'DEF:neardlmax=%s:neardl:MAX' % rrdname, | |
176 'CDEF:neardldif=neardlmax,neardlmin,-', | |
177 | |
178 'LINE0.001:neardlmin#000000:', | |
179 'AREA:neardldif#00dc76::STACK', | |
180 'LINE1:neardlmax#00ff00:Near download', | |
181 | |
182 'DEF:fardl=%s:fardl:AVERAGE' % rrdname, | |
183 'DEF:fardlmin=%s:fardl:MIN' % rrdname, | |
184 'DEF:fardlmax=%s:fardl:MAX' % rrdname, | |
185 'CDEF:fardldif=fardlmax,fardlmin,-', | |
186 | |
187 'LINE0.001:fardlmin#000000:', | |
188 'AREA:fardldif#dc0076::STACK', | |
189 'LINE1:fardlmax#ff0000:Far download', | |
190 ) | |
191 rrdtool.graph('%s/bw-hour-link.svg' % (graphdir), | |
192 '--width', '768', | |
193 '--height', '256', | |
194 '--start', 'end - 1d', | |
195 '--end', 'now', | |
196 *bwargs) | |
197 rrdtool.graph('%s/bw-daily-link.svg' % (graphdir), | |
198 '--width', '768', | |
199 '--height', '256', | |
200 '--start', 'end - 7d', | |
201 '--end', 'now', | |
202 *bwargs) | |
203 | |
204 if __name__ == '__main__': | |
205 main() |