Browse Source

Start gathering performance statistics

So far: public-key operations
To come: bytes sent and received
Ian Goldberg 4 years ago
parent
commit
a9e18e9a76
3 changed files with 127 additions and 22 deletions
  1. 35 8
      client.py
  2. 55 6
      dirauth.py
  3. 37 8
      relay.py

+ 35 - 8
client.py

@@ -10,8 +10,8 @@ import relay
 class CellClient(relay.CellHandler):
     """The subclass of CellHandler for clients."""
 
-    def __init__(self, myaddr, dirauthaddrs):
-        super().__init__(myaddr, dirauthaddrs)
+    def __init__(self, myaddr, dirauthaddrs, perfstats):
+        super().__init__(myaddr, dirauthaddrs, perfstats)
         self.guardaddr = None
         if network.thenetwork.womode == network.WOMode.VANILLA:
             self.consensus_cdf = []
@@ -47,14 +47,14 @@ class CellClient(relay.CellHandler):
         if network.thenetwork.womode == network.WOMode.VANILLA:
             self.ensure_guard_vanilla()
 
-    def create_circuit_vanilla(self):
+    def new_circuit_vanilla(self):
         """Create a new circuit from this client. (Vanilla Onion Routing
         version)"""
 
-    def create_circuit(self):
+    def new_circuit(self):
         """Create a new circuit from this client."""
         if network.thenetwork.womode == network.WOMode.VANILLA:
