|
@@ -12,9 +12,10 @@ import relay
|
|
|
import client
|
|
|
|
|
|
class Simulator:
|
|
|
- def __init__(self, relaytarget, clienttarget):
|
|
|
+ def __init__(self, relaytarget, clienttarget, statslogger):
|
|
|
self.relaytarget = relaytarget
|
|
|
self.clienttarget = clienttarget
|
|
|
+ self.statslogger = statslogger
|
|
|
|
|
|
# Some (for now) hard-coded parameters
|
|
|
|
|
@@ -24,6 +25,9 @@ class Simulator:
|
|
|
# The fraction of relays that are fallback relays
|
|
|
fracfallbackrelays = 0.05
|
|
|
|
|
|
+ # Mean number of circuits created per client per epoch
|
|
|
+ self.gamma = 8.9
|
|
|
+
|
|
|
# Start some dirauths
|
|
|
self.dirauthaddrs = []
|
|
|
self.dirauths = []
|
|
@@ -59,14 +63,119 @@ class Simulator:
|
|
|
for i in range(clienttarget):
|
|
|
self.clients.append(client.Client(self.dirauthaddrs))
|
|
|
|
|
|
+ # Throw away all the performance statistics to this point
|
|
|
+ for d in self.dirauths: d.perfstats.reset()
|
|
|
+ for r in self.relays: r.perfstats.reset()
|
|
|
+ for c in self.clients: c.perfstats.reset()
|
|
|
+
|
|
|
# Tick the epoch to bootstrap the clients
|
|
|
network.thenetwork.nextepoch()
|
|
|
|
|
|
- # Throw away all the performance statistics to this point
|
|
|
+ def one_epoch(self):
|
|
|
+ """Simulate one epoch."""
|
|
|
+
|
|
|
+ epoch = network.thenetwork.getepoch()
|
|
|
+
|
|
|
+ # Each client will start a random number of circuits in a
|
|
|
+ # Poisson distribution with mean gamma. To randomize the order
|
|
|
+ # of the clients creating each circuit, we actually use a
|
|
|
+ # Poisson distribution with mean (gamma*num_clients), and assign
|
|
|
+ # each event to a uniformly random client. (This does in fact
|
|
|
+ # give the required distribution.)
|
|
|
+
|
|
|
+ numclients = len(self.clients)
|
|
|
+
|
|
|
+ # simtime is the simulated time, measured in epochs (i.e.,
|
|
|
+ # 0=start of this epoch; 1=end of this epoch)
|
|
|
+ simtime = 0
|
|
|
+
|
|
|
+ allcircs = []
|
|
|
+
|
|
|
+ lastpercent = -1
|
|
|
+ while simtime < 1.0:
|
|
|
+ allcircs.append(
|
|
|
+ random.choice(self.clients).channelmgr.new_circuit())
|
|
|
+ simtime += random.expovariate(self.gamma * numclients)
|
|
|
+ percent = int(100*simtime)
|
|
|
+ if percent != lastpercent:
|
|
|
+ logging.info("Creating circuits in epoch %s: %d%%",
|
|
|
+ epoch, percent)
|
|
|
+ lastpercent = percent
|
|
|
+
|
|
|
+ # gather stats
|
|
|
+ totsent = 0
|
|
|
+ totrecv = 0
|
|
|
+ dirasent = 0
|
|
|
+ dirarecv = 0
|
|
|
+ relaysent = 0
|
|
|
+ relayrecv = 0
|
|
|
+ clisent = 0
|
|
|
+ clirecv = 0
|
|
|
+ dirastats = network.PerfStatsStats()
|
|
|
+ for d in self.dirauths:
|
|
|
+ logging.debug("%s", d.perfstats)
|
|
|
+ dirasent += d.perfstats.bytes_sent
|
|
|
+ dirarecv += d.perfstats.bytes_received
|
|
|
+ dirastats.accum(d.perfstats)
|
|
|
+ totsent += dirasent
|
|
|
+ totrecv += dirarecv
|
|
|
+ relaystats = network.PerfStatsStats()
|
|
|
+ relaybstats = network.PerfStatsStats()
|
|
|
+ relaynbstats = network.PerfStatsStats()
|
|
|
+ for r in self.relays:
|
|
|
+ logging.debug("%s", r.perfstats)
|
|
|
+ relaysent += r.perfstats.bytes_sent
|
|
|
+ relayrecv += r.perfstats.bytes_received
|
|
|
+ relaystats.accum(r.perfstats)
|
|
|
+ if r.perfstats.is_bootstrapping:
|
|
|
+ relaybstats.accum(r.perfstats)
|
|
|
+ else:
|
|
|
+ relaynbstats.accum(r.perfstats)
|
|
|
+ totsent += relaysent
|
|
|
+ totrecv += relayrecv
|
|
|
+ clistats = network.PerfStatsStats()
|
|
|
+ clibstats = network.PerfStatsStats()
|
|
|
+ clinbstats = network.PerfStatsStats()
|
|
|
+ for c in self.clients:
|
|
|
+ logging.debug("%s", c.perfstats)
|
|
|
+ clisent += c.perfstats.bytes_sent
|
|
|
+ clirecv += c.perfstats.bytes_received
|
|
|
+ clistats.accum(c.perfstats)
|
|
|
+ if c.perfstats.is_bootstrapping:
|
|
|
+ clibstats.accum(c.perfstats)
|
|
|
+ else:
|
|
|
+ clinbstats.accum(c.perfstats)
|
|
|
+ totsent += clisent
|
|
|
+ totrecv += clirecv
|
|
|
+ self.statslogger.info("DirAuths sent=%s recv=%s" % (dirasent, dirarecv))
|
|
|
+ self.statslogger.info("Relays sent=%s recv=%s" % (relaysent, relayrecv))
|
|
|
+ self.statslogger.info("Client sent=%s recv=%s" % (clisent, clirecv))
|
|
|
+ self.statslogger.info("Total sent=%s recv=%s" % (totsent, totrecv))
|
|
|
+ numdirauths = len(self.dirauths)
|
|
|
+ numrelays = len(self.relays)
|
|
|
+ numclients = len(self.clients)
|
|
|
+ self.statslogger.info("Dirauths %s", dirastats)
|
|
|
+ self.statslogger.info("Relays %s", relaystats)
|
|
|
+ self.statslogger.info("Relays(B) %s", relaybstats)
|
|
|
+ self.statslogger.info("Relays(NB) %s", relaynbstats)
|
|
|
+ self.statslogger.info("Clients %s", clistats)
|
|
|
+ self.statslogger.info("Clients(B) %s", clibstats)
|
|
|
+ self.statslogger.info("Clients(NB) %s", clinbstats)
|
|
|
+
|
|
|
+ # Close circuits
|
|
|
+ for c in allcircs:
|
|
|
+ c.close()
|
|
|
+
|
|
|
+ # Reset stats
|
|
|
for d in self.dirauths: d.perfstats.reset()
|
|
|
for r in self.relays: r.perfstats.reset()
|
|
|
for c in self.clients: c.perfstats.reset()
|
|
|
|
|
|
+ # TODO: churn relays and clients
|
|
|
+
|
|
|
+ # Tick the epoch
|
|
|
+ network.thenetwork.nextepoch()
|
|
|
+
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
# Args: womode snipauthmode networkscale numepochs randseed
|
|
@@ -79,15 +188,27 @@ if __name__ == '__main__':
|
|
|
networkscale = float(sys.argv[3])
|
|
|
numepochs = int(sys.argv[4])
|
|
|
randseed = int(sys.argv[5])
|
|
|
+ logfile = "%s_%s_%f_%s_%s.log" % (womode.name, snipauthmode.name,
|
|
|
+ networkscale, numepochs, randseed)
|
|
|
|
|
|
# Seed the PRNG. On Ubuntu 18.04, this in fact makes future calls
|
|
|
# to (non-cryptographic) random numbers deterministic. On Ubuntu
|
|
|
# 16.04, it does not.
|
|
|
random.seed(randseed)
|
|
|
|
|
|
+ loglevel = logging.INFO
|
|
|
# Uncomment to see all the debug messages
|
|
|
- # logging.basicConfig(level=logging.DEBUG)
|
|
|
- logging.basicConfig(level=logging.INFO)
|
|
|
+ # loglevel = logging.DEBUG
|
|
|
+
|
|
|
+ logging.basicConfig(level=loglevel,
|
|
|
+ format="%(asctime)s:%(levelname)s:%(message)s")
|
|
|
+
|
|
|
+ # The gathered statistics get logged separately
|
|
|
+ statslogger = logging.getLogger("simulator")
|
|
|
+ handler = logging.FileHandler(logfile)
|
|
|
+ handler.setFormatter(logging.Formatter("%(asctime)s:%(message)s"))
|
|
|
+ statslogger.addHandler(handler)
|
|
|
+ statslogger.setLevel(logging.INFO)
|
|
|
|
|
|
# Set the Walking Onions style to use
|
|
|
network.thenetwork.set_wo_style(womode, snipauthmode)
|
|
@@ -97,9 +218,11 @@ if __name__ == '__main__':
|
|
|
clienttarget = math.ceil(2500000 * networkscale)
|
|
|
|
|
|
# Create the simulation
|
|
|
- simulator = Simulator(relaytarget, clienttarget)
|
|
|
+ simulator = Simulator(relaytarget, clienttarget, statslogger)
|
|
|
|
|
|
- network.thenetwork.nextepoch()
|
|
|
+ for e in range(numepochs):
|
|
|
+ statslogger.info("Starting epoch %s simulation", e+3)
|
|
|
+ simulator.one_epoch()
|
|
|
|
|
|
maxmemmib = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024
|
|
|
- logging.info("%d MiB used", maxmemmib)
|
|
|
+ statslogger.info("%d MiB used", maxmemmib)
|