0f8db34c28e289abf74081afe1d547bb9ba3832e
[myops.git] / web / collect / client / check_bw.py
1 #!/usr/bin/python
2
3 import commands
4 import os
5 import sys
6 import re
7 import socket
8 import struct
9 import time
10
11 #import ctypes
12 # TODO: maybe when there's more time; for better readability.
13 #class History(Structure):
14 #    _fields_ = [ ("version", c_int),
15 #                 ("index", c_int),
16 #                 ("history", c_float * HISTORY_LENGTH), ]
17
18 # allocate fixed space on disk to save persistent state.
19 # what to store in this file?
20 # slice_history : x,x,x,x,x,...
21 # root_history : y,y,y,y,y,y...
22
23 HISTORY_LENGTH = 24*30  # 30 days, if checked once an hour
24 HISTORY_fmt = ('iif', 'f'*HISTORY_LENGTH )
25 HISTORY_version = 1
26
27
28 def get_network_bytes(interface):
29     for line in open('/proc/net/dev', 'r'):
30         if interface in line:
31             data = line.split('%s:' % interface)[1].split()
32             rx_bytes, tx_bytes = (data[0], data[8])
33             return (float(rx_bytes), float(tx_bytes))
34     return None
35
36 def read_safe_history(filename):
37     """
38         This function guarantees that space is preserved.
39         If one of the file operations fail, it will throw an exception.
40     """
41     if os.path.exists(filename):
42         # read existing data
43         fd = os.open(filename, os.O_RDONLY)
44         a = os.read(fd, os.path.getsize(filename))
45         try:
46             (version, i, last_value) = struct.unpack_from(HISTORY_fmt[0], a, 0)
47             assert version == HISTORY_version
48             history = struct.unpack_from(HISTORY_fmt[1], a, struct.calcsize(HISTORY_fmt[0]))
49             history = [ h for h in history ]
50         except:
51             # TODO: in the future a more clever version migration might be nice.
52             os.remove(filename) # just nuke the old version
53             # create for the first time, with empty data
54             (i, last_value, history) = (0, 0.0, [0]*HISTORY_LENGTH)
55             write_safe_history(filename, (i, last_value, history), False)
56
57         os.close(fd)
58             
59     else:
60         # create for the first time, with empty data
61         (i, last_value, history) = (0, 0.0, [0]*HISTORY_LENGTH)
62         write_safe_history(filename, (i, last_value, history), False)
63
64     return (i, last_value, history)
65
66 def write_safe_history(filename, (i, last_value, history), check_for_file=True):
67     # length should match, and the file should already exist
68     assert len(history) == HISTORY_LENGTH
69     if check_for_file:
70         assert os.path.exists(filename)
71
72     # open without TRUNC nor APPEND, then seek to beginning to preserve space on disk
73     fd = os.open(filename, os.O_WRONLY|os.O_CREAT)
74     os.lseek(fd, 0, 0)
75     ret  = os.write(fd, struct.pack(HISTORY_fmt[0], HISTORY_version, i, last_value))
76     ret += os.write(fd, struct.pack(HISTORY_fmt[1], *history))
77     os.close(fd)
78     return ret
79
80 def add_to_history((i, last_value, history), data):
81     try:
82         # note, this won't be the case when reboot occurs, or on first run.
83         assert last_value > 0.0
84         assert data > last_value
85         #print "Recording: %s"% (data-last_value)
86         history[i] = data-last_value
87         i += 1
88         i = i % HISTORY_LENGTH
89     except:
90         # on init when last_value is 0, or reboot when counter resets.
91         # do not record data except for last_value, do not increment index
92         pass
93
94     last_value = data
95     return (i, last_value, history)
96
97 def record_data(filename, data):
98     rh = read_safe_history(filename)
99     return write_safe_history(filename, add_to_history(rh, data))
100
101 def get_percentile(filename, percentile):
102     (idx,last_version, history) = read_safe_history(filename)
103     summary = history[idx:] + history[:idx]
104     measured = filter(lambda x: x != 0, summary)
105     if len(measured) == 0: 
106         return 0
107
108     # convert bytes to bw
109     bw = map(lambda x: x/(60*60*24.0), measured)
110     bw.sort()
111     l = len(bw)
112     pct = bw[int(l*percentile)]
113     #print bw
114
115     return pct
116
117 def timed(method):
118
119     def timeit(*args, **kw):
120         ts = time.time()
121         result = method(*args, **kw)
122         te = time.time()
123
124         #print '%r (%r, %r) %2.2f sec' % \
125         #      (method.__name__, args, kw, te-ts)
126         return (result, te-ts)
127
128     return timeit
129
130 @timed
131 def check_dns(ip, protocol='udp'):
132     try:
133         #ip = ip[:-1] + "0"
134         ro = DNS.Request(name="www.yahoo.com", qtype="A", server=ip)
135         r = ro.req(protocol=protocol)
136         r = "OK"
137     except DNS.Base.DNSError, e:
138         r = "Error: %s" % e
139     return r
140         
141 def get_nameserver_ips(filename):
142     ip_re = re.compile("\d+\.\d+\.\d+\.\d+")
143     ret = {}
144     if not os.path.exists(filename):
145         return ret
146
147     f = open(filename, 'r')
148
149     if 'resolv' in filename:
150         for l in f:
151             for field in l.strip().split():
152                 if ip_re.match(field) and field not in ret:
153                     ret[field] = 0
154
155     if 'ifcfg' in filename:
156         for l in f:
157             if 'DNS' not in l:
158                 continue
159             for field in l.strip().split('='):
160                 field = field.replace('"', '')
161                 field = field.replace("'", '')
162                 if ip_re.match(field) and field not in ret:
163                     ret[field] = 0
164     return ret
165
166 def main():
167
168     for interface in ['eth0', 'eth1', 'eth2', 'eth3']:
169         t_bytes = get_network_bytes(interface)
170         if t_bytes != None:
171             break
172     if t_bytes == None:
173         # massive fail.  cannot continue.
174         sys.exit(1)
175
176     # take diff b/t sum(t_bytes) and last_value
177     record_data("bw_history.dat", sum(t_bytes))
178     record_data("bw_history_rx.dat", t_bytes[0])
179     record_data("bw_history_tx.dat", t_bytes[1])
180
181     print get_percentile("bw_history.dat", 0.90),
182     print get_percentile("bw_history_rx.dat", 0.90), 
183     print get_percentile("bw_history_tx.dat", 0.90), 
184     
185     print ""
186
187
188 if __name__ == "__main__":
189     main()
190
191
192 # TODO: comon?
193 #url = """http://comon.cs.princeton.edu/status/tabulator.cgi?table=table_nodeviewshort&select='dns1udp>80 && dns2udp>80&&name=="%s"'&format=formatcsv&dumpcols='dns1udp,dns1tcp,dns2udp,dns2tcp'""" % os.popen("hostname").read().strip()