Merge "wifiblaster: allow measurements to be triggered."
diff --git a/waveguide/waveguide.py b/waveguide/waveguide.py
index 982bf23..b419ced 100755
--- a/waveguide/waveguide.py
+++ b/waveguide/waveguide.py
@@ -198,7 +198,8 @@
class WlanManager(object):
"""A class representing one wifi interface on the local host."""
- def __init__(self, phyname, vdevname, high_power, tv_box):
+ def __init__(self, phyname, vdevname, high_power, tv_box,
+ wifiblaster_controller):
self.phyname = phyname
self.vdevname = vdevname
self.mac = '\0\0\0\0\0\0'
@@ -224,6 +225,7 @@
self.ap_signals = {}
self.auto_disabled = None
self.autochan_2g = self.autochan_5g = self.autochan_free = 0
+ self.wifiblaster_controller = wifiblaster_controller
helpers.Unlink(self.Filename('disabled'))
def Filename(self, suffix):
@@ -753,7 +755,7 @@
self.Debug('assoc err:%r stdout:%r stderr:%r', errcode, stdout[:70], stderr)
if errcode: return
now = time.time()
- self.assoc_list = {}
+ assoc_list = {}
mac = None
rssi = 0
last_seen = now
@@ -764,7 +766,8 @@
a = wgdata.Assoc(mac=mac, rssi=rssi, last_seen=last_seen, can5G=can5G)
if mac not in self.assoc_list:
self.Debug('Added: %r', a)
- self.assoc_list[mac] = a
+ self.wifiblaster_controller.Measure(self.vdevname, mac)
+ assoc_list[mac] = a
for line in stdout.split('\n'):
line = line.strip()
@@ -783,6 +786,7 @@
if g:
rssi = float(g.group(1))
AddEntry()
+ self.assoc_list = assoc_list
def _AssocCan5G(self, mac):
"""Check whether a station supports 5GHz.
@@ -811,7 +815,7 @@
return False
-def CreateManagers(managers, high_power, tv_box):
+def CreateManagers(managers, high_power, tv_box, wifiblaster_controller):
"""Create WlanManager() objects, one per wifi interface."""
def ParseDevList(errcode, stdout, stderr):
@@ -858,31 +862,41 @@
for phy, dev in phy_devs.iteritems():
if dev not in existing_devs:
log.Debug('Creating wlan manager for (%r, %r)', phy, dev)
- managers.append(WlanManager(phy, dev, high_power=high_power,
- tv_box=tv_box))
+ managers.append(
+ WlanManager(phy, dev, high_power=high_power, tv_box=tv_box,
+ wifiblaster_controller=wifiblaster_controller))
RunProc(callback=ParseDevList, args=['iw', 'dev'])
class WifiblasterController(object):
- """State machine and scheduler for packet blast testing.
+ """WiFi performance measurement using wifiblaster.
+
+ There are two modes: automated and triggered.
+
+ In automated mode, WifiblasterController measures random clients at random
+ times as governed by a Poisson process with rate = 1 / interval. Thus,
+ measurements are distributed uniformly over time, and every point in time is
+ equally likely to be measured. The average number of measurements in any given
+ window of W seconds is W / interval.
+
+ In triggered mode, WifiblasterController immediately measures the requested
+ client.
WifiblasterController reads parameters from files:
- wifiblaster.duration Packet blast duration in seconds.
- wifiblaster.enable Enable packet blast testing.
- wifiblaster.fraction Number of samples per duration.
- wifiblaster.interval Average time between packet blasts.
- wifiblaster.size Packet size in bytes.
+ - Scheduling parameters
- When enabled, WifiblasterController runs packet blasts at random times as
- governed by a Poisson process with rate = 1 / interval. Thus, packet blasts
- are distributed uniformly over time, and every point in time is equally likely
- to be measured by a packet blast. The average number of packet blasts in any
- given window of W seconds is W / interval.
+ wifiblaster.enable Enable WiFi performance measurement.
+ wifiblaster.interval Average time between automated measurements in
+ seconds, or 0 to disable automated measurements.
+ wifiblaster.measureall Unix time at which to measure all clients.
- Each packet blast tests a random associated client. The results output by
- wifiblaster are anonymized and written directly to the log.
+ - Measurement parameters
+
+ wifiblaster.duration Measurement duration in seconds.
+ wifiblaster.fraction Number of samples per measurement.
+ wifiblaster.size Packet size in bytes.
"""
def __init__(self, managers, basedir):
@@ -890,7 +904,8 @@
self._managers = managers
self._basedir = basedir
self._interval = 0 # Disabled.
- self._next_packet_blast_time = float('inf')
+ self._next_measurement_time = float('inf')
+ self._last_measureall_time = 0
self._next_timeout = 0
def _ReadParameter(self, name, typecast):
@@ -928,64 +943,71 @@
"""Returns True if a string expresses a true value."""
return s.rstrip().lower() in ('true', '1')
+ def _GetAllClients(self):
+ """Returns all associated clients."""
+ return [(manager.vdevname, assoc.mac)
+ for manager in self._managers for assoc in manager.GetState().assoc]
+
def NextTimeout(self):
"""Returns the time of the next event."""
return self._next_timeout
- def NextBlast(self):
- """Return the time of the next packet blast event."""
- return self._next_packet_blast_time
+ def NextMeasurement(self):
+ """Return the time of the next measurement event."""
+ return self._next_measurement_time
+
+ def Measure(self, interface, client):
+ """Measures the performance of a client."""
+ enable = self._ReadParameter('enable', self._StrToBool)
+ duration = self._ReadParameter('duration', float)
+ fraction = self._ReadParameter('fraction', int)
+ size = self._ReadParameter('size', int)
+ if enable and duration > 0 and fraction > 0 and size > 0:
+ RunProc(callback=self._HandleResults,
+ args=[WIFIBLASTER_BIN, '-i', interface, '-d', str(duration),
+ '-f', str(fraction), '-s', str(size),
+ helpers.DecodeMAC(client)])
def Poll(self, now):
"""Polls the state machine."""
def Disable():
self._interval = 0
- self._next_packet_blast_time = float('inf')
+ self._next_measurement_time = float('inf')
- def StartPacketBlastTimer(interval):
+ def StartMeasurementTimer(interval):
self._interval = interval
# Inter-arrival times in a Poisson process are exponentially distributed.
- # The timebase slip prevents a burst of packet blasts in case we fall
+ # The timebase slip prevents a burst of measurements in case we fall
# behind.
- self._next_packet_blast_time = now + random.expovariate(1 / interval)
+ self._next_measurement_time = now + random.expovariate(1 / interval)
- # Read parameters.
- duration = self._ReadParameter('duration', float)
- enable = self._ReadParameter('enable', self._StrToBool)
- fraction = self._ReadParameter('fraction', int)
interval = self._ReadParameter('interval', float)
- rapidpolling = self._ReadParameter('rapidpolling', int)
- size = self._ReadParameter('size', int)
-
- if rapidpolling > time.time():
- interval = 10.0
-
- if (not enable or duration <= 0 or fraction <= 0 or interval <= 0 or
- size <= 0):
+ if interval <= 0:
Disable()
elif self._interval != interval:
# Enable or change interval.
- StartPacketBlastTimer(interval)
- elif now >= self._next_packet_blast_time:
- # Packet blast.
- StartPacketBlastTimer(interval)
- clients = [
- (manager.vdevname, assoc.mac)
- for manager in self._managers for assoc in manager.GetState().assoc
- ]
- if clients:
- (interface, client) = random.choice(clients)
- RunProc(
- callback=self._HandleResults,
- args=[WIFIBLASTER_BIN, '-i', interface, '-d', str(duration),
- '-f', str(fraction), '-s', str(size),
- helpers.DecodeMAC(client)])
+ StartMeasurementTimer(interval)
+ elif now >= self._next_measurement_time:
+ # Measure a random client.
+ StartMeasurementTimer(interval)
+ try:
+ (interface, client) = random.choice(self._GetAllClients())
+ except IndexError:
+ pass
+ else:
+ self.Measure(interface, client)
+
+ measureall = self._ReadParameter('measureall', float)
+ if time.time() >= measureall and measureall > self._last_measureall_time:
+ self._last_measureall_time = measureall
+ for (interface, client) in self._GetAllClients():
+ self.Measure(interface, client)
# Poll again in at most one second. This allows parameter changes (e.g. a
- # long interval to a short interval) to take effect sooner than the next
- # scheduled packet blast.
- self._next_timeout = min(now + 1, self._next_packet_blast_time)
+ # measureall request or a long interval to a short interval) to take effect
+ # sooner than the next scheduled measurement.
+ self._next_timeout = min(now + 1, self._next_measurement_time)
def do_ssids_match(managers):
@@ -1050,6 +1072,7 @@
# Seed the consensus key with random data.
UpdateConsensus(0, os.urandom(16))
managers = []
+ wifiblaster_controller = WifiblasterController(managers, opt.status_dir)
if opt.fake:
for k, fakemac in flags:
if k == '--fake':
@@ -1060,17 +1083,17 @@
wlm = WlanManager(phyname='phy-%s' % fakemac[12:],
vdevname='wlan-%s' % fakemac[12:],
high_power=opt.high_power,
- tv_box=opt.tv_box)
+ tv_box=opt.tv_box,
+ wifiblaster_controller=wifiblaster_controller)
wlm.mac = helpers.EncodeMAC(fakemac)
managers.append(wlm)
else:
# The list of managers is also refreshed occasionally in the main loop
- CreateManagers(managers, high_power=opt.high_power, tv_box=opt.tv_box)
+ CreateManagers(managers, high_power=opt.high_power, tv_box=opt.tv_box,
+ wifiblaster_controller=wifiblaster_controller)
if not managers:
raise Exception('no wifi AP-mode devices found. Try --fake.')
- wifiblaster_controller = WifiblasterController(managers, opt.status_dir)
-
last_sent = last_autochan = last_print = 0
while 1:
TouchAliveFile()
@@ -1123,7 +1146,8 @@
if ((opt.tx_interval and now - last_sent > opt.tx_interval) or (
opt.autochan_interval and now - last_autochan > opt.autochan_interval)):
if not opt.fake:
- CreateManagers(managers, high_power=opt.high_power, tv_box=opt.tv_box)
+ CreateManagers(managers, high_power=opt.high_power, tv_box=opt.tv_box,
+ wifiblaster_controller=wifiblaster_controller)
for m in managers:
m.UpdateStationInfo()
if opt.tx_interval and now - last_sent > opt.tx_interval:
diff --git a/waveguide/wifiblaster_controller_test.py b/waveguide/wifiblaster_controller_test.py
index 18b6564..4d97084 100755
--- a/waveguide/wifiblaster_controller_test.py
+++ b/waveguide/wifiblaster_controller_test.py
@@ -125,9 +125,11 @@
for f in glob.glob(os.path.join(d, '*')):
os.remove(f)
- manager = waveguide.WlanManager(**flags)
+ managers = []
+ wc = waveguide.WifiblasterController(managers, d)
+ manager = waveguide.WlanManager(wifiblaster_controller=wc, **flags)
+ managers.append(manager)
manager.UpdateStationInfo()
- wc = waveguide.WifiblasterController([manager], d)
def WriteConfig(k, v):
open(os.path.join(d, 'wifiblaster.%s' % k), 'w').write(v)
@@ -136,10 +138,10 @@
WriteConfig('enable', 'False')
WriteConfig('fraction', '10')
WriteConfig('interval', '10')
- WriteConfig('rapidpolling', '10')
+ WriteConfig('measureall', '0')
WriteConfig('size', '1470')
- # Disabled. No packet blasts should be run.
+ # Disabled. No measurements should be run.
print manager.GetState()
for t in xrange(0, 100):
wc.Poll(t)
@@ -156,19 +158,19 @@
CountRuns() # get rid of any leftovers
wvtest.WVPASSEQ(CountRuns(), 0)
- # The first packet blast should be one
- # cycle later than the start time. This is not an implementation detail:
- # it prevents multiple APs from running simultaneous packet blasts if
- # packet blasts are enabled at the same time.
+ # The first measurement should be one cycle later than the start time.
+ # This is not an implementation detail: it prevents multiple APs from
+ # running simultaneous measurements if measurements are enabled at the
+ # same time.
WriteConfig('enable', 'True')
wc.Poll(100)
- wvtest.WVPASSGE(wc.NextBlast(), 100)
+ wvtest.WVPASSGE(wc.NextMeasurement(), 100)
for t in xrange(101, 200):
wc.Poll(t)
wvtest.WVPASSGE(CountRuns(), 1)
# Invalid parameter.
- # Disabled. No packet blasts should be run.
+ # Disabled. No measurements should be run.
WriteConfig('duration', '-1')
for t in xrange(200, 300):
wc.Poll(t)
@@ -181,7 +183,7 @@
wc.Poll(t)
wvtest.WVPASSGE(CountRuns(), 1)
- # Run the packet blast at t=400 to restart the timer.
+ # Run the measurement at t=400 to restart the timer.
wc.Poll(400)
wvtest.WVPASSGE(CountRuns(), 0)
@@ -191,9 +193,9 @@
# Enabled with longer average interval. The change in interval should
# trigger a change in next poll timeout.
WriteConfig('interval', '0.5')
- old_to = wc.NextBlast()
+ old_to = wc.NextMeasurement()
wc.Poll(401)
- wvtest.WVPASSNE(old_to, wc.NextBlast())
+ wvtest.WVPASSNE(old_to, wc.NextMeasurement())
for t in xrange(402, 410):
wc.Poll(t)
wvtest.WVPASSGE(CountRuns(), 1)
@@ -203,26 +205,18 @@
ok = False
for t in xrange(410, 600):
wc.Poll(t)
- if wc.NextBlast() > t + 200:
+ if wc.NextMeasurement() > t + 200:
ok = True
wvtest.WVPASS(ok)
- # And then try rapid polling for a limited time
- WriteConfig('rapidpolling', '800')
- ok = False
- for t in xrange(600, 700):
- wc.Poll(t)
- if wc.NextBlast() < t + 20:
- ok = True
- wvtest.WVPASS(ok)
-
- # Make sure rapid polling auto-disables
- ok = False
- for t in xrange(700, 900):
- wc.Poll(t)
- if wc.NextBlast() > t + 200:
- ok = True
- wvtest.WVPASS(ok)
+ # And then request all clients to be measured and make sure it only
+ # happens once. Disable automated measurement so they are not counted.
+ WriteConfig('interval', '0')
+ WriteConfig('measureall', str(faketime[0]))
+ wc.Poll(600)
+ wvtest.WVPASSEQ(CountRuns(), 1)
+ wc.Poll(601)
+ wvtest.WVPASSEQ(CountRuns(), 0)
finally:
time.time = oldtime
shutil.rmtree(d)