[CalendarServer-changes] [7547] CalendarServer/trunk/contrib/performance

source_changes at macosforge.org source_changes at macosforge.org
Thu Jun 2 08:51:46 PDT 2011


Revision: 7547
          http://trac.macosforge.org/projects/calendarserver/changeset/7547
Author:   exarkun at twistedmatrix.com
Date:     2011-06-02 08:51:45 -0700 (Thu, 02 Jun 2011)
Log Message:
-----------
Add some support for specifying distributions of values instead of values; use it in the event creator to spread events out (unrealistically) over time.

Modified Paths:
--------------
    CalendarServer/trunk/contrib/performance/loadtest/profiles.py
    CalendarServer/trunk/contrib/performance/loadtest/sim.py
    CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
    CalendarServer/trunk/contrib/performance/stats.py
    CalendarServer/trunk/contrib/performance/test_stats.py

Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2011-06-01 14:24:23 UTC (rev 7546)
+++ CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2011-06-02 15:51:45 UTC (rev 7547)
@@ -37,7 +37,7 @@
 from twisted.internet.task import LoopingCall
 from twisted.web.http import PRECONDITION_FAILED
 
-from stats import mean, median
+from stats import NearFutureDistribution, UniformDiscreteDistribution, mean, median
 from loadtest.logger import SummarizingMixin
 from loadtest.ical import IncorrectResponseCode
 
@@ -340,8 +340,16 @@
 END:VEVENT
 END:VCALENDAR
 """))[0]
-    def setParameters(self, interval=25):
+    def setParameters(
+        self, interval=25,
+        eventStartDistribution=NearFutureDistribution(),
+        eventDurationDistribution=UniformDiscreteDistribution([
+                15 * 60, 30 * 60,
+                45 * 60, 60 * 60,
+                120 * 60])):
         self._interval = interval
+        self._eventStartDistribution = eventStartDistribution
+        self._eventDurationDistribution = eventDurationDistribution
 
 
     def run(self):
@@ -363,12 +371,16 @@
             vevent = vcalendar.contents[u'vevent'][0]
             tz = vevent.contents[u'created'][0].value.tzinfo
             dtstamp = datetime.now(tz)
+            dtstart = datetime.fromtimestamp(self._eventStartDistribution.sample(), tz)
+            dtend = dtstart + timedelta(seconds=self._eventDurationDistribution.sample())
             vevent.contents[u'created'][0].value = dtstamp
             vevent.contents[u'dtstamp'][0].value = dtstamp
-            vevent.contents[u'dtstart'][0].value = dtstamp
-            vevent.contents[u'dtend'][0].value = dtstamp + timedelta(hours=1)
+            vevent.contents[u'dtstart'][0].value = dtstart
+            vevent.contents[u'dtend'][0].value = dtend
             vevent.contents[u'uid'][0].value = unicode(uuid4())
 
+            print 'Creating event from', dtstart, 'to', dtend
+
             href = '%s%s.ics' % (
                 calendar.url, vevent.contents[u'uid'][0].value)
             d = self._client.addEvent(href, vcalendar)

Modified: CalendarServer/trunk/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/sim.py	2011-06-01 14:24:23 UTC (rev 7546)
+++ CalendarServer/trunk/contrib/performance/loadtest/sim.py	2011-06-02 15:51:45 UTC (rev 7547)
@@ -197,7 +197,7 @@
                         namedAny(clientConfig["software"]),
                         [ProfileType(
                                 namedAny(profile["class"]),
-                                profile["params"])
+                                cls._convertParams(profile["params"]))
                          for profile in clientConfig["profiles"]]))
         if not parameters.clients:
             parameters.addClient(
@@ -217,8 +217,32 @@
         return cls(server, arrival, parameters,
                    observers=observers, records=records)
 
+    @classmethod
+    def _convertParams(cls, params):
+        """
+        Find parameter values which should be more structured than plistlib is
+        capable of constructing and replace them with the more structured form.
 
