7f31baecfe97555b938ab11e971613ed8a28b3e8
[affinity.git] / scripts / analytics.py
1 #!/usr/local/bin/python
2
3 import httplib2
4 import json
5 import sys
6 import time
7
8 # 1. Start the controller
9 # 2. On the local machine (e.g., your laptop), start this script.
10 #    > python analytics.py
11 # 3. On the mininet VM, run:
12 #    > sudo mn --controller=remote,ip=192.168.56.1 --topo tree,2
13 #    > h1 ping h3
14 # 4. Give commands to analytics.py.  For instance:
15 #    > host bytes 10.0.0.1 10.0.0.3
16 #   (There is a usage prompt that prints at the beginning of analytics.py)
17 # 5. Type 'quit' to exit analytics.py
18
19
20 '''
21 Class for keeping track of host stats or affinity link stats, depending.
22 '''
23 class Stats:
24
25     # TODO: Each stat should probably be a thread, and handle its
26     # own output and refreshing for the EWMA
27
28     def __init__(self, stat_type, **kwargs):
29         self.stat_type = stat_type
30         if stat_type == "host":
31             self.src = kwargs['src']
32             self.dst = kwargs['dst']
33             self.url_prefix = "http://localhost:8080/affinity/nb/v2/analytics/default/hoststats/" 
34         elif stat_type == "affinityLink":
35             self.al = kwargs['al']
36             self.url_prefix = "http://localhost:8080/affinity/nb/v2/analytics/default/affinitylinkstats/"
37         else:
38             print "incorrect stat type", stat_type
39
40         self.stats = {}
41         self.rate_ewma = None
42
43         self.http = httplib2.Http(".cache")
44         self.http.add_credentials('admin', 'admin')
45         self.refresh()
46
47     # Refresh statistics
48     def refresh(self):
49         if (self.stat_type == "host"):
50             resp, content = self.http.request(self.url_prefix + self.src + "/" + self.dst, "GET")
51         elif (self.stat_type == "affinityLink"):
52             resp, content = self.http.request(self.url_prefix + self.al, "GET")
53         self.stats = json.loads(content)
54         self.handle_rate_ewma()
55
56     # EWMA calculation for bit rate
57     def handle_rate_ewma(self):
58         alpha = .25
59         anomaly_threshold = 2.0
60         new_bitrate = self.get_bit_rate()
61
62         if self.rate_ewma == None:
63             self.rate_ewma = new_bitrate
64         else:
65             new_rate_ewma = alpha * new_bitrate + (1 - alpha) * self.rate_ewma
66             if (self.rate_ewma > 0 and new_rate_ewma > anomaly_threshold * self.rate_ewma):
67                 if (self.stat_type == "host"):
68                     print "Anomaly detected between %s and %s" % (self.src, self.dst)
69                 elif (self.stat_type == "affinityLink"):
70                     print "Anomaly detected on AffinityLink %s" % (self.al)
71                 print "Rate rose from %1.1f mbit/s to %1.1f mbit/s" % ((self.rate_ewma/10**6), (new_rate_ewma/10**6))
72             self.rate_ewma = new_rate_ewma
73
74     # Bytes
75     def get_bytes(self):
76         try:
77             bytes = long(self.stats["byteCount"])
78         except Exception as e:
79             bytes = 0
80         return bytes
81
82     # Bit Rate
83     def get_bit_rate(self):
84         try:
85             bitrate = float(self.stats["bitRate"])
86         except Exception as e:
87             bitrate = 0.0
88         return bitrate
89
90
91 '''
92 Class for controlling subnets.  Right now, just adds subnets and
93 checks whether they exist, because that's all we need.
94 '''
95 class SubnetControl:
96
97     def __init__(self):
98         self.http = httplib2.Http(".cache")
99         self.http.add_credentials("admin", "admin")
100         self.url_prefix = "http://localhost:8080/controller/nb/v2/subnetservice/default/"
101
102     # Checks whether subnet exists.  Checks against the actual subnet
103     # string (e.g., "10.0.0.255/1"), not the subnet name.  Will not
104     # catch things like overlapping subnets.
105     def exists(self, subnet):
106         resp, content = self.http.request(self.url_prefix + "subnets", "GET")
107         if (resp.status != 200):
108             print "Fatal error - can't check for subnet existence"
109             sys.exit(-1)
110         data = json.loads(content)
111
112         for key in data["subnetConfig"]:
113             if (key["subnet"] == subnet):
114                 return True
115         return False
116
117     # Add a subnet if it doesn't already exist.
118     def add_subnet(self, subnet_name, subnet):
119         if (self.exists(subnet)):
120             print "subnet", subnet, "already exists"
121             return
122         subnet_config = dict(name=subnet_name, subnet=subnet)
123         json_data = json.dumps(subnet_config)
124         resp, content = self.http.request(self.url_prefix + "subnet/" + subnet_name, "POST", json_data, {'Content-Type': 'application/json'})
125         if (resp.status == 201):
126             print "subnet", subnet, "added"
127         else:
128             print "subnet", subnet, "could not be added"
129
130
131 def run_interactive_mode():
132
133     print "Usage: [host | link] [bytes | rate] [src dst | link-name]"
134
135     # Demo mode
136     while True:
137         request = raw_input("> ")
138         try:
139             request = request.split()
140             request_type = request[0]
141
142             if (request_type == "quit"):
143                 sys.exit()
144
145             action = request[1]
146
147             if (request_type == "host"):
148                 src, dst = request[2:4]
149                 if (action == "bytes"):
150                     host_stat = Stats("host", src=src, dst=dst)
151                     print("%d bytes between %s and %s" % (host_stat.get_bytes(), src, dst))
152                 elif (action == "rate"):
153                     host_stat = Stats("host", src=src, dst=dst)
154                     print("%f bit/s between %s and %s" % (host_stat.get_bit_rate(), src, dst))
155                 else:
156                     raise Exception
157
158             # TODO: Change this to use AffinityLinkStats
159             elif (request_type == "link"):
160                 link = request[2]
161                 h = httplib2.Http(".cache")
162                 h.add_credentials("admin", "admin")
163                 resp, content = h.request("http://localhost:8080/affinity/nb/v2/analytics/default/affinitylinkstats/" + link, "GET")
164                 al_stats = json.loads(content)
165
166                 if (action == "bytes"):
167                     print("%d bytes on %s" % (long(al_stats["byteCount"]), link))
168                 elif (action == "rate"):
169                     print("%f bit/s on %s" % (float(al_stats["bitRate"]), link))
170                 else:
171                     raise Exception
172
173             else:
174                 raise Exception
175         except Exception as e:
176             print "Error"
177
178
179 def get_all_hosts():
180
181     h = httplib2.Http(".cache")
182     h.add_credentials("admin", "admin")
183
184     resp, content = h.request("http://localhost:8080/controller/nb/v2/hosttracker/default/hosts/active", "GET")
185     host_content = json.loads(content)
186
187     # Even if there are no active hosts, host_content['hostConfig']
188     # still exists (and is empty)
189     active_hosts = []
190     for host_data in host_content['hostConfig']:
191         active_hosts.append(host_data['networkAddress'])
192     return active_hosts
193
194
195 def run_passive_mode():
196
197     affinity_link_stats = {}
198     affinity_links = set(["testAL"]) # TODO: Get these automatically
199
200     while True:
201         # Go through all affinity link stats
202         for al in affinity_links:
203             if al not in affinity_link_stats:
204                 affinity_link_stats[al] = Stats("affinityLink", al=al)
205             stat = affinity_link_stats[al]
206             stat.refresh()
207             print "%d bytes (%1.1f mbit/s) on %s" % (stat.get_bytes(), (stat.get_bit_rate() / (10**6)), al)
208
209         time.sleep(2)
210
211 def main():
212
213     # Default subnet is required for the host tracker to work.  Run
214     # this script once *before* you start mininet.
215     subnet_control = SubnetControl()
216     subnet_control.add_subnet("defaultSubnet", "10.0.0.254/8")
217
218     interactive_mode = False
219
220     if interactive_mode:
221         run_interactive_mode()
222     else:
223         run_passive_mode()
224
225 if __name__ == "__main__":
226     main()