experiments: Allow pinging a percentage of nodes

refs: #3265

Change-Id: I80a37d4a8cf6ca5a25d03f1f98cdcf9394feb92f
diff --git a/ndn/experiments/experiment.py b/ndn/experiments/experiment.py
index d6d5df7..385ff2a 100644
--- a/ndn/experiments/experiment.py
+++ b/ndn/experiments/experiment.py
@@ -23,6 +23,7 @@
 
 import time
 import sys
+from itertools import cycle
 
 from ndn import ExperimentManager
 
@@ -34,6 +35,11 @@
         self.convergenceTime = args["ctime"]
         self.nPings = args["nPings"]
         self.strategy = args["strategy"]
+        self.pctTraffic = float(args["pctTraffic"])
+
+        # Used to restart pings on the recovered node if any
+        self.pingedDict = {}
+
 
     def start(self):
         self.setup()
@@ -105,6 +111,36 @@
         host.nfd.setStrategy("/ndn/edu", self.strategy)
         host.cmd("ndnpingserver /ndn/edu/" + str(host) + " > ping-server &")
 
+    def startPctPings(self):
+        nNodesToPing = int(round(len(self.net.hosts)*self.pctTraffic))
+        print "Each node will ping %d node(s)" % nNodesToPing
+        # Temporarily store all the nodes being pinged by a particular node
+        nodesPingedList = []
+
+        for host in self.net.hosts:
+            # Create a circular list
+            pool = cycle(self.net.hosts)
+
+            # Move iterator to current node
+            next(x for x in pool if host.name == x.name)
+
+            # Track number of nodes to ping scheduled for this node
+            nNodesScheduled = 0
+
+            while nNodesScheduled < nNodesToPing:
+                other = pool.next()
+
+                # Do not ping self
+                if host.name != other.name:
+                    self.ping(host, other, self.nPings)
+                    nodesPingedList.append(other)
+
+                # Always increment because in 100% case a node should not ping itself
+                nNodesScheduled = nNodesScheduled + 1
+
+            self.pingedDict[host] = nodesPingedList
+            nodesPingedList = []
+
     @staticmethod
     def register(name, experimentClass):
         ExperimentManager.register(name, experimentClass)
diff --git a/ndn/experiments/failure_experiment.py b/ndn/experiments/failure_experiment.py
index 8bca445..d8d7dc9 100644
--- a/ndn/experiments/failure_experiment.py
+++ b/ndn/experiments/failure_experiment.py
@@ -36,7 +36,7 @@
         self.PING_COLLECTION_TIME_AFTER_RECOVERY = 120
 
     def run(self):
-        self.startPings()
+        self.startPctPings()
 
         # After the pings are scheduled, collect pings for 1 minute
         time.sleep(self.PING_COLLECTION_TIME_BEFORE_FAILURE)
diff --git a/ndn/experiments/multiple_failure_experiment.py b/ndn/experiments/multiple_failure_experiment.py
index bea9a78..2759c67 100644
--- a/ndn/experiments/multiple_failure_experiment.py
+++ b/ndn/experiments/multiple_failure_experiment.py
@@ -36,7 +36,8 @@
         self.RECOVERY_INTERVAL = 60
 
         # This is the number of pings required to make it through the full experiment
-        nInitialPings = self.PING_COLLECTION_TIME_BEFORE_FAILURE + len(args["net"].hosts)*(self.FAILURE_INTERVAL + self.RECOVERY_INTERVAL)
+        nInitialPings = (self.PING_COLLECTION_TIME_BEFORE_FAILURE +
+                         len(args["net"].hosts)*(self.FAILURE_INTERVAL + self.RECOVERY_INTERVAL))
         print("Scheduling with %s initial pings" % nInitialPings)
 
         args["nPings"] = nInitialPings
@@ -44,7 +45,7 @@
         Experiment.__init__(self, args)
 
     def run(self):
-        self.startPings()
+        self.startPctPings()
 
         # After the pings are scheduled, collect pings for 1 minute
         time.sleep(self.PING_COLLECTION_TIME_BEFORE_FAILURE)
@@ -56,26 +57,25 @@
             # Fail the node
             self.failNode(host)
 
-            # Stay in failure state for FAILURE_INTERVAL
+            # Stay in failure state for FAILURE_INTERVAL seconds
             time.sleep(self.FAILURE_INTERVAL)
 
             # Bring the node back up
+            start_time = time.time()
             self.recoverNode(host)
+            recovery_time = int(time.time() - start_time)
 
             # Number of pings required to reach the end of the test
-            nPings = self.RECOVERY_INTERVAL + nNodesRemainingToFail*(self.FAILURE_INTERVAL + self.RECOVERY_INTERVAL)
-            nNodesRemainingToFail = nNodesRemainingToFail - 1
+            nNodesRemainingToFail -= 1
+            nPings = ((self.RECOVERY_INTERVAL - recovery_time) +
+                      nNodesRemainingToFail*(self.FAILURE_INTERVAL + self.RECOVERY_INTERVAL))
 
-            # Wait for NFD and NLSR to fully recover
-            time.sleep(1)
             print("Scheduling with %s remaining pings" % nPings)
 
             # Restart pings
-            for other in self.net.hosts:
-                # Do not ping self
-                if host.name != other.name:
-                    self.ping(host, other, nPings)
+            for nodeToPing in self.pingedDict[host]:
+                self.ping(host, nodeToPing, nPings)
 
-            time.sleep(self.RECOVERY_INTERVAL)
+            time.sleep(self.RECOVERY_INTERVAL - recovery_time)
 
 Experiment.register("multiple-failure", MultipleFailureExperiment)
diff --git a/ndn/experiments/pingall_experiment.py b/ndn/experiments/pingall_experiment.py
index 76f0ef6..60c8e4e 100644
--- a/ndn/experiments/pingall_experiment.py
+++ b/ndn/experiments/pingall_experiment.py
@@ -31,10 +31,11 @@
 
         Experiment.__init__(self, args)
         self.COLLECTION_PERIOD_BUFFER = 10
-
+        self.pctTraffic = float(args["pctTraffic"])
+        print "Using %f traffic" % self.pctTraffic
 
     def run(self):
-        self.startPings()
+        self.startPctPings()
 
         # For pingall experiment sleep for the number of pings + some offset
         time.sleep(self.nPings + self.COLLECTION_PERIOD_BUFFER)