-            self.create_circuit_vanilla()
+            self.new_circuit_vanilla()
 
     def received_msg(self, msg, peeraddr, peer):
         """Callback when a NetMsg not specific to a circuit is
@@ -62,7 +62,8 @@ class CellClient(relay.CellHandler):
         print("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
         if isinstance(msg, relay.RelayConsensusMsg):
             self.consensus = msg.consensus
-            dirauth.Consensus.verify(self.consensus, network.thenetwork.dirauthkeys())
+            dirauth.Consensus.verify(self.consensus, \
+                    network.thenetwork.dirauthkeys(), self.perfstats)
             if network.thenetwork.womode == network.WOMode.VANILLA:
                 self.consensus_cdf = self.consensus.bw_cdf()
         else:
@@ -81,7 +82,10 @@ class Client:
         # Get a network address for client-side use only (do not bind it
         # to the network)
         self.netaddr = network.NetAddr()
-        self.cellhandler = CellClient(self.netaddr, dirauthaddrs)
+        self.perfstats = dirauth.PerfStats(dirauth.EntType.CLIENT)
+        self.perfstats.name = "Client at %s" % self.netaddr
+        self.perfstats.is_bootstrapping = True
+        self.cellhandler = CellClient(self.netaddr, dirauthaddrs, self.perfstats)
 
         # Register for epoch tick notifications
         network.thenetwork.wantepochticks(self, True)
@@ -119,11 +123,15 @@ class Client:
 
 
 if __name__ == '__main__':
+    perfstats = dirauth.PerfStats(dirauth.EntType.NONE)
+
     # Start some dirauths
     numdirauths = 9
     dirauthaddrs = []
+    dirauths = []
     for i in range(numdirauths):
         dira = dirauth.DirAuth(i, numdirauths)
+        dirauths.append(dira)
         dirauthaddrs.append(network.thenetwork.bind(dira))
 
     # Start some relays
@@ -149,7 +157,7 @@ if __name__ == '__main__':
     # Tick the epoch
     network.thenetwork.nextepoch()
 
-    dirauth.Consensus.verify(dirauth.DirAuth.consensus, network.thenetwork.dirauthkeys())
+    dirauth.Consensus.verify(dirauth.DirAuth.consensus, network.thenetwork.dirauthkeys(), perfstats)
 
     print('ticked; epoch=', network.thenetwork.getepoch())
 
@@ -208,3 +216,22 @@ if __name__ == '__main__':
 
             if ch.peer.cellhandler.channels[caddr].peer is not ch:
                 print('asymmetry:', caddr, ad, ch, ch.peer.cellhandler.channels[caddr].peer)
+
+    clients[0].cellhandler.new_circuit()
+
+totsent = 0
+totrecv = 0
+for d in dirauths:
+    print(d.perfstats)
+    totsent += d.perfstats.bytes_sent
+    totrecv += d.perfstats.bytes_received
+for r in relays:
+    print(r.perfstats)
+    totsent += r.perfstats.bytes_sent
+    totrecv += r.perfstats.bytes_received
+for c in clients:
+    print(c.perfstats)
+    totsent += c.perfstats.bytes_sent
+    totrecv += c.perfstats.bytes_received
+
+print("Total sent=%d recv=%d" % (totsent, totrecv))

+ 55 - 6
dirauth.py

@@ -7,6 +7,46 @@ import nacl.encoding
 import nacl.signing
 import network
 
+from enum import Enum
+
+class EntType(Enum):
+    """The different types of entities in the system."""
+    NONE = 0
+    DIRAUTH = 1
+    RELAY = 2
+    CLIENT = 3
+
+
+class PerfStats:
+    """A class to store performance statistics for a relay or client.
+    We keep track of bytes sent, bytes received, and counts of
+    public-key operations of various types.  We will reset these every
+    epoch."""
+
+    def __init__(self, ent_type):
+        # Which type of entity is this for (DIRAUTH, RELAY, CLIENT)
+        self.ent_type = ent_type
+        # A printable name for the entity
+        self.name = None
+        # True if bootstrapping this epoch
+        self.is_bootstrapping = False
+        # Bytes sent and received
+        self.bytes_sent = 0
+        self.bytes_received = 0
+        # Public-key operations: key generation, signing, verification,
+        # Diffie-Hellman
+        self.keygens = 0
+        self.sigs = 0
+        self.verifs = 0
+        self.dhs = 0
+
+    def __str__(self):
+        return "%s: type=%s boot=%s sent=%d recv=%d keygen=%d sig=%d verif=%d dh=%d" % \
+            (self.name, self.ent_type.name, self.is_bootstrapping, \
+            self.bytes_sent, self.bytes_received, self.keygens, \
+            self.sigs, self.verifs, self.dhs)
+
+
 # A relay descriptor is a dict containing:
 #  epoch: epoch id
 #  idkey: a public identity key
@@ -35,15 +75,17 @@ class RelayDescriptor:
         res += "]\n"
         return res
 
-    def sign(self, signingkey):
+    def sign(self, signingkey, perfstats):
         serialized = self.__str__(False)
         signed = signingkey.sign(serialized.encode("ascii"))
+        perfstats.sigs += 1
         self.descdict["sig"] = signed.signature
 
     @staticmethod
-    def verify(desc):
+    def verify(desc, perfstats):
         assert(type(desc) is RelayDescriptor)
         serialized = desc.__str__(False)
+        perfstats.verifs += 1
         desc.descdict["idkey"].verify(serialized.encode("ascii"), desc.descdict["sig"])
 
 
@@ -75,11 +117,12 @@ class Consensus:
         res += "]\n"
         return res
 
-    def sign(self, signingkey, index):
+    def sign(self, signingkey, index, perfstats):
         """Use the given signing key to sign the consensus, storing the
         result in the sigs list at the given index."""
         serialized = self.__str__(False)
         signed = signingkey.sign(serialized.encode("ascii"))
+        perfstats.sigs += 1
         if 'sigs' not in self.consdict:
             self.consdict['sigs'] = []
         if index >= len(self.consdict['sigs']):
@@ -110,12 +153,13 @@ class Consensus:
         return self.consdict['relays'][idx-1]
 
     @staticmethod
-    def verify(consensus, verifkeylist):
+    def verify(consensus, verifkeylist, perfstats):
         """Use the given list of verification keys to check the
         signatures on the consensus."""
         assert(type(consensus) is Consensus)
         serialized = consensus.__str__(False)
         for i, vk in enumerate(verifkeylist):
+            perfstats.verifs += 1
             vk.verify(serialized.encode("ascii"), consensus.consdict['sigs'][i])
 
 
@@ -206,9 +250,13 @@ class DirAuth(network.Server):
         self.me = me
         self.tot = tot
         self.name = "Dirauth %d of %d" % (me+1, tot)
+        self.perfstats = PerfStats(EntType.DIRAUTH)
+        self.perfstats.name = self.name
+        self.perfstats.is_bootstrapping = True
 
         # Create the dirauth signature keypair
         self.sigkey = nacl.signing.SigningKey.generate()
+        self.perfstats.keygens += 1
 
         network.thenetwork.setdirauthkey(me, self.sigkey.verify_key)
         network.thenetwork.wantepochticks(self, True, True)
@@ -234,17 +282,18 @@ class DirAuth(network.Server):
                 consensusdescs.append(desc)
         DirAuth.consensus = Consensus(epoch, consensusdescs)
 
-
     def epoch_ending(self, epoch):
         # Only dirauth 0 actually needs to generate the consensus
         # because of the shared class-static state, but everyone has to
         # sign it.  Note that this code relies on dirauth 0's
         # epoch_ending callback being called before any of the other
         # dirauths'.
+        if (epoch+1) not in DirAuth.uploadeddescs:
+            DirAuth.uploadeddescs[epoch+1] = dict()
         if self.me == 0:
             self.generate_consensus(epoch+1)
             del DirAuth.uploadeddescs[epoch+1]
-        DirAuth.consensus.sign(self.sigkey, self.me)
+        DirAuth.consensus.sign(self.sigkey, self.me, self.perfstats)
 
     def received(self, client, msg):
         if isinstance(msg, DirAuthUploadDescMsg):

+ 37 - 8
relay.py

@@ -37,6 +37,24 @@ class RelayRandomHopMsg(RelayNetMsg):
         return "RandomHop TTL=%d" % self.ttl
 
 
+class VanillaCreateCircuitMsg(RelayNetMsg):
+    """The message for requesting circuit creation in Vanilla Onion
+    Routing."""
+
+    def __init__(self, circid, ntor_request):
+        self.circid = circid
+        self.ntor_request = ntor_request
+
+
+class VanillaCreatedCircuitMsg(RelayNetMsg):
+    """The message for responding to circuit creation in Vanilla Onion
+    Routing."""
+
+    def __init__(self, circid, ntor_response):
+        self.circid = circid
+        self.ntor_response = ntor_response
+
+
 class CircuitCellMsg(RelayNetMsg):
     """Send a message tagged with a circuit id."""
     def __init__(self, circuitid, cell):
@@ -142,12 +160,13 @@ class CellHandler:
     on-demand channels to relays, to gracefully handle the closing of
     channels, and to handle commands received over the channels."""
 
