[CalendarServer-changes] [7495] CalendarServer/trunk/contrib/performance/loadtest

source_changes at macosforge.org source_changes at macosforge.org
Wed May 18 11:04:53 PDT 2011


Revision: 7495
          http://trac.macosforge.org/projects/calendarserver/changeset/7495
Author:   exarkun at twistedmatrix.com
Date:     2011-05-18 11:04:52 -0700 (Wed, 18 May 2011)
Log Message:
-----------
Add a helper for logging recent traffic

Added Paths:
-----------
    CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py
    CalendarServer/trunk/contrib/performance/loadtest/trafficlogger.py

Added: CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py	2011-05-18 18:04:52 UTC (rev 7495)
@@ -0,0 +1,174 @@
+##
+# Copyright (c) 2011 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+##
+
+from zope.interface import Interface, implements
+
+from twisted.internet.protocol import ClientFactory, Protocol
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransport, MemoryReactor
+from twisted.protocols.wire import Discard
+
+from loadtest.trafficlogger import _TrafficLoggingFactory, loggedReactor
+
+
+class IProbe(Interface):
+    """
+    An interface which can be used to verify some interface-related behavior of
+    L{loggedReactor}.
+    """
+    def probe():
+        pass
+
+
+class Probe(object):
+    implements(IProbe)
+
+    _probed = False
+
+    def __init__(self, result=None):
+        self._result = result
+
+    def probe(self):
+        self._probed = True
+        return self._result
+
+
+class TrafficLoggingReactorTests(TestCase):
+    """
+    Tests for L{loggedReactor}.
+    """
+    def test_nothing(self):
+        """
+        L{loggedReactor} returns the object passed to it, if the object passed
+        to it doesn't provide any interfaces.  This is mostly for testing
+        convenience rather than a particularly useful feature.
+        """
+        probe = object()
+        self.assertIdentical(probe, loggedReactor(probe))
+
+
+    def test_interfaces(self):
+        """
+        The object returned by L{loggedReactor} provides all of the interfaces
+        provided by the object passed to it.
+        """
+        probe = Probe()
+        reactor = loggedReactor(probe)
+        self.assertTrue(IProbe.providedBy(reactor))
+
+
+    def test_passthrough(self):
+        """
+        Methods on interfaces on the object passed to L{loggedReactor} can be
+        called by calling them on the object returned by L{loggedReactor}.
+        """
+        expected = object()
+        probe = Probe(expected)
+        reactor = loggedReactor(probe)
+        result = reactor.probe()
+        self.assertTrue(probe._probed)
+        self.assertIdentical(expected, result)
+
+
+    def test_connectTCP(self):
+        """
+        Called on the object returned by L{loggedReactor}, C{connectTCP} calls
+        the wrapped reactor's C{connectTCP} method with the original factory
+        wrapped in a L{_TrafficLoggingFactory}.
+        """
+        class RecordDataProtocol(Protocol):
+            def dataReceived(self, data):
+                self.data = data
+        proto = RecordDataProtocol()
+        factory = ClientFactory()
+        factory.protocol = lambda: proto
+        reactor = MemoryReactor()
+        logged = loggedReactor(reactor)
+        logged.connectTCP('192.168.1.2', 1234, factory)
+        [(host, port, factory, backlog, interface)] = reactor.tcpClients
+        self.assertEqual('192.168.1.2', host)
+        self.assertEqual(1234, port)
+        self.assertIsInstance(factory, _TrafficLoggingFactory)
+
+        # Verify that the factory and protocol specified are really being used
+        protocol = factory.buildProtocol(None)
+        protocol.makeConnection(None)
+        protocol.dataReceived("foo")
+        self.assertEqual(proto.data, "foo")
+
+
+class TrafficLoggingFactoryTests(TestCase):
+    """
+    Tests for L{_TrafficLoggingFactory}.
+    """
+    def setUp(self):
+        self.wrapped = ClientFactory()
+        self.wrapped.protocol = Discard
+        self.factory = _TrafficLoggingFactory(self.wrapped)
+        
+    def test_receivedBytesLogged(self):
+        """
+        When bytes are delivered through a protocol created by
+        L{_TrafficLoggingFactory}, they are added to a log kept on that
+        factory.
+        """
+        protocol = self.factory.buildProtocol(None)
+
+        # The factory should now have a new StringIO log file
+        self.assertEqual(1, len(self.factory.logs))
+
+        transport = StringTransport()
+        protocol.makeConnection(transport)
+
+        protocol.dataReceived("hello, world")
+        self.assertEqual(
+            "*\nC 0: 'hello, world'\n", self.factory.logs[0].getvalue())
+
+
+    def test_finishedLogs(self):
+        """
+        When connections are lost, the corresponding log files are moved into
+        C{_TrafficLoggingFactory.finishedLogs}.
+        """
+        protocol = self.factory.buildProtocol(None)
+        transport = StringTransport()
+        protocol.makeConnection(transport)
+        logfile = self.factory.logs[0]
+        protocol.connectionLost(None)
+        self.assertEqual(0, len(self.factory.logs))
+        self.assertEqual([logfile], self.factory.finishedLogs)
+
+
+    def test_finishedLogsLimit(self):
+        """
+        Only the most recent C{_TrafficLoggingFactory.LOGFILE_LIMIT} logfiles
+        are kept in C{_TrafficLoggingFactory.finishedLogs}.
+        """
+        self.factory.LOGFILE_LIMIT = 2
+        first = self.factory.buildProtocol(None)
+        first.makeConnection(StringTransport())
+        second = self.factory.buildProtocol(None)
+        second.makeConnection(StringTransport())
+        third = self.factory.buildProtocol(None)
+        third.makeConnection(StringTransport())
+
+        second.connectionLost(None)
+        first.connectionLost(None)
+        third.connectionLost(None)
+
+        self.assertEqual(
+            [first.logfile, third.logfile], self.factory.finishedLogs)

