28
|
1 #!/usr/bin/env python
|
|
2
|
|
3 import ConfigParser
|
|
4 import optparse
|
|
5 import os
|
|
6 import re
|
|
7 import rrdtool
|
|
8 import subprocess
|
|
9 import time
|
|
10
|
|
11 def main():
|
|
12 conf = ConfigParser.ConfigParser()
|
|
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:
|
|
40 print 'Fetching stats...'
|
|
41 stats = fetchstats(conf)
|
|
42 if opts.verbose:
|
|
43 print stats
|
|
44 if opts.verbose:
|
|
45 print 'Updating RRD'
|
|
46 updaterrd(opts.rrd, stats)
|
|
47 if opts.verbose:
|
|
48 print 'Updating graph'
|
|
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:
|
|
72 print 'Error %d fetching \'%s\'' % (p.returncode, url)
|
|
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()
|
|
79 l = stdout.split('\n')
|
|
80 if len(l) != 6:
|
|
81 print 'Unable to parse ping line:', l
|
|
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)
|
|
113 except OSError, e:
|
|
114 if e.errno == 2:
|
|
115 print 'Creating RRD...'
|
|
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()
|