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

source_changes at macosforge.org source_changes at macosforge.org
Tue Apr 10 20:10:52 PDT 2012


Revision: 9026
          http://trac.macosforge.org/projects/calendarserver/changeset/9026
Author:   cdaboo at apple.com
Date:     2012-04-10 20:10:50 -0700 (Tue, 10 Apr 2012)
Log Message:
-----------
Adds a basic web UI that allows the sim to be stopped or for current results to be reported.

Modified Paths:
--------------
    CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist
    CalendarServer/trunk/contrib/performance/loadtest/config.plist
    CalendarServer/trunk/contrib/performance/loadtest/ical.py
    CalendarServer/trunk/contrib/performance/loadtest/logger.py
    CalendarServer/trunk/contrib/performance/loadtest/population.py
    CalendarServer/trunk/contrib/performance/loadtest/profiles.py
    CalendarServer/trunk/contrib/performance/loadtest/sim.py

Added Paths:
-----------
    CalendarServer/trunk/contrib/performance/loadtest/test_webadmin.py
    CalendarServer/trunk/contrib/performance/loadtest/webadmin.py

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist	2012-04-11 03:10:50 UTC (rev 9026)
@@ -31,10 +31,21 @@
 			<string>./python contrib/performance/loadtest/ampsim.py</string>
 			<string>./python contrib/performance/loadtest/ampsim.py</string>
 		</array>
+
 		<!-- Identify the server to be load tested. -->
 		<key>server</key>
 		<string>https://127.0.0.1:8443/</string>
 
+		<!-- Configure Admin Web UI. -->
+		<key>webadmin</key>
+		<dict>
+			<key>enabled</key>
+			<true/>
+			
+			<key>HTTPPort</key>
+			<integer>8080</integer>
+		</dict>
+
 		<!-- Define the credentials of the clients which will be used to load test 
 			the server. These credentials must already be valid on the server. -->
 		<key>accounts</key>

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.plist	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.plist	2012-04-11 03:10:50 UTC (rev 9026)
@@ -23,6 +23,16 @@
 		<key>server</key>
 		<string>https://127.0.0.1:8443/</string>
 
+		<!-- Configure Admin Web UI. -->
+		<key>webadmin</key>
+		<dict>
+			<key>enabled</key>
+			<true/>
+			
+			<key>HTTPPort</key>
+			<integer>8080</integer>
+		</dict>
+
 		<!-- Define the credentials of the clients which will be used to load test 
 			the server. These credentials must already be valid on the server. -->
 		<key>accounts</key>

Modified: CalendarServer/trunk/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ical.py	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/ical.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -737,11 +737,15 @@
         we should emulate.
         """
 
+        result = True
         try:
             result = yield self._newOperation("push" if push else "poll", self._poll(calendarHomeSet, firstTime))
         finally:
             if result:
-                self._checking.remove(calendarHomeSet)
+                try:
+                    self._checking.remove(calendarHomeSet)
+                except KeyError:
+                    pass
         returnValue(result)
 
     @inlineCallbacks
@@ -1609,7 +1613,7 @@
             print (self.format % formatArgs).encode('utf-8')
 
 
-    def report(self):
+    def report(self, output):
         pass
 
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/logger.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/logger.py	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/logger.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -19,7 +19,7 @@
 
 class SummarizingMixin(object):
 
-    def printHeader(self, fields):
+    def printHeader(self, output, fields):
         """
         Print a header for the summarization data which will be reported.
 
@@ -34,8 +34,8 @@
             format.append('%%%ds' % (width,))
             labels.append(label)
         header = ' '.join(format) % tuple(labels)
-        print header
-        print "-" * len(header)
+        output.write("%s\n" % header)
+        output.write("%s\n" % ("-" * len(header),))
 
 
     def _summarizeData(self, operation, data):
@@ -68,12 +68,12 @@
                 (mean(durations), median(durations), stddev(durations), "FAIL" if failure else "")
 
 