Added: CalendarServer/trunk/contrib/performance/loadtest/trafficlogger.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/trafficlogger.py	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/trafficlogger.py	2011-05-18 18:04:52 UTC (rev 7495)
@@ -0,0 +1,85 @@
+##
+# Copyright (c) 2011 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+##
+
+"""
+This module implements a reactor wrapper which will cause all traffic on
+connections set up using that reactor to be logged.
+"""
+
+__all__ = ['loggedReactor']
+
+from StringIO import StringIO
+
+from zope.interface import providedBy
+
+from twisted.python.components import proxyForInterface
+from twisted.internet.interfaces import IReactorCore, IReactorTime, IReactorTCP
+from twisted.protocols.policies import WrappingFactory, TrafficLoggingProtocol
+
+def loggedReactor(reactor):
+    """
+    Construct and return a wrapper around the given C{reactor} which provides
+    all of the same interfaces, but which will log all traffic over outgoing
+    TCP connections it establishes.
+    """
+    bases = []
+    for iface in providedBy(reactor):
+        if iface is IReactorTCP:
+            bases.append(_TCPTrafficLoggingReactor)
+        else:
+            bases.append(proxyForInterface(iface, '_reactor'))
+    if bases:
+        return type('(Logged Reactor)', tuple(bases), {})(reactor)
+    return reactor
+
+
+class _TCPTrafficLoggingReactor(proxyForInterface(IReactorTCP, '_reactor')):
+    """
+    A mixin for a reactor wrapper which defines C{connectTCP} so as to cause
+    traffic to be logged.
+    """
+    def connectTCP(self, host, port, factory):
+        return self._reactor.connectTCP(
+            host, port, _TrafficLoggingFactory(factory))
+
+
+class _TrafficLoggingFactory(WrappingFactory):
+    """
+    A wrapping factory which applies L{TrafficLoggingProtocolWrapper}.
+    """
+    LOGFILE_LIMIT = 20
+
+    protocol = TrafficLoggingProtocol
+
+    def __init__(self, wrappedFactory):
+        WrappingFactory.__init__(self, wrappedFactory)
+        self.logs = []
+        self.finishedLogs = []
+
+
+    def unregisterProtocol(self, protocol):
+        WrappingFactory.unregisterProtocol(self, protocol)
+        self.logs.remove(protocol.logfile)
+        self.finishedLogs.append(protocol.logfile)
+        del self.finishedLogs[:-self.LOGFILE_LIMIT]
+
+
+    def buildProtocol(self, addr):
+        logfile = StringIO()
+        self.logs.append(logfile)
+        return self.protocol(
+            self, self.wrappedFactory.buildProtocol(addr), logfile, None, 0)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110518/83f003fa/attachment.html>


More information about the calendarserver-changes mailing list