Module supervisor
[hide private]
[frames] | no frames]

Source Code for Module supervisor

  1  """ 
  2  Testing infrastructure - enforce runtime restrictions 
  3   
  4  Computer Systems Architecture Course 
  5  Assignment 1 
  6  March 2016 
  7  """ 
  8   
  9  import os 
 10  import random 
 11  import sys 
 12  import time 
 13  import threading 
 14   
 15  from collections import namedtuple 
 16  from device import Device 
 17  from random import shuffle, uniform 
 18  from threading import current_thread, Event, Semaphore, Thread 
 19  from time import sleep 
 20  from traceback import print_stack 
21 22 23 -class Supervisor(object):
24 """ 25 Class used to globally check accesses from device threads and verify result 26 correctness. 27 """ 28 29 # pylint: disable=protected-access 30
31 - def __init__(self, testcase, die_on_error=True):
32 """ 33 !!! This is not part of the assignment API, do not call it !!! 34 35 Create a new supervisor for the test case. 36 37 @type testcase: testcase.TestCase 38 @param testcase: the test to supervise 39 40 @type die_on_error: Boolean 41 @param die_on_error: true for the test to be killed on first error 42 """ 43 self.testcase = testcase 44 self.setup_event = Event() 45 self.start_event = Event() 46 self.devices = {} 47 self.threads = {} 48 self.waits = {} 49 self.die_on_error = die_on_error 50 self.banned_threads = set() 51 self.messages = [] 52 self.scripts = {i : {j : [] for j in range(len(self.testcase.devices))} for i in range(self.testcase.duration + self.testcase.extra_duration)} 53 for script_td in self.testcase.scripts: 54 script = Script(self.testcase.script_sleep) 55 script._Script__set_supervisor(self) 56 self.scripts[script_td.time_point][script_td.device].append(ScriptRunData(script=script, location=script_td.location))
57
58 - def register_banned_thread(self, thread=None):
59 """ 60 !!! This is not part of the assignment API, do not call it !!! 61 62 Registers a tester thread. This thread must not be used by devices for 63 any method execution. 64 65 @type thread: Thread 66 @param thread: the thread 67 """ 68 if thread is None: 69 thread = current_thread() 70 self.banned_threads.add(thread)
71
72 - def check_execution(self, method, device):
73 """ 74 !!! This is not part of the assignment API, do not call it !!! 75 76 Check device execution. 77 78 @type method: String 79 @param method: the name of the checked method 80 @type device: device.Device 81 @param device: the device which is checked 82 """ 83 thread = current_thread() 84 if thread in self.banned_threads: 85 # ERROR: called from tester thread 86 self.report("device '%s' is trying to execute %s on \ 87 tester thread '%s'" % (str(device), method, thread.name)) 88 return
89
90 - def check_termination(self):
91 """ 92 !!! This is not part of the assignment API, do not call it !!! 93 94 Checks for correct device shutdown. There must not be any active 95 device threads. 96 """ 97 for thrd in threading.enumerate(): 98 if thrd in self.banned_threads: 99 continue 100 self.report("thread '%s' did not terminate" 101 % str(thrd.name), die_on_error=False)
102
103 - def validate(self, crt_timepoint):
104 """ 105 !!! This is not part of the assignment API, do not call it !!! 106 107 Validates the current state of the data. 108 """ 109 data = {} 110 for device_testdata in self.testcase.devices: 111 device_id = device_testdata.id 112 sensor_data = {loc : data for (loc, data) in device_testdata.locations} 113 data[device_id] = sensor_data 114 115 for tpt in range(crt_timepoint + 1): 116 # print "timepoint %d" % tpt 117 # for (dev, dat) in data.items(): 118 # print "dev: %d %s" % (dev, str(dat)) 119 120 for run_tpt in range(tpt + 1): 121 for (dev, scripts) in self.scripts[run_tpt].items(): 122 for script_rd in scripts: 123 scrpt = script_rd.script 124 location = script_rd.location 125 neighbour_ids = self.__compute_neighbour_ids(dev, tpt) 126 127 # print "dev: %d %s" % (dev, neighbour_ids) 128 129 script_data = [] 130 # collect data from current neighbours 131 for neigh in neighbour_ids: 132 if location in data[neigh]: 133 script_data.append(data[neigh][location]) 134 # add our data, if any 135 if location in data[dev]: 136 script_data.append(data[dev][location]) 137 138 # run script on data 139 if script_data != []: 140 result = scrpt._Script__update(script_data) 141 142 # update data of neighbours 143 for neigh in neighbour_ids: 144 if location in data[neigh]: 145 data[neigh][location] = result 146 # update our data 147 if location in data[dev]: 148 data[dev][location] = result 149 150 for (dev_id, sens_data) in data.items(): 151 for (loc, ref_data) in sens_data.items(): 152 calc_data = self.devices[dev_id].device.get_data(loc) 153 if ref_data != calc_data: 154 self.report("after timepoint %d, data for location %d on device %d differs: expected %f, found %f\n" % (crt_timepoint, loc, dev_id, ref_data, calc_data))
155
156 - def report(self, message, die_on_error=None):
157 """ 158 !!! This is not part of the assignment API, do not call it !!! 159 160 Reports an error message. All messages are stored in a list for 161 retrieval at the end of the test. 162 163 @type message: String 164 @param message: the error message to log 165 """ 166 if die_on_error is None: 167 die_on_error = self.die_on_error 168 169 if die_on_error: 170 print >> sys.stderr, message + "\n", 171 print_stack() 172 os.abort() 173 174 self.messages.append(message)
175
176 - def status(self):
177 """ 178 !!! This is not part of the assignment API, do not call it !!! 179 180 Returns the list of logged error messages. 181 182 @rtype: List of String 183 @return: the list of encountered errors 184 """ 185 return self.messages
186 187 @staticmethod
188 - def __setup_devices(setup_event, device, neighbours):
189 setup_event.wait() 190 device.setup_devices(neighbours)
191 192 @staticmethod
193 - def __send_scripts(device, scripts, delay, wait):
194 time.sleep(delay) 195 for script_rd in scripts: 196 if script_rd is scripts[-1]: 197 time.sleep(delay) 198 device.assign_script(script_rd.script, script_rd.location) 199 wait.release()
200 201 @staticmethod
202 - def __send_end(device, wait, count):
203 for i in range(count): 204 wait.acquire() 205 device.assign_script(None, None)
206
207 - def __compute_neighbour_ids(self, device_id, time_point):
208 neighbours = set() 209 # ugly linear search is ugly 210 for enc in self.testcase.devices[device_id].encounters: 211 if enc.time_point != time_point: 212 continue 213 neighbours |= set(enc.devices) 214 return list(neighbours)
215
216 - def get_neighbours(self, device_id):
217 """ 218 !!! This is not part of the assignment API, do not call it !!! 219 220 Returns the list of neighbours for device_id for the current timepoint, and increments the 221 timepoint for the next invocation. This method is wrapped by Runtime. 222 WARNING: this method is not thread-safe and must not be called concurrently with 223 the same device_id. 224 225 @type device_id: Integer 226 @param device_id: the id of the device for which neighbours must be returned 227 228 @rtype: List of device.Device 229 @return: the list of neighbours for the current timepoint 230 """ 231 self.start_event.wait() 232 233 device = self.devices[device_id].device 234 crt_timepoint = self.devices[device_id].crt_timepoint 235 236 self.check_execution("get_neighbours", device) 237 238 for dev_rd in self.devices.values(): 239 if dev_rd.crt_timepoint < crt_timepoint or dev_rd.crt_timepoint > crt_timepoint + 1: 240 self.report("device %d called 'get_neighbours' without waiting for other devices\n" % device_id, True) 241 242 for thrd in self.threads[device_id]: 243 thrd.join() 244 245 if crt_timepoint == self.testcase.duration + self.testcase.extra_duration: 246 return None 247 248 if crt_timepoint > self.testcase.duration + self.testcase.extra_duration: 249 self.report("called 'get_neighbours' from device %d, on timepoint %d, after simulation end at %d\n" % (device_id, crt_timepoint, self.testcase.duration + self.testcase.extra_duration), True) 250 251 neighbour_ids = self.__compute_neighbour_ids(device_id, crt_timepoint) 252 253 neighbours = [self.devices[neigh_id].device for neigh_id in neighbour_ids] 254 255 scripts = self.scripts[crt_timepoint][device_id] 256 257 for scrpt in scripts: 258 scrpt.script._Script__set_device(device) 259 260 if self.testcase.parallel_script: 261 scripts = [[script] for script in scripts] 262 else: 263 scripts = [scripts] 264 265 self.waits[device_id] = Semaphore(0) 266 267 self.threads[device_id] = [] 268 for scrpt in scripts: 269 delay_min = self.testcase.script_delay[0] 270 delay_max = self.testcase.script_delay[1] 271 thread = Thread(name="Sender", 272 target=Supervisor.__send_scripts, 273 args=(device, scrpt, random.uniform(delay_min, delay_max), self.waits[device_id])) 274 self.register_banned_thread(thread) 275 self.threads[device_id].append(thread) 276 thread.start() 277 278 thread = Thread(name="Ender", 279 target=Supervisor.__send_end, 280 args=(device, self.waits[device_id], len(scripts))) 281 self.threads[device_id].append(thread) 282 thread.start() 283 284 self.devices[device_id].crt_timepoint = crt_timepoint + 1 285 286 return neighbours
287
288 - def run_testcase(self):
289 """ 290 !!! This is not part of the assignment API, do not call it !!! 291 292 Runs the test case by creating the devices, unblocking the script assignment and waiting 293 for device termination. 294 295 @rtype: Integer 296 @return: the number of errors 297 """ 298 for device_testdata in self.testcase.devices: 299 device_id = device_testdata.id 300 sensor_data = {loc : data for (loc, data) in device_testdata.locations} 301 supervisor = Runtime(self, device_id) 302 device = Device(device_id, sensor_data, supervisor) 303 self.devices[device_id] = DeviceRunData(device=device, crt_timepoint=0) 304 self.threads[device_id] = [] 305 306 devices = [device_rd.device for device_rd in self.devices.values()] 307 setup_threads = [] 308 for dev in devices: 309 neighbours = devices[:] 310 shuffle(neighbours) 311 setup_threads.append(Thread(name = "Setup", 312 target = Supervisor.__setup_devices, 313 args = (self.setup_event, dev, neighbours))) 314 setup_threads[-1].start() 315 316 self.setup_event.set() 317 318 for thread in setup_threads: 319 thread.join() 320 321 self.start_event.set() 322 323 for dev in self.devices.values(): 324 dev.device.shutdown() 325 326 self.check_termination() 327 328 self.validate(self.testcase.duration + self.testcase.extra_duration - 1) 329 330 for msg in self.status(): 331 print >> sys.stderr, msg 332 333 return len(self.status())
334
335 336 -class Runtime(object):
337 """ 338 Object called by a device to get its neighbours at each timepoint. Each device will get a 339 different instance of this type which wraps the Supervisor object. 340 """
341 - def __init__(self, supervisor, device_id):
342 """ 343 !!! This is not part of the assignment API, do not call it !!! 344 345 Creates a new Runtime object. 346 347 @type supervisor: Supervisor 348 @param supervisor: the supervisor object to wrap 349 @type device_id: Integer 350 @param device_id: the id of the device which uses this Runtime instance 351 """ 352 self.supervisor = supervisor 353 self.device_id = device_id
354
355 - def get_neighbours(self):
356 """ 357 Returns the list of neighbours for the current timepoint and increments the timepoint for 358 the next invocation. 359 WARNING: this method is not thread-safe, do not call it concurrently 360 361 @rtype: List of Device 362 @return: the list of current neighbours 363 """ 364 return self.supervisor.get_neighbours(self.device_id)
365
366 367 -class DeviceRunData:
368 - def __init__(self, device, crt_timepoint):
369 self.device = device 370 self.crt_timepoint = crt_timepoint
371 372 373 ScriptRunData = namedtuple("ScriptRunData", ['script', 'location'])
374 375 376 -class Script(object):
377 """ 378 Encapsulates the algoritm for improving noisy measurement data. 379 """
380 - def __init__(self, delay=None, threshold=30):
381 """ 382 !!! This is not part of the assignment API, do not call it !!! 383 384 Creates a new script. 385 """ 386 self.__delay = delay 387 self.__threshold = threshold 388 self.__supervisor = None 389 self.__device = None
390
391 - def run(self, data):
392 """ 393 Executes this script. 394 395 @type data: List of Integer 396 @param data: list containing data relevant for location, from one or multiple devices 397 398 @rtype: Integer 399 @return: improved measurement for the location 400 """ 401 self.__supervisor.check_execution("run", self.__device) 402 403 if self.__delay is not None: 404 sleep(uniform(self.__delay[0], self.__delay[1])) 405 406 return self.__update(data)
407
408 - def __update(self, sensor_data):
409 # sa nu uit sa implementez algoritmul ;) 410 return max(self.__threshold, max(sensor_data))
411
412 - def __set_supervisor(self, supervisor):
413 self.__supervisor = supervisor
414
415 - def __set_device(self, device):
416 self.__device = device
417