[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[minion-cvs] Many hacks, checkpointing.



Update of /home/minion/cvsroot/src/minion/lib/mixminion/server
In directory moria.mit.edu:/tmp/cvs-serv24513/lib/mixminion/server

Modified Files:
	Modules.py ServerConfig.py ServerMain.py 
Added Files:
	EventStats.py 
Log Message:
Many hacks, checkpointing.

README, Main, ServerMain:
- Rename "mixminion server" to "mixminion server-start" for consistency.
  The old command is there, but deprecated.

BuildMessage, ClientMain:
- Die with a useful error message if the path is too long to fit the 
  address in the last hop.  (This can happen even if path_len < 32.)
- Remove dead code for "stateful" reply blocks.
- Use multiple SURB keys to detect which identity was used; resists
  George's SURB-swapping attack.

ClientMain:
- s/www.mixminion.net/mixminion.net/
- Beautify list-servers output a little
- Change key storage format to allow multiple keys to be stored with same
  password
- Avoid dumping binary messages to ttys unless --force is given.

Main:
- Don't print a stack trace when the user hits ctrl-c.

HashLog:
- Correct comment

ServerMain, EventStats, Modules, ServerConfig, Main, mixminiond.conf:
- Beginnings of code to track server statistics.



--- NEW FILE: EventStats.py ---
# Copyright 2002-2003 Nick Mathewson.  See LICENSE for licensing information.
# $Id: EventStats.py,v 1.1 2003/03/26 16:36:46 nickm Exp $

"""mixminion.server.EventStats

   Classes to gather time-based server statistics"""

__all__ = [ 'EventLog', 'NilEventLog' ]

import cPickle
import os
from threading import RLock
from time import time

from mixminion.Common import formatTime, LOG

EVENTS = [ 'ReceivedPacket',
           'AttemptedRelay',
           'SuccessfulRelay', 'FailedRelay', 'UnretriableRelay',
           'AttemptedDelivery',
           'SuccessfulDelivery', 'FailedDelivery', 'UnretriableDelivery',
            ]

class NilEventLog:
    def __init__(self):
        pass
    def save(self):
        pass
    def log(self, event, arg=None):
        pass

class EventLog(NilEventLog):
    # Fields:
    # count (event -> arg -> value)
    # lastRotation
    # filename, historyFile
    # rotateInterval
    def __init__(self, filename, historyFile, interval):
        NilEventLog.__init__(self)
        if os.path.exists(filename):
            # XXXX If this doesn't work, then we should 
            f = open(filename, 'rb')
            self.__dict__.update(cPickle.load(f))
            f.close()
            assert self.count is not None
            assert self.lastRotation is not None
        else:
            self.count = {}
            for e in EVENTS:
                self.count[e] = {}
            self.lastRotation = time()
        self.filename = filename
        self.historyFilename = historyFile
        self.rotateInterval = interval
        self._lock = RLock()
        self.save()

    def save(self):
        try:
            self._lock.acquire()
            if time() > self.lastRotation + self.rotateInterval:
                self._rotate()
            self._save()
        finally:
            self._lock.release()
            
    def _save(self):
        # Must hold lock
        LOG.debug("Syncing statistics to disk")
        tmpfile = self.filename + "_tmp"
        try:
            os.unlink(tmpfile)
        except:
            pass
        f = open(tmpfile, 'wb')
        cPickle.dump({ 'count' : self.count, 'filename' : self.filename,
                       'lastRotation' : self.lastRotation },
                     f, 1)
        f.close()
        os.rename(tmpfile, self.filename)

    def log(self, event, arg=None):
        try:
            self._lock.acquire()
            if time() > self.lastRotation + self.rotateInterval:
                self._rotate()
            try:
                self.count[event][arg] += 1
            except KeyError:
                try:
                    self.count[event][arg] = 1
                except KeyError:
                    raise KeyError("No such event: %r" % event)
        finally:
            self._lock.release()

    def _rotate(self, now=None):
        # XXXX Change behavior: rotate indexed on midnight.
        
        # Must hold lock
        LOG.debug("Flushing statistics log")
        if now is None:
            now = time()
        f = open(self.historyFilename, 'a')
        self.dump(f)
        f.close()
        
        self.count = {}
        for e in EVENTS:
            self.count[e] = {}
        self.lastRotation = now
        self._save()

    def dump(self, f):
        try:
            self._lock.acquire()
            startTime = self.lastRotation
            endTime = time()
            print >>f, "========== From %s to %s:" % (formatTime(startTime,1),
                                                      formatTime(endTime,1))
            for event in EVENTS:
                count = self.count[event]
                if len(count) == 0:
                    print >>f, "  %s: 0" % event
                    continue
                elif len(count) == 1 and count.keys()[0] is None:
                    print >>f, "  %s: %s" % (event, count[None])
                    continue
                print >>f, "  %s:" % event
                total = 0
                args = count.keys()
                args.sort()
                length = max([ len(str(arg)) for arg in args ])
                fmt = "    %"+str(length)+"s: %s"
                for arg in args:
                    v = count[arg]
                    if arg is None: arg = "{Unknown}"
                    print >>f, fmt % (arg, v)
                    total += v
                print >>f, fmt % ("Total", total)
        finally:
            self._lock.release()

def setLog(eventLog):
    global THE_EVENT_LOG
    global log
    global save
    THE_EVENT_LOG = eventLog
    log = THE_EVENT_LOG.log
    save = THE_EVENT_LOG.save

def configureLog(config):
    if config['Server']['LogStats']:
        LOG.info("Enabling statistics logging")
        statsfile = config['Server']['StatsFile']
        if not statsfile:
            homedir = config['Server']['Homedir']
            statsfile = os.path.join(homedir, "stats")
        workfile = statsfile + ".work"
        setLog(EventLog(
            workfile, statsfile, config['Server']['StatsInterval'][2]))
    else:
        LOG.info("Statistics logging disabled")

setLog(NilEventLog())

Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/Modules.py,v
retrieving revision 1.33
retrieving revision 1.34
diff -u -d -r1.33 -r1.34
--- Modules.py	20 Feb 2003 02:19:56 -0000	1.33
+++ Modules.py	26 Mar 2003 16:36:46 -0000	1.34
@@ -28,6 +28,7 @@
 import mixminion.BuildMessage
 import mixminion.Config
 import mixminion.Packet
+import mixminion.server.EventStats as EventStats
 import mixminion.server.ServerQueue
 import mixminion.server.ServerConfig
 from mixminion.Config import ConfigError, _parseBoolean, _parseCommand, \
@@ -134,16 +135,21 @@
         """Instead of queueing our message, pass it directly to the underlying
            DeliveryModule."""
         try:
+            EventStats.log('AttemptedDelivery') #XXXX
             res = self.module.processMessage(packet)
             if res == DELIVER_OK:
+                EventStats.log('SuccessfulDelivery') #XXXXq
                 return
             elif res == DELIVER_FAIL_RETRY:
                 LOG.error("Unable to retry delivery for message")
+                EventStats.log('UnretriableDelivery') #XXXX
             else:
                 LOG.error("Unable to deliver message")
+                EventStats.log('UnretriableDelivery') #XXXX
         except:
             LOG.error_exc(sys.exc_info(),
                                "Exception delivering message")
+            EventStats.log('UnretriableDelivery') #XXXX
 
     def sendReadyMessages(self):
         # We do nothing here; we already delivered the messages
@@ -166,18 +172,23 @@
     def _deliverMessages(self, msgList):
         for handle, packet, n_retries in msgList:
             try:
+                EventStats.log('AttemptedDelivery') #XXXX
                 result = self.module.processMessage(packet)
                 if result == DELIVER_OK:
                     self.deliverySucceeded(handle)
+                    EventStats.log('SuccessfulDelivery') #XXXX
                 elif result == DELIVER_FAIL_RETRY:
                     self.deliveryFailed(handle, 1)
+                    EventStats.log('FailedDelivery') #XXXX
                 else:
                     LOG.error("Unable to deliver message")
                     self.deliveryFailed(handle, 0)
+                    EventStats.log('UnretriableDelivery') #XXXX
             except:
                 LOG.error_exc(sys.exc_info(),
                                    "Exception delivering message")
                 self.deliveryFailed(handle, 0)
+                EventStats.log('UnretriableDelivery') #XXXX
 
 class DeliveryThread(threading.Thread):
     """A thread object used by ModuleManager to send messages in the

Index: ServerConfig.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerConfig.py,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -d -r1.18 -r1.19
--- ServerConfig.py	9 Feb 2003 22:30:58 -0000	1.18
+++ ServerConfig.py	26 Mar 2003 16:36:46 -0000	1.19
@@ -194,6 +194,10 @@
                      'Daemon' : ('ALLOW', C._parseBoolean, "no"),
                      # Deprecated.
                      'NoDaemon' : ('ALLOW', C._parseBoolean, None),
+                     'LogStats' : ('ALLOW', C._parseBoolean, 'yes'),
+                     'StatsInterval' : ('ALLOW', C._parseInterval,
+                                        "1 day"),
+                     'StatsFile' : ('ALLOW', None, None),
                      'EncryptIdentityKey' :('ALLOW', C._parseBoolean, "no"),
                      'IdentityKeyBits': ('ALLOW', C._parseInt, "2048"),
                      'PublicKeyLifetime' : ('ALLOW', C._parseInterval,

Index: ServerMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/ServerMain.py,v
retrieving revision 1.44
retrieving revision 1.45
diff -u -d -r1.44 -r1.45
--- ServerMain.py	20 Feb 2003 16:57:40 -0000	1.44
+++ ServerMain.py	26 Mar 2003 16:36:46 -0000	1.45
@@ -30,6 +30,7 @@
 import mixminion.server.ServerQueue
 import mixminion.server.ServerConfig
 import mixminion.server.ServerKeys
+import mixminion.server.EventStats as EventStats
 
 from bisect import insort
 from mixminion.Common import LOG, LogStream, MixError, MixFatalError,\
@@ -258,12 +259,20 @@
 
     def onMessageReceived(self, msg):
         self.incomingQueue.queueMessage(msg)
+        EventStats.log("ReceivedPacket", None) # XXXX Replace with server.
 
     def onMessageSent(self, msg, handle):
         self.outgoingQueue.deliverySucceeded(handle)
+        EventStats.log("AttemptedRelay", None) # XXXX replace with addr
+        EventStats.log("SuccessfulRelay", None) # XXXX replace with addr
 
     def onMessageUndeliverable(self, msg, handle, retriable):
         self.outgoingQueue.deliveryFailed(handle, retriable)
+        EventStats.log("AttemptedRelay", None) # XXXX replace with addr
+        if retriable:
+            EventStats.log("FailedRelay", None) # XXXX replace with addr
+        else:
+            EventStats.log("UnretriableRelay", None) # XXXX replace with addr
         
 #----------------------------------------------------------------------
 class CleaningThread(threading.Thread):
@@ -526,6 +535,7 @@
                     LOG.info("Caught sighup")
                     LOG.info("Resetting logs")
                     LOG.reset()
+                    EventStats.save()
                     GOT_HUP = 0
                 # Make sure that our worker threads are still running.
                 if not (self.cleaningThread.isAlive() and
@@ -599,8 +609,11 @@
         self.cleaningThread.join()
         self.processingThread.join()
         self.moduleManager.join()
-        
+
         self.packetHandler.close()
+
+        EventStats.save()
+
         try:
             self.lockFile.release()
             os.unlink(self.pidFile)
@@ -659,6 +672,7 @@
     sys.exit(0)
 
 def configFromServerArgs(cmd, args):
+    #XXXX
     options, args = getopt.getopt(args, "hf:", ["help", "config="])
     if args:
         usageAndExit(cmd)
@@ -672,6 +686,7 @@
     return readConfigFile(configFile)
 
 def readConfigFile(configFile):
+    #XXXX
     if configFile is None:
         if os.path.exists(os.path.expanduser("~/.mixminiond.conf")):
             configFile = os.path.expanduser("~/.mixminiond.conf")
@@ -698,6 +713,9 @@
 
 #----------------------------------------------------------------------
 def runServer(cmd, args):
+    if cmd.endswith(" server"):
+        print "Obsolete command. Use 'mixminion server-start' instead."
+    
     config = configFromServerArgs(cmd, args)
     try:
         # Configure the log, but delay disabling stderr until the last
@@ -717,11 +735,17 @@
         try:
             daemonize()
         except:
-            info = sys.exc_info()
-            LOG.fatal_exc(info,
+            LOG.fatal_exc(sys.exc_info(),
                           "Exception while starting server in the background")
             os._exit(0)
 
+    # Configure event log
+    try:
+        EventStats.configureLog(config)
+    except:
+        LOG.fatal_exc(sys.exc_info(), "")
+        os._exit(0)
+
     installSIGCHLDHandler()
     installSignalHandlers()
 
@@ -752,9 +776,25 @@
     server.close()
     LOG.info("Server is shut down")
 
+    LOG.close()
     sys.exit(0)
 
 #----------------------------------------------------------------------
+_PRINT_STATS_USAGE = """\
+Usage: mixminion server-stats [options]
+Options:
+  -h, --help:                Print this usage message and exit.
+  -f <file>, --config=<file> Use a configuration file other than the default.
+""".strip()
+
+def printServerStats(cmd, args):
+    #XXXX
+    config = configFromServerArgs(cmd, args)
+    _signalServer(config, 1)
+    EventStats.configureLog(config)
+    EventStats.THE_EVENT_LOG.dump(sys.stdout)
+
+#----------------------------------------------------------------------
 _SIGNAL_SERVER_USAGE = """\
 Usage: %s [options]
 Options:
@@ -793,6 +833,10 @@
         print _SIGNAL_SERVER_USAGE % { 'cmd' : cmd }
         return
 
+    _signalServer(config, reload)
+
+def _signalServer(config, reload):
+    """DOCDOC"""
     homeDir = config['Server']['Homedir']
     pidFile = os.path.join(homeDir, "pid")
     if not os.path.exists(pidFile):