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

source_changes at macosforge.org source_changes at macosforge.org
Fri Jan 20 13:25:21 PST 2012


Revision: 8569
          http://trac.macosforge.org/projects/calendarserver/changeset/8569
Author:   cdaboo at apple.com
Date:     2012-01-20 13:25:20 -0800 (Fri, 20 Jan 2012)
Log Message:
-----------
Add more realistic invite generator to the sim.

Modified Paths:
--------------
    CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist
    CalendarServer/trunk/contrib/performance/loadtest/config.plist
    CalendarServer/trunk/contrib/performance/loadtest/profiles.py
    CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py
    CalendarServer/trunk/contrib/performance/stats.py

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist	2012-01-20 21:00:18 UTC (rev 8568)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.dist.plist	2012-01-20 21:25:20 UTC (rev 8569)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-    Copyright (c) 2011 Apple Inc. All rights reserved.
+    Copyright (c) 2011-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.
@@ -184,7 +184,8 @@
 						</dict>
 					</dict>
 
-					<!-- This profile invites new attendees to existing events. -->
+					<!-- This profile invites new attendees to existing events. 
+					     This profile should no longer be used - use RealisticInviter instead. -->
 					<dict>
 						<key>class</key>
 						<string>contrib.performance.loadtest.profiles.Inviter</string>
@@ -192,6 +193,54 @@
 						<key>params</key>
 						<dict>
 							<key>enabled</key>
+							<false/>
+
+							<!-- Define the frequency at which new invitations will be sent out. -->
+							<key>sendInvitationDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.NormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mu gives the mean of the normal distribution (in seconds). -->
+									<key>mu</key>
+									<integer>60</integer>
+
+									<!-- and sigma gives its standard deviation. -->
+									<key>sigma</key>
+									<integer>5</integer>
+								</dict>
+							</dict>
+
+							<!-- Define the distribution of who will be invited to an event. Each 
+								set of credentials loaded by the load tester has an index; samples from this 
+								distribution will be added to that index to arrive at the index of some other 
+								credentials, which will be the target of the invitation. -->
+							<key>inviteeDistanceDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.UniformIntegerDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- The minimum value (inclusive) of the uniform distribution. -->
+									<key>min</key>
+									<integer>-100</integer>
+									<!-- The maximum value (exclusive) of the uniform distribution. -->
+									<key>max</key>
+									<integer>101</integer>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
+					<!-- This profile invites some number of new attendees to new events. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.RealisticInviter</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
 							<true/>
 
 							<!-- Define the frequency at which new invitations will be sent out. -->
@@ -229,9 +278,80 @@
 									<integer>101</integer>
 								</dict>
 							</dict>
+
+							<!-- Define the distribution of how many attendees will be invited to an event.
+							    Experience shows that sigma should equal sqrt(mu) to give a peak at around 1.
+							    mu = 0.5  sigma = 0.71 gives an average of 1.6 attendees
+							    mu = 0.75 sigma = 0.87 gives an average of 2.6 attendees
+							    mu = 1.0  sigma = 1.0 gives an average of 4 attendees
+							    mu = 1.1  sigma = 1.05 gives an average of 4.7 attendees
+							    mu = 1.2  sigma = 1.1 gives an average of 5.5 attendees
+							    mu = 1.3  sigma = 1.14 gives an average of 6.5 attendees
+							    mu = 1.4  sigma = 1.18 gives an average of 7.6 attendees
+							    mu = 1.5  sigma = 1.22 gives an average of 8.8 attendees
+							    mu = 1.75  sigma = 1.32 gives an average of 12.5 attendees
+							    mu = 2.0  sigma = 1.41 gives an average of 17.4 attendees
+							     -->
+							<key>inviteeCountDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.LogNormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mean -->
+									<key>mu</key>
+									<real>1.3</real>
+									<!-- standard deviation -->
+									<key>sigma</key>
+									<real>1.14</real>
+									<!-- maximum -->
+									<key>maximum</key>
+									<real>100</real>
 						</dict>
 					</dict>
 
