[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