[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