+							<!-- Define how start times (DTSTART) for the randomly generated events 
+								will be selected. This is an example of a "Distribution" parameter. The value 
+								for most "Distribution" parameters are interchangeable and extensible. -->
+							<key>eventStartDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized. It produces timestamps 
+									in the near future, limited to certain days of the week and certain hours 
+									of the day. -->
+								<key>type</key>
+								<string>contrib.performance.stats.WorkDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- These are the days of the week the distribution will use. -->
+									<key>daysOfWeek</key>
+									<array>
+										<string>mon</string>
+										<string>tue</string>
+										<string>wed</string>
+										<string>thu</string>
+										<string>fri</string>
+									</array>
+
+									<!-- The earliest hour of a day at which an event might be scheduled. -->
+									<key>beginHour</key>
+									<integer>8</integer>
+
+									<!-- And the latest hour of a day (at which an event will be scheduled 
+										to begin!). -->
+									<key>endHour</key>
+									<integer>16</integer>
+
+									<!-- The timezone in which the event is scheduled. (XXX Does this 
+										really work right?) -->
+									<key>tzname</key>
+									<string>America/Los_Angeles</string>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
 					<!-- This profile accepts invitations to events, handles cancels, and
 					     handles replies received. -->
 					<dict>

Modified: CalendarServer/trunk/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/config.plist	2012-01-20 21:00:18 UTC (rev 8568)
+++ CalendarServer/trunk/contrib/performance/loadtest/config.plist	2012-01-20 21:25:20 UTC (rev 8569)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-    Copyright (c) 2011 Apple Inc. All rights reserved.
+    Copyright (c) 2011-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.
@@ -172,7 +172,8 @@
 						</dict>
 					</dict>
 
-					<!-- This profile invites new attendees to existing events. -->
+					<!-- This profile invites new attendees to existing events. 
+					     This profile should no longer be used - use RealisticInviter instead. -->
 					<dict>
 						<key>class</key>
 						<string>contrib.performance.loadtest.profiles.Inviter</string>
@@ -180,6 +181,54 @@
 						<key>params</key>
 						<dict>
 							<key>enabled</key>
+							<false/>
+
+							<!-- Define the frequency at which new invitations will be sent out. -->
+							<key>sendInvitationDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.NormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mu gives the mean of the normal distribution (in seconds). -->
+									<key>mu</key>
+									<integer>60</integer>
+
+									<!-- and sigma gives its standard deviation. -->
+									<key>sigma</key>
+									<integer>5</integer>
+								</dict>
+							</dict>
+
+							<!-- Define the distribution of who will be invited to an event. Each 
+								set of credentials loaded by the load tester has an index; samples from this 
+								distribution will be added to that index to arrive at the index of some other 
+								credentials, which will be the target of the invitation. -->
+							<key>inviteeDistanceDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.UniformIntegerDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- The minimum value (inclusive) of the uniform distribution. -->
+									<key>min</key>
+									<integer>-100</integer>
+									<!-- The maximum value (exclusive) of the uniform distribution. -->
+									<key>max</key>
+									<integer>101</integer>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
+					<!-- This profile invites some number of new attendees to new events. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.RealisticInviter</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
 							<true/>
 
 							<!-- Define the frequency at which new invitations will be sent out. -->
@@ -217,6 +266,77 @@
 									<integer>101</integer>
 								</dict>
 							</dict>
