Mercurial > ~darius > hgwebdir.cgi > adslstats
annotate speedcheck.py @ 39:1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Mon, 04 Jul 2022 13:18:58 +0930 |
parents | 815e6b61d76e |
children | 947fa4062b01 |
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 |
39
1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
Daniel O'Connor <darius@dons.net.au>
parents:
35
diff
changeset
|
83 ploss = re.match('.* received, ([0-9.]+)% packet loss', plossline) |
1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
Daniel O'Connor <darius@dons.net.au>
parents:
35
diff
changeset
|
84 if ploss is not None: |
1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
Daniel O'Connor <darius@dons.net.au>
parents:
35
diff
changeset
|
85 ploss = float(ploss.groups()[0]) |
1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
Daniel O'Connor <darius@dons.net.au>
parents:
35
diff
changeset
|
86 latency = re.match('.*std-?dev = [0-9.]+/([0-9.]+)/.* ms', latline) |
1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
Daniel O'Connor <darius@dons.net.au>
parents:
35
diff
changeset
|
87 if latency is not None: |
1a87c79cf103
Handle new ping which does IPv6 (and spells stddev as std-dev!)
Daniel O'Connor <darius@dons.net.au>
parents:
35
diff
changeset
|
88 latency = float(latency.groups()[0]) |
28 | 89 return ploss, latency |
90 | |
91 def createrrd(rrdname): | |
92 # Create RRD for upstream/downstream speed, packet loss and | |
93 # latency for near and far site | |
94 # Do a test every half and hour | |
95 # Average 2 for hourly stats (keep 168 - a weeks worth) | |
96 # Average 48 for hourly stats (keep 1825 - 5 years worth) | |
97 # Detemine minimum & maximum for an hour and keep a weeks worth. | |
98 rrdtool.create(rrdname, | |
99 '--step', '300', | |
100 'DS:neardl:GAUGE:3600:0:U', | |
101 'DS:nearul:GAUGE:3600:0:U', | |
102 'DS:nearpl:GAUGE:3600:0:100', | |
103 'DS:nearlat:GAUGE:3600:0:U', | |
104 'DS:fardl:GAUGE:3600:0:U', | |
105 'DS:farul:GAUGE:3600:0:U', | |
106 'DS:farpl:GAUGE:3600:0:100', | |
107 'DS:farlat:GAUGE:3600:0:U', | |
108 'RRA:AVERAGE:0.1:2:168', | |
109 'RRA:AVERAGE:0.1:48:1825', | |
110 'RRA:MIN:0.1:2:168', | |
111 'RRA:MAX:0.1:2:168', | |
112 ) | |
113 | |
114 def updaterrd(rrdname, stats): | |
115 try: | |
116 os.stat(rrdname) | |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
117 except OSError as e: |
28 | 118 if e.errno == 2: |
32
1af6865189ce
Update to work with Python 3.
Daniel O'Connor <darius@dons.net.au>
parents:
29
diff
changeset
|
119 print('Creating RRD...') |
28 | 120 createrrd(rrdname) |
121 s = '%d:' % (int(time.time())) | |
122 for a in ['neardl', 'nearul', 'nearpl', 'nearlat', 'fardl', 'farul', 'farpl', 'farlat']: | |
123 if a in stats: | |
124 s += '%f:' % (stats[a]) | |
125 else: | |
126 s += 'U:' | |
127 s = s[0:-1] | |
128 rrdtool.update(rrdname, s) | |
129 | |
130 def graphrrd(rrdname, graphdir): | |
131 latencyargs = ( | |
132 '-a', 'SVG', | |
133 '--vertical-label', 'milliseconds', | |
134 | |
135 'DEF:nearlat=%s:nearlat:AVERAGE' % rrdname, | |
136 'DEF:nearlatmin=%s:nearlat:MIN' % rrdname, | |
137 'DEF:nearlatmax=%s:nearlat:MAX' % rrdname, | |
138 'CDEF:nearlatdif=nearlatmax,nearlatmin,-', | |
139 | |
140 'LINE0.001:nearlatmin#000000:', | |
141 'AREA:nearlatdif#00dc76::STACK', | |
142 'LINE1:nearlatmax#00ff00:Near latency', | |
143 | |
144 'DEF:farlat=%s:farlat:AVERAGE' % rrdname, | |
145 'DEF:farlatmin=%s:farlat:MIN' % rrdname, | |
146 'DEF:farlatmax=%s:farlat:MAX' % rrdname, | |
147 'CDEF:farlatdif=farlatmax,farlatmin,-', | |
148 | |
149 'LINE0.001:farlatmin#000000:', | |
150 'AREA:farlatdif#dc0076::STACK', | |
151 'LINE1:farlatmax#ff0000:Far packetloss', | |
152 | |
153 'DEF:nearpl=%s:nearpl:AVERAGE' % rrdname, | |
154 'LINE1:nearpl#0000ff:Near packet loss (%)', | |
155 | |
156 'DEF:farpl=%s:farpl:AVERAGE' % rrdname, | |
157 'LINE1:nearpl#ffff00:Far packet loss (%)', | |
158 ) | |
159 rrdtool.graph('%s/latency-hour-link.svg' % (graphdir), | |
160 '--width', '768', | |
161 '--height', '256', | |
162 '--start', 'end - 7d', | |
163 '--end', 'now', | |
164 *latencyargs) | |
165 rrdtool.graph('%s/latency-daily-link.svg' % (graphdir), | |
166 '--width', '768', | |
167 '--height', '256', | |
168 '--start', 'end - 365d', | |
169 '--end', 'now', | |
170 *latencyargs) | |
171 | |
172 bwargs = ( | |
173 '-a', 'SVG', | |
174 '-X', '0', | |
175 '--vertical-label', 'kbit/sec', | |
176 | |
177 'DEF:neardl=%s:neardl:AVERAGE' % rrdname, | |
178 'DEF:neardlmin=%s:neardl:MIN' % rrdname, | |
179 'DEF:neardlmax=%s:neardl:MAX' % rrdname, | |
180 'CDEF:neardldif=neardlmax,neardlmin,-', | |
181 | |
182 'LINE0.001:neardlmin#000000:', | |
183 'AREA:neardldif#00dc76::STACK', | |
184 'LINE1:neardlmax#00ff00:Near download', | |
185 | |
186 'DEF:fardl=%s:fardl:AVERAGE' % rrdname, | |
187 'DEF:fardlmin=%s:fardl:MIN' % rrdname, | |
188 'DEF:fardlmax=%s:fardl:MAX' % rrdname, | |
189 'CDEF:fardldif=fardlmax,fardlmin,-', | |
190 | |
191 'LINE0.001:fardlmin#000000:', | |
192 'AREA:fardldif#dc0076::STACK', | |
193 'LINE1:fardlmax#ff0000:Far download', | |
194 ) | |
195 rrdtool.graph('%s/bw-hour-link.svg' % (graphdir), | |
196 '--width', '768', | |
197 '--height', '256', | |
198 '--start', 'end - 1d', | |
199 '--end', 'now', | |
200 *bwargs) | |
201 rrdtool.graph('%s/bw-daily-link.svg' % (graphdir), | |
202 '--width', '768', | |
203 '--height', '256', | |
204 '--start', 'end - 7d', | |
205 '--end', 'now', | |
206 *bwargs) | |
207 | |
208 if __name__ == '__main__': | |
209 main() |