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):
 class CellClient(relay.CellHandler):
     """The subclass of CellHandler for clients."""
     """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
         self.guardaddr = None
         if network.thenetwork.womode == network.WOMode.VANILLA:
         if network.thenetwork.womode == network.WOMode.VANILLA:
             self.consensus_cdf = []
             self.consensus_cdf = []
@@ -47,14 +47,14 @@ class CellClient(relay.CellHandler):
         if network.thenetwork.womode == network.WOMode.VANILLA:
         if network.thenetwork.womode == network.WOMode.VANILLA:
             self.ensure_guard_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
         """Create a new circuit from this client. (Vanilla Onion Routing
         version)"""
         version)"""
 
 
-    def create_circuit(self):
+    def new_circuit(self):
         """Create a new circuit from this client."""
         """Create a new circuit from this client."""
         if network.thenetwork.womode == network.WOMode.VANILLA:
         if network.thenetwork.womode == network.WOMode.VANILLA:
-            self.create_circuit_vanilla()
+            self.new_circuit_vanilla()
 
 
     def received_msg(self, msg, peeraddr, peer):
     def received_msg(self, msg, peeraddr, peer):
         """Callback when a NetMsg not specific to a circuit is
         """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))
         print("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
         if isinstance(msg, relay.RelayConsensusMsg):
         if isinstance(msg, relay.RelayConsensusMsg):
             self.consensus = msg.consensus
             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:
             if network.thenetwork.womode == network.WOMode.VANILLA:
                 self.consensus_cdf = self.consensus.bw_cdf()
                 self.consensus_cdf = self.consensus.bw_cdf()
         else:
         else:
@@ -81,7 +82,10 @@ class Client:
         # Get a network address for client-side use only (do not bind it
         # Get a network address for client-side use only (do not bind it
         # to the network)
         # to the network)
         self.netaddr = network.NetAddr()
         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
         # Register for epoch tick notifications
         network.thenetwork.wantepochticks(self, True)
         network.thenetwork.wantepochticks(self, True)
@@ -119,11 +123,15 @@ class Client:
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
+    perfstats = dirauth.PerfStats(dirauth.EntType.NONE)
+
     # Start some dirauths
     # Start some dirauths
     numdirauths = 9
     numdirauths = 9
     dirauthaddrs = []
     dirauthaddrs = []
+    dirauths = []
     for i in range(numdirauths):
     for i in range(numdirauths):
         dira = dirauth.DirAuth(i, numdirauths)
         dira = dirauth.DirAuth(i, numdirauths)
+        dirauths.append(dira)
         dirauthaddrs.append(network.thenetwork.bind(dira))
         dirauthaddrs.append(network.thenetwork.bind(dira))
 
 
     # Start some relays
     # Start some relays
@@ -149,7 +157,7 @@ if __name__ == '__main__':
     # Tick the epoch
     # Tick the epoch
     network.thenetwork.nextepoch()
     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())
     print('ticked; epoch=', network.thenetwork.getepoch())
 
 
@@ -208,3 +216,22 @@ if __name__ == '__main__':
 
 
             if ch.peer.cellhandler.channels[caddr].peer is not ch:
             if ch.peer.cellhandler.channels[caddr].peer is not ch:
                 print('asymmetry:', caddr, ad, ch, ch.peer.cellhandler.channels[caddr].peer)
                 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 nacl.signing
 import network
 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:
 # A relay descriptor is a dict containing:
 #  epoch: epoch id
 #  epoch: epoch id
 #  idkey: a public identity key
 #  idkey: a public identity key
@@ -35,15 +75,17 @@ class RelayDescriptor:
         res += "]\n"
         res += "]\n"
         return res
         return res
 
 
-    def sign(self, signingkey):
+    def sign(self, signingkey, perfstats):
         serialized = self.__str__(False)
         serialized = self.__str__(False)
         signed = signingkey.sign(serialized.encode("ascii"))
         signed = signingkey.sign(serialized.encode("ascii"))
+        perfstats.sigs += 1
         self.descdict["sig"] = signed.signature
         self.descdict["sig"] = signed.signature
 
 
     @staticmethod
     @staticmethod
-    def verify(desc):
+    def verify(desc, perfstats):
         assert(type(desc) is RelayDescriptor)
         assert(type(desc) is RelayDescriptor)
         serialized = desc.__str__(False)
         serialized = desc.__str__(False)