+
+							<!-- Define the distribution of how many attendees will be invited to an event.
+							    Experience shows that sigma should equal sqrt(mu) to give a peak at around 1.
+							    mu = 0.5  sigma = 0.71 gives an average of 1.6 attendees
+							    mu = 0.75 sigma = 0.87 gives an average of 2.6 attendees
+							    mu = 1.0  sigma = 1.0 gives an average of 4 attendees
+							    mu = 1.1  sigma = 1.05 gives an average of 4.7 attendees
+							    mu = 1.2  sigma = 1.1 gives an average of 5.5 attendees
+							    mu = 1.3  sigma = 1.14 gives an average of 6.5 attendees
+							    mu = 1.4  sigma = 1.18 gives an average of 7.6 attendees
+							    mu = 1.5  sigma = 1.22 gives an average of 8.8 attendees
+							    mu = 1.75  sigma = 1.32 gives an average of 12.5 attendees
+							    mu = 2.0  sigma = 1.41 gives an average of 17.4 attendees
+							     -->
+							<key>inviteeCountDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.LogNormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mean -->
+									<key>mu</key>
+									<real>1.3</real>
+									<!-- standard deviation -->
+									<key>sigma</key>
+									<real>1.14</real>
+									<!-- maximum -->
+									<key>maximum</key>
+									<real>100</real>
+								</dict>
+							</dict>
+
+							<!-- Define how start times (DTSTART) for the randomly generated events 
+								will be selected. This is an example of a "Distribution" parameter. The value 
+								for most "Distribution" parameters are interchangeable and extensible. -->
+							<key>eventStartDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized. It produces timestamps 
+									in the near future, limited to certain days of the week and certain hours 
+									of the day. -->
+								<key>type</key>
+								<string>contrib.performance.stats.WorkDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- These are the days of the week the distribution will use. -->
+									<key>daysOfWeek</key>
+									<array>
+										<string>mon</string>
+										<string>tue</string>
+										<string>wed</string>
+										<string>thu</string>
+										<string>fri</string>
+									</array>
+
+									<!-- The earliest hour of a day at which an event might be scheduled. -->
+									<key>beginHour</key>
+									<integer>8</integer>
+
+									<!-- And the latest hour of a day (at which an event will be scheduled 
+										to begin!). -->
+									<key>endHour</key>
+									<integer>16</integer>
+
+									<!-- The timezone in which the event is scheduled. (XXX Does this 
+										really work right?) -->
+									<key>tzname</key>
+									<string>America/Los_Angeles</string>
+								</dict>
+							</dict>
 						</dict>
 					</dict>
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2012-01-20 21:00:18 UTC (rev 8568)
+++ CalendarServer/trunk/contrib/performance/loadtest/profiles.py	2012-01-20 21:25:20 UTC (rev 8569)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-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.
@@ -36,6 +36,7 @@
 from twistedcaldav.ical import Property, Component
 
 from contrib.performance.stats import NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, mean, median
+from contrib.performance.stats import LogNormalDistribution
 from contrib.performance.loadtest.logger import SummarizingMixin
 from contrib.performance.loadtest.ical import IncorrectResponseCode
 
@@ -241,6 +242,141 @@
 
 
 