+        Specifically, find keys that end with C{"Distribution"} and convert
+        them into some kind of distribution object using the associated
+        dictionary of keyword arguments.
+        """
+        for k, v in params.iteritems():
+            if k.endswith('Distribution'):
+                params[k] = cls._convertDistribution(v)
+        return params
+
+
     @classmethod
+    def _convertDistribution(cls, value):
+        """
+        Construct and return a new distribution object using the type and
+        params specified by C{value}.
+        """
+        return namedAny(value['type'])(**value['params'])
+
+
+    @classmethod
     def main(cls, args=None):
         simulator = cls.fromCommandLine(args)
         raise SystemExit(simulator.run())

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_sim.py	2011-06-01 14:24:23 UTC (rev 7546)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_sim.py	2011-06-02 15:51:45 UTC (rev 7547)
@@ -30,6 +30,7 @@
 from twistedcaldav.directory.idirectory import IDirectoryService
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 
+from stats import NormalDistribution
 from loadtest.ical import SnowLeopard
 from loadtest.profiles import Eventer, Inviter, Accepter
 from loadtest.population import (
@@ -306,13 +307,25 @@
         config.setContent(writePlistToString({
                     "clients": [{
                             "software": "loadtest.ical.SnowLeopard",
-                            "profiles": [{"class": "loadtest.profiles.Eventer", "params": {"interval": 25}}],
+                            "profiles": [{
+                                    "params": {
+                                        "interval": 25,
+                                        "eventStartDistribution": {
+                                            "type": "stats.NormalDistribution",
+                                            "params": {
+                                                "mu": 123,
+                                                "sigma": 456,
+                                                }}},
+                                    "class": "loadtest.profiles.Eventer"}],
                             "weight": 3,
                             }]}))
+                            
         sim = LoadSimulator.fromCommandLine(['--config', config.path])
         expectedParameters = PopulationParameters()
         expectedParameters.addClient(
-            3, ClientType(SnowLeopard, [ProfileType(Eventer, {"interval": 25})]))
+            3, ClientType(SnowLeopard, [ProfileType(Eventer, {
+                            "interval": 25,
+                            "eventStartDistribution": NormalDistribution(123, 456)})]))
         self.assertEquals(sim.parameters, expectedParameters)
 
         

Modified: CalendarServer/trunk/contrib/performance/stats.py
===================================================================
--- CalendarServer/trunk/contrib/performance/stats.py	2011-06-01 14:24:23 UTC (rev 7546)
+++ CalendarServer/trunk/contrib/performance/stats.py	2011-06-02 15:51:45 UTC (rev 7547)
@@ -14,6 +14,12 @@
 # limitations under the License.
 ##
 
+import random, time
+
+from zope.interface import Interface, implements
+
+from twisted.python.util import FancyEqMixin
+
 import sqlparse
 
 NANO = 1000000000.0
@@ -210,3 +216,74 @@
     """
     buckets = {}
     return []
+
+
+class IPopulation(Interface):
+    def sample():
+        pass
+
+
+class UniformDiscreteDistribution(object, FancyEqMixin):
+    """
+    
+    """
+    implements(IPopulation)
+
+    compareAttributes = ['_values']
+
+    def __init__(self, values):
+        self._values = values
+        self._refill()
+
+
+    def _refill(self):
+        self._remaining = self._values[:]
+        random.shuffle(self._remaining)
+
+
+    def sample(self):
+        if not self._remaining:
+            self._refill()
+        return self._remaining.pop()
+
+
+
+class LogNormalDistribution(object, FancyEqMixin):
+    """
+    """
+    implements(IPopulation)
+
+    compareAttributes = ['_mu', '_sigma']
+
+    def __init__(self, mu, sigma):
+        self._mu = mu
+        self._sigma = sigma
+
+
+    def sample(self):
+        return random.lognormvariate(self._mu, self._sigma)
+
+
+
+class NearFutureDistribution(object, FancyEqMixin):
+    compareAttributes = ['_offset']
+
+    def __init__(self):
+        self._offset = LogNormalDistribution(7, 0.8)
+
+
+    def sample(self):
+        return time.time() + self._offset.sample()
+
+
+
+class NormalDistribution(object, FancyEqMixin):
+    compareAttributes = ['_mu', '_sigma']
+
+    def __init__(self, mu, sigma):
+        self._mu = mu
+        self._sigma = sigma
+
+
+    def sample(self):
+        return random.normalvariate(self._mu, self._sigma)

Modified: CalendarServer/trunk/contrib/performance/test_stats.py
===================================================================
--- CalendarServer/trunk/contrib/performance/test_stats.py	2011-06-01 14:24:23 UTC (rev 7546)
+++ CalendarServer/trunk/contrib/performance/test_stats.py	2011-06-02 15:51:45 UTC (rev 7547)
@@ -16,7 +16,7 @@
 
 from twisted.trial.unittest import TestCase
 
-from stats import SQLDuration, quantize
+from stats import SQLDuration, LogNormalDistribution, UniformDiscreteDistribution, quantize
 
 class SQLDurationTests(TestCase):
     def setUp(self):
@@ -41,6 +41,27 @@
             'SELECT foo FROM bar WHERE ?')
 
 
+
+class DistributionTests(TestCase):
+    def test_lognormal(self):
+        dist = LogNormalDistribution(1, 1)
+        for i in range(100):
+            value = dist.sample()
+            self.assertIsInstance(value, float)
+            self.assertTrue(value >= 0.0, "negative value %r" % (value,))
+            self.assertTrue(value <= 1000, "implausibly high value %r" % (value,))
+
+
+    def test_uniformdiscrete(self):
+        population = [1, 5, 6, 9]
+        counts = dict.fromkeys(population, 0)
+        dist = UniformDiscreteDistribution(population)
+        for i in range(len(population) * 10):
+            counts[dist.sample()] += 1
+        self.assertEqual(dict.fromkeys(population, 10), counts)
+
+
+
 class QuantizationTests(TestCase):
     """
     Tests for L{quantize} which constructs discrete datasets of
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110602/018c584e/attachment-0001.html>


More information about the calendarserver-changes mailing list