[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