+class RealisticInviter(ProfileBase):
+    """
+    A Calendar user who invites other users to new events.
+    """
+    _eventTemplate = Component.fromString("""\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20101018T155431Z
+UID:C98AD237-55AD-4F7D-9009-0D355D835822
+DTEND;TZID=America/New_York:20101021T130000
+TRANSP:OPAQUE
+SUMMARY:Simple event
+DTSTART;TZID=America/New_York:20101021T120000
+DTSTAMP:20101018T155438Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+    
+    def setParameters(
+        self,
+        enabled=True,
+        sendInvitationDistribution=NormalDistribution(600, 60),
+        inviteeDistanceDistribution=UniformDiscreteDistribution(range(-10, 11)),
+        inviteeCountDistribution=LogNormalDistribution(1.2, 1.2),
+        eventStartDistribution=NearFutureDistribution(),
+        eventDurationDistribution=UniformDiscreteDistribution([
+            15 * 60, 30 * 60,
+            45 * 60, 60 * 60,
+            120 * 60
+        ])
+    ):
+        self.enabled = enabled
+        self._sendInvitationDistribution = sendInvitationDistribution
+        self._inviteeDistanceDistribution = inviteeDistanceDistribution
+        self._inviteeCountDistribution = inviteeCountDistribution
+        self._eventStartDistribution = eventStartDistribution
+        self._eventDurationDistribution = eventDurationDistribution
+
+
+    def run(self):
+        return loopWithDistribution(
+            self._reactor, self._sendInvitationDistribution, self._invite)
+
+
+    def _addAttendee(self, event, attendees):
+        """
+        Create a new attendee to add to the list of attendees for the
+        given event.
+        """
+        selfRecord = self._sim.getUserRecord(self._number)
+        invitees = set([u'urn:uuid:%s' % (selfRecord.uid,)])
+        for att in attendees:
+            invitees.add(att.value())
+
+        for _ignore_i in range(10):
+            invitee = max(
+                0, self._number + self._inviteeDistanceDistribution.sample())
+            try:
+                record = self._sim.getUserRecord(invitee)
+            except IndexError:
+                continue
+            uuid = u'urn:uuid:%s' % (record.uid,)
+            if uuid not in invitees:
+                break
+        else:
+            raise CannotAddAttendee("Can't find uninvited user to invite.")
+
+        attendee = Property(
+            name=u'ATTENDEE',
+            value=uuid.encode("utf-8"),
+            params={
+            'CN': record.commonName,
+            'CUTYPE': 'INDIVIDUAL',
+            'EMAIL': record.email,
+            'PARTSTAT': 'NEEDS-ACTION',
+            'ROLE': 'REQ-PARTICIPANT',
+            'RSVP': 'TRUE',
+            #'SCHEDULE-STATUS': '1.2',
+            },
+        )
+
+        event.addProperty(attendee)
+        attendees.append(attendee)
+
+
+    def _invite(self):
+        """
+        Try to add a new event, or perhaps remove an
+        existing attendee from an event.
+
+        @return: C{None} if there are no events to play with,
+            otherwise a L{Deferred} which fires when the attendee
+            change has been made.
+        """
+        # Find calendars which are eligible for invites
+        calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+
+        while calendars:
+            # Pick one at random from which to try to create an event
+            # to modify.
+            calendar = self.random.choice(calendars)
+            calendars.remove(calendar)
+
+            # Copy the template event and fill in some of its fields
+            # to make a new event to create on the calendar.
+            vcalendar = self._eventTemplate.duplicate()
+            vevent = vcalendar.mainComponent()
+            uid = str(uuid4())
+            dtstart = self._eventStartDistribution.sample()
+            dtend = dtstart + PyCalendarDuration(seconds=self._eventDurationDistribution.sample())
+            vevent.replaceProperty(Property("CREATED", PyCalendarDateTime.getNowUTC()))
+            vevent.replaceProperty(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
+            vevent.replaceProperty(Property("DTSTART", dtstart))
+            vevent.replaceProperty(Property("DTEND", dtend))
+            vevent.replaceProperty(Property("UID", uid))
+
+            vevent.addProperty(self._client._makeSelfOrganizer())
+            vevent.addProperty(self._client._makeSelfAttendee())
+
+            attendees = list(vevent.properties('ATTENDEE'))
+            for _ignore in range(int(self._inviteeCountDistribution.sample())):
+                try:
+                    self._addAttendee(vevent, attendees)
+                except CannotAddAttendee:
+                    return succeed(None)
+
+            href = '%s%s.ics' % (calendar.url, uid)
+            d = self._client.addEvent(href, vcalendar)
+            return self._newOperation("invite", d)
+
 class Accepter(ProfileBase):
     """
     A Calendar user who accepts invitations to events. As well as accepting requests, this

Modified: CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2012-01-20 21:00:18 UTC (rev 8568)
+++ CalendarServer/trunk/contrib/performance/loadtest/test_profiles.py	2012-01-20 21:25:20 UTC (rev 8569)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-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.
@@ -29,9 +29,10 @@
 from twisted.web.http import NO_CONTENT, PRECONDITION_FAILED
 from twisted.web.client import Response
 
-from twistedcaldav.ical import Component
+from twistedcaldav.ical import Component, Property
 
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter, OperationLogger
+from contrib.performance.loadtest.profiles import RealisticInviter
 from contrib.performance.loadtest.population import Populator, CalendarClientSimulator
 from contrib.performance.loadtest.ical import IncorrectResponseCode, Calendar, Event, BaseClient
 from contrib.performance.loadtest.sim import _DirectoryRecord
@@ -201,9 +202,10 @@
         self._events = {}
         self._calendars = {}
         self.record = _DirectoryRecord(
-            u"user%02d" % (number,), u"user%02d" % (number,),
-            u"User %02d" % (number,), u"user%02d at example.org" % (number,))
-        self.email = u"mailto:user%02d at example.com" % (number,)
+            "user%02d" % (number,), "user%02d" % (number,),
+            "User %02d" % (number,), "user%02d at example.org" % (number,))
+        self.email = "mailto:user%02d at example.com" % (number,)
+        self.uuid = "urn:uuid:user%02d" % (number,)
         self.rescheduled = set()
 
 
@@ -242,7 +244,31 @@
         return succeed(None)
 
 