-    def __init__(self, myaddr, dirauthaddrs):
+    def __init__(self, myaddr, dirauthaddrs, perfstats):
         # A dictionary of Channels to other hosts, indexed by NetAddr
         self.channels = dict()
         self.myaddr = myaddr
         self.dirauthaddrs = dirauthaddrs
         self.consensus = None
+        self.perfstats = perfstats
 
     def terminate(self):
         """Close all connections we're managing."""
@@ -206,15 +225,16 @@ class CellHandler:
 class CellRelay(CellHandler):
     """The subclass of CellHandler for relays."""
 
-    def __init__(self, myaddr, dirauthaddrs):
-        super().__init__(myaddr, dirauthaddrs)
+    def __init__(self, myaddr, dirauthaddrs, perfstats):
+        super().__init__(myaddr, dirauthaddrs, perfstats)
 
     def get_consensus(self):
         """Download a fresh consensus from a random dirauth."""
         a = random.choice(self.dirauthaddrs)
         c = network.thenetwork.connect(self, a)
         self.consensus = c.getconsensus()
-        dirauth.Consensus.verify(self.consensus, network.thenetwork.dirauthkeys())
+        dirauth.Consensus.verify(self.consensus, \
+                network.thenetwork.dirauthkeys(), self.perfstats)
         c.close()
 
     def received_msg(self, msg, peeraddr, peer):
@@ -242,13 +262,19 @@ class Relay(network.Server):
     """The class representing an onion relay."""
 
     def __init__(self, dirauthaddrs, bw, flags):
+        # Gather performance statistics
+        self.perfstats = dirauth.PerfStats(dirauth.EntType.RELAY)
+        self.perfstats.is_bootstrapping = True
+
         # Create the identity and onion keys
         self.idkey = nacl.signing.SigningKey.generate()
         self.onionkey = nacl.public.PrivateKey.generate()
+        self.perfstats.keygens += 2
         self.name = self.idkey.verify_key.encode(encoder=nacl.encoding.HexEncoder).decode("ascii")
 
         # Bind to the network to get a network address
         self.netaddr = network.thenetwork.bind(self)
+        self.perfstats.name = "Relay at %s" % self.netaddr
 
         # Our bandwidth and flags
         self.bw = bw
@@ -259,7 +285,7 @@ class Relay(network.Server):
         network.thenetwork.wantepochticks(self, True)
 
         # Create the CellRelay connection manager
-        self.cellhandler = CellRelay(self.netaddr, dirauthaddrs)
+        self.cellhandler = CellRelay(self.netaddr, dirauthaddrs, self.perfstats)
 
         # Initially, we're not a fallback relay
         self.is_fallbackrelay = False
@@ -311,8 +337,8 @@ class Relay(network.Server):
         descdict["bw"] = self.bw
         descdict["flags"] = self.flags
         desc = dirauth.RelayDescriptor(descdict)
-        desc.sign(self.idkey)
-        dirauth.RelayDescriptor.verify(desc)
+        desc.sign(self.idkey, self.perfstats)
+        dirauth.RelayDescriptor.verify(desc, self.perfstats)
 
         if upload:
             descmsg = dirauth.DirAuthUploadDescMsg(desc)
@@ -355,6 +381,8 @@ class Relay(network.Server):
         return peerchannel
 
 if __name__ == '__main__':
+    perfstats = dirauth.PerfStats(dirauth.EntType.NONE)
+
     # Start some dirauths
     numdirauths = 9
     dirauthaddrs = []
@@ -385,7 +413,8 @@ if __name__ == '__main__':
     # Tick the epoch
     network.thenetwork.nextepoch()
 
-    dirauth.Consensus.verify(dirauth.DirAuth.consensus, network.thenetwork.dirauthkeys())
+    dirauth.Consensus.verify(dirauth.DirAuth.consensus, \
+            network.thenetwork.dirauthkeys(), perfstats)
 
     print('ticked; epoch=', network.thenetwork.getepoch())