+        perfstats.verifs += 1
         desc.descdict["idkey"].verify(serialized.encode("ascii"), desc.descdict["sig"])
         desc.descdict["idkey"].verify(serialized.encode("ascii"), desc.descdict["sig"])
 
 
 
 
@@ -75,11 +117,12 @@ class Consensus:
         res += "]\n"
         res += "]\n"
         return res
         return res
 
 
-    def sign(self, signingkey, index):
+    def sign(self, signingkey, index, perfstats):
         """Use the given signing key to sign the consensus, storing the
         """Use the given signing key to sign the consensus, storing the
         result in the sigs list at the given index."""
         result in the sigs list at the given index."""
         serialized = self.__str__(False)
         serialized = self.__str__(False)
         signed = signingkey.sign(serialized.encode("ascii"))
         signed = signingkey.sign(serialized.encode("ascii"))
+        perfstats.sigs += 1
         if 'sigs' not in self.consdict:
         if 'sigs' not in self.consdict:
             self.consdict['sigs'] = []
             self.consdict['sigs'] = []
         if index >= len(self.consdict['sigs']):
         if index >= len(self.consdict['sigs']):
@@ -110,12 +153,13 @@ class Consensus:
         return self.consdict['relays'][idx-1]
         return self.consdict['relays'][idx-1]
 
 
     @staticmethod
     @staticmethod
-    def verify(consensus, verifkeylist):
+    def verify(consensus, verifkeylist, perfstats):
         """Use the given list of verification keys to check the
         """Use the given list of verification keys to check the
         signatures on the consensus."""
         signatures on the consensus."""
         assert(type(consensus) is Consensus)
         assert(type(consensus) is Consensus)
         serialized = consensus.__str__(False)
         serialized = consensus.__str__(False)
         for i, vk in enumerate(verifkeylist):
         for i, vk in enumerate(verifkeylist):
+            perfstats.verifs += 1
             vk.verify(serialized.encode("ascii"), consensus.consdict['sigs'][i])
             vk.verify(serialized.encode("ascii"), consensus.consdict['sigs'][i])
 
 
 
 
@@ -206,9 +250,13 @@ class DirAuth(network.Server):
         self.me = me
         self.me = me
         self.tot = tot
         self.tot = tot
         self.name = "Dirauth %d of %d" % (me+1, 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
         # Create the dirauth signature keypair
         self.sigkey = nacl.signing.SigningKey.generate()
         self.sigkey = nacl.signing.SigningKey.generate()
+        self.perfstats.keygens += 1
 
 
         network.thenetwork.setdirauthkey(me, self.sigkey.verify_key)
         network.thenetwork.setdirauthkey(me, self.sigkey.verify_key)
         network.thenetwork.wantepochticks(self, True, True)
         network.thenetwork.wantepochticks(self, True, True)
@@ -234,17 +282,18 @@ class DirAuth(network.Server):
                 consensusdescs.append(desc)
                 consensusdescs.append(desc)
         DirAuth.consensus = Consensus(epoch, consensusdescs)
         DirAuth.consensus = Consensus(epoch, consensusdescs)
 
 
-
     def epoch_ending(self, epoch):
     def epoch_ending(self, epoch):
         # Only dirauth 0 actually needs to generate the consensus
         # Only dirauth 0 actually needs to generate the consensus
         # because of the shared class-static state, but everyone has to
         # because of the shared class-static state, but everyone has to
         # sign it.  Note that this code relies on dirauth 0's
         # sign it.  Note that this code relies on dirauth 0's
         # epoch_ending callback being called before any of the other
         # epoch_ending callback being called before any of the other
         # dirauths'.
         # dirauths'.
+        if (epoch+1) not in DirAuth.uploadeddescs:
+            DirAuth.uploadeddescs[epoch+1] = dict()
         if self.me == 0:
         if self.me == 0:
             self.generate_consensus(epoch+1)
             self.generate_consensus(epoch+1)
             del DirAuth.uploadeddescs[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):
     def received(self, client, msg):
         if isinstance(msg, DirAuthUploadDescMsg):
         if isinstance(msg, DirAuthUploadDescMsg):

+ 37 - 8
relay.py

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