+    def _makeSelfAttendee(self):
+        attendee = Property(
+            name=u'ATTENDEE',
+            value=self.uuid,
+            params={
+                'CN': self.record.commonName,
+                'CUTYPE': 'INDIVIDUAL',
+                'PARTSTAT': 'ACCEPTED',
+            },
+        )
+        return attendee
 
+
+    def _makeSelfOrganizer(self):
+        organizer = Property(
+            name=u'ORGANIZER',
+            value=self.uuid,
+            params={
+                'CN': self.record.commonName,
+            },
+        )
+        return organizer
+
+
+
 class SequentialDistribution(object):
     def __init__(self, values):
         self.values = values
@@ -453,6 +479,175 @@
 
 
 
+class RealisticInviterTests(TestCase):
+    """
+    Tests for loadtest.profiles.RealisticInviter.
+    """
+    def setUp(self):
+        self.sim = CalendarClientSimulator(
+            AnyUser(), Populator(None), None, None, None)
+
+
+    def _simpleAccount(self, userNumber, eventText):
+        vevent = Component.fromString(eventText)
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None)
+        event = Event(calendar.url + u'1234.ics', None, vevent)
+        calendar.events = {u'1234.ics': event}
+        client = StubClient(userNumber)
+        client._events.update({event.url: event})
+        client._calendars.update({calendar.url: calendar})
+
+        return vevent, event, calendar, client
+
+
+    def test_enabled(self):
+        userNumber = 13
+        client = StubClient(userNumber)
+
+        inviter = RealisticInviter(None, self.sim, client, userNumber, **{"enabled":False})
+        self.assertEqual(inviter.enabled, False)
+
+        inviter = RealisticInviter(None, self.sim, client, userNumber, **{"enabled":True})
+        self.assertEqual(inviter.enabled, True)
+
+    def test_doNotAddInviteToInbox(self):
+        """
+        When the only calendar with any events is a schedule inbox, no
+        attempt is made to add attendees to that calendar.
+        """
+        calendar = Calendar(
+            caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None)
+        userNumber = 13
+        client = StubClient(userNumber)
+        client._calendars.update({calendar.url: calendar})
+
+        inviter = RealisticInviter(None, self.sim, client, userNumber, **{"enabled":False})
+        inviter._invite()
+
+        self.assertEquals(client._events, {})
+
+
+    def test_doNotAddInviteToNoCalendars(self):
+        """
+        When there are no calendars and no events at all, the inviter
+        does nothing.
+        """
+        userNumber = 13
+        client = StubClient(userNumber)
+        inviter = RealisticInviter(None, self.sim, client, userNumber)
+        inviter._invite()
+        self.assertEquals(client._events, {})
+        self.assertEquals(client._calendars, {})
+
+
+    def test_addInvite(self):
+        """
+        When there is a normal calendar, inviter adds an invite to it.
+        """
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        userNumber = 16
+        client = StubClient(userNumber)
+        client._calendars.update({calendar.url: calendar})
+        inviter = RealisticInviter(Clock(), self.sim, client, userNumber)
+        inviter.setParameters(
+            inviteeDistanceDistribution=Deterministic(1),
+            inviteeCountDistribution=Deterministic(1)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 1)
+        attendees = tuple(client._events.values()[0].vevent.mainComponent().properties('ATTENDEE'))
+        expected = set(("urn:uuid:user%02d" %  (userNumber,), "urn:uuid:user%02d" %  (userNumber + 1,),))
+        for attendee in attendees:
+            expected.remove(attendee.value())
+        self.assertEqual(len(expected), 0)
+
+
+
+    def test_doNotAddSelfToEvent(self):
+        """
+        If the inviter randomly selects its own user to be added to
+        the attendee list, a different user is added instead.
+        """
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        selfNumber = 12
+        client = StubClient(selfNumber)
+        client._calendars.update({calendar.url: calendar})
+
+        otherNumber = 20
+        values = [selfNumber - selfNumber, otherNumber - selfNumber]
+
+        inviter = RealisticInviter(Clock(), self.sim, client, selfNumber)
+        inviter.setParameters(
+            inviteeDistanceDistribution=SequentialDistribution(values),
+            inviteeCountDistribution=Deterministic(1)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 1)
+        attendees = tuple(client._events.values()[0].vevent.mainComponent().properties('ATTENDEE'))
+        expected = set(("urn:uuid:user%02d" %  (selfNumber,), "urn:uuid:user%02d" %  (otherNumber,),))
+        for attendee in attendees:
+            expected.remove(attendee.value())
+        self.assertEqual(len(expected), 0)
+
+
+
+    def test_doNotAddExistingToEvent(self):
+        """
+        If the inviter randomly selects a user which is already an
+        invitee on the event, a different user is added instead.
+        """
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        selfNumber = 1
+        client = StubClient(selfNumber)
+        client._calendars.update({calendar.url: calendar})
+
+        inviteeNumber = 20
+        anotherNumber = inviteeNumber + 5
+        values = [inviteeNumber - selfNumber, inviteeNumber - selfNumber, anotherNumber - selfNumber]
+
+        inviter = RealisticInviter(Clock(), self.sim, client, selfNumber)
+        inviter.setParameters(
+            inviteeDistanceDistribution=SequentialDistribution(values),
+            inviteeCountDistribution=Deterministic(2)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 1)
+        attendees = tuple(client._events.values()[0].vevent.mainComponent().properties('ATTENDEE'))
+        expected = set((
+            "urn:uuid:user%02d" %  (selfNumber,),
+            "urn:uuid:user%02d" %  (inviteeNumber,),
+            "urn:uuid:user%02d" %  (anotherNumber,),
+        ))
+        for attendee in attendees:
+            expected.remove(attendee.value())
+        self.assertEqual(len(expected), 0)
+
+
+    def test_everybodyInvitedAlready(self):
+        """
+        If the first so-many randomly selected users we come across
+        are already attendees on the event, the invitation attempt is
+        abandoned.
+        """
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        userNumber = 1
+        client = StubClient(userNumber)
+        client._calendars.update({calendar.url: calendar})
+        inviter = RealisticInviter(Clock(), self.sim, client, userNumber)
+        inviter.setParameters(
+            inviteeDistanceDistribution=Deterministic(1),
+            inviteeCountDistribution=Deterministic(2)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 0)
+
+
+
 class AccepterTests(TestCase):
     """
     Tests for loadtest.profiles.Accepter.

Modified: CalendarServer/trunk/contrib/performance/stats.py
===================================================================
--- CalendarServer/trunk/contrib/performance/stats.py	2012-01-20 21:00:18 UTC (rev 8568)
+++ CalendarServer/trunk/contrib/performance/stats.py	2012-01-20 21:25:20 UTC (rev 8569)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2010-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2010-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.
@@ -256,15 +256,24 @@
     """
     implements(IPopulation)
 
