[CalendarServer-changes] [6916] CalendarServer/trunk/contrib/performance
source_changes at macosforge.org
source_changes at macosforge.org
Wed Feb 9 12:57:56 PST 2011
Revision: 6916
http://trac.macosforge.org/projects/calendarserver/changeset/6916
Author: exarkun at twistedmatrix.com
Date: 2011-02-09 12:57:56 -0800 (Wed, 09 Feb 2011)
Log Message:
-----------
Add the start of a real runner for the load simulator.
Modified Paths:
--------------
CalendarServer/trunk/contrib/performance/loadtest/population.py
Added Paths:
-----------
CalendarServer/trunk/contrib/performance/loadtest/config.plist
CalendarServer/trunk/contrib/performance/loadtest/sim.py
CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
CalendarServer/trunk/contrib/performance/sim
Added: CalendarServer/trunk/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.plist (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.plist 2011-02-09 20:57:56 UTC (rev 6916)
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+
+ <key>server</key>
+ <dict>
+ <key>host</key>
+ <string>127.0.0.1</string>
+
+ <key>port</key>
+ <integer>8008</integer>
+ </dict>
+
+ <key>arrival</key>
+ <dict>
+ <key>factory</key>
+ <string>loadtest.population.SmoothRampUp</string>
+
+ <key>groups</key>
+ <integer>10</integer>
+
+ <key>groupSize</key>
+ <integer>1</integer>
+
+ <key>interval</key>
+ <integer>5</integer>
+
+ </dict>
+ </dict>
+</plist>
Modified: CalendarServer/trunk/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/population.py 2011-02-09 00:07:45 UTC (rev 6915)
+++ CalendarServer/trunk/contrib/performance/loadtest/population.py 2011-02-09 20:57:56 UTC (rev 6916)
@@ -27,6 +27,17 @@
from loadtest.profiles import Eventer, Inviter, Accepter
+class ClientType(object):
+ """
+ @ivar clientType: An L{ICalendarClient} implementation
+ @ivar profileTypes: A list of L{ICalendarUserProfile} implementations
+ """
+ def __init__(self, clientType, profileTypes):
+ self.clientType = clientType
+ self.profileTypes = profileTypes
+
+
+
class PopulationParameters(object):
"""
Descriptive statistics about a population of Calendar Server users.
@@ -36,7 +47,9 @@
Return a list of two-tuples giving the weights and types of
clients in the population.
"""
- return [(1, SnowLeopard)]
+ return [
+ (1, ClientType(SnowLeopard, [Eventer, Inviter, Accepter])),
+ ]
@@ -67,7 +80,7 @@
population with the given parameters.
@type parameters: L{PopulationParameters}
- @rtype: generator of L{ICalendarClient} providers
+ @rtype: generator of L{ClientType} instances
"""
for (clientType,) in izip(self._cycle(parameters.clientTypes())):
yield clientType
@@ -106,15 +119,34 @@
for n in range(numClients):
number = self._nextUserNumber()
user, auth = self._createUser(number)
- client = self._pop.next()(self.reactor, self.host, self.port, user, auth)
+
+ clientType = self._pop.next()
+ client = clientType.clientType(
+ self.reactor, self.host, self.port, user, auth)
client.run()
- Eventer(self.reactor, client, number).run()
- Inviter(self.reactor, client, number).run()
- Accepter(self.reactor, client, number).run()
+
+ for profileType in clientType.profileTypes:
+ profileType(self.reactor, client, number).run()
+
print 'Now running', self._user - 1, 'clients.'
+class SmoothRampUp(object):
+ def __init__(self, reactor, groups, groupSize, interval):
+ self.reactor = reactor
+ self.groups = groups
+ self.groupSize = groupSize
+ self.interval = interval
+
+
+ def run(self, simulator):
+ for i in range(self.groups):
+ self.reactor.callLater(
+ self.interval * i, simulator.add, self.groupSize)
+
+
+
class StatisticsBase(object):
def observe(self, event):
if event.get('type') == 'response':
@@ -214,9 +246,9 @@
simulator = CalendarClientSimulator(
populator, parameters, reactor, '127.0.0.1', 8008)
- # Add some clients.
- for i in range(10):
- reactor.callLater(i * 30, simulator.add, 1)
+ arrivalPolicy = SmoothRampUp(groups=10, groupSize=1, interval=3)
+ arrivalPolicy.run(reactor, simulator)
+
reactor.run()
report.summarize()
Added: CalendarServer/trunk/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/sim.py (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/sim.py 2011-02-09 20:57:56 UTC (rev 6916)
@@ -0,0 +1,138 @@
+##
+# 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 sys import argv
+from random import Random
+from plistlib import readPlist
+from collections import namedtuple
+
+from twisted.python.filepath import FilePath
+from twisted.python.usage import UsageError, Options
+from twisted.python.reflect import namedAny
+
+from loadtest.population import (
+ Populator, PopulationParameters, SmoothRampUp,
+ CalendarClientSimulator)
+
+
+class SimOptions(Options):
+ """
+ Command line configuration options for the load simulator.
+ """
+ config = None
+
+ def opt_config(self, path):
+ """
+ ini-syntax configuration file from which to read simulation
+ parameters.
+ """
+ try:
+ configFile = FilePath(path).open()
+ except IOError, e:
+ raise UsageError("--config %s: %s" % (path, e.strerror))
+ try:
+ self.config = readPlist(configFile)
+ except Exception, e:
+ raise UsageError(
+ "--config %s: %s" % (path, str(e)))
+
+
+ def postOptions(self):
+ if self.config is None:
+ raise UsageError("Specify a configuration file using --config <path>")
+
+
+Server = namedtuple('Server', 'host port')
+Arrival = namedtuple('Arrival', 'factory parameters')
+
+
+class LoadSimulator(object):
+ """
+ A L{LoadSimulator} simulates some configuration of calendar
+ clients.
+
+ @type server: L{Server}
+ """
+ def __init__(self, server, arrival, reactor=None):
+ if reactor is None:
+ from twisted.internet import reactor
+ self.server = server
+ self.arrival = arrival
+ self.reactor = reactor
+
+
+ @classmethod
+ def fromCommandLine(cls, args=None):
+ if args is None:
+ args = argv[1:]
+
+ options = SimOptions()
+ try:
+ options.parseOptions(args)
+ except UsageError, e:
+ raise SystemExit(str(e))
+
+ if 'server' in options.config:
+ server = Server(
+ options.config['server']['host'],
+ options.config['server']['port'])
+ else:
+ server = Server('127.0.0.1', 8008)
+
+ if 'arrival' in options.config:
+ params = options.config['arrival']
+ factory = namedAny(params.pop('factory'))
+ arrival = Arrival(factory, params)
+ else:
+ arrival = Arrival(
+ SmoothRampUp, dict(groups=10, groupSize=1, interval=3))
+
+ return cls(server, arrival)
+
+
+ @classmethod
+ def main(cls, args=None):
+ simulator = cls.fromCommandLine(args)
+ raise SystemExit(simulator.run())
+
+
+ def createPopulationParameters(self):
+ return PopulationParameters()
+
+
+ def createSimulator(self):
+ host = self.server.host
+ port = self.server.port
+ populator = Populator(Random())
+ parameters = self.createPopulationParameters()
+ return CalendarClientSimulator(
+ populator, parameters, self.reactor, host, port)
+
+
+ def createArrivalPolicy(self):
+ return SmoothRampUp(
+ self.reactor, groups=10, groupSize=1, interval=3)
+
+
+ def run(self):
+ sim = self.createSimulator()
+ arrivalPolicy = self.createArrivalPolicy()
+ arrivalPolicy.run(sim)
+ self.reactor.run()
+
+
+main = LoadSimulator.main
Added: CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_sim.py (rev 0)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_sim.py 2011-02-09 20:57:56 UTC (rev 6916)
@@ -0,0 +1,172 @@
+##
+# 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 plistlib import writePlistToString
+
+from twisted.python.usage import UsageError
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import succeed
+from twisted.internet.task import Clock
+
+from loadtest.sim import Server, Arrival, SimOptions, LoadSimulator, main
+from loadtest.population import SmoothRampUp, CalendarClientSimulator
+
+VALID_CONFIG = {
+ 'server': {
+ 'host': '127.0.0.1',
+ 'port': 8008,
+ },
+ 'arrival': {
+ 'factory': 'loadtest.population.SmoothRampUp',
+ 'groups': 10,
+ 'groupSize': 1,
+ 'interval': 3,
+ },
+ }
+
+VALID_CONFIG_PLIST = writePlistToString(VALID_CONFIG)
+
+
+class SimOptionsTests(TestCase):
+ def test_missingConfig(self):
+ """
+ If the I{config} option is not specified,
+ L{SimOptions.parseOptions} raises a L{UsageError} indicating
+ it is required.
+ """
+ options = SimOptions()
+ exc = self.assertRaises(UsageError, options.parseOptions, [])
+ self.assertEquals(
+ str(exc), "Specify a configuration file using --config <path>")
+
+
+ def test_configFileNotFound(self):
+ """
+ If the filename given to the I{config} option is not found,
+ L{SimOptions.parseOptions} raises a L{UsageError} indicating
+ this.
+ """
+ name = self.mktemp()
+ options = SimOptions()
+ exc = self.assertRaises(
+ UsageError, options.parseOptions, ['--config', name])
+ self.assertEquals(
+ str(exc), "--config %s: No such file or directory" % (name,))
+
+
+ def test_configFileNotParseable(self):
+ """
+ If the contents of the file given to the I{config} option
+ cannot be parsed by L{ConfigParser},
+ L{SimOptions.parseOptions} raises a L{UsageError} indicating
+ this.
+ """
+ config = self.mktemp()
+ FilePath(config).setContent("some random junk")
+ options = SimOptions()
+ exc = self.assertRaises(
+ UsageError, options.parseOptions, ['--config', config])
+ self.assertEquals(
+ str(exc),
+ "--config %s: syntax error: line 1, column 0" % (config,))
+
+
+
+class StubSimulator(LoadSimulator):
+ def run(self):
+ return 3
+
+
+
+class LoadSimulatorTests(TestCase):
+ def test_main(self):
+ """
+ L{LoadSimulator.main} raises L{SystemExit} with the result of
+ L{LoadSimulator.run}.
+ """
+ config = FilePath(self.mktemp())
+ config.setContent(VALID_CONFIG_PLIST)
+
+ exc = self.assertRaises(
+ SystemExit, StubSimulator.main, ['--config', config.path])
+ self.assertEquals(exc.args, (StubSimulator(None, None).run(),))
+
+
+ def test_createSimulator(self):
+ """
+ L{LoadSimulator.createSimulator} creates a
+ L{CalendarClientSimulator} with its own reactor and host and
+ port information from the configuration file.
+ """
+ host = '127.0.0.7'
+ port = 1243
+ reactor = object()
+ sim = LoadSimulator(Server(host, port), None, reactor)
+ calsim = sim.createSimulator()
+ self.assertIsInstance(calsim, CalendarClientSimulator)
+ self.assertIdentical(calsim.reactor, reactor)
+ self.assertEquals(calsim.host, host)
+ self.assertEquals(calsim.port, port)
+
+
+ def test_loadServerConfig(self):
+ """
+ The Calendar Server host and port are loaded from the [server]
+ section of the configuration file specified.
+ """
+ config = FilePath(self.mktemp())
+ config.setContent(writePlistToString({
+ "server": {
+ "host": "127.0.0.1",
+ "port": 1234,
+ },
+ }))
+ sim = LoadSimulator.fromCommandLine(['--config', config.path])
+ self.assertEquals(sim.server, Server("127.0.0.1", 1234))
+
+
+ def test_loadArrivalConfig(self):
+ """
+ The arrival policy type and arguments are loaded from the
+ [arrival] section of the configuration file specified.
+ """
+ config = FilePath(self.mktemp())
+ config.setContent(writePlistToString({
+ "arrival": {
+ "factory": "loadtest.population.SmoothRampUp",
+ "groups": 10,
+ "groupSize": 1,
+ "interval": 3,
+ },
+ }))
+ sim = LoadSimulator.fromCommandLine(['--config', config.path])
+ self.assertEquals(
+ sim.arrival,
+ Arrival(SmoothRampUp, dict(groups=10, groupSize=1, interval=3)))
+
+
+ def test_createArrivalPolicy(self):
+ """
+ L{LoadSimulator.createArrivalPolicy} creates an arrival
+ policy.
+ """
+ reactor = object()
+ sim = LoadSimulator(None, None, reactor)
+ arrival = sim.createArrivalPolicy()
+ self.assertIsInstance(arrival, SmoothRampUp)
+ self.assertIdentical(arrival.reactor, reactor)
Added: CalendarServer/trunk/contrib/performance/sim
===================================================================
--- CalendarServer/trunk/contrib/performance/sim (rev 0)
+++ CalendarServer/trunk/contrib/performance/sim 2011-02-09 20:57:56 UTC (rev 6916)
@@ -0,0 +1,20 @@
+#!/usr/bin/python
+##
+# 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 loadtest.sim import main
+main()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110209/a076618a/attachment-0001.html>
More information about the calendarserver-changes
mailing list