-    def _printRow(self, formats, values):
+    def _printRow(self, output, formats, values):
         format = ' '.join(formats)
-        print format % values
+        output.write("%s\n" % format % values)
 
 
-    def printData(self, formats, perOperationTimes):
+    def printData(self, output, formats, perOperationTimes):
         """
         Print one or more rows of data with the given formatting.
 
@@ -85,4 +85,4 @@
             (C{True} if so, C{False} if not) and how long the operation took.
         """
         for method, data in perOperationTimes:
-            self._printRow(formats, self._summarizeData(method, data))
+            self._printRow(output, formats, self._summarizeData(method, data))

Modified: CalendarServer/trunk/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/population.py	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/population.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -283,7 +283,7 @@
             self.clientFailure(event)
 
 
-    def report(self):
+    def report(self, output):
         pass
 
 
@@ -376,34 +376,34 @@
         self._failed_clients.append(event['reason'])
 
 
-    def printMiscellaneous(self, items):
+    def printMiscellaneous(self, output, items):
         for k, v in sorted(items.iteritems()):
-            print k.title(), ':', v
+            output.write("%s:%s\n" % (k.title(), v,))
 
 
-    def report(self):
-        print
-        print "** REPORT **"
-        print
-        self.printMiscellaneous({
+    def report(self, output):
+        output.write("\n")
+        output.write("** REPORT **\n")
+        output.write("\n")
+        self.printMiscellaneous(output, {
             'users': self.countUsers(),
             'clients': self.countClients(),
         })
         if self.countClientFailures() > 0:
-            self.printMiscellaneous({
+            self.printMiscellaneous(output, {
                 'Failed clients': self.countClientFailures(),
             })
             for ctr, reason in enumerate(self._failed_clients, 1):
-                self.printMiscellaneous({
+                self.printMiscellaneous(output, {
                     'Failure #%d' % (ctr,): reason,
                 })
             
-        print
-        self.printHeader([
+        output.write("\n")
+        self.printHeader(output, [
                 (label, width)
                 for (label, width, _ignore_fmt)
                 in self._fields])
-        self.printData(
+        self.printData(output,
             [fmt for (label, width, fmt) in self._fields],
             sorted(self._perMethodTimes.items()))
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -767,13 +767,13 @@
         return data[:-1] + (avglag,) + data[-1:]
 
 
-    def report(self):
-        print
-        self.printHeader([
+    def report(self, output):
+        output.write("\n")
+        self.printHeader(output, [
                 (label, width)
                 for (label, width, _ignore_fmt)
                 in self._fields])
-        self.printData(
+        self.printData(output,
             [fmt for (label, width, fmt) in self._fields],
             sorted(self._perOperationTimes.items()))
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/sim.py	2012-04-11 00:11:58 UTC (rev 9025)
+++ CalendarServer/trunk/contrib/performance/loadtest/sim.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -16,12 +16,12 @@
 #
 ##
 
+from collections import namedtuple
 from os import environ
+from plistlib import readPlist
+from random import Random
+from sys import argv, stdout
 from xml.parsers.expat import ExpatError
-from sys import argv, stdout
-from random import Random
-from plistlib import readPlist
-from collections import namedtuple
 
 from twisted.python import context
 from twisted.python.filepath import FilePath
@@ -32,6 +32,8 @@
 from twisted.application.service import Service
 from twisted.application.service import MultiService
 
+from twisted.internet.defer import Deferred
+from twisted.internet.defer import gatherResults
 from twisted.internet.protocol import ProcessProtocol
 
 from contrib.performance.loadtest.ical import OS_X_10_6
@@ -39,8 +41,7 @@
 from contrib.performance.loadtest.population import (
     Populator, ProfileType, ClientType, PopulationParameters, SmoothRampUp,
     CalendarClientSimulator)
-from twisted.internet.defer import Deferred
-from twisted.internet.defer import gatherResults
+from contrib.performance.loadtest.webadmin import LoadSimAdminResource
 
 
 class _DirectoryRecord(object):
@@ -176,6 +177,8 @@
 Arrival = namedtuple('Arrival', 'factory parameters')
 
 
+from twisted.web import server
+
 class LoadSimulator(object):
     """
     A L{LoadSimulator} simulates some configuration of calendar
@@ -189,15 +192,17 @@
         user information about the accounts on the server being put
         under load.
     """
-    def __init__(self, server, arrival, parameters, observers=None,
+    def __init__(self, server, webdadminPort, arrival, parameters, observers=None,
                  records=None, reactor=None, runtime=None, workers=None,
                  configTemplate=None, workerID=None, workerCount=1):
         if reactor is None:
             from twisted.internet import reactor
         self.server = server
+        self.webdadminPort = webdadminPort
         self.arrival = arrival
         self.parameters = parameters
         self.observers = observers
+        self.reporter = None
         self.records = records
         self.reactor = LagTrackingReactor(reactor)
         self.runtime = runtime
@@ -235,9 +240,15 @@
             workerCount = config.get("workerCount", 1)
             configTemplate = None
             server = 'http://127.0.0.1:8008/'
+            webdadminPort = None
+
             if 'server' in config:
                 server = config['server']
 
+            if 'webadmin' in config:
+                if config['webadmin']['enabled']:
+                    webadminPort = config['webadmin']['HTTPPort']
+
             if 'arrival' in config:
                 arrival = Arrival(
                     namedAny(config['arrival']['factory']), 
@@ -265,6 +276,7 @@
         else:
             # Manager / observer process.
             server = ''
+            webadminPort = None
             arrival = None
             parameters = None
             workerID = 0
@@ -283,7 +295,7 @@
             records.extend(namedAny(loader)(**params))
             output.write("Loaded {0} accounts.\n".format(len(records)))
 
-        return cls(server, arrival, parameters, observers=observers,
+        return cls(server, webadminPort, arrival, parameters, observers=observers,
                    records=records, runtime=runtime, reactor=reactor,
                    workers=workers, configTemplate=configTemplate,
                    workerID=workerID, workerCount=workerCount)
@@ -361,6 +373,8 @@
         self.attachServices(output)
         if self.runtime is not None:
             self.reactor.callLater(self.runtime, self.reactor.stop)
+        if self.webdadminPort:
+            self.reactor.listenTCP(self.webdadminPort, server.Site(LoadSimAdminResource(self)))
         self.reactor.run()
 
 
@@ -433,24 +447,38 @@
     simulator when it is stopped.
     """
 
+    def startService(self):
+        """
+        Start observing.
+        """
+        super(ReporterService, self).startService()
+        self.loadsim.reporter = self
+
+
     def stopService(self):
         """
         Emit the report to the specified output file.
         """
         super(ReporterService, self).stopService()
+        self.generateReport(self.output)
+
+
+    def generateReport(self, output):
+        """
+        Emit the report to the specified output file.
+        """
         failures = []
         for obs in self.loadsim.observers:
-            obs.report()
+            obs.report(output)
             failures.extend(obs.failures())
         if failures:
-            self.output.write('\n*** FAIL\n')
-            self.output.write('\n'.join(failures))
-            self.output.write('\n')
+            output.write('\n*** FAIL\n')
+            output.write('\n'.join(failures))
+            output.write('\n')
         else:
-            self.output.write('\n*** PASS\n')
+            output.write('\n*** PASS\n')
 
 
-
 class ProcessProtocolBridge(ProcessProtocol):
 
     def __init__(self, spawner, proto):

Added: CalendarServer/trunk/contrib/performance/loadtest/test_webadmin.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_webadmin.py	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_webadmin.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -0,0 +1,134 @@
+##
+# Copyright (c) 2012 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 twisted.trial.unittest import TestCase
+from contrib.performance.loadtest.webadmin import LoadSimAdminResource
+
+class WebAdminTests(TestCase):
+    """
+    Tests for L{LoadSimAdminResource}.
+    """
+
+    class FakeReporter(object):
+        
+        def generateReport(self, output):
+            output.write("FakeReporter")
+
+
+    class FakeReactor(object):
+        
+        def __init__(self):
+            self.running = True
+        
+        def stop(self):
+            self.running = False
+
+
+    class FakeLoadSim(object):
+        
+        def __init__(self):
+            self.reactor = WebAdminTests.FakeReactor()
+            self.reporter = WebAdminTests.FakeReporter()
+
+    
+    class FakeRequest(object):
+        
+        def __init__(self, **kwargs):
+            self.args = kwargs
+
+
+    def test_resourceGET(self):
+        """
+        Test render_GET
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        
+        response = resource.render_GET(WebAdminTests.FakeRequest())
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        
+    def test_resourcePOST_Stop(self):
+        """
+        Test render_POST when Stop button is clicked
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(resource.token,),
+            stop=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertFalse(loadsim.reactor.running)
+        
+    def test_resourcePOST_Stop_BadToken(self):
+        """
+        Test render_POST when Stop button is clicked but token is wrong
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=("xyz",),
+            stop=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(loadsim.reactor.running)
+        
+    def test_resourcePOST_Results(self):
+        """
+        Test render_POST when Results button is clicked
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(resource.token,),
+            results=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find("FakeReporter") != -1)
+        self.assertTrue(loadsim.reactor.running)
+        
+    def test_resourcePOST_Results_BadToken(self):
+        """
+        Test render_POST when Results button is clicked and token is wrong
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=("xyz",),
+            results=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find("FakeReporter") == -1)
+        self.assertTrue(loadsim.reactor.running)

Added: CalendarServer/trunk/contrib/performance/loadtest/webadmin.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/webadmin.py	                        (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/webadmin.py	2012-04-11 03:10:50 UTC (rev 9026)
@@ -0,0 +1,81 @@
+##
+# Copyright (c) 2012 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.
+#
+##
+
+"""
+loadsim Web Admin UI.
+"""
+
+__all__ = [
+    "LoadSimAdminResource",
+]
+
+import cStringIO as StringIO
+import uuid
+
+from twisted.web import resource
+
+
+class LoadSimAdminResource (resource.Resource):
+    """
+    Web administration HTTP resource.
+    """
+    isLeaf = True
+
+    BODY = """<html>
+<body>
+    <form method="POST">
+        <input name="token" type="hidden" value="%s" />
+        <input name="results" type="submit" value="Results" />
+        <p />
+        <input name="stop" type="submit" value="Stop Sim" />
+    </form>
+</body>
+</html>
+"""
+
+    BODY_RESULTS = """<html>
+<body>
+    <form method="POST">
+        <input name="token" type="hidden" value="%s" />
+        <input name="results" type="submit" value="Results" />
+        <p />
+        <input name="stop" type="submit" value="Stop Sim" />
+    </form>
+    <p />
+    <pre>%s</pre>
+</body>
+</html>
+"""
+
+    def __init__(self, loadsim):
+        self.loadsim = loadsim
+        self.token = str(uuid.uuid4())
+
+    def render_GET(self, request):
+        return self.BODY % (self.token,)
+
+    def render_POST(self, request):
+        if 'token' not in request.args or request.args['token'][0] != self.token:
+            return self.BODY % (self.token,)
+
+        if 'stop' in request.args:
+            self.loadsim.reactor.stop()
+        elif 'results' in request.args:
+            report = StringIO.StringIO()
+            self.loadsim.reporter.generateReport(report)
+            return self.BODY_RESULTS % (self.token, report.getvalue(),)
+        return self.BODY % (self.token,)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120410/3f7c875d/attachment-0001.html>


More information about the calendarserver-changes mailing list