-    compareAttributes = ['_mu', '_sigma']
+    compareAttributes = ['_mu', '_sigma', '_maximum']
 
-    def __init__(self, mu, sigma):
+    def __init__(self, mu, sigma, maximum=None):
         self._mu = mu
         self._sigma = sigma
+        self._maximum = maximum
 
 
     def sample(self):
-        return random.lognormvariate(self._mu, self._sigma)
+        result = random.lognormvariate(self._mu, self._sigma)
+        if self._maximum is not None and result > self._maximum:
+            for _ignore in range(10):
+                result = random.lognormvariate(self._mu, self._sigma)
+                if result <= self._maximum:
+                    break
+            else:
+                raise ValueError("Unable to generate LogNormalDistribution sample within required range")
+        return result
 
 
 
@@ -368,3 +377,24 @@
                 return result
             offset.setDuration(offset.getTotalSeconds() - (end - start).getTotalSeconds())
             beginning = end
+
+if __name__ == '__main__':
+    
+    from collections import defaultdict
+    mu = 1.5
+    sigma = 1.22
+    distribution = LogNormalDistribution(mu, sigma, 100)
+    result = defaultdict(int)
+    for i in range(100000):
+        s = int(distribution.sample())
+        if s > 300:
+            continue
+        result[s] += 1
+    
+    total = 0
+    for k, v in sorted(result.items(), key=lambda x:x[0]):
+        print "%d\t%.5f" % (k, float(v)/result[1])
+        total += k * v
+        
+    print "Average: %.2f" % (float(total) / sum(result.values()),)
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120120/92ac7ba8/attachment-0001.html>


More information about the calendarserver-changes mailing list