[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