<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[15120] CalendarServer/branches/users/sagen/clientsim</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/15120">15120</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2015-09-10 13:25:07 -0700 (Thu, 10 Sep 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Checkpoint of sim</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenclientsimcalendarserverpushamppushpy">CalendarServer/branches/users/sagen/clientsim/calendarserver/push/amppush.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestampsimpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ampsim.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesticalpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ical.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestloggerpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/logger.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestpopulationpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/population.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestprofilespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/profiles.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsimpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sim.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigseventupdatesonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/event-updates-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigseventsonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/events-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigsinvitesacceptsplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-accepts.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigsinvitesonlyrecurringplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only-recurring.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigsinvitesonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsubscribepy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/subscribe.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestwebadminpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/webadmin.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestclientspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestconfigpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestdebugpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/debug.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestdistributionspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/distributions.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestpubsubpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/pubsub.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestpushpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/push.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrecordspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/records.py</a></li>
<li>CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/</li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11Profile">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/Profile</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11StartupProfile">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_calendar_depth1_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_calendar_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_calendarhome_depth1_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_notification_depth1_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11post_freebusyrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11principal_search_reportrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11report_principal_searchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_color_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_description_proppatchrequestxml">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_displayname_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_order_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_timezone_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_transparent_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendarhome_default_alarm_date_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendarhome_default_alarm_datetime_proppatchrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_create_calendarrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_delegate_principal_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principal_expandrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principal_initial_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principal_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principals_reportrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_query_events_depth1_reportrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_well_known_propfindrequest">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequesterpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/requester.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestresourcespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/resources.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsandboxpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sandbox.py</a></li>
<li>CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/</li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsREADMEmd">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/README.md</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettings__init__py">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/__init__.py</a></li>
<li>CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/</li>
<li>CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/</li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistcalendarsonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/calendars-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistclientsoldplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients-old.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistclientsplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistconfigdistplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.dist.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistconfigplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistdemoclientsplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/demo-clients.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplisteventupdatesonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/event-updates-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplisteventsonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/events-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistinvitesacceptsplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-accepts.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistinvitesonlyrecurringplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only-recurring.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistinvitesonlyplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsclientspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/clients.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsconfigdistpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config-dist.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsconfigpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsdefaultspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/defaults.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingspreset_distributionspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/preset_distributions.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttemplatespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/templates.py</a></li>
<li>CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/</li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttests__init__py">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/__init__.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_distributionspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_distributions.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_icalpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_ical.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_loggerpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_logger.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_populationpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_population.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_profilespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_profiles.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_pubsubpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_pubsub.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_pushpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_push.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_recordspy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_records.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_requesterpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_requester.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_resourcespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_resources.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_simpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_sim.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_templatespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_templates.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_trafficloggerpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_trafficlogger.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_webadminpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_webadmin.py</a></li>
<li>CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/www/</li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestwwwlayouthtml">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/www/layout.html</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestclientsplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestconfigdistplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.dist.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestconfigplist">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.plist</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_icalpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_ical.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_populationpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_population.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_profilespy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_profiles.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_simpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_sim.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_trafficloggerpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_trafficlogger.py</a></li>
<li><a href="#CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_webadminpy">CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_webadmin.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserssagenclientsimcalendarserverpushamppushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/calendarserver/push/amppush.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/calendarserver/push/amppush.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/calendarserver/push/amppush.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -42,7 +42,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class UnsubscribeFromID(amp.Command):
</span><del>-    arguments = [('token', amp.String()), ('id', amp.String())]
</del><ins>+    arguments = [('id', amp.String())]
</ins><span class="cx">     response = [('status', amp.String())]
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -257,7 +257,7 @@
</span><span class="cx">         return {&quot;status&quot; : &quot;OK&quot;}
</span><span class="cx">     SubscribeToID.responder(subscribe)
</span><span class="cx"> 
</span><del>-    def unsubscribe(self, token, id):
</del><ins>+    def unsubscribe(self, id):
</ins><span class="cx">         try:
</span><span class="cx">             del self.subscriptions[id]
</span><span class="cx">         except KeyError:
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestampsimpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ampsim.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ampsim.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ampsim.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -46,7 +46,6 @@
</span><span class="cx">             exit(0)
</span><span class="cx">     runmain()
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> from copy import deepcopy
</span><span class="cx"> 
</span><span class="cx"> from plistlib import writePlistToString, readPlistFromString
</span><span class="lines">@@ -56,15 +55,18 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.adbapi2 import Pickle
</span><span class="cx"> 
</span><del>-from contrib.performance.loadtest.sim import _DirectoryRecord, LoadSimulator
</del><ins>+from contrib.performance.loadtest.sim import LoadSimulator
+from contrib.performance.loadtest.records import DirectoryRecord
+from contrib.performance.loadtest.config import Config
</ins><span class="cx"> 
</span><span class="cx"> class Configure(Command):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Configure this worker process with the text of an XML property list.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    arguments = [(&quot;plist&quot;, String())]
</del><ins>+    arguments = [(&quot;cfg&quot;, Pickle())]
</ins><span class="cx">     # Pass OSError exceptions through, presenting the exception message to the user.
</span><del>-    errors = {OSError: 'OSError'}
</del><ins>+    # errors = {OSError: 'OSError'}
+    errors = {Exception: 'Exception'}
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -79,7 +81,7 @@
</span><span class="cx"> 
</span><span class="cx"> class Account(Command):
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    This message represents a L{_DirectoryRecord} loaded by the manager process
</del><ins>+    This message represents a L{DirectoryRecord} loaded by the manager process
</ins><span class="cx">     being relayed to a worker.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     arguments = [
</span><span class="lines">@@ -106,16 +108,21 @@
</span><span class="cx"> 
</span><span class="cx">     @Account.responder
</span><span class="cx">     def account(self, **kw):
</span><del>-        self.records.append(_DirectoryRecord(**kw))
</del><ins>+        self.records.append(DirectoryRecord(**kw))
</ins><span class="cx">         return {}
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @Configure.responder
</span><del>-    def config(self, plist):
</del><ins>+    def config(self, cfg):
</ins><span class="cx">         from sys import stderr
</span><del>-        cfg = readPlistFromString(plist)
</del><ins>+        # cfg = readPlistFromString(plist)
+        config = Config.deserializeFromWorker(cfg)
+        with open('logs.txt', 'a') as f:
+            f.write('here')
+            f.write(str(config.__dict__))
+
</ins><span class="cx">         addObserver(self.emit)
</span><del>-        sim = LoadSimulator.fromConfig(cfg)
</del><ins>+        sim = LoadSimulator.fromConfigObject(config)
</ins><span class="cx">         sim.records = self.records
</span><span class="cx">         sim.attachServices(stderr)
</span><span class="cx">         return {}
</span><span class="lines">@@ -160,26 +167,29 @@
</span><span class="cx">                             email=record.email,
</span><span class="cx">                             guid=record.guid)
</span><span class="cx"> 
</span><del>-        workerConfig = deepcopy(self.loadsim.configTemplate)
-        # The list of workers is for the manager only; the workers themselves
-        # know they're workers because they _don't_ receive this list.
-        del workerConfig[&quot;workers&quot;]
-        # The manager loads the accounts via the configured loader, then sends
-        # them out to the workers (right above), which look at the state at an
-        # instance level and therefore don't need a globally-named directory
-        # record loader.
-        del workerConfig[&quot;accounts&quot;]
</del><ins>+        workerConfig = self.loadsim.configTemplate(self.whichWorker, self.numWorkers)
+        dupe = deepcopy(workerConfig)
+        del dupe['records']
+        print dupe
+        # # The list of workers is for the manager only; the workers themselves
+        # # know they're workers because they _don't_ receive this list.
+        # del workerConfig[&quot;workers&quot;]
+        # # The manager loads the accounts via the configured loader, then sends
+        # # them out to the workers (right above), which look at the state at an
+        # # instance level and therefore don't need a globally-named directory
+        # # record loader.
+        # del workerConfig[&quot;accounts&quot;]
</ins><span class="cx"> 
</span><del>-        workerConfig[&quot;workerID&quot;] = self.whichWorker
-        workerConfig[&quot;workerCount&quot;] = self.numWorkers
-        workerConfig[&quot;observers&quot;] = []
-        workerConfig.pop(&quot;accounts&quot;, None)
</del><ins>+        # workerConfig[&quot;workerID&quot;] = self.whichWorker
+        # workerConfig[&quot;workerCount&quot;] = self.numWorkers
+        # workerConfig[&quot;observers&quot;] = []
+        # workerConfig.pop(&quot;accounts&quot;, None)
</ins><span class="cx"> 
</span><del>-        plist = writePlistToString(workerConfig)
</del><ins>+        # plist = writePlistToString(workerConfig)
</ins><span class="cx">         self.output.write(&quot;Initiating worker configuration\n&quot;)
</span><span class="cx">         def completed(x):
</span><span class="cx">             self.output.write(&quot;Worker configuration complete.\n&quot;)
</span><del>-        self.callRemote(Configure, plist=plist).addCallback(completed)
</del><ins>+        self.callRemote(Configure, cfg=workerConfig).addCallback(completed)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @LogMessage.responder
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestclientsplist"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,548 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Define the kinds of software and user behavior the load simulation
-                        will simulate. --&gt;
-                &lt;key&gt;clients&lt;/key&gt;
-
-                &lt;!-- Have as many different kinds of software and user behavior configurations
-                        as you want. Each is a dict --&gt;
-                &lt;array&gt;
-
-                        &lt;dict&gt;
-
-                                &lt;!-- Here is a OS X client simulator. --&gt;
-                                &lt;key&gt;software&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
-
-                                &lt;!-- Arguments to use to initialize the OS_X_10_7 instance. --&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- Name that appears in logs. --&gt;
-                                        &lt;key&gt;title&lt;/key&gt;
-                                        &lt;string&gt;10.7&lt;/string&gt;
-        
-                                        &lt;!-- OS_X_10_7 can poll the calendar home at some interval. This is
-                                                in seconds. --&gt;
-                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
-                                        &lt;integer&gt;30&lt;/integer&gt;
-
-                                        &lt;!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications
-                                                about calendar home changes instead of polling for them periodically. If
-                                                this option is true, then look for the server advertisement for xmpp push
-                                                and use it if possible. Still fall back to polling if there is no xmpp push
-                                                advertised. --&gt;
-                                        &lt;key&gt;supportPush&lt;/key&gt;
-                                        &lt;false /&gt;
-
-                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
-                                        &lt;true/&gt;
-                                        &lt;key&gt;ampPushHost&lt;/key&gt;
-                                        &lt;string&gt;localhost&lt;/string&gt;
-                                        &lt;key&gt;ampPushPort&lt;/key&gt;
-                                        &lt;integer&gt;62311&lt;/integer&gt;
-                                &lt;/dict&gt;
-
-                                &lt;!-- The profiles define certain types of user behavior on top of the
-                                        client software being simulated. --&gt;
-                                &lt;key&gt;profiles&lt;/key&gt;
-                                &lt;array&gt;
-
-                                        &lt;!-- First an event-creating profile, which will periodically create
-                                                new events at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;60&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;5&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
-                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.NormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
-                                                                        &lt;key&gt;mu&lt;/key&gt;
-                                                                        &lt;integer&gt;60&lt;/integer&gt;
-
-                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
-                                                                        &lt;key&gt;sigma&lt;/key&gt;
-                                                                        &lt;integer&gt;5&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define the distribution of who will be invited to an event.
-                                                        
-                                                                When inviteeClumping is turned on each invitee is based on a sample of
-                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
-                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
-                                                                those larger numbers will simply fail (the sim will report that situation).
-                                                                
-                                                                When inviteeClumping is off invitees will be sampled across an entire
-                                                                range of account indexes. In this case the distribution ought to be a
-                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
-                                                        --&gt;
-                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformIntegerDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;min&lt;/key&gt;
-                                                                        &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;max&lt;/key&gt;
-                                                                        &lt;integer&gt;99&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
-                                                        
-                                                                LogNormal is the best fit to observed data.
-
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
-                                                                mode should typically be 1, and mean whatever matches the user behavior.
-                                                                Our typical mean is 6.                                                         
-                                                             --&gt;
-                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;1&lt;/integer&gt;
-                                                                        &lt;!-- mean - average--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;6&lt;/integer&gt;
-                                                                        &lt;!-- maximum --&gt;
-                                                                        &lt;key&gt;maximum&lt;/key&gt;
-                                                                        &lt;real&gt;60&lt;/real&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
-                                             handles replies received. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define how long to wait after seeing a new invitation before
-                                                                accepting it.
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
-                                                                (i.e., half of the user have accepted by that time).                                                                
-                                                        --&gt;
-                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;300&lt;/integer&gt;
-                                                                        &lt;!-- median - 50% done--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;1800&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- A task-creating profile, which will periodically create
-                                                new tasks at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new task. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;300&lt;/integer&gt;
-
-                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                &lt;/array&gt;
-
-                                &lt;!-- Determine the frequency at which this client configuration will
-                                        appear in the clients which are created by the load tester. --&gt;
-                                &lt;key&gt;weight&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestclientspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/clients.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,398 @@
</span><ins>+from caldavclientlibrary.protocol.webdav.definitions import davxml
+
+from contrib.performance.loadtest.ical import BaseAppleClient
+
+from pycalendar.datetime import DateTime
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from twisted.python.filepath import FilePath
+
+def loadRequestBody(clientType, label):
+    return FilePath(__file__).sibling('request-data').child(clientType).child(label + '.request').getContent()
+
+class iOS_5(BaseAppleClient):
+    &quot;&quot;&quot;
+    Implementation of the iOS 5 network behavior.
+    &quot;&quot;&quot;
+
+    _client_type = &quot;iOS 5&quot;
+
+    USER_AGENT = &quot;iOS/5.1 (9B179) dataaccessd/1.0&quot;
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by Snow
+    # Leopard iCal.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 50
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = False
+
+    # Request body data
+    _LOAD_PATH = &quot;iOS_5&quot;
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_VEVENT_TR_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vevent_tr_query')
+    _POLL_CALENDAR_VTODO_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vtodo_query')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+
+    @inlineCallbacks
+    def _pollFirstTime1(self, homeNode, calendars):
+        # Patch calendar properties
+        for cal in calendars:
+            if cal.name != &quot;inbox&quot;:
+                yield self._proppatch(
+                    cal.url,
+                    self._STARTUP_PROPPATCH_CALENDAR_COLOR,
+                    method_label=&quot;PROPPATCH{calendar}&quot;,
+                )
+                yield self._proppatch(
+                    cal.url,
+                    self._STARTUP_PROPPATCH_CALENDAR_ORDER,
+                    method_label=&quot;PROPPATCH{calendar}&quot;,
+                )
+
+
+    def _pollFirstTime2(self):
+        # Nothing here
+        return succeed(None)
+
+
+    def _updateCalendar(self, calendar, newToken):
+        &quot;&quot;&quot;
+        Update the local cached data for a calendar in an appropriate manner.
+        &quot;&quot;&quot;
+        if calendar.name == &quot;inbox&quot;:
+            # Inbox is done as a PROPFIND Depth:1
+            return self._updateCalendar_PROPFIND(calendar, newToken)
+        elif &quot;VEVENT&quot; in calendar.componentTypes:
+            # VEVENTs done as time-range VEVENT-only queries
+            return self._updateCalendar_VEVENT(calendar, newToken)
+        elif &quot;VTODO&quot; in calendar.componentTypes:
+            # VTODOs done as VTODO-only queries
+            return self._updateCalendar_VTODO(calendar, newToken)
+
+
+    @inlineCallbacks
+    def _updateCalendar_VEVENT(self, calendar, newToken):
+        &quot;&quot;&quot;
+        Sync all locally cached VEVENTs using a VEVENT-only time-range query.
+        &quot;&quot;&quot;
+
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+        now = DateTime.getNowUTC()
+        now.setDateOnly(True)
+        now.offsetMonth(-1) # 1 month back default
+        result = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_VEVENT_TR_QUERY % {&quot;start-date&quot;: now.getText()},
+            depth='1',
+            method_label=&quot;REPORT{vevent}&quot;,
+        )
+
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def _updateCalendar_VTODO(self, calendar, newToken):
+        &quot;&quot;&quot;
+        Sync all locally cached VTODOs using a VTODO-only query.
+        &quot;&quot;&quot;
+
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+        result = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_VTODO_QUERY,
+            depth='1',
+            method_label=&quot;REPORT{vtodo}&quot;,
+        )
+
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def startup(self):
+
+        # Try to read data from disk - if it succeeds self.principalURL will be set
+        self.deserialize()
+
+        if self.principalURL is None:
+            # PROPFIND well-known with redirect
+            response = yield self._startupPropfindWellKnown()
+            hrefs = response.getHrefProperties()
+            if davxml.current_user_principal in hrefs:
+                self.principalURL = hrefs[davxml.current_user_principal].toString()
+            elif davxml.principal_URL in hrefs:
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+            else:
+                # PROPFIND principal path to retrieve actual principal-URL
+                response = yield self._principalPropfindInitial(self.record.uid)
+                hrefs = response.getHrefProperties()
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._extractPrincipalDetails()
+        returnValue(principal)
+
+
+
+class iOS_9(iOS_5): # As far as I can see, the behavior of an iOS 9 client is the same as the behavior of the iOS 5 client.
+    _client_type = &quot;iOS 9&quot;
+
+    USER_AGENT = &quot;iOS/9 dataaccessd/1.0&quot;
+
+    _SYNC_REPORT = False
+
+    _LOAD_PATH = &quot;iOS_5&quot; # Just use the request bodies from iOS 5
+
+
+class OS_X_10_6(BaseAppleClient):
+    &quot;&quot;&quot;
+    Implementation of the OS X 10.6 iCal network behavior.
+
+    Anything OS X 10.6 iCal does on its own, or any particular
+    network behaviors it takes in response to a user action, belong on
+    this class.
+
+    Usage-profile based behaviors (&quot;the user modifies an event every
+    3.2 minutes&quot;) belong elsewhere.
+    &quot;&quot;&quot;
+
+    _client_type = &quot;OS X 10.6&quot;
+
+    USER_AGENT = &quot;DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)&quot;
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by Snow
+    # Leopard iCal.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 200
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = &quot;OS_X_10_6&quot;
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = None
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+    @inlineCallbacks
+    def startup(self):
+
+        # Try to read data from disk - if it succeeds self.principalURL will be set
+        self.deserialize()
+
+        if self.principalURL is None:
+            # PROPFIND principal path to retrieve actual principal-URL
+            response = yield self._principalPropfindInitial(self.record.uid)
+            hrefs = response.getHrefProperties()
+            self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = (yield self._extractPrincipalDetails())
+        returnValue(principal)
+
+
+class OS_X_10_7(BaseAppleClient):
+    &quot;&quot;&quot;
+    Implementation of the OS X 10.7 iCal network behavior.
+    &quot;&quot;&quot;
+
+    _client_type = &quot;OS X 10.7&quot;
+
+    USER_AGENT = &quot;CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50)&quot;
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by Snow
+    # Leopard iCal.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 50
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = True
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = &quot;OS_X_10_7&quot;
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_sync')
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+
+    @inlineCallbacks
+    def startup(self):
+
+        # Try to read data from disk - if it succeeds self.principalURL will be set
+        self.deserialize()
+
+        if self.principalURL is None:
+            # PROPFIND well-known with redirect
+            response = yield self._startupPropfindWellKnown()
+            hrefs = response.getHrefProperties()
+            if davxml.current_user_principal in hrefs:
+                self.principalURL = hrefs[davxml.current_user_principal].toString()
+            elif davxml.principal_URL in hrefs:
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+            else:
+                # PROPFIND principal path to retrieve actual principal-URL
+                response = yield self._principalPropfindInitial(self.record.uid)
+                hrefs = response.getHrefProperties()
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._extractPrincipalDetails()
+        returnValue(principal)
+
+
+
+class OS_X_10_11(BaseAppleClient):
+    &quot;&quot;&quot;
+    Implementation of the OS X 10.11 Calendar.app network behavior.
+    &quot;&quot;&quot;
+
+    _client_type = &quot;OS X 10.11&quot;
+
+    USER_AGENT = &quot;Mac+OS+X/10.11 (15A216g) CalendarAgent/353&quot;
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by El
+    # Capital Calendar.app.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60  # in seconds
+
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 50
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = True
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = &quot;OS_X_10_11&quot;
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known_propfind')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_initial_propfind')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    # _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+
+    _STARTUP_CREATE_CALENDAR = loadRequestBody(_LOAD_PATH, 'startup_create_calendar')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    # _STARTUP_PROPPATCH_CALENDAR_NAME = loadRequestBody(_LOAD_PATH, 'startup_calendar_displayname_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_depth1_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_depth1_propfind')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody('OS_X_10_7', 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody('OS_X_10_7', 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = loadRequestBody('OS_X_10_7', 'poll_calendar_sync')
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_depth1_propfind')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody('OS_X_10_7', 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody('OS_X_10_7', 'post_availability')
+
+    _CALENDARSERVER_PRINCIPAL_SEARCH_REPORT = loadRequestBody(_LOAD_PATH, 'principal_search_report')
+
+    @inlineCallbacks
+    def startup(self):
+        # Try to read data from disk - if it succeeds self.principalURL will be set
+        self.deserialize()
+
+        if self.principalURL is None:
+            # print(&quot;No cached principal URL found - starting from scratch&quot;)
+            # PROPFIND well-known with redirect
+            response = yield self._startupPropfindWellKnown()
+            hrefs = response.getHrefProperties()
+            if davxml.current_user_principal in hrefs:
+                self.principalURL = hrefs[davxml.current_user_principal].toString()
+            elif davxml.principal_URL in hrefs:
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+            else:
+                # PROPFIND principal path to retrieve actual principal-URL
+                response = yield self._principalPropfindInitial(self.record.uid)
+                hrefs = response.getHrefProperties()
+                self.principalURL = hrefs[davxml.principal_URL].toString()
+        # print(&quot;Principal URL: &quot; + self.principalURL)
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._extractPrincipalDetails()
+        # print(&quot;Principal: &quot; + str(principal))
+        returnValue(principal)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestconfigdistplist"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.dist.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.dist.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.dist.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,188 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- This is a distributed orchestrator configuration; 'workers' is a list of
-                                                        shell commands to run sub-processes.
-                                                        --&gt;
-                &lt;key&gt;workers&lt;/key&gt;
-                &lt;array&gt;
-                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
-                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
-                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
-                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
-                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
-                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
-                &lt;/array&gt;
-
-                &lt;!-- Identify the server to be load tested. --&gt;
-                &lt;key&gt;server&lt;/key&gt;
-                &lt;string&gt;https://127.0.0.1:8443&lt;/string&gt;
-
-                &lt;!-- The template URI for doing initial principal lookup on. --&gt;
-                &lt;key&gt;principalPathTemplate&lt;/key&gt;
-                &lt;string&gt;/principals/users/%s/&lt;/string&gt;
-
-                &lt;!-- Configure Admin Web UI. --&gt;
-                &lt;key&gt;webadmin&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;key&gt;enabled&lt;/key&gt;
-                        &lt;true/&gt;
-                        
-                        &lt;key&gt;HTTPPort&lt;/key&gt;
-                        &lt;integer&gt;8080&lt;/integer&gt;
-                &lt;/dict&gt;
-
-                &lt;!--  Define whether server supports stats socket. --&gt;
-                &lt;key&gt;serverStats&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;key&gt;enabled&lt;/key&gt;
-                        &lt;true/&gt;
-                        &lt;key&gt;Port&lt;/key&gt;
-                        &lt;integer&gt;8100&lt;/integer&gt;
-                &lt;/dict&gt;
-
-                &lt;!--  Define whether client data should be re-used. It will always be saved to the specified path.--&gt;
-                &lt;key&gt;clientDataSerialization&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;key&gt;UseOldData&lt;/key&gt;
-                        &lt;true/&gt;
-                        &lt;key&gt;Path&lt;/key&gt;
-                        &lt;string&gt;/tmp/sim&lt;/string&gt;
-                &lt;/dict&gt;
-
-                &lt;!-- Define the credentials of the clients which will be used to load test 
-                        the server. These credentials must already be valid on the server. --&gt;
-                &lt;key&gt;accounts&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;!-- The loader is the fully-qualified Python name of a callable which 
-                                returns a list of directory service records defining all of the client accounts 
-                                to use. contrib.performance.loadtest.sim.recordsFromCSVFile reads username, 
-                                password, mailto triples from a CSV file and returns them as a list of faked 
-                                directory service records. --&gt;
-                        &lt;key&gt;loader&lt;/key&gt;
-                        &lt;string&gt;contrib.performance.loadtest.sim.recordsFromCSVFile&lt;/string&gt;
-
-                        &lt;!-- Keyword arguments may be passed to the loader. --&gt;
-                        &lt;key&gt;params&lt;/key&gt;
-                        &lt;dict&gt;
-                                &lt;!-- recordsFromCSVFile interprets the path relative to the config.plist, 
-                                        to make it independent of the script's working directory while still allowing 
-                                        a relative path. This isn't a great solution. --&gt;
-                                &lt;key&gt;path&lt;/key&gt;
-                                &lt;string&gt;contrib/performance/loadtest/accounts.csv&lt;/string&gt;
-                        &lt;/dict&gt;
-                &lt;/dict&gt;
-
-                &lt;!-- Define how many clients will participate in the load test and how 
-                        they will show up. --&gt;
-                &lt;key&gt;arrival&lt;/key&gt;
-                &lt;dict&gt;
-
-                        &lt;!-- Specify a class which creates new clients and introduces them into 
-                                the test. contrib.performance.loadtest.population.SmoothRampUp introduces 
-                                groups of new clients at fixed intervals up to a maximum. The size of the 
-                                group, interval, and maximum are configured by the parameters below. The 
-                                total number of clients is groups * groupSize, which needs to be no larger 
-                                than the number of credentials created in the accounts section. --&gt;
-                        &lt;key&gt;factory&lt;/key&gt;
-                        &lt;string&gt;contrib.performance.loadtest.population.SmoothRampUp&lt;/string&gt;
-
-                        &lt;key&gt;params&lt;/key&gt;
-                        &lt;dict&gt;
-                                &lt;!-- groups gives the total number of groups of clients to introduce. --&gt;
-                                &lt;key&gt;groups&lt;/key&gt;
-                                &lt;integer&gt;99&lt;/integer&gt;
-
-                                &lt;!-- groupSize is the number of clients in each group of clients. It's 
-                                        really only a &quot;smooth&quot; ramp up if this is pretty small. --&gt;
-                                &lt;key&gt;groupSize&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-
-                                &lt;!-- Number of seconds between the introduction of each group. --&gt;
-                                &lt;key&gt;interval&lt;/key&gt;
-                                &lt;integer&gt;3&lt;/integer&gt;
-
-                                &lt;!-- Number of clients each user is assigned to. --&gt;
-                                &lt;!-- Set weight of clients to 1 if this is &gt; 1. Number of clients must match this value if &gt; 1. --&gt;
-                                &lt;key&gt;clientsPerUser&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-
-                &lt;/dict&gt;
-
-                &lt;!-- Define some log observers to report on the load test. --&gt;
-                &lt;key&gt;observers&lt;/key&gt;
-                &lt;array&gt;
-                        &lt;!-- ReportStatistics generates an end-of-run summary of the HTTP requests 
-                                made, their timings, and their results. --&gt;
-                        &lt;dict&gt;
-                                &lt;key&gt;type&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.population.ReportStatistics&lt;/string&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- The thresholds for each request type --&gt;
-                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
-                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
-                                        
-                                        &lt;!-- The benchmarks for overall QoS --&gt;
-                                        &lt;key&gt;benchmarksPath&lt;/key&gt;
-                                        &lt;string&gt;contrib/performance/loadtest/benchmarks.json&lt;/string&gt;
-
-                                        &lt;!-- The % of failures that constitute a failed test --&gt;
-                                        &lt;key&gt;failCutoff&lt;/key&gt;
-                                        &lt;real&gt;1.0&lt;/real&gt;
-                                &lt;/dict&gt;
-                        &lt;/dict&gt;
-        
-                        &lt;!-- RequestLogger generates a realtime log of all HTTP requests made 
-                                during the load test. --&gt;
-                        &lt;dict&gt;
-                                &lt;key&gt;type&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.RequestLogger&lt;/string&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                &lt;/dict&gt;
-                        &lt;/dict&gt;
-        
-                        &lt;!-- OperationLogger generates an end-of-run summary of the gross operations 
-                                performed (logical operations which may span more than one HTTP request, 
-                                such as inviting an attendee to an event). --&gt;
-                        &lt;dict&gt;
-                                &lt;key&gt;type&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.profiles.OperationLogger&lt;/string&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- The thresholds for each operation type --&gt;
-                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
-                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
-                                        
-                                        &lt;!-- The % of operations beyond the lag cut-off that constitute a failed test --&gt;
-                                        &lt;key&gt;lagCutoff&lt;/key&gt;
-                                        &lt;real&gt;1.0&lt;/real&gt;
-                                        
-                                        &lt;!-- The % of failures that constitute a failed test --&gt;
-                                        &lt;key&gt;failCutoff&lt;/key&gt;
-                                        &lt;real&gt;1.0&lt;/real&gt;
-                                &lt;/dict&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestconfigplist"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,175 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Identify the server to be load tested. --&gt;
-                &lt;key&gt;server&lt;/key&gt;
-                &lt;string&gt;https://127.0.0.1:8443&lt;/string&gt;
-
-                &lt;!-- The template URI for doing initial principal lookup on. --&gt;
-                &lt;key&gt;principalPathTemplate&lt;/key&gt;
-                &lt;string&gt;/principals/users/%s/&lt;/string&gt;
-
-                &lt;!-- Configure Admin Web UI. --&gt;
-                &lt;key&gt;webadmin&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;key&gt;enabled&lt;/key&gt;
-                        &lt;true/&gt;
-
-                        &lt;key&gt;HTTPPort&lt;/key&gt;
-                        &lt;integer&gt;8080&lt;/integer&gt;
-                &lt;/dict&gt;
-
-                &lt;!--  Define whether server supports stats socket. --&gt;
-                &lt;key&gt;serverStats&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;key&gt;enabled&lt;/key&gt;
-                        &lt;true/&gt;
-                        &lt;key&gt;Port&lt;/key&gt;
-                        &lt;integer&gt;8100&lt;/integer&gt;
-                &lt;/dict&gt;
-
-                &lt;!--  Define whether client data should be re-used. It will always be saved to the specified path.--&gt;
-                &lt;key&gt;clientDataSerialization&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;key&gt;UseOldData&lt;/key&gt;
-                        &lt;true/&gt;
-                        &lt;key&gt;Path&lt;/key&gt;
-                        &lt;string&gt;/tmp/sim&lt;/string&gt;
-                &lt;/dict&gt;
-
-                &lt;!-- Define the credentials of the clients which will be used to load test
-                        the server. These credentials must already be valid on the server. --&gt;
-                &lt;key&gt;accounts&lt;/key&gt;
-                &lt;dict&gt;
-                        &lt;!-- The loader is the fully-qualified Python name of a callable which
-                                returns a list of directory service records defining all of the client accounts
-                                to use. contrib.performance.loadtest.sim.recordsFromCSVFile reads username,
-                                password, mailto triples from a CSV file and returns them as a list of faked
-                                directory service records. --&gt;
-                        &lt;key&gt;loader&lt;/key&gt;
-                        &lt;string&gt;contrib.performance.loadtest.sim.recordsFromCSVFile&lt;/string&gt;
-
-                        &lt;!-- Keyword arguments may be passed to the loader. --&gt;
-                        &lt;key&gt;params&lt;/key&gt;
-                        &lt;dict&gt;
-                                &lt;!-- recordsFromCSVFile interprets the path relative to the config.plist,
-                                        to make it independent of the script's working directory while still allowing
-                                        a relative path. This isn't a great solution. --&gt;
-                                &lt;key&gt;path&lt;/key&gt;
-                                &lt;string&gt;contrib/performance/loadtest/accounts.csv&lt;/string&gt;
-                        &lt;/dict&gt;
-                &lt;/dict&gt;
-
-                &lt;!-- Define how many clients will participate in the load test and how
-                        they will show up. --&gt;
-                &lt;key&gt;arrival&lt;/key&gt;
-                &lt;dict&gt;
-
-                        &lt;!-- Specify a class which creates new clients and introduces them into
-                                the test. contrib.performance.loadtest.population.SmoothRampUp introduces
-                                groups of new clients at fixed intervals up to a maximum. The size of the
-                                group, interval, and maximum are configured by the parameters below. The
-                                total number of clients is groups * groupSize, which needs to be no larger
-                                than the number of credentials created in the accounts section. --&gt;
-                        &lt;key&gt;factory&lt;/key&gt;
-                        &lt;string&gt;contrib.performance.loadtest.population.SmoothRampUp&lt;/string&gt;
-
-                        &lt;key&gt;params&lt;/key&gt;
-                        &lt;dict&gt;
-                                &lt;!-- groups gives the total number of groups of clients to introduce. --&gt;
-                                &lt;key&gt;groups&lt;/key&gt;
-                                &lt;integer&gt;20&lt;/integer&gt;
-
-                                &lt;!-- groupSize is the number of clients in each group of clients. It's
-                                        really only a &quot;smooth&quot; ramp up if this is pretty small. --&gt;
-                                &lt;key&gt;groupSize&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-
-                                &lt;!-- Number of seconds between the introduction of each group. --&gt;
-                                &lt;key&gt;interval&lt;/key&gt;
-                                &lt;integer&gt;3&lt;/integer&gt;
-
-                                &lt;!-- Number of clients each user is assigned to. --&gt;
-                                &lt;!-- Set weight of clients to 1 if this is &gt; 1. Number of clients must match this value if &gt; 1. --&gt;
-                                &lt;key&gt;clientsPerUser&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-
-                &lt;/dict&gt;
-
-                &lt;!-- Define some log observers to report on the load test. --&gt;
-                &lt;key&gt;observers&lt;/key&gt;
-                &lt;array&gt;
-                        &lt;!-- ReportStatistics generates an end-of-run summary of the HTTP requests 
-                                made, their timings, and their results. --&gt;
-                        &lt;dict&gt;
-                                &lt;key&gt;type&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.population.ReportStatistics&lt;/string&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- The thresholds for each request type --&gt;
-                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
-                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
-                                        
-                                        &lt;!-- The benchmarks for overall QoS --&gt;
-                                        &lt;key&gt;benchmarksPath&lt;/key&gt;
-                                        &lt;string&gt;contrib/performance/loadtest/benchmarks.json&lt;/string&gt;
-
-                                        &lt;!-- The % of failures that constitute a failed test --&gt;
-                                        &lt;key&gt;failCutoff&lt;/key&gt;
-                                        &lt;real&gt;1.0&lt;/real&gt;
-                                &lt;/dict&gt;
-                        &lt;/dict&gt;
-        
-                        &lt;!-- RequestLogger generates a realtime log of all HTTP requests made 
-                                during the load test. --&gt;
-                        &lt;dict&gt;
-                                &lt;key&gt;type&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.RequestLogger&lt;/string&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                &lt;/dict&gt;
-                        &lt;/dict&gt;
-        
-                        &lt;!-- OperationLogger generates an end-of-run summary of the gross operations 
-                                performed (logical operations which may span more than one HTTP request, 
-                                such as inviting an attendee to an event). --&gt;
-                        &lt;dict&gt;
-                                &lt;key&gt;type&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.profiles.OperationLogger&lt;/string&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- The thresholds for each operation type --&gt;
-                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
-                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
-                                        
-                                        &lt;!-- The % of operations beyond the lag cut-off that constitute a failed test --&gt;
-                                        &lt;key&gt;lagCutoff&lt;/key&gt;
-                                        &lt;real&gt;1.0&lt;/real&gt;
-                                        
-                                        &lt;!-- The % of failures that constitute a failed test --&gt;
-                                        &lt;key&gt;failCutoff&lt;/key&gt;
-                                        &lt;real&gt;1.0&lt;/real&gt;
-                                &lt;/dict&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestconfigpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/config.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,186 @@
</span><ins>+from importlib import import_module
+
+from twisted.python.log import msg
+from contrib.performance.loadtest.logger import ReportStatistics, RequestLogger, OperationLogger
+from contrib.performance.loadtest.records import recordsFromCSVFile
+from contrib.performance.loadtest.population import ClientFactory, PopulationParameters
+
+class Config(object):
+    def __init__(self):
+        pass
+
+    def populateFrom(self, serverConfig, clientConfig, usePlist=False):
+        # If there is a list of workers, then this process is *not* a worker
+        isManager = serverConfig.get('workers') is not None
+
+        if usePlist:
+            # If the supplied files are plists, we need to convert the named objects into real Python objects.
+            # The ensuing hacky code is why I recommend we remove support for plist-based configuration
+            workers = config['workers']
+            if not isManager:
+                # Client / place where the simulator actually runs configuration
+                workerID = config.get(&quot;workerID&quot;, 0)
+                workerCount = config.get(&quot;workerCount&quot;, 1)
+                configTemplate = None
+                server = config.get('server', 'http://127.0.0.1:8008')
+                serializationPath = None
+
+                serializationPath = config['serializationPath']
+
+                if 'arrival' in config:
+                    arrival = Arrival(
+                        namedAny(config['arrival']['factory']),
+                        config['arrival']['params'])
+                else:
+                    arrival = Arrival(
+                        SmoothRampUp, dict(groups=10, groupSize=1, interval=3))
+
+                parameters = PopulationParameters()
+                if 'clients' in config:
+                    for clientConfig in config['clients']:
+                        parameters.addClient(
+                            clientConfig[&quot;weight&quot;],
+                            ClientType(
+                                clientConfig[&quot;software&quot;],
+                                clientConfig[&quot;params&quot;],
+                                clientConfig[&quot;profiles&quot;]
+                            )
+                        )
+                            # ClientType(
+                            #     namedAny(clientConfig[&quot;software&quot;]),
+                            #     cls._convertParams(clientConfig[&quot;params&quot;]),
+                            #     [
+                            #         ProfileType(
+                            #             namedAny(profile[&quot;class&quot;]),
+                            #             cls._convertParams(profile[&quot;params&quot;])
+                            #         ) for profile in clientConfig[&quot;profiles&quot;]
+                            #     ]))
+                if not parameters.clients:
+                    parameters.addClient(1,
+                                         ClientType(OS_X_10_6, {},
+                                                    [Eventer, Inviter, Accepter]))
+            else:
+                # Manager / observer process.
+                server = ''
+                serializationPath = None
+                arrival = None
+                parameters = None
+                workerID = 0
+                configTemplate = config
+                workerCount = 1
+
+            # webadminPort = 
+            webadminPort = None
+            if 'webadmin' in config:
+                if config['webadmin']['enabled']:
+                    webadminPort = config['webadmin']['HTTPPort']
+
+            serverStats = None
+            if 'serverStats' in config:
+                if config['serverStats']['enabled']:
+                    serverStats = config['serverStats']
+                    serverStats['server'] = config['server'] if 'server' in config else ''
+
+            observers = []
+            if 'observers' in config:
+                for observer in config['observers']:
+                    observerName = observer[&quot;type&quot;]
+                    observerParams = observer[&quot;params&quot;]
+                    observers.append(namedAny(observerName)(**observerParams))
+
+            records = []
+            if 'accounts' in config:
+                loader = config['accounts']['loader']
+                params = config['accounts']['params']
+                records.extend(namedAny(loader)(**params))
+                output.write(&quot;Loaded {0} accounts.\n&quot;.format(len(records)))
+
+        else:
+            # Python configuration - super easy! Look! It's great!
+                self.webadminPort = serverConfig.get('webadminPort')
+                self.serverStats = serverConfig.get('serverStatsPort')
+                self.observers = serverConfig.get('observers') # Workers shouldn't need this
+                self.workers = serverConfig.get('workers')
+
+                self.server = serverConfig.get('server')
+                self.serializationPath = serverConfig.get('serializationPath')
+                self.arrival = serverConfig.get('arrival')
+                self.records = serverConfig.get('records')
+                self.workerID = serverConfig.get('workerID', 0)
+                self.workerCount = serverConfig.get('workerCount', 1)
+                self.parameters = self.buildParameters(clientConfig)
+
+    def buildParameters(self, clients):
+        parameters = PopulationParameters()
+        for client in clients:
+            parameters.addClient(
+                client[&quot;weight&quot;],
+                ClientFactory(
+                    client[&quot;software&quot;],
+                    client[&quot;params&quot;],
+                    client[&quot;profiles&quot;]
+                )
+            )
+        return parameters
+
+    def buildSerializationPath(self):
+        if self.serializationPath:
+            if not isdir(serializationPath):
+                try:
+                    mkdir(serializationPath)
+                except OSError:
+                    print(&quot;Unable to create client data serialization directory: %s&quot; % (serializationPath))
+                    print(&quot;Please consult the clientDataSerialization stanza of contrib/performance/loadtest/config.plist&quot;)
+                    raise
+
+    def serializeForWorker(self, workerID, workerCount):
+        if not self.workers: # If we are workers, don't try to be a manager
+            return {}
+        # print &quot;Trying to serialize for worker #&quot; + str(workerID)
+        # print &quot;My info, btw is &quot; + str(self.__dict__)
+        info = {
+            'webadminPort': '',
+            'serverStats': '',
+            'workers': [],
+            'observers': [],
+            'workerID': workerID,
+            'workerCount': workerCount,
+            # Workers need some information to work correctly
+            'server': self.server,
+            'serializationPath': self.serializationPath,
+            'arrival': self.arrival,
+            'records': self.records,
+            'parameters': self.parameters
+        }
+        return info
+
+    @classmethod
+    def deserializeFromWorker(cls, info):
+        base = cls()
+        base.__dict__.update(info)
+        return base
+
+    # Goodness, how awkward is this code? If we dropped support for plists, we could do away with it
+    @classmethod
+    def _convertParams(cls, params):
+        &quot;&quot;&quot;
+        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{&quot;Distribution&quot;} and convert
+        them into some kind of distribution object using the associated
+        dictionary of keyword arguments.
+        &quot;&quot;&quot;
+        for k, v in params.iteritems():
+            if k.endswith('Distribution'): # Goodness how fragile
+                params[k] = cls._convertDistribution(v)
+        return params
+
+
+    @classmethod
+    def _convertDistribution(cls, value):
+        &quot;&quot;&quot;
+        Construct and return a new distribution object using the type and
+        params specified by C{value}.
+        &quot;&quot;&quot;
+        return namedAny(value['type'])(**value['params'])
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestdebugpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/debug.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/debug.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/debug.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,27 @@
</span><ins>+def printargs(func):
+    &quot;&quot;&quot;
+    This decorator prints the arguments passed to a function before calling it
+
+    Example:
+        @printargs
+        def foo(a, b, c, *args, **kwargs):
+            pass
+
+        foo(1, 2, 3, 4, 5, x=6, y=7)
+        # prints `foo(a:1, b:2, c:3, args=(4, 5), kwargs={'y': 7, 'x': 6})`
+
+    &quot;&quot;&quot;
+    fname = func.func_name
+    fc = func.func_code
+    argcount = fc.co_argcount
+    argnames = fc.co_varnames[:argcount]
+    def wrapper(*args, **kwargs):
+        named_args = ', '.join(['{0}: {1}'.format(arg, val) for arg, val in zip(argnames, args[:argcount])])
+        print &quot;{0}({1}, args={2}, kwargs={3})&quot;.format(
+            fname,
+            named_args,
+            args[argcount:],
+            kwargs
+        )
+        return func(*args, **kwargs)
+    return wrapper
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestdistributionspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/distributions.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/distributions.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/distributions.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,440 @@
</span><ins>+##
+# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+
+&quot;&quot;&quot;
+Implementation of a statistics library for Calendar performance analysis.
+Exports:
+
+IDistribution interface exposes:
+  sample()
+
+Sampling from this distribution must *not* change the underlying behavior of a distribution
+
+Distributions (all of which implement IDistribution):
+  # Discrete Distributions / Finite Support
+  Bernoulli
+  Binomial
+  Rademacher
+  Fixed
+  UniformDiscrete
+  UniformInteger
+
+  # Discrete Distributions / Infinite Support (&gt; 0)
+  Poisson
+  Geometric
+
+  LogNormal
+
+  Normal
+  UniformReal
+  Triangular
+  Beta
+  ChiSquared
+  Exponential
+  Gamma
+
+  # CalendarServer Specific
+  NearFuture
+  Work
+  Recurrence
+
+# TODO
+Implement simple ones through large ones
+Squeeze / pinch
+&quot;&quot;&quot;
+from math import log, sqrt
+from time import mktime
+import random
+import numpy.random as nprandom
+
+from pycalendar.datetime import DateTime
+from pycalendar.duration import Duration as PyDuration
+from pycalendar.icalendar.property import Property
+from pycalendar.timezone import Timezone
+
+from zope.interface import Interface, implements
+from twisted.python.util import FancyEqMixin
+
+
+class IDistribution(Interface):
+    &quot;&quot;&quot;Interface for a class that provides a single function, `sample`, which returns a float&quot;&quot;&quot;
+    def sample(): #@NoSelf
+        pass
+
+
+class UniformDiscreteDistribution(object, FancyEqMixin):
+    &quot;&quot;&quot;
+
+    &quot;&quot;&quot;
+    implements(IDistribution)
+
+    compareAttributes = ['_values']
+
+    def __init__(self, values):
+        self._values = values
+
+    def sample(self):
+        return random.choice(self._values)
+
+
+
+class LogNormalDistribution(object, FancyEqMixin):
+    &quot;&quot;&quot;
+    &quot;&quot;&quot;
+    implements(IDistribution)
+
+    compareAttributes = ['_mu', '_sigma', '_maximum']
+
+    def __init__(self, mu=None, sigma=None, mean=None, mode=None, median=None, maximum=None):
+
+        if mu is not None and sigma is not None:
+            scale = 1.0
+        elif not (mu is None and sigma is None):
+            raise ValueError(&quot;mu and sigma must both be defined or both not defined&quot;)
+        elif mode is None:
+            raise ValueError(&quot;When mu and sigma are not defined, mode must be defined&quot;)
+        elif median is not None:
+            scale = mode
+            median /= mode
+            mode = 1.0
+            mu = log(median)
+            sigma = sqrt(log(median) - log(mode))
+        elif mean is not None:
+            scale = mode
+            mean /= mode
+            mode = 1.0
+            mu = log(mean) + log(mode) / 2.0
+            sigma = sqrt(log(mean) - log(mode) / 2.0)
+        else:
+            raise ValueError(&quot;When using mode one of median or mean must be defined&quot;)
+
+        self._mu = mu
+        self._sigma = sigma
+        self._scale = scale
+        self._maximum = maximum
+
+
+    def sample(self):
+        result = self._scale * random.lognormvariate(self._mu, self._sigma)
+        if self._maximum is not None and result &gt; self._maximum:
+            for _ignore_i in range(10):
+                result = self._scale * random.lognormvariate(self._mu, self._sigma)
+                if result &lt;= self._maximum:
+                    break
+            else:
+                raise ValueError(&quot;Unable to generate LogNormalDistribution sample within required range&quot;)
+        return result
+
+
+
+class FixedDistribution(object, FancyEqMixin):
+    &quot;&quot;&quot;
+    &quot;&quot;&quot;
+    implements(IDistribution)
+
+    compareAttributes = ['_value']
+
+    def __init__(self, value):
+        self._value = value
+
+
+    def sample(self):
+        return self._value
+
+
+
+class NearFutureDistribution(object, FancyEqMixin):
+    compareAttributes = ['_offset']
+
+    def __init__(self):
+        self._offset = LogNormalDistribution(7, 0.8)
+
+
+    def sample(self):
+        now = DateTime.getNowUTC()
+        now.offsetSeconds(int(self._offset.sample()))
+        return now
+
+
+
+class NormalDistribution(object, FancyEqMixin):
+    compareAttributes = ['_mu', '_sigma']
+
+    def __init__(self, mu, sigma):
+        self._mu = mu
+        self._sigma = sigma
+
+
+    def sample(self):
+        # Only return positive values or zero
+        v = random.normalvariate(self._mu, self._sigma)
+        while v &lt; 0:
+            v = random.normalvariate(self._mu, self._sigma)
+        return v
+
+
+
+class UniformIntegerDistribution(object, FancyEqMixin):
+    compareAttributes = ['_min', '_max']
+
+    def __init__(self, min, max):
+        self._min = min
+        self._max = max
+
+
+    def sample(self):
+        return int(random.uniform(self._min, self._max))
+
+
+class UniformRealDistribution(object, FancyEqMixin):
+    compareAttributes = ['_min', '_max']
+
+    def __init__(self, min, max):
+        self._min = min
+        self._max = max
+
+
+    def sample(self):
+        return random.uniform(self._min, self._max)
+
+
+class BernoulliDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_p&quot;]
+
+    def __init__(self, proportion=0.5):
+        &quot;&quot;&quot;Initializes a bernoulli distribution with success probability given by p
+        Prereq: 0 &lt;= p &lt;= 1
+        Returns 1 with probability p, 0 with probability q = 1-p
+        &quot;&quot;&quot;
+        self._p = proportion
+
+    def sample(self):
+        return 1 if random.random() &lt;= self._p else 0
+
+
+class RademacherDistribution(object, FancyEqMixin):
+    &quot;&quot;&quot;
+    Takes value 1 with probability 1/2 and value -1 with probability 1/2
+    &quot;&quot;&quot;
+    def __init__(self):
+        &quot;&quot;&quot;
+        &quot;&quot;&quot;
+        self._d = BernoulliDistribution(proportion=0.5)
+
+    def sample(self):
+        return [-1, 1][self._d.sample()]
+
+
+
+class BinomialDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_successProbability&quot;, &quot;_numTrials&quot;]
+
+    def __init__(self, p=0.5, n=10):
+        self._successProbability = p
+        self._numTrials = n
+
+    def sample(self):
+        return nprandom.binomial(self._numTrials, self._successProbability)
+
+
+
+class TriangularDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_left&quot;, &quot;_mode&quot;, &quot;_right&quot;]
+
+    def __init__(self, left, mode, right):
+        self._left = left
+        self._mode = mode
+        self._right = right
+
+    def sample(self):
+        return nprandom.triangular(self._left, self._mode, self._right)
+
+
+class GeometricDistribution(object, FancyEqMixin):
+    &quot;&quot;&quot;
+    Expected number of Bernoulli trials before the first success
+    &quot;&quot;&quot;
+    compareAttributes = [&quot;_p&quot;]
+    def __init__(self, proportion=0.5):
+        self._p = proportion
+
+    def sample(self):
+        return nprandom.geometric(self._p)
+
+
+class PoissonDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_lambda&quot;]
+    def __init__(self, lam):
+        self._lambda = lam
+
+    def sample(self):
+        return nprandom.possion(self._lambda)
+
+
+class BetaDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_alpha&quot;, &quot;_beta&quot;]
+    def __init__(self, alpha, beta):
+        self._alpha = alpha
+        self._beta = beta
+
+    def sample(self):
+        return nprandom.beta(self._alpha, self._beta)
+
+
+class ChiSquaredDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_df&quot;]
+    def __init__(self, degreesOfFreedom):
+        self._df = degreesOfFreedom
+
+    def sample(self):
+        return nprandom.chisquare(self._df)
+
+
+class ExponentialDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_scale&quot;]
+    def __init__(self, scale):
+        self._scale = scale
+
+    def sample(self):
+        return nprandom.exponential(self._scale)
+
+
+
+class GammaDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_shape&quot;, &quot;_scale&quot;]
+    def __init__(self, shape, scale=1.0):
+        self._shape = shape
+        self._scale = scale
+
+    def sample(self):
+        return nprandom.gamma(self._shape, self._scale)
+
+NUM_WEEKDAYS = 7
+
+class WorkDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_daysOfWeek&quot;, &quot;_beginHour&quot;, &quot;_endHour&quot;]
+
+    _weekdayNames = [&quot;sun&quot;, &quot;mon&quot;, &quot;tue&quot;, &quot;wed&quot;, &quot;thu&quot;, &quot;fri&quot;, &quot;sat&quot;]
+
+    def __init__(self, daysOfWeek=[&quot;mon&quot;, &quot;tue&quot;, &quot;wed&quot;, &quot;thu&quot;, &quot;fri&quot;], beginHour=8, endHour=17, tzname=&quot;UTC&quot;):
+        self._daysOfWeek = [self._weekdayNames.index(day) for day in daysOfWeek]
+        self._beginHour = beginHour
+        self._endHour = endHour
+        self._tzname = tzname
+        self._helperDistribution = NormalDistribution(
+            # Mean 6 workdays in the future
+            60 * 60 * 8 * 6,
+            # Standard deviation of 4 workdays
+            60 * 60 * 8 * 4)
+        self.now = DateTime.getNow
+
+
+    def astimestamp(self, dt):
+        return mktime(dt.timetuple())
+
+
+    def _findWorkAfter(self, when):
+        &quot;&quot;&quot;
+        Return a two-tuple of the start and end of work hours following
+        C{when}.  If C{when} falls within work hours, then the start time will
+        be equal to when.
+        &quot;&quot;&quot;
+        # Find a workday that follows the timestamp
+        weekday = when.getDayOfWeek()
+        for i in range(NUM_WEEKDAYS):
+            day = when + PyDuration(days=i)
+            if (weekday + i) % NUM_WEEKDAYS in self._daysOfWeek:
+                # Joy, a day on which work might occur.  Find the first hour on
+                # this day when work may start.
+                day.setHHMMSS(self._beginHour, 0, 0)
+                begin = day
+                end = begin.duplicate()
+                end.setHHMMSS(self._endHour, 0, 0)
+                if end &gt; when:
+                    return begin, end
+
+
+    def sample(self):
+        offset = PyDuration(seconds=int(self._helperDistribution.sample()))
+        beginning = self.now(Timezone(tzid=self._tzname))
+        while offset:
+            start, end = self._findWorkAfter(beginning)
+            if end - start &gt; offset:
+                result = start + offset
+                result.setMinutes(result.getMinutes() // 15 * 15)
+                result.setSeconds(0)
+                return result
+            offset.setDuration(offset.getTotalSeconds() - (end - start).getTotalSeconds())
+            beginning = end
+
+
+
+class RecurrenceDistribution(object, FancyEqMixin):
+    compareAttributes = [&quot;_allowRecurrence&quot;, &quot;_weights&quot;]
+
+    _model_rrules = {
+        &quot;none&quot;: None,
+        &quot;daily&quot;: &quot;RRULE:FREQ=DAILY&quot;,
+        &quot;weekly&quot;: &quot;RRULE:FREQ=WEEKLY&quot;,
+        &quot;monthly&quot;: &quot;RRULE:FREQ=MONTHLY&quot;,
+        &quot;yearly&quot;: &quot;RRULE:FREQ=YEARLY&quot;,
+        &quot;dailylimit&quot;: &quot;RRULE:FREQ=DAILY;COUNT=14&quot;,
+        &quot;weeklylimit&quot;: &quot;RRULE:FREQ=WEEKLY;COUNT=4&quot;,
+        &quot;workdays&quot;: &quot;RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR&quot;
+    }
+
+    def __init__(self, allowRecurrence, weights={}):
+        self._allowRecurrence = allowRecurrence
+        self._rrules = []
+        if self._allowRecurrence:
+            for rrule, count in sorted(weights.items(), key=lambda x: x[0]):
+                for _ignore in range(count):
+                    self._rrules.append(self._model_rrules[rrule])
+        self._helperDistribution = UniformIntegerDistribution(0, len(self._rrules) - 1)
+
+
+    def sample(self):
+
+        if self._allowRecurrence:
+            index = self._helperDistribution.sample()
+            rrule = self._rrules[index]
+            if rrule:
+                prop = Property.parseText(rrule)
+                return prop
+
+        return None
+
+if __name__ == '__main__':
+    from collections import defaultdict
+    mu = 15
+    sigma = 12
+    print(&quot;Testing LogNormalDistribution with mu={mu}, sigma={sigma}&quot;.format(
+        mu=mu, sigma=sigma
+    ))
+    distribution = LogNormalDistribution(mu, sigma, 100)
+    result = defaultdict(int)
+    for _ignore_i in xrange(100000):
+        s = int(distribution.sample())
+        if s &gt; 300:
+            continue
+        result[s] += 1
+
+    total = 0
+    for k, v in sorted(result.items(), key=lambda x: x[0]):
+        print(&quot;%d\t%.5f&quot; % (k, float(v) / result[1]))
+        total += k * v
+
+    print(&quot;Average: %.2f&quot; % (float(total) / sum(result.values()),))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesticalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ical.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ical.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/ical.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -18,40 +18,28 @@
</span><span class="cx"> 
</span><span class="cx"> from caldavclientlibrary.protocol.caldav.definitions import caldavxml
</span><span class="cx"> from caldavclientlibrary.protocol.caldav.definitions import csxml
</span><del>-from caldavclientlibrary.protocol.url import URL
</del><span class="cx"> from caldavclientlibrary.protocol.webdav.definitions import davxml
</span><del>-from caldavclientlibrary.protocol.webdav.propfindparser import PropFindParser
</del><ins>+from caldavclientlibrary.protocol.url import URL
</ins><span class="cx"> 
</span><del>-from calendarserver.push.amppush import subscribeToIDs
-from calendarserver.tools.notifications import PubSubClientFactory
</del><ins>+from contrib.performance.httpclient import readBody
+from contrib.performance.loadtest.pubsub import Publisher
+from contrib.performance.loadtest.resources import Event, Calendar
+from contrib.performance.loadtest.requester import Requester, IncorrectResponseCode
+from contrib.performance.loadtest.push import PushMonitor
</ins><span class="cx"> 
</span><del>-from contrib.performance.httpauth import AuthHandlerAgent
-from contrib.performance.httpclient import StringProducer, readBody
-from contrib.performance.loadtest.subscribe import Periodical
-
</del><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> from pycalendar.duration import Duration
</span><span class="cx"> from pycalendar.timezone import Timezone
</span><span class="cx"> 
</span><del>-from twext.internet.adaptendpoint import connect
-from twext.internet.gaiendpoint import GAIEndpoint
-from twisted.internet.ssl import ClientContextFactory
-
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue, \
-    succeed
</del><span class="cx"> from twisted.internet.task import LoopingCall
</span><del>-from twisted.python.filepath import FilePath
-from twisted.python.log import addObserver, err, msg
-from twisted.python.util import FancyEqMixin
-from twisted.web.client import Agent, ContentDecoderAgent, GzipDecoder, \
-    _DeprecatedToCurrentPolicyForHTTPS
-from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT, PRECONDITION_FAILED, MOVED_PERMANENTLY, \
-    FORBIDDEN, FOUND
</del><ins>+from twisted.internet.defer import succeed, Deferred, inlineCallbacks, returnValue
+from twisted.python.log import err, msg
+from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT, FORBIDDEN, PRECONDITION_FAILED, MOVED_PERMANENTLY, FOUND
</ins><span class="cx"> from twisted.web.http_headers import Headers
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.ical import Component, Property
</span><span class="cx"> 
</span><del>-from urlparse import urlparse, urlunparse, urlsplit, urljoin
</del><ins>+from urlparse import urlparse, urlsplit
</ins><span class="cx"> from uuid import uuid4
</span><span class="cx"> from xml.etree import ElementTree
</span><span class="cx"> 
</span><span class="lines">@@ -59,196 +47,32 @@
</span><span class="cx"> import os
</span><span class="cx"> import random
</span><span class="cx"> 
</span><del>-ElementTree.QName.__repr__ = lambda self: '&lt;QName %r&gt;' % (self.text,)
</del><ins>+&quot;&quot;&quot;
+run
+  startup
+    deserialize
+    _startupPropfindWellKnown
+    _principalPropfindInitial
+    _extractPrincipalDetails
+    _checkCalendarsForEvents
+&quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-def loadRequestBody(clientType, label):
-    return FilePath(__file__).sibling('request-data').child(clientType).child(label + '.request').getContent()
</del><span class="cx"> 
</span><ins>+ElementTree.QName.__repr__ = lambda self: '&lt;QName %r&gt;' % (self.text,)
</ins><span class="cx"> 
</span><del>-SUPPORTED_REPORT_SET = '{DAV:}supported-report-set'
</del><ins>+SUPPORTED_REPORT_SET = davxml.supported_report_set.text
</ins><span class="cx"> 
</span><del>-class IncorrectResponseCode(Exception):
-    &quot;&quot;&quot;
-    Raised when a response has a code other than the one expected.
</del><ins>+class Attendee(Property):
+    def __init__(self, ):
+        pass
</ins><span class="cx"> 
</span><del>-    @ivar expected: The response codes which was expected.
-    @type expected: C{tuple} of C{int}
</del><span class="cx"> 
</span><del>-    @ivar response: The response which was received
-    @type response: L{twisted.web.client.Response}
-    &quot;&quot;&quot;
-    def __init__(self, expected, response):
-        self.expected = expected
-        self.response = response
-
-
-
</del><span class="cx"> class MissingCalendarHome(Exception):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Raised when the calendar home for a user is 404
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-class XMPPPush(object, FancyEqMixin):
-    &quot;&quot;&quot;
-    This represents an XMPP PubSub location where push notifications for
-    particular calendar home might be received.
-    &quot;&quot;&quot;
-    compareAttributes = ('server', 'uri', 'pushkey')
-
-    def __init__(self, server, uri, pushkey):
-        self.server = server
-        self.uri = uri
-        self.pushkey = pushkey
-
-
-
-def u2str(data):
-    return data.encode(&quot;utf-8&quot;) if type(data) is unicode else data
-
-
-
-class Event(object):
-    def __init__(self, serializeBasePath, url, etag, component=None):
-        self.serializeBasePath = serializeBasePath
-        self.url = url
-        self.etag = etag
-        self.scheduleTag = None
-        if component is not None:
-            self.component = component
-        self.uid = component.resourceUID() if component is not None else None
-
-
-    def getUID(self):
-        &quot;&quot;&quot;
-        Return the UID of the calendar resource.
-        &quot;&quot;&quot;
-        return self.uid
-
-
-    def serializePath(self):
-        if self.serializeBasePath:
-            calendar = os.path.join(self.serializeBasePath, self.url.split(&quot;/&quot;)[-2])
-            if not os.path.exists(calendar):
-                os.makedirs(calendar)
-            return os.path.join(calendar, self.url.split(&quot;/&quot;)[-1])
-        else:
-            return None
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a dict of the data so we can serialize as JSON.
-        &quot;&quot;&quot;
-
-        result = {}
-        for attr in (&quot;url&quot;, &quot;etag&quot;, &quot;scheduleTag&quot;, &quot;uid&quot;,):
-            result[attr] = getattr(self, attr)
-        return result
-
-
-    @staticmethod
-    def deserialize(serializeLocation, data):
-        &quot;&quot;&quot;
-        Convert dict (deserialized from JSON) into an L{Event}.
-        &quot;&quot;&quot;
-
-        event = Event(serializeLocation, None, None)
-        for attr in (&quot;url&quot;, &quot;etag&quot;, &quot;scheduleTag&quot;, &quot;uid&quot;,):
-            setattr(event, attr, u2str(data[attr]))
-        return event
-
-
-    @property
-    def component(self):
-        &quot;&quot;&quot;
-        Data always read from disk - never cached in the object.
-        &quot;&quot;&quot;
-        path = self.serializePath()
-        if path and os.path.exists(path):
-            f = open(path)
-            comp = Component.fromString(f.read())
-            f.close()
-            return comp
-        else:
-            return None
-
-
-    @component.setter
-    def component(self, component):
-        &quot;&quot;&quot;
-        Data always written to disk - never cached on the object.
-        &quot;&quot;&quot;
-        path = self.serializePath()
-        if path:
-            if component is None:
-                os.remove(path)
-            else:
-                f = open(path, &quot;w&quot;)
-                f.write(str(component))
-                f.close()
-        self.uid = component.resourceUID() if component is not None else None
-
-
-    def removed(self):
-        &quot;&quot;&quot;
-        Resource no longer exists on the server - remove associated data.
-        &quot;&quot;&quot;
-        path = self.serializePath()
-        if path and os.path.exists(path):
-            os.remove(path)
-
-
-
-class Calendar(object):
-    def __init__(self, resourceType, componentTypes, name, url, changeToken):
-        self.resourceType = resourceType
-        self.componentTypes = componentTypes
-        self.name = name
-        self.url = url
-        self.changeToken = changeToken
-        self.events = {}
-
-        if self.name is None and self.url is not None:
-            self.name = self.url.rstrip(&quot;/&quot;).split(&quot;/&quot;)[-1]
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a dict of the data so we can serialize as JSON.
-        &quot;&quot;&quot;
-
-        result = {}
-        for attr in (&quot;resourceType&quot;, &quot;name&quot;, &quot;url&quot;, &quot;changeToken&quot;):
-            result[attr] = getattr(self, attr)
-        result[&quot;componentTypes&quot;] = list(sorted(self.componentTypes))
-        result[&quot;events&quot;] = sorted(self.events.keys())
-        return result
-
-
-    @staticmethod
-    def deserialize(data, events):
-        &quot;&quot;&quot;
-        Convert dict (deserialized from JSON) into an L{Calendar}.
-        &quot;&quot;&quot;
-
-        calendar = Calendar(None, None, None, None, None)
-        for attr in (&quot;resourceType&quot;, &quot;name&quot;, &quot;url&quot;, &quot;changeToken&quot;):
-            setattr(calendar, attr, u2str(data[attr]))
-        calendar.componentTypes = set(map(u2str, data[&quot;componentTypes&quot;]))
-
-        for event in data[&quot;events&quot;]:
-            url = urljoin(calendar.url, event)
-            if url in events:
-                calendar.events[event] = events[url]
-            else:
-                # Ughh - an event is missing - force changeToken to empty to trigger full resync
-                calendar.changeToken = &quot;&quot;
-        return calendar
-
-
-
</del><span class="cx"> class BaseClient(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Base interface for all simulated clients.
</span><span class="lines">@@ -262,7 +86,7 @@
</span><span class="cx">     _client_id = None   # Unique id for the client itself
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _setEvent(self, href, event):
</del><ins>+    def _cacheEvent(self, href, event):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Cache the provided event
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -271,7 +95,7 @@
</span><span class="cx">         self._calendars[calendar + '/'].events[basePath] = event
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _removeEvent(self, href):
</del><ins>+    def _invalidateEvent(self, href):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Remove event from local cache.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -281,6 +105,21 @@
</span><span class="cx">         del self._calendars[calendar + '/'].events[basePath]
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _cacheCalendar(self, href, calendar):
+        &quot;&quot;&quot;
+        Cache the provided L{Calendar}
+        &quot;&quot;&quot;
+        self._calendars[href] = calendar
+
+
+    def _invalidateCalendar(self, href):
+        &quot;&quot;&quot;
+        Remove calendar from the local cache
+        &quot;&quot;&quot;
+        if href in self._calendars:
+            del self._calendars[href]
+
+
</ins><span class="cx">     def addEvent(self, href, calendar):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Called when a profile needs to add an event (no scheduling).
</span><span class="lines">@@ -324,46 +163,29 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         raise NotImplementedError(&quot;%r does not implement changeEventAttendee&quot; % (self.__class__,))
</span><span class="cx"> 
</span><ins>+    def addCalendar(self, href, calendar):
+        &quot;&quot;&quot;
+        Called when a profile needs to add a new calendar.
+        &quot;&quot;&quot;
+        raise NotImplementedError(&quot;%r does not implement addCalendar&quot; % (self.__class__,))
</ins><span class="cx"> 
</span><ins>+    def changeCalendar(self, href, calendar):
+        &quot;&quot;&quot;
+        Called when a profile needs to change a calendar.
+        &quot;&quot;&quot;
+        raise NotImplementedError(&quot;%r does not implement changeCalendar&quot; % (self.__class__,))
</ins><span class="cx"> 
</span><del>-class _PubSubClientFactory(PubSubClientFactory):
-    &quot;&quot;&quot;
-    Factory for XMPP pubsub functionality.
-    &quot;&quot;&quot;
-    def __init__(self, client, *args, **kwargs):
-        PubSubClientFactory.__init__(self, *args, **kwargs)
-        self._client = client
</del><span class="cx"> 
</span><ins>+    def deleteCalendar(self, href):
+        &quot;&quot;&quot;
+        Called when a profile needs to delete a calendar.
+        &quot;&quot;&quot;
+        raise NotImplementedError(&quot;%r does not implement deleteCalendar&quot; % (self.__class__,))
</ins><span class="cx"> 
</span><del>-    def initFailed(self, reason):
-        print('XMPP initialization failed', reason)
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def authFailed(self, reason):
-        print('XMPP Authentication failed', reason)
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def handleMessageEventItems(self, iq):
-        item = iq.firstChildElement().firstChildElement()
-        if item:
-            node = item.getAttribute(&quot;node&quot;)
-            if node:
-                url, _ignore_name, _ignore_kind = self.nodes.get(node, (None, None, None))
-                if url is not None:
-                    self._client._checkCalendarsForEvents(url, push=True)
-
-
-
-class WebClientContextFactory(ClientContextFactory):
-    &quot;&quot;&quot;
-    A web context factory which ignores the hostname and port and does no
-    certificate verification.
-    &quot;&quot;&quot;
-    def getContext(self, hostname, port):
-        return ClientContextFactory.getContext(self)
-
-
-
</del><span class="cx"> class BaseAppleClient(BaseClient):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Implementation of common OS X/iOS client behavior.
</span><span class="lines">@@ -371,7 +193,8 @@
</span><span class="cx"> 
</span><span class="cx">     _client_type = &quot;Generic&quot;
</span><span class="cx"> 
</span><del>-    USER_AGENT = None   # Override this for specific clients
</del><ins>+    # Override this for specific clients
+    USER_AGENT = None
</ins><span class="cx"> 
</span><span class="cx">     # The default interval, used if none is specified in external
</span><span class="cx">     # configuration.
</span><span class="lines">@@ -383,9 +206,6 @@
</span><span class="cx">     # Override and turn on if client supports Sync REPORT
</span><span class="cx">     _SYNC_REPORT = False
</span><span class="cx"> 
</span><del>-    # Override and turn on if client syncs using time-range queries
-    _SYNC_TIMERANGE = False
-
</del><span class="cx">     # Override and turn off if client does not support attendee lookups
</span><span class="cx">     _ATTENDEE_LOOKUPS = True
</span><span class="cx"> 
</span><span class="lines">@@ -419,32 +239,22 @@
</span><span class="cx">         self,
</span><span class="cx">         reactor,
</span><span class="cx">         root,
</span><del>-        principalPathTemplate,
</del><span class="cx">         serializePath,
</span><span class="cx">         record,
</span><span class="cx">         auth,
</span><span class="cx">         title=None,
</span><span class="cx">         calendarHomePollInterval=None,
</span><del>-        supportPush=True,
</del><span class="cx">         supportAmpPush=True,
</span><span class="cx">         ampPushHost=None,
</span><span class="cx">         ampPushPort=62311,
</span><span class="cx">     ):
</span><del>-
</del><span class="cx">         self._client_id = str(uuid4())
</span><del>-
</del><span class="cx">         self.reactor = reactor
</span><span class="cx"> 
</span><del>-        # The server might use gzip encoding
-        agent = Agent(
-            self.reactor,
-            contextFactory=_DeprecatedToCurrentPolicyForHTTPS(WebClientContextFactory()),
</del><ins>+        self.requester = Requester(
+            root, self.getDefaultHeaders(), title,
+            record.uid, self._client_id, auth, self.reactor
</ins><span class="cx">         )
</span><del>-        agent = ContentDecoderAgent(agent, [(&quot;gzip&quot;, GzipDecoder)])
-        self.agent = AuthHandlerAgent(agent, auth)
-
-        self.root = root
-        self.principalPathTemplate = principalPathTemplate
</del><span class="cx">         self.record = record
</span><span class="cx"> 
</span><span class="cx">         self.title = title if title else self._client_type
</span><span class="lines">@@ -453,208 +263,62 @@
</span><span class="cx">             calendarHomePollInterval = self.CALENDAR_HOME_POLL_INTERVAL
</span><span class="cx">         self.calendarHomePollInterval = calendarHomePollInterval
</span><span class="cx"> 
</span><del>-        self.supportPush = supportPush
</del><ins>+        if supportAmpPush:
+            if ampPushHost is None:
+                ampPushHost = urlparse(root)[1].split(&quot;:&quot;)[0]
+            self.monitor = PushMonitor(self.reactor, ampPushHost, ampPushPort, self.updateCalendarHomeFromPush)
+        else:
+            self.monitor = None
</ins><span class="cx"> 
</span><del>-        self.supportAmpPush = supportAmpPush
-        if ampPushHost is None:
-            ampPushHost = urlparse(self.root)[1].split(&quot;:&quot;)[0]
-        self.ampPushHost = ampPushHost
-        self.ampPushPort = ampPushPort
-
</del><span class="cx">         self.serializePath = serializePath
</span><span class="cx"> 
</span><span class="cx">         self.supportSync = self._SYNC_REPORT
</span><span class="cx"> 
</span><del>-        # Keep track of the calendars on this account, keys are
-        # Calendar URIs, values are Calendar instances.
-        self._calendars = {}
-
</del><span class="cx">         # The principalURL found during discovery
</span><span class="cx">         self.principalURL = None
</span><span class="cx"> 
</span><span class="cx">         # The principal collection found during startup
</span><span class="cx">         self.principalCollection = None
</span><span class="cx"> 
</span><ins>+        # Keep track of the calendars on this account, keys are
+        # Calendar URIs, values are Calendar instances.
+        self._calendars = {}
+
</ins><span class="cx">         # Keep track of the events on this account, keys are event
</span><span class="cx">         # URIs (which are unambiguous across different calendars
</span><span class="cx">         # because they start with the uri of the calendar they are
</span><span class="cx">         # part of), values are Event instances.
</span><span class="cx">         self._events = {}
</span><span class="cx"> 
</span><del>-        # Keep track of which calendar homes are being polled
-        self._checking = set()
-
-        # Keep track of XMPP parameters for calendar homes we encounter.  This
-        # dictionary has calendar home URLs as keys and XMPPPush instances as
-        # values.
-        self.xmpp = {}
-
-        self.ampPushKeys = {}
-
-        # Keep track of push factories so we can unsubscribe at shutdown
-        self._pushFactories = []
-
</del><span class="cx">         # Allow events to go out into the world.
</span><span class="cx">         self.catalog = {
</span><del>-            &quot;eventChanged&quot;: Periodical(),
</del><ins>+            &quot;eventChanged&quot;: Publisher(),
</ins><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        self._checking = set()
</ins><span class="cx"> 
</span><del>-    def _addDefaultHeaders(self, headers):
-        &quot;&quot;&quot;
-        Add the clients default set of headers to ones being used in a request.
-        Default is to add User-Agent, sub-classes should override to add other
-        client specific things, Accept etc.
-        &quot;&quot;&quot;
-        headers.setRawHeaders('User-Agent', [self.USER_AGENT])
-
-
-    @inlineCallbacks
-    def _request(self, expectedResponseCodes, method, url, headers=None, body=None, method_label=None):
-        &quot;&quot;&quot;
-        Execute a request and check against the expected response codes.
-        &quot;&quot;&quot;
-        if type(expectedResponseCodes) is int:
-            expectedResponseCodes = (expectedResponseCodes,)
-        if headers is None:
-            headers = Headers({})
-        self._addDefaultHeaders(headers)
-        msg(
-            type=&quot;request&quot;,
-            method=method_label if method_label else method,
-            url=url,
-            user=self.record.uid,
-            client_type=self.title,
-            client_id=self._client_id,
-        )
-
-        before = self.reactor.seconds()
-        response = yield self.agent.request(method, url, headers, body)
-
-        # XXX This is time to receive response headers, not time
-        # to receive full response.  Should measure the latter, if
-        # not both.
-        after = self.reactor.seconds()
-
-        success = response.code in expectedResponseCodes
-
-        msg(
-            type=&quot;response&quot;,
-            success=success,
-            method=method_label if method_label else method,
-            headers=headers,
-            body=body,
-            code=response.code,
-            user=self.record.uid,
-            client_type=self.title,
-            client_id=self._client_id,
-            duration=(after - before),
-            url=url,
-        )
-
-        if success:
-            returnValue(response)
-
-        raise IncorrectResponseCode(expectedResponseCodes, response)
-
-
-    def _parseMultiStatus(self, response, otherTokens=False):
-        &quot;&quot;&quot;
-        Parse a &lt;multistatus&gt; - might need to return other top-level elements
-        in the response - e.g. DAV:sync-token
-        I{PROPFIND} request for the principal URL.
-
-        @type response: C{str}
-        @rtype: C{cls}
-        &quot;&quot;&quot;
-        parser = PropFindParser()
-        parser.parseData(response)
-        if otherTokens:
-            return (parser.getResults(), parser.getOthers(),)
-        else:
-            return parser.getResults()
-
</del><span class="cx">     _CALENDAR_TYPES = set([
</span><span class="cx">         caldavxml.calendar,
</span><span class="cx">         caldavxml.schedule_inbox,
</span><span class="cx">     ])
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def _propfind(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), method_label=None):
-        &quot;&quot;&quot;
-        Issue a PROPFIND on the chosen URL
-        &quot;&quot;&quot;
-        hdrs = Headers({'content-type': ['text/xml']})
-        if depth is not None:
-            hdrs.addRawHeader('depth', depth)
-        response = yield self._request(
-            allowedStatus,
-            'PROPFIND',
-            self.root + url.encode('utf-8'),
-            hdrs,
-            StringProducer(body),
-            method_label=method_label,
-        )
</del><ins>+    def getDefaultHeaders(self):
+        return {
+            'User-Agent': [self.USER_AGENT],
+            'Accept': ['*/*'],
+            'Accept-Language': ['en-us'],
+            'Accept-Encoding': ['gzip,deflate'],
+            'Connection': ['keep-alive']
+        }
</ins><span class="cx"> 
</span><del>-        body = yield readBody(response)
-        result = self._parseMultiStatus(body) if response.code == MULTI_STATUS else None
-
-        returnValue((response, result,))
-
-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def _proppatch(self, url, body, method_label=None):
-        &quot;&quot;&quot;
-        Issue a PROPPATCH on the chosen URL
-        &quot;&quot;&quot;
-        hdrs = Headers({'content-type': ['text/xml']})
-        response = yield self._request(
-            (OK, MULTI_STATUS,),
-            'PROPPATCH',
-            self.root + url.encode('utf-8'),
-            hdrs,
-            StringProducer(body),
-            method_label=method_label,
-        )
-        if response.code == MULTI_STATUS:
-            body = yield readBody(response)
-            result = self._parseMultiStatus(body)
-            returnValue(result)
-        else:
-            returnValue(None)
-
-
-    @inlineCallbacks
-    def _report(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), otherTokens=False, method_label=None):
-        &quot;&quot;&quot;
-        Issue a REPORT on the chosen URL
-        &quot;&quot;&quot;
-        hdrs = Headers({'content-type': ['text/xml']})
-        if depth is not None:
-            hdrs.addRawHeader('depth', depth)
-        response = yield self._request(
-            allowedStatus,
-            'REPORT',
-            self.root + url.encode('utf-8'),
-            hdrs,
-            StringProducer(body),
-            method_label=method_label,
-        )
-
-        body = yield readBody(response)
-        result = self._parseMultiStatus(body, otherTokens) if response.code == MULTI_STATUS else None
-
-        returnValue(result)
-
-
-    @inlineCallbacks
</del><span class="cx">     def _startupPropfindWellKnown(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Issue a PROPFIND on the /.well-known/caldav/ URL
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         location = &quot;/.well-known/caldav/&quot;
</span><del>-        response, result = yield self._propfind(
</del><ins>+        response, result = yield self.requester.propfind(
</ins><span class="cx">             location,
</span><span class="cx">             self._STARTUP_WELL_KNOWN,
</span><span class="cx">             allowedStatus=(MULTI_STATUS, MOVED_PERMANENTLY, FOUND,),
</span><span class="lines">@@ -665,7 +329,7 @@
</span><span class="cx">         if response.code in (MOVED_PERMANENTLY, FOUND,):
</span><span class="cx">             location = response.headers.getRawHeaders(&quot;location&quot;)[0]
</span><span class="cx">             location = urlsplit(location)[2]
</span><del>-            response, result = yield self._propfind(
</del><ins>+            response, result = yield self.requester.propfind(
</ins><span class="cx">                 location,
</span><span class="cx">                 self._STARTUP_WELL_KNOWN,
</span><span class="cx">                 allowedStatus=(MULTI_STATUS),
</span><span class="lines">@@ -681,8 +345,8 @@
</span><span class="cx">         Issue a PROPFIND on the /principals/users/&lt;uid&gt; URL to retrieve
</span><span class="cx">         the /principals/__uids__/&lt;guid&gt; principal URL
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        principalPath = self.principalPathTemplate % (user,)
-        _ignore_response, result = yield self._propfind(
</del><ins>+        principalPath = '/principals/users/%s' % (user,)
+        _ignore_response, result = yield self.requester.propfind(
</ins><span class="cx">             principalPath,
</span><span class="cx">             self._STARTUP_PRINCIPAL_PROPFIND_INITIAL,
</span><span class="cx">             method_label=&quot;PROPFIND{find-principal}&quot;,
</span><span class="lines">@@ -697,7 +361,7 @@
</span><span class="cx">         user and return a L{Principal} instance constructed from the
</span><span class="cx">         response.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        _ignore_response, result = yield self._propfind(
</del><ins>+        _ignore_response, result = yield self.requester.propfind(
</ins><span class="cx">             self.principalURL,
</span><span class="cx">             self._STARTUP_PRINCIPAL_PROPFIND,
</span><span class="cx">             method_label=&quot;PROPFIND{principal}&quot;,
</span><span class="lines">@@ -709,7 +373,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Issue a principal-search-property-set REPORT against the chosen URL
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return self._report(
</del><ins>+        return self.requester.report(
</ins><span class="cx">             principalCollectionSet,
</span><span class="cx">             self._STARTUP_PRINCIPALS_REPORT,
</span><span class="cx">             allowedStatus=(OK,),
</span><span class="lines">@@ -718,28 +382,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _calendarHomePropfind(self, calendarHomeSet):
-        &quot;&quot;&quot;
-        Do the poll Depth:1 PROPFIND on the calendar home.
-        &quot;&quot;&quot;
-        if not calendarHomeSet.endswith('/'):
-            calendarHomeSet = calendarHomeSet + '/'
-        _ignore_response, result = yield self._propfind(
-            calendarHomeSet,
-            self._POLL_CALENDARHOME_PROPFIND,
-            depth='1',
-            method_label=&quot;PROPFIND{home}&quot;,
-        )
-        calendars = self._extractCalendars(result, calendarHomeSet)
-        returnValue((calendars, result,))
-
-
-    @inlineCallbacks
</del><span class="cx">     def _extractPrincipalDetails(self):
</span><span class="cx">         # Using the actual principal URL, retrieve principal information
</span><ins>+        # XXX We could be recording more information here
</ins><span class="cx">         principal = yield self._principalPropfind()
</span><span class="cx"> 
</span><span class="cx">         hrefs = principal.getHrefProperties()
</span><ins>+        # from pprint import pprint
+        # pprint(hrefs)
</ins><span class="cx"> 
</span><span class="cx">         # Remember our outbox and ignore notifications
</span><span class="cx">         self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
</span><span class="lines">@@ -767,14 +417,115 @@
</span><span class="cx"> 
</span><span class="cx">         returnValue(principal)
</span><span class="cx"> 
</span><ins>+    def startup(self):
+        &quot;&quot;&quot;
+        Overridden by subclasses of BaseAppleClient.
+        &quot;&quot;&quot;
+        raise NotImplementedError
</ins><span class="cx"> 
</span><ins>+    def calendarCheckLoop(self, calendarHome):
+        &quot;&quot;&quot;
+        Periodically check the calendar home for changes to calendars.
+        &quot;&quot;&quot;
+        pollCalendarHome = LoopingCall(
+            self.checkCalendarsForEvents, calendarHome)
+        return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
+
+    ### TODO this doesn't seem to always work
+    @inlineCallbacks
+    def updateCalendarHomeFromPush(self, calendarHomeSet):
+        &quot;&quot;&quot;
+        Emulate the client behavior upon receiving a notification that the
+        given calendar home has changed.
+        &quot;&quot;&quot;
+        # Todo - ensure that the self._checking set is properly cleared even if there is an error
+        self._checking.add(calendarHomeSet)
+        result = yield self._poll(calendarHomeSet, firstTime=False)
+
+        # Todo - should this be a returnValue?
+        yield self._newOperation(&quot;push&quot;, result)
+
+    @inlineCallbacks
+    def checkCalendarsForEvents(self, calendarHomeSet, firstTime=False):
+        &quot;&quot;&quot;
+        The actions a client does when polling for changes, or in response to a
+        push notification of a change. There are some actions done on the first poll
+        we should emulate.
+        &quot;&quot;&quot;
+
+        result = True
+        try:
+            result = yield self._newOperation(&quot;poll&quot;, self._poll(calendarHomeSet, firstTime))
+        finally:
+            if result:
+                try:
+                    self._checking.remove(calendarHomeSet)
+                except KeyError:
+                    pass
+        returnValue(result)
+
+    &quot;&quot;&quot;
+    REFRESH UTILITIES
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def _poll(self, calendarHomeSet, firstTime):
+        if calendarHomeSet in self._checking:
+            returnValue(False)
+        self._checking.add(calendarHomeSet)
+
+        calendars, results = yield self._calendarHomePropfind(calendarHomeSet)
+
+        # First time operations
+        if firstTime:
+            yield self._pollFirstTime1(results[calendarHomeSet], calendars)
+
+        # Normal poll
+        for cal in calendars:
+            newToken = cal.changeToken
+            if cal.url not in self._calendars:
+                # Calendar seen for the first time - reload it
+                self._calendars[cal.url] = cal
+                cal.changeToken = &quot;&quot;
+                yield self._updateCalendar(self._calendars[cal.url], newToken)
+            elif self._calendars[cal.url].changeToken != newToken:
+                # Calendar changed - reload it
+                yield self._updateCalendar(self._calendars[cal.url], newToken)
+
+        # When there is no sync REPORT, clients have to do a full PROPFIND
+        # on the notification collection because there is no ctag
+        if self.notificationURL is not None and not self.supportSync:
+            yield self._notificationPropfind(self.notificationURL)
+            yield self._notificationChangesPropfind(self.notificationURL)
+
+        # One time delegate expansion
+        if firstTime:
+            # yield self._pollFirstTime2()
+            pass
+
+        returnValue(True)
+
+    @inlineCallbacks
+    def _calendarHomePropfind(self, calendarHomeSet):
+        &quot;&quot;&quot;
+        Do the poll Depth:1 PROPFIND on the calendar home.
+        &quot;&quot;&quot;
+        if not calendarHomeSet.endswith('/'):
+            calendarHomeSet = calendarHomeSet + '/'
+        _ignore_response, result = yield self.requester.propfind(
+            calendarHomeSet,
+            self._POLL_CALENDARHOME_PROPFIND,
+            depth='1',
+            method_label=&quot;PROPFIND{home}&quot;,
+        )
+        calendars = self._extractCalendars(result, calendarHomeSet)
+        returnValue((calendars, result,))
+
+
</ins><span class="cx">     def _extractCalendars(self, results, calendarHome=None):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Parse a calendar home PROPFIND response and create local state
</span><span class="cx">         representing the calendars it contains.
</span><del>-
-        If XMPP push is enabled, also look for and record information about
-        that from the response.
</del><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         calendars = []
</span><span class="cx">         for href in results:
</span><span class="lines">@@ -787,19 +538,9 @@
</span><span class="cx">                 except KeyError:
</span><span class="cx">                     pass
</span><span class="cx">                 else:
</span><del>-                    if pushkey:
-                        self.ampPushKeys[href] = pushkey
</del><ins>+                    if pushkey and self.monitor:
+                        self.monitor.addPushkey(pushkey, href)
</ins><span class="cx"> 
</span><del>-                try:
-                    server = text[csxml.xmpp_server]
-                    uri = text[csxml.xmpp_uri]
-                    pushkey = text[csxml.pushkey]
-                except KeyError:
-                    pass
-                else:
-                    if server and uri:
-                        self.xmpp[href] = XMPPPush(server, uri, pushkey)
-
</del><span class="cx">             nodes = results[href].getNodeProperties()
</span><span class="cx">             for nodeType in nodes[davxml.resourcetype]:
</span><span class="cx">                 if nodeType.tag in self._CALENDAR_TYPES:
</span><span class="lines">@@ -822,6 +563,40 @@
</span><span class="cx">         return calendars
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+
+    @inlineCallbacks
+    def _pollFirstTime1(self, homeNode, calendars):
+        # Detect sync report if needed
+        if self.supportSync:
+            nodes = homeNode.getNodeProperties()
+            syncnodes = nodes[davxml.supported_report_set].findall(
+                str(davxml.supported_report) + &quot;/&quot; +
+                str(davxml.report) + &quot;/&quot; +
+                str(davxml.sync_collection)
+            )
+            self.supportSync = len(syncnodes) != 0
+
+        # Patch calendar properties
+        for cal in calendars:
+            if cal.name != &quot;inbox&quot;:
+                # yield self.requester.proppatch(
+                #     cal.url,
+                #     self._STARTUP_PROPPATCH_CALENDAR_COLOR,
+                #     method_label=&quot;PROPPATCH{calendar}&quot;,
+                # )
+                yield self.requester.proppatch(
+                    cal.url,
+                    self._STARTUP_PROPPATCH_CALENDAR_ORDER,
+                    method_label=&quot;PROPPATCH{calendar}&quot;,
+                )
+                yield self.requester.proppatch(
+                    cal.url,
+                    self._STARTUP_PROPPATCH_CALENDAR_TIMEZONE,
+                    method_label=&quot;PROPPATCH{calendar}&quot;,
+                )
+
+
</ins><span class="cx">     def _updateCalendar(self, calendar, newToken):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Update the local cached data for a calendar in an appropriate manner.
</span><span class="lines">@@ -843,11 +618,11 @@
</span><span class="cx">         # the sim can fire a PUT between the PROPFIND and when process the removals.
</span><span class="cx">         old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
</span><span class="cx"> 
</span><del>-        _ignore_response, result = yield self._propfind(
</del><ins>+        _ignore_response, result = yield self.requester.propfind(
</ins><span class="cx">             calendar.url,
</span><span class="cx">             self._POLL_CALENDAR_PROPFIND_D1,
</span><ins>+            method_label=&quot;PROPFIND{calendar}&quot;,
</ins><span class="cx">             depth='1',
</span><del>-            method_label=&quot;PROPFIND{calendar}&quot;
</del><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         yield self._updateApplyChanges(calendar, result, old_hrefs)
</span><span class="lines">@@ -871,7 +646,7 @@
</span><span class="cx">         # Get changes from sync REPORT (including the other nodes at the top-level
</span><span class="cx">         # which will have the new sync token.
</span><span class="cx">         fullSync = not calendar.changeToken
</span><del>-        result = yield self._report(
</del><ins>+        result = yield self.requester.report(
</ins><span class="cx">             calendar.url,
</span><span class="cx">             self._POLL_CALENDAR_SYNC_REPORT % {'sync-token': calendar.changeToken},
</span><span class="cx">             depth='1',
</span><span class="lines">@@ -882,7 +657,7 @@
</span><span class="cx">         if result is None:
</span><span class="cx">             if not fullSync:
</span><span class="cx">                 fullSync = True
</span><del>-                result = yield self._report(
</del><ins>+                result = yield self.requester.report(
</ins><span class="cx">                     calendar.url,
</span><span class="cx">                     self._POLL_CALENDAR_SYNC_REPORT % {'sync-token': ''},
</span><span class="cx">                     depth='1',
</span><span class="lines">@@ -908,13 +683,13 @@
</span><span class="cx">             # Differentiate a remove vs new/update result
</span><span class="cx">             if result[responseHref].getStatus() / 100 == 2:
</span><span class="cx">                 if responseHref not in self._events:
</span><del>-                    self._setEvent(responseHref, Event(self.serializeLocation(), responseHref, None))
</del><ins>+                    self._cacheEvent(responseHref, Event(self.serializeLocation(), responseHref, None))
</ins><span class="cx"> 
</span><span class="cx">                 event = self._events[responseHref]
</span><span class="cx">                 if event.etag != etag:
</span><span class="cx">                     changed.append(responseHref)
</span><span class="cx">             elif result[responseHref].getStatus() == 404:
</span><del>-                self._removeEvent(responseHref)
</del><ins>+                self._invalidateEvent(responseHref)
</ins><span class="cx"> 
</span><span class="cx">         yield self._updateChangedEvents(calendar, changed)
</span><span class="cx"> 
</span><span class="lines">@@ -923,7 +698,7 @@
</span><span class="cx">             # Detect removed items and purge them
</span><span class="cx">             remove_hrefs = old_hrefs - set(changed)
</span><span class="cx">             for href in remove_hrefs:
</span><del>-                self._removeEvent(href)
</del><ins>+                self._invalidateEvent(href)
</ins><span class="cx"> 
</span><span class="cx">         # Now update calendar to the new token taken from the report
</span><span class="cx">         for node in others:
</span><span class="lines">@@ -954,7 +729,7 @@
</span><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="cx">             if responseHref not in self._events:
</span><del>-                self._setEvent(responseHref, Event(self.serializeLocation(), responseHref, None))
</del><ins>+                self._cacheEvent(responseHref, Event(self.serializeLocation(), responseHref, None))
</ins><span class="cx"> 
</span><span class="cx">             event = self._events[responseHref]
</span><span class="cx">             if event.etag != etag:
</span><span class="lines">@@ -966,7 +741,7 @@
</span><span class="cx">         # Detect removed items and purge them
</span><span class="cx">         remove_hrefs = old_hrefs - set(all_hrefs)
</span><span class="cx">         for href in remove_hrefs:
</span><del>-            self._removeEvent(href)
</del><ins>+            self._invalidateEvent(href)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -982,9 +757,9 @@
</span><span class="cx">             changed = changed[self.MULTIGET_BATCH_SIZE:]
</span><span class="cx"> 
</span><span class="cx">             multistatus = yield self._eventReport(calendar.url, batchedHrefs)
</span><del>-            for responseHref in batchedHrefs:
</del><ins>+            for href in batchedHrefs:
</ins><span class="cx">                 try:
</span><del>-                    res = multistatus[responseHref]
</del><ins>+                    res = multistatus[href]
</ins><span class="cx">                 except KeyError:
</span><span class="cx">                     # Resource might have been deleted
</span><span class="cx">                     continue
</span><span class="lines">@@ -996,18 +771,10 @@
</span><span class="cx">                     except KeyError:
</span><span class="cx">                         scheduleTag = None
</span><span class="cx">                     body = text[caldavxml.calendar_data]
</span><del>-                    self.eventChanged(responseHref, etag, scheduleTag, body)
</del><ins>+                    component = Component.fromString(body)
+                    self._updateEventCache(href, etag, scheduleTag, component)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def eventChanged(self, href, etag, scheduleTag, body):
-        event = self._events[href]
-        event.etag = etag
-        if scheduleTag is not None:
-            event.scheduleTag = scheduleTag
-        event.component = Component.fromString(body)
-        self.catalog[&quot;eventChanged&quot;].issue(href)
-
-
</del><span class="cx">     def _eventReport(self, calendar, events):
</span><span class="cx">         # Next do a REPORT on events that might have information
</span><span class="cx">         # we don't know about.
</span><span class="lines">@@ -1021,7 +788,7 @@
</span><span class="cx">         if len(events) &gt; 75:
</span><span class="cx">             label_suffix = &quot;huge&quot;
</span><span class="cx"> 
</span><del>-        return self._report(
</del><ins>+        return self.requester.report(
</ins><span class="cx">             calendar,
</span><span class="cx">             self._POLL_CALENDAR_MULTIGET_REPORT % {'hrefs': hrefs},
</span><span class="cx">             depth=None,
</span><span class="lines">@@ -1030,101 +797,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _checkCalendarsForEvents(self, calendarHomeSet, firstTime=False, push=False):
-        &quot;&quot;&quot;
-        The actions a client does when polling for changes, or in response to a
-        push notification of a change. There are some actions done on the first poll
-        we should emulate.
-        &quot;&quot;&quot;
-
-        result = True
-        try:
-            result = yield self._newOperation(&quot;push&quot; if push else &quot;poll&quot;, self._poll(calendarHomeSet, firstTime))
-        finally:
-            if result:
-                try:
-                    self._checking.remove(calendarHomeSet)
-                except KeyError:
-                    pass
-        returnValue(result)
-
-
-    @inlineCallbacks
-    def _poll(self, calendarHomeSet, firstTime):
-        if calendarHomeSet in self._checking:
-            returnValue(False)
-        self._checking.add(calendarHomeSet)
-
-        calendars, results = yield self._calendarHomePropfind(calendarHomeSet)
-
-        # First time operations
-        if firstTime:
-            yield self._pollFirstTime1(results[calendarHomeSet], calendars)
-
-        # Normal poll
-        for cal in calendars:
-            newToken = cal.changeToken
-            if cal.url not in self._calendars:
-                # Calendar seen for the first time - reload it
-                self._calendars[cal.url] = cal
-                cal.changeToken = &quot;&quot;
-                yield self._updateCalendar(self._calendars[cal.url], newToken)
-            elif self._calendars[cal.url].changeToken != newToken:
-                # Calendar changed - reload it
-                yield self._updateCalendar(self._calendars[cal.url], newToken)
-
-        # When there is no sync REPORT, clients have to do a full PROPFIND
-        # on the notification collection because there is no ctag
-        if self.notificationURL is not None and not self.supportSync:
-            yield self._notificationPropfind(self.notificationURL)
-            yield self._notificationChangesPropfind(self.notificationURL)
-
-        # One time delegate expansion
-        if firstTime:
-            yield self._pollFirstTime2()
-
-        returnValue(True)
-
-
-    @inlineCallbacks
-    def _pollFirstTime1(self, homeNode, calendars):
-        # Detect sync report if needed
-        if self.supportSync:
-            nodes = homeNode.getNodeProperties()
-            syncnodes = nodes[davxml.supported_report_set].findall(
-                str(davxml.supported_report) + &quot;/&quot; +
-                str(davxml.report) + &quot;/&quot; +
-                str(davxml.sync_collection)
-            )
-            self.supportSync = len(syncnodes) != 0
-
-        # Patch calendar properties
-        for cal in calendars:
-            if cal.name != &quot;inbox&quot;:
-                yield self._proppatch(
-                    cal.url,
-                    self._STARTUP_PROPPATCH_CALENDAR_COLOR,
-                    method_label=&quot;PROPPATCH{calendar}&quot;,
-                )
-                yield self._proppatch(
-                    cal.url,
-                    self._STARTUP_PROPPATCH_CALENDAR_ORDER,
-                    method_label=&quot;PROPPATCH{calendar}&quot;,
-                )
-                yield self._proppatch(
-                    cal.url,
-                    self._STARTUP_PROPPATCH_CALENDAR_TIMEZONE,
-                    method_label=&quot;PROPPATCH{calendar}&quot;,
-                )
-
-
-    def _pollFirstTime2(self):
-        return self._principalExpand(self.principalURL)
-
-
-    @inlineCallbacks
</del><span class="cx">     def _notificationPropfind(self, notificationURL):
</span><del>-        _ignore_response, result = yield self._propfind(
</del><ins>+        _ignore_response, result = yield self.requester.propfind(
</ins><span class="cx">             notificationURL,
</span><span class="cx">             self._POLL_NOTIFICATION_PROPFIND,
</span><span class="cx">             method_label=&quot;PROPFIND{notification}&quot;,
</span><span class="lines">@@ -1134,7 +808,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _notificationChangesPropfind(self, notificationURL):
</span><del>-        _ignore_response, result = yield self._propfind(
</del><ins>+        _ignore_response, result = yield self.requester.propfind(
</ins><span class="cx">             notificationURL,
</span><span class="cx">             self._POLL_NOTIFICATION_PROPFIND_D1,
</span><span class="cx">             depth='1',
</span><span class="lines">@@ -1142,10 +816,12 @@
</span><span class="cx">         )
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><ins>+    def _pollFirstTime2(self):
+        return self._principalExpand(self.principalURL)
</ins><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _principalExpand(self, principalURL):
</span><del>-        result = yield self._report(
</del><ins>+        result = yield self.requester.report(
</ins><span class="cx">             principalURL,
</span><span class="cx">             self._STARTUP_PRINCIPAL_EXPAND,
</span><span class="cx">             depth=None,
</span><span class="lines">@@ -1154,19 +830,7 @@
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def startup(self):
-        raise NotImplementedError
</del><span class="cx"> 
</span><del>-
-    def _calendarCheckLoop(self, calendarHome):
-        &quot;&quot;&quot;
-        Periodically check the calendar home for changes to calendars.
-        &quot;&quot;&quot;
-        pollCalendarHome = LoopingCall(
-            self._checkCalendarsForEvents, calendarHome)
-        return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _newOperation(self, label, deferred):
</span><span class="cx">         before = self.reactor.seconds()
</span><span class="lines">@@ -1205,55 +869,7 @@
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _monitorPubSub(self, home, params):
-        &quot;&quot;&quot;
-        Start monitoring the
-        &quot;&quot;&quot;
-        host, port = params.server.split(':')
-        port = int(port)
-
-        service, _ignore_stuff = params.uri.split('?')
-        service = service.split(':', 1)[1]
-
-        # XXX What is the domain of the 2nd argument supposed to be?  The
-        # hostname we use to connect, or the same as the email address in the
-        # user record?
-        factory = _PubSubClientFactory(
-            self, &quot;%s@%s&quot; % (self.record.uid, host),
-            self.record.password, service,
-            {params.pushkey: (home, home, &quot;Calendar home&quot;)}, False,
-            sigint=False)
-        self._pushFactories.append(factory)
-        connect(GAIEndpoint(self.reactor, host, port), factory)
-
-
-    def _receivedPush(self, inboundID, dataChangedTimestamp, priority=5):
-        for href, id in self.ampPushKeys.iteritems():
-            if inboundID == id:
-                self._checkCalendarsForEvents(href, push=True)
-                break
-        else:
-            # somehow we are not subscribed to this id
-            pass
-
-
-    def _monitorAmpPush(self, home, pushKeys):
-        &quot;&quot;&quot;
-        Start monitoring for AMP-based push notifications
-        &quot;&quot;&quot;
-        subscribeToIDs(
-            self.ampPushHost, self.ampPushPort, pushKeys,
-            self._receivedPush, self.reactor
-        )
-
-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def _unsubscribePubSub(self):
-        for factory in self._pushFactories:
-            yield factory.unsubscribeAll()
-
-
-    @inlineCallbacks
</del><span class="cx">     def run(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Emulate a CalDAV client.
</span><span class="lines">@@ -1265,37 +881,30 @@
</span><span class="cx">             calendarHome = hrefs[caldavxml.calendar_home_set].toString()
</span><span class="cx">             if calendarHome is None:
</span><span class="cx">                 raise MissingCalendarHome
</span><del>-            yield self._checkCalendarsForEvents(calendarHome, firstTime=True)
</del><ins>+            yield self.checkCalendarsForEvents(calendarHome, firstTime=True)
</ins><span class="cx">             returnValue(calendarHome)
</span><span class="cx">         calendarHome = yield self._newOperation(&quot;startup: %s&quot; % (self.title,), startup())
</span><del>-
</del><span class="cx">         self.started = True
</span><span class="cx"> 
</span><del>-        # Start monitoring PubSub notifications, if possible.
-        # _checkCalendarsForEvents populates self.xmpp if it finds
-        # anything.
-        if self.supportPush and calendarHome in self.xmpp:
-            self._monitorPubSub(calendarHome, self.xmpp[calendarHome])
</del><ins>+        # Start monitoring AMP push notifications, if possible
+        if self.monitor and self.monitor.isSubscribedTo(calendarHome):
+            yield self.monitor.begin()
</ins><span class="cx">             # Run indefinitely.
</span><span class="cx">             yield Deferred()
</span><del>-        elif self.supportAmpPush and calendarHome in self.ampPushKeys:
-            pushKeys = self.ampPushKeys.values()
-            self._monitorAmpPush(calendarHome, pushKeys)
-            # Run indefinitely.
-            yield Deferred()
</del><span class="cx">         else:
</span><span class="cx">             # This completes when the calendar home poll loop completes, which
</span><span class="cx">             # currently it never will except due to an unexpected error.
</span><del>-            yield self._calendarCheckLoop(calendarHome)
</del><ins>+            yield self.calendarCheckLoop(calendarHome)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def stop(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Called before connections are closed, giving a chance to clean up
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-
</del><span class="cx">         self.serialize()
</span><del>-        return self._unsubscribePubSub()
</del><ins>+        if not self.monitor:
+            return succeed(None)
+        return self.monitor.end()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def serializeLocation(self):
</span><span class="lines">@@ -1387,94 +996,67 @@
</span><span class="cx">         )
</span><span class="cx">         return organizer
</span><span class="cx"> 
</span><ins>+    def _getEventSizeDescription(self, numAttendees):
+        if numAttendees &gt; 75:
+            return &quot;huge&quot;
+        if numAttendees &gt; 20:
+            return &quot;large&quot;
+        if numAttendees &gt; 5:
+            return &quot;medium&quot;
+        return &quot;small&quot;
</ins><span class="cx"> 
</span><ins>+    &quot;&quot;&quot; literally wtf is this event stuff
+submitEvent(event: Event, )
+
+&quot;&quot;&quot;
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def addEventAttendee(self, href, attendee):
</span><ins>+        individual = attendee.parameterValue('CUTYPE') == 'INDIVIDUAL'
</ins><span class="cx"> 
</span><span class="cx">         event = self._events[href]
</span><span class="cx">         component = event.component
</span><ins>+        vevent = component.mainComponent()
</ins><span class="cx"> 
</span><ins>+        query = attendee.parameterValue('CN')
+
+        from pprint import pprint
</ins><span class="cx">         # Trigger auto-complete behavior
</span><del>-        yield self._attendeeAutoComplete(component, attendee)
</del><ins>+        matchingPrincipals = yield self._principalSearchReport(query, isAttendeeSearch=individual)
+        # for k, v in matchingPrincipals.items():
+            # pprint(k)
+            # for prop, val in v.getNodeProperties().items():
+            #     # print(&quot;%s %s&quot; % (prop, val.__dict__))
+            #     for child in val._children:
+            #         # print(child.text)
</ins><span class="cx"> 
</span><del>-        # If the event has no attendees, add ourselves as an attendee.
-        attendees = list(component.mainComponent().properties('ATTENDEE'))
-        if len(attendees) == 0:
-            # First add ourselves as a participant and as the
-            # organizer.  In the future for this event we should
-            # already have those roles.
-            component.mainComponent().addProperty(self._makeSelfOrganizer())
-            component.mainComponent().addProperty(self._makeSelfAttendee())
-        attendees.append(attendee)
-        component.mainComponent().addProperty(attendee)
</del><ins>+        uuids = []
</ins><span class="cx"> 
</span><del>-        label_suffix = &quot;small&quot;
-        if len(attendees) &gt; 5:
-            label_suffix = &quot;medium&quot;
-        if len(attendees) &gt; 20:
-            label_suffix = &quot;large&quot;
-        if len(attendees) &gt; 75:
-            label_suffix = &quot;huge&quot;
</del><ins>+        for principal_url, propfindresult in matchingPrincipals.items():
+            props = propfindresult.getNodeProperties()
+            for cuaddr in props.get(caldavxml.calendar_user_address_set):
+                # print(cuaddr)
+                uuids.append(cuaddr.text)
+                break
</ins><span class="cx"> 
</span><del>-        # At last, upload the new event definition
-        response = yield self._request(
-            (NO_CONTENT, PRECONDITION_FAILED,),
-            'PUT',
-            self.root + href.encode('utf-8'),
-            Headers({
-                    'content-type': ['text/calendar'],
-                    'if-match': [event.etag]}),
-            StringProducer(component.getTextWithTimezones(includeTimezones=True)),
-            method_label=&quot;PUT{organizer-%s}&quot; % (label_suffix,)
-        )
</del><ins>+        # print(uuids)
</ins><span class="cx"> 
</span><del>-        # Finally, re-retrieve the event to update the etag
-        yield self._updateEvent(response, href)
</del><span class="cx"> 
</span><ins>+        start = vevent.getStartDateUTC()
+        end = vevent.getEndDateUTC()
</ins><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def _attendeeAutoComplete(self, component, attendee):
</del><ins>+        yield self.requestAvailability(start, end, uuids)
</ins><span class="cx"> 
</span><del>-        if self._ATTENDEE_LOOKUPS:
-            # Temporarily use some non-test names (some which will return
-            # many results, and others which will return fewer) because the
-            # test account names are all too similar
-            # name = attendee.parameterValue('CN').encode(&quot;utf-8&quot;)
-            # prefix = name[:4].lower()
-            prefix = random.choice([
-                &quot;chris&quot;, &quot;cyru&quot;, &quot;dre&quot;, &quot;eric&quot;, &quot;morg&quot;,
-                &quot;well&quot;, &quot;wilfr&quot;, &quot;witz&quot;
-            ])
</del><ins>+        # # Do free-busy lookups
+        # if individual:
+        #     # When adding individual attendees, we only look up the availability
+        #     # of the specific attendee
+        #     yield self.checkAvailability()
+        # else:
+        #     # When adding a location, we look up the availability of each location
+        #     # returned by the principal search.
+        #     yield self.checkAvailability()
</ins><span class="cx"> 
</span><del>-            email = attendee.value()
-            if email.startswith(&quot;mailto:&quot;):
-                email = email[7:]
-            elif attendee.hasParameter('EMAIL'):
-                email = attendee.parameterValue('EMAIL').encode(&quot;utf-8&quot;)
</del><span class="cx"> 
</span><del>-            # First try to discover some names to supply to the
-            # auto-completion
-            yield self._report(
-                self.principalCollection,
-                self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
-                    'displayname': prefix,
-                    'email': prefix,
-                    'firstname': prefix,
-                    'lastname': prefix,
-                },
-                depth=None,
-                method_label=&quot;REPORT{psearch}&quot;,
-            )
-
-            # Now learn about the attendee's availability
-            yield self.requestAvailability(
-                component.mainComponent().getStartDateUTC(),
-                component.mainComponent().getEndDateUTC(),
-                [self.email, u'mailto:' + email],
-                [component.resourceUID()]
-            )
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def changeEventAttendee(self, href, oldAttendee, newAttendee):
</span><span class="cx">         event = self._events[href]
</span><span class="lines">@@ -1483,149 +1065,197 @@
</span><span class="cx">         # Change the event to have the new attendee instead of the old attendee
</span><span class="cx">         component.mainComponent().removeProperty(oldAttendee)
</span><span class="cx">         component.mainComponent().addProperty(newAttendee)
</span><del>-        okCodes = NO_CONTENT
-        headers = Headers({
-            'content-type': ['text/calendar'],
-        })
</del><ins>+
+        headers = Headers()
</ins><span class="cx">         if event.scheduleTag is not None:
</span><span class="cx">             headers.addRawHeader('if-schedule-tag-match', event.scheduleTag)
</span><del>-            okCodes = (NO_CONTENT, PRECONDITION_FAILED,)
-
</del><ins>+        event.component = component
</ins><span class="cx">         attendees = list(component.mainComponent().properties('ATTENDEE'))
</span><del>-        label_suffix = &quot;small&quot;
-        if len(attendees) &gt; 5:
-            label_suffix = &quot;medium&quot;
-        if len(attendees) &gt; 20:
-            label_suffix = &quot;large&quot;
-        if len(attendees) &gt; 75:
-            label_suffix = &quot;huge&quot;
</del><ins>+        label_suffix = self._getEventSizeDescription(len(attendees))
+        method_label = &quot;PUT{attendee-%s}&quot; % (label_suffix,)
</ins><span class="cx"> 
</span><del>-        response = yield self._request(
-            okCodes,
-            'PUT',
-            self.root + href.encode('utf-8'),
-            headers, StringProducer(component.getTextWithTimezones(includeTimezones=True)),
-            method_label=&quot;PUT{attendee-%s}&quot; % (label_suffix,),
-        )
</del><ins>+        yield self.putEvent(href, event, headers=headers, method_label=method_label, new=False)
</ins><span class="cx"> 
</span><del>-        # Finally, re-retrieve the event to update the etag
-        yield self._updateEvent(response, href)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def deleteEvent(self, href):
</del><ins>+    def addInvite(self, event):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Issue a DELETE for the given URL and remove local state
-        associated with that event.
</del><ins>+        Add an event that is an invite - i.e., has attendees. Presumably the appropriate principal searches and
+        free-busy lookups have already been accounted for (in addEventAttendee)
</ins><span class="cx">         &quot;&quot;&quot;
</span><ins>+        vevent = event.component.mainComponent()
+        # If the event has no attendees, add ourselves as an attendee.
+        attendees = list(vevent.properties('ATTENDEE'))
+        if len(attendees) == 0:
+            # First add ourselves as a participant and as the
+            # organizer.  In the future for this event we should
+            # already have those roles.
+            vevent.addProperty(self._makeSelfOrganizer())
+            vevent.addProperty(self._makeSelfAttendee())
</ins><span class="cx"> 
</span><del>-        self._removeEvent(href)
</del><ins>+        label_suffix = self._getEventSizeDescription(len(attendees))
+        method_label = &quot;PUT{organizer-%s}&quot; % (label_suffix,)
</ins><span class="cx"> 
</span><del>-        response = yield self._request(
-            NO_CONTENT,
-            'DELETE',
-            self.root + href.encode('utf-8'),
-            method_label=&quot;DELETE{event}&quot;,
-        )
-        returnValue(response)
</del><ins>+        yield self.updateEvent(event, method_label=method_label)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def addEvent(self, href, component, invite=False):
</del><ins>+    def addEvent(self, href, event):
+        &quot;&quot;&quot;
+        client.addEvent(
+            Event e
+        &quot;&quot;&quot;
</ins><span class="cx">         headers = Headers({
</span><del>-            'content-type': ['text/calendar'],
</del><ins>+            'if-none-match': ['*']
</ins><span class="cx">         })
</span><ins>+        yield self.putEvent(
+            href,
+            event,
+            headers=headers,
+            method_label=&quot;PUT{event}&quot;
+        )
</ins><span class="cx"> 
</span><del>-        attendees = list(component.mainComponent().properties('ATTENDEE'))
-        label_suffix = &quot;small&quot;
-        if len(attendees) &gt; 5:
-            label_suffix = &quot;medium&quot;
-        if len(attendees) &gt; 20:
-            label_suffix = &quot;large&quot;
-        if len(attendees) &gt; 75:
-            label_suffix = &quot;huge&quot;
</del><ins>+    # attendees = list(component.mainComponent().properties('ATTENDEE'))
+    # label_suffix = self._getEventSizeDescription(len(attendees))
+    # method_label = &quot;PUT{organizer-%s}&quot; % (label_suffix,) if invite else &quot;PUT{event}&quot;
</ins><span class="cx"> 
</span><del>-        response = yield self._request(
-            CREATED,
-            'PUT',
-            self.root + href.encode('utf-8'),
-            headers,
-            StringProducer(component.getTextWithTimezones(includeTimezones=True)),
-            method_label=&quot;PUT{organizer-%s}&quot; % (label_suffix,) if invite else &quot;PUT{event}&quot;,
-        )
-        self._localUpdateEvent(response, href, component)
</del><ins>+    @inlineCallbacks
+    def updateEvent(self, event, method_label=&quot;PUT{event}&quot;):
+        headers = Headers({
+            'if-match': [event.etag]
+        })
+        yield self.putEvent(event.url, event, headers=headers, method_label=method_label)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def addInvite(self, href, component):
</del><ins>+    def putEvent(self, href, event, headers=None, method_label=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Add an event that is an invite - i.e., has attendees. We will do attendee lookups and freebusy
-        checks on each attendee to simulate what happens when an organizer creates a new invite.
</del><ins>+        PUT an event to the server
</ins><span class="cx">         &quot;&quot;&quot;
</span><ins>+        if headers == None:
+            headers = Headers()
+        headers.addRawHeader('content-type', 'text/calendar')
</ins><span class="cx"> 
</span><del>-        # Do lookup and free busy of each attendee (not self)
-        attendees = list(component.mainComponent().properties('ATTENDEE'))
-        for attendee in attendees:
-            if attendee.value() in (self.uuid, self.email):
-                continue
-            yield self._attendeeAutoComplete(component, attendee)
</del><ins>+        okCodes = (CREATED, NO_CONTENT, PRECONDITION_FAILED)
</ins><span class="cx"> 
</span><del>-        # Now do a normal PUT
-        yield self.addEvent(href, component, invite=True)
</del><ins>+        # At last, upload the new event definition
+        response = yield self.requester.put(
+            okCodes,
+            href,
+            event.component,
+            headers=headers,
+            method_label=method_label
+        )
+        # If the server doesn't return an etag, it has changed the resource
+        # and we need to refetch it
+        if not response.headers.hasHeader('etag'):
+            yield self._refreshEvent(href)
+        else:
+            etag, scheduleTag = self.extractTags(response)
+            yield succeed(self._updateEventCache(href, etag=etag, scheduleTag=scheduleTag, component=event.component))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def changeEvent(self, href):
</del><ins>+    def _refreshEvent(self, href):
+        &quot;&quot;&quot;
+        Issues a GET to the specified href (representing an event that already exists on the server)
+        and uses the response to update local state associated with that event
+        &quot;&quot;&quot;
+        response = yield self.requester.get(href, method_label=&quot;GET{event}&quot;)
+        etag, scheduleTag = self.extractTags(response)
+        body = yield readBody(response)
+        component = Component.fromString(body)
+        self._updateEventCache(href, etag=etag, scheduleTag=scheduleTag, component=component)
</ins><span class="cx"> 
</span><del>-        event = self._events[href]
-        component = event.component
</del><ins>+    def _updateEventCache(self, href, etag=None, scheduleTag=None, component=None):
+        &quot;&quot;&quot;
+        Update local state associated with the event at href
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-        # At last, upload the new event definition
-        response = yield self._request(
-            (NO_CONTENT, PRECONDITION_FAILED,),
-            'PUT',
-            self.root + href.encode('utf-8'),
-            Headers({
-                'content-type': ['text/calendar'],
-                'if-match': [event.etag]
-            }),
-            StringProducer(component.getTextWithTimezones(includeTimezones=True)),
-            method_label=&quot;PUT{update}&quot;
-        )
</del><ins>+        if href in self._events:
+            event = self._events[href]
+        else: # This is a new resource
+            event = Event(self.serializeLocation(), href, None, None)
</ins><span class="cx"> 
</span><del>-        # Finally, re-retrieve the event to update the etag
-        yield self._updateEvent(response, href)
</del><ins>+        if etag:
+            event.etag = etag
+        if scheduleTag:
+            event.scheduleTag = scheduleTag
+        if component:
+            event.component = component
</ins><span class="cx"> 
</span><ins>+        if True: # XXX some other test
+            self.catalog[&quot;eventChanged&quot;].issue(href)
+        self._cacheEvent(href, event)
</ins><span class="cx"> 
</span><del>-    def _localUpdateEvent(self, response, href, component):
</del><ins>+    @inlineCallbacks
+    def deleteEvent(self, href):
+        &quot;&quot;&quot;
+        Issue a DELETE for the given URL and remove local state
+        associated with that event.
+        &quot;&quot;&quot;
+        self._invalidateEvent(href)
+        yield self.requester.delete(href, method_label=&quot;DELETE{event}&quot;)
+
+    def extractTags(self, response):
</ins><span class="cx">         headers = response.headers
</span><span class="cx">         etag = headers.getRawHeaders(&quot;etag&quot;, [None])[0]
</span><span class="cx">         scheduleTag = headers.getRawHeaders(&quot;schedule-tag&quot;, [None])[0]
</span><ins>+        return etag, scheduleTag
</ins><span class="cx"> 
</span><del>-        event = Event(self.serializeLocation(), href, etag, component)
-        event.scheduleTag = scheduleTag
-        self._setEvent(href, event)
</del><ins>+    # @inlineCallbacks
+    # def _attendeeAutoComplete(self, component, attendee):
</ins><span class="cx"> 
</span><ins>+    #     if self._ATTENDEE_LOOKUPS:
+    #         # Temporarily use some non-test names (some which will return
+    #         # many results, and others which will return fewer) because the
+    #         # test account names are all too similar
+    #         # name = attendee.parameterValue('CN').encode(&quot;utf-8&quot;)
+    #         # prefix = name[:4].lower()
+    #         prefix = random.choice([
+    #             &quot;chris&quot;, &quot;cyru&quot;, &quot;dre&quot;, &quot;eric&quot;, &quot;morg&quot;,
+    #             &quot;well&quot;, &quot;wilfr&quot;, &quot;witz&quot;
+    #         ])
</ins><span class="cx"> 
</span><del>-    def updateEvent(self, href):
-        return self._updateEvent(None, href)
</del><ins>+    #         email = attendee.value()
+    #         if email.startswith(&quot;mailto:&quot;):
+    #             email = email[7:]
+    #         elif attendee.hasParameter('EMAIL'):
+    #             email = attendee.parameterValue('EMAIL').encode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><ins>+    #         # First try to discover some names to supply to the
+    #         # auto-completion
+    #         yield self.requester.report(
+    #             self.principalCollection,
+    #             self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
+    #                 'displayname': prefix,
+    #                 'email': prefix,
+    #                 'firstname': prefix,
+    #                 'lastname': prefix,
+    #             },
+    #             depth=None,
+    #             method_label=&quot;REPORT{psearch}&quot;,
+    #         )
</ins><span class="cx"> 
</span><ins>+    #         # Now learn about the attendee's availability
+    #         yield self.requestAvailability(
+    #             component.mainComponent().getStartDateUTC(),
+    #             component.mainComponent().getEndDateUTC(),
+    #             [self.email, u'mailto:' + email],
+    #             [component.resourceUID()]
+    #         )
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def _updateEvent(self, ignored, href):
-        response = yield self._request(
-            OK,
-            'GET',
-            self.root + href.encode('utf-8'),
-            method_label=&quot;GET{event}&quot;,
-        )
-        headers = response.headers
-        etag = headers.getRawHeaders('etag')[0]
-        scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
-        body = yield readBody(response)
-        self.eventChanged(href, etag, scheduleTag, body)
</del><ins>+    def _principalSearchReport(self, query, isAttendeeSearch):
+        &quot;&quot;&quot; context = attendee if isAttendeeSearch else location &quot;&quot;&quot;
+        context = &quot;attendee&quot; if isAttendeeSearch else &quot;location&quot;
+        tokens = query.split()
+        search = '\n'.join([&quot;&lt;C:search-token&gt;%s&lt;/C:search-token&gt;&quot; % (token, ) for token in tokens])
+        body = self._CALENDARSERVER_PRINCIPAL_SEARCH_REPORT.format(context=context, searchTokens=search)
+        principals = yield self.requester.report('/principals/', body, depth=None)
+        # print(&quot;Found some principals:&quot;)
+        returnValue(principals)
</ins><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def requestAvailability(self, start, end, users, mask=set()):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1647,8 +1277,6 @@
</span><span class="cx">         @return: A C{Deferred} which fires with a C{dict}.  Keys in the dict
</span><span class="cx">             are user UUIDs (those requested) and values are something else.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        outbox = self.root + self.outbox
-
</del><span class="cx">         if mask:
</span><span class="cx">             maskStr = u'\r\n'.join(['X-CALENDARSERVER-MASK-UID:' + uid
</span><span class="cx">                                     for uid in mask]) + u'\r\n'
</span><span class="lines">@@ -1672,21 +1300,16 @@
</span><span class="cx">         end = end.getText()
</span><span class="cx">         now = DateTime.getNowUTC().getText()
</span><span class="cx"> 
</span><del>-        label_suffix = &quot;small&quot;
-        if len(users) &gt; 5:
-            label_suffix = &quot;medium&quot;
-        if len(users) &gt; 20:
-            label_suffix = &quot;large&quot;
-        if len(users) &gt; 75:
-            label_suffix = &quot;huge&quot;
</del><ins>+        label_suffix = self._getEventSizeDescription(len(users))
</ins><span class="cx"> 
</span><del>-        response = yield self._request(
-            OK, 'POST', outbox,
-            Headers({
-                    'content-type': ['text/calendar'],
-                    'originator': [self.email],
-                    'recipient': [u', '.join(users).encode('utf-8')]}),
-            StringProducer(self._POST_AVAILABILITY % {
</del><ins>+        headers = Headers({
+            'content-type': ['text/calendar'],
+            'originator': [self.email],
+            'recipient': [u', '.join(users).encode('utf-8')]
+        })
+        response = yield self.requester.post(
+            self.outbox,
+            self._POST_AVAILABILITY % {
</ins><span class="cx">                 'attendees': attendeeStr,
</span><span class="cx">                 'summary': (u'Availability for %s' % (', '.join(users),)).encode('utf-8'),
</span><span class="cx">                 'organizer': self.email.encode('utf-8'),
</span><span class="lines">@@ -1695,385 +1318,103 @@
</span><span class="cx">                 'start': start,
</span><span class="cx">                 'end': end,
</span><span class="cx">                 'now': now,
</span><del>-            }),
</del><ins>+            },
+            headers=headers,
</ins><span class="cx">             method_label=&quot;POST{fb-%s}&quot; % (label_suffix,),
</span><span class="cx">         )
</span><ins>+
</ins><span class="cx">         body = yield readBody(response)
</span><span class="cx">         returnValue(body)
</span><span class="cx"> 
</span><del>-
-
-class OS_X_10_6(BaseAppleClient):
-    &quot;&quot;&quot;
-    Implementation of the OS X 10.6 iCal network behavior.
-
-    Anything OS X 10.6 iCal does on its own, or any particular
-    network behaviors it takes in response to a user action, belong on
-    this class.
-
-    Usage-profile based behaviors (&quot;the user modifies an event every
-    3.2 minutes&quot;) belong elsewhere.
-    &quot;&quot;&quot;
-
-    _client_type = &quot;OS X 10.6&quot;
-
-    USER_AGENT = &quot;DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)&quot;
-
-    # The default interval, used if none is specified in external
-    # configuration.  This is also the actual value used by Snow
-    # Leopard iCal.
-    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
-
-    # The maximum number of resources to retrieve in a single multiget
-    MULTIGET_BATCH_SIZE = 200
-
-    # Override and turn on if client supports Sync REPORT
-    _SYNC_REPORT = False
-
-    # Override and turn on if client syncs using time-range queries
-    _SYNC_TIMERANGE = False
-
-    # Override and turn off if client does not support attendee lookups
-    _ATTENDEE_LOOKUPS = True
-
-    # Request body data
-    _LOAD_PATH = &quot;OS_X_10_6&quot;
-
-    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
-    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
-    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
-    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
-    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
-    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
-    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
-    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
-
-    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
-    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
-    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
-    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
-    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
-    _POLL_CALENDAR_SYNC_REPORT = None
-    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
-    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
-
-    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
-    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def startup(self):
</del><ins>+    def postAttachment(self, href, content):
+        url = &quot;{0}?{1}&quot;.format(href, &quot;action=attachment-add&quot;)
+        filename = 'file-{}.txt'.format(len(content))
+        headers = Headers({
+        #     'Transfer-Encoding': ['Chunked'],
+            'Content-Disposition': ['attachment; filename=&quot;{}&quot;'.format(filename)]
+        })
+        l = len(content)
+        # lengthPrefix = hex(l)[2:].upper() # For some reason, this attachment is length-prefixed in hex
+        label_suffix = self._getEventSizeDescription(l / 1024)
+        # body = &quot;{0}\n{1}\n0\n&quot;.format(lengthPrefix, content) # XXX There HAS to be a better way to do this
+        yield self.requester.post(
+            url,
+            content,
+            headers=headers,
+            method_label=&quot;POST{attach-%s}&quot; % (label_suffix,)
+        )
</ins><span class="cx"> 
</span><del>-        # Try to read data from disk - if it succeeds self.principalURL will be set
-        self.deserialize()
-
-        if self.principalURL is None:
-            # PROPFIND principal path to retrieve actual principal-URL
-            response = yield self._principalPropfindInitial(self.record.uid)
-            hrefs = response.getHrefProperties()
-            self.principalURL = hrefs[davxml.principal_URL].toString()
-
-        # Using the actual principal URL, retrieve principal information
-        principal = (yield self._extractPrincipalDetails())
-        returnValue(principal)
-
-
-
-class OS_X_10_7(BaseAppleClient):
-    &quot;&quot;&quot;
-    Implementation of the OS X 10.7 iCal network behavior.
-    &quot;&quot;&quot;
-
-    _client_type = &quot;OS X 10.7&quot;
-
-    USER_AGENT = &quot;CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50)&quot;
-
-    # The default interval, used if none is specified in external
-    # configuration.  This is also the actual value used by Snow
-    # Leopard iCal.
-    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
-
-    # The maximum number of resources to retrieve in a single multiget
-    MULTIGET_BATCH_SIZE = 50
-
-    # Override and turn on if client supports Sync REPORT
-    _SYNC_REPORT = True
-
-    # Override and turn on if client syncs using time-range queries
-    _SYNC_TIMERANGE = False
-
-    # Override and turn off if client does not support attendee lookups
-    _ATTENDEE_LOOKUPS = True
-
-    # Request body data
-    _LOAD_PATH = &quot;OS_X_10_7&quot;
-
-    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
-    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
-    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
-    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
-    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
-    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
-    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
-    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
-
-    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
-    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
-    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
-    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
-    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
-    _POLL_CALENDAR_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_sync')
-    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
-    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
-
-    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
-    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
-
-
-    def _addDefaultHeaders(self, headers):
-        &quot;&quot;&quot;
-        Add the clients default set of headers to ones being used in a request.
-        Default is to add User-Agent, sub-classes should override to add other
-        client specific things, Accept etc.
-        &quot;&quot;&quot;
-
-        super(OS_X_10_7, self)._addDefaultHeaders(headers)
-        headers.setRawHeaders('Accept', ['*/*'])
-        headers.setRawHeaders('Accept-Language', ['en-us'])
-        headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
-        headers.setRawHeaders('Connection', ['keep-alive'])
-
-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def startup(self):
-
-        # Try to read data from disk - if it succeeds self.principalURL will be set
-        self.deserialize()
-
-        if self.principalURL is None:
-            # PROPFIND well-known with redirect
-            response = yield self._startupPropfindWellKnown()
-            hrefs = response.getHrefProperties()
-            if davxml.current_user_principal in hrefs:
-                self.principalURL = hrefs[davxml.current_user_principal].toString()
-            elif davxml.principal_URL in hrefs:
-                self.principalURL = hrefs[davxml.principal_URL].toString()
-            else:
-                # PROPFIND principal path to retrieve actual principal-URL
-                response = yield self._principalPropfindInitial(self.record.uid)
-                hrefs = response.getHrefProperties()
-                self.principalURL = hrefs[davxml.principal_URL].toString()
-
-        # Using the actual principal URL, retrieve principal information
-        principal = yield self._extractPrincipalDetails()
-        returnValue(principal)
-
-
-
-class iOS_5(BaseAppleClient):
-    &quot;&quot;&quot;
-    Implementation of the iOS 5 network behavior.
-    &quot;&quot;&quot;
-
-    _client_type = &quot;iOS 5&quot;
-
-    USER_AGENT = &quot;iOS/5.1 (9B179) dataaccessd/1.0&quot;
-
-    # The default interval, used if none is specified in external
-    # configuration.  This is also the actual value used by Snow
-    # Leopard iCal.
-    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
-
-    # The maximum number of resources to retrieve in a single multiget
-    MULTIGET_BATCH_SIZE = 50
-
-    # Override and turn on if client supports Sync REPORT
-    _SYNC_REPORT = False
-
-    # Override and turn on if client syncs using time-range queries
-    _SYNC_TIMERANGE = True
-
-    # Override and turn off if client does not support attendee lookups
-    _ATTENDEE_LOOKUPS = False
-
-    # Request body data
-    _LOAD_PATH = &quot;iOS_5&quot;
-
-    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
-    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
-    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
-    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
-    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
-    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
-
-    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
-    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
-    _POLL_CALENDAR_VEVENT_TR_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vevent_tr_query')
-    _POLL_CALENDAR_VTODO_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vtodo_query')
-    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
-    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
-    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
-
-
-    def _addDefaultHeaders(self, headers):
</del><ins>+    def addCalendar(self, href, calendar_xml):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Add the clients default set of headers to ones being used in a request.
-        Default is to add User-Agent, sub-classes should override to add other
-        client specific things, Accept etc.
</del><ins>+        client.addCalendar(
+            '/calendars/__uids__/10000000-0000-0000-0000-000000000001/1C1A8475-2671-4B97-AC58-DD9777B5CD93/',
+            # &lt;Component: 'BEGIN:VCALENDAR\r\n...END:VCALENDAR\r\n'&gt;)
+        )
</ins><span class="cx">         &quot;&quot;&quot;
</span><ins>+        response = yield self.requester.mkcalendar(
+            href,
+            calendar_xml,
+            method_label=&quot;MK{calendar}&quot;,
+        )
+        # self._cacheCalendar(href, calendar)
</ins><span class="cx"> 
</span><del>-        super(iOS_5, self)._addDefaultHeaders(headers)
-        headers.setRawHeaders('Accept', ['*/*'])
-        headers.setRawHeaders('Accept-Language', ['en-us'])
-        headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
-        headers.setRawHeaders('Connection', ['keep-alive'])
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def _pollFirstTime1(self, homeNode, calendars):
-        # Patch calendar properties
-        for cal in calendars:
-            if cal.name != &quot;inbox&quot;:
-                yield self._proppatch(
-                    cal.url,
-                    self._STARTUP_PROPPATCH_CALENDAR_COLOR,
-                    method_label=&quot;PROPPATCH{calendar}&quot;,
-                )
-                yield self._proppatch(
-                    cal.url,
-                    self._STARTUP_PROPPATCH_CALENDAR_ORDER,
-                    method_label=&quot;PROPPATCH{calendar}&quot;,
-                )
</del><ins>+    def changeCalendar(self, href, body):
</ins><span class="cx"> 
</span><ins>+        calendar = self._calendars[href]
+        headers = Headers({
+            'content-type': ['text/xml']
+        })
</ins><span class="cx"> 
</span><del>-    def _pollFirstTime2(self):
-        # Nothing here
-        return succeed(None)
</del><ins>+        # At last, upload the new event definition
+        response = yield self.requester.proppatch(
+            href,
+            body,
+            headers=headers,
+            method_label=&quot;PATCH{calendar}&quot;
+        )
</ins><span class="cx"> 
</span><ins>+        # Finally, re-retrieve the event to update the etag
+        # yield self._updateEvent(response, href)
</ins><span class="cx"> 
</span><del>-    def _updateCalendar(self, calendar, newToken):
-        &quot;&quot;&quot;
-        Update the local cached data for a calendar in an appropriate manner.
-        &quot;&quot;&quot;
-        if calendar.name == &quot;inbox&quot;:
-            # Inbox is done as a PROPFIND Depth:1
-            return self._updateCalendar_PROPFIND(calendar, newToken)
-        elif &quot;VEVENT&quot; in calendar.componentTypes:
-            # VEVENTs done as time-range VEVENT-only queries
-            return self._updateCalendar_VEVENT(calendar, newToken)
-        elif &quot;VTODO&quot; in calendar.componentTypes:
-            # VTODOs done as VTODO-only queries
-            return self._updateCalendar_VTODO(calendar, newToken)
-
-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def _updateCalendar_VEVENT(self, calendar, newToken):
-        &quot;&quot;&quot;
-        Sync all locally cached VEVENTs using a VEVENT-only time-range query.
-        &quot;&quot;&quot;
-
-        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
-        # the sim can fire a PUT between the PROPFIND and when process the removals.
-        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
-
-        now = DateTime.getNowUTC()
-        now.setDateOnly(True)
-        now.offsetMonth(-1) # 1 month back default
-        result = yield self._report(
-            calendar.url,
-            self._POLL_CALENDAR_VEVENT_TR_QUERY % {&quot;start-date&quot;: now.getText()},
-            depth='1',
-            method_label=&quot;REPORT{vevent}&quot;,
</del><ins>+    def postXML(self, href, xml):
+        headers = Headers({
+            'content-type': ['text/xml']
+        })
+        response = yield self.requester.post(
+            href,
+            xml,
+            headers=headers,
+            method_label=&quot;SHARE{calendar}&quot;
</ins><span class="cx">         )
</span><span class="cx"> 
</span><del>-        yield self._updateApplyChanges(calendar, result, old_hrefs)
</del><span class="cx"> 
</span><del>-        # Now update calendar to the new token
-        self._calendars[calendar.url].changeToken = newToken
-
-
</del><span class="cx">     @inlineCallbacks
</span><del>-    def _updateCalendar_VTODO(self, calendar, newToken):
</del><ins>+    def deleteCalendar(self, href):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Sync all locally cached VTODOs using a VTODO-only query.
</del><ins>+        Issue a DELETE for the given URL and remove local state
+        associated with that calendar.
+
+        Usage: client.deleteCalendar('/calendars/__uids__/&lt;user-uid&gt;/&lt;calendar-uid&gt;/')
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
-        # the sim can fire a PUT between the PROPFIND and when process the removals.
-        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
</del><ins>+        self._invalidateCalendar(href)
</ins><span class="cx"> 
</span><del>-        result = yield self._report(
-            calendar.url,
-            self._POLL_CALENDAR_VTODO_QUERY,
-            depth='1',
-            method_label=&quot;REPORT{vtodo}&quot;,
-        )
</del><ins>+        response = yield self.requester.delete(href, method_label=&quot;DELETE{calendar}&quot;)
+        returnValue(response)
</ins><span class="cx"> 
</span><del>-        yield self._updateApplyChanges(calendar, result, old_hrefs)
</del><span class="cx"> 
</span><del>-        # Now update calendar to the new token
-        self._calendars[calendar.url].changeToken = newToken
</del><span class="cx"> 
</span><del>-
-    @inlineCallbacks
-    def startup(self):
-
-        # Try to read data from disk - if it succeeds self.principalURL will be set
-        self.deserialize()
-
-        if self.principalURL is None:
-            # PROPFIND well-known with redirect
-            response = yield self._startupPropfindWellKnown()
-            hrefs = response.getHrefProperties()
-            if davxml.current_user_principal in hrefs:
-                self.principalURL = hrefs[davxml.current_user_principal].toString()
-            elif davxml.principal_URL in hrefs:
-                self.principalURL = hrefs[davxml.principal_URL].toString()
-            else:
-                # PROPFIND principal path to retrieve actual principal-URL
-                response = yield self._principalPropfindInitial(self.record.uid)
-                hrefs = response.getHrefProperties()
-                self.principalURL = hrefs[davxml.principal_URL].toString()
-
-        # Using the actual principal URL, retrieve principal information
-        principal = yield self._extractPrincipalDetails()
-        returnValue(principal)
-
-
-
-class RequestLogger(object):
-    format = u&quot;%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s&quot;
-    success = u&quot;\N{CHECK MARK}&quot;
-    failure = u&quot;\N{BALLOT X}&quot;
-
-    def observe(self, event):
-        if event.get(&quot;type&quot;) == &quot;response&quot;:
-            formatArgs = dict(
-                user=event['user'],
-                method=event['method'],
-                url=urlunparse(('', '') + urlparse(event['url'])[2:]),
-                code=event['code'],
-                duration=event['duration'],
-            )
-
-            if event['success']:
-                formatArgs['success'] = self.success
-            else:
-                formatArgs['success'] = self.failure
-            print((self.format % formatArgs).encode('utf-8'))
-
-
-    def report(self, output):
-        pass
-
-
-    def failures(self):
-        return []
-
-
-
</del><span class="cx"> def main():
</span><span class="cx">     from urllib2 import HTTPDigestAuthHandler
</span><span class="cx">     from twisted.internet import reactor
</span><ins>+    from twisted.python.log import addObserver
+    from contrib.performance.loadtest.logger import RequestLogger
+    from contrib.performance.loadtest.clients import OS_X_10_11
</ins><span class="cx">     auth = HTTPDigestAuthHandler()
</span><span class="cx">     auth.add_password(
</span><span class="cx">         realm=&quot;Test Realm&quot;,
</span><span class="lines">@@ -2083,14 +1424,17 @@
</span><span class="cx"> 
</span><span class="cx">     addObserver(RequestLogger().observe)
</span><span class="cx"> 
</span><del>-    from sim import _DirectoryRecord
-    client = OS_X_10_6(
-        reactor, 'http://127.0.0.1:8008/',
-        _DirectoryRecord(
-            u'user01', u'user01', u'User 01', u'user01@example.org'),
-        auth)
</del><ins>+    from contrib.performance.loadtest.records import DirectoryRecord
+    client = OS_X_10_11(
+        reactor,
+        'http://127.0.0.1:8008/',   # root
+        '/tmp/sim',                 # serializePath
+        DirectoryRecord(u'user01', u'user01', u'User 01', u'user01@example.org', u'10000000-0000-0000-0000-000000000001'),
+        auth,
+        title='OS X 10.11 Client Simulator'
+    )
</ins><span class="cx">     d = client.run()
</span><del>-    d.addErrback(err, &quot;10.6 client run() problem&quot;)
</del><ins>+    d.addErrback(err, &quot;10.11 client run() problem&quot;)
</ins><span class="cx">     d.addCallback(lambda ignored: reactor.stop())
</span><span class="cx">     reactor.run()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestloggerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/logger.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/logger.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/logger.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -14,9 +14,27 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> #
</span><span class="cx"> ##
</span><ins>+import json
+import collections
+import os
+import sys
+from datetime import datetime
</ins><span class="cx"> 
</span><del>-from contrib.performance.stats import mean, median, stddev
</del><ins>+from urlparse import urlparse, urlunparse
</ins><span class="cx"> 
</span><ins>+from contrib.performance.stats import mean, median, stddev, mad
+
+class TerminalColors:
+    HEADER = '\033[95m'
+    OKBLUE = '\033[94m'
+    OKGREEN = '\033[92m'
+    WARNING = '\033[93m'
+    LIGHTBLUE = '\033[36m'
+    FAIL = '\033[91m'
+    ENDC = '\033[0m'
+    BOLD = '\033[1m'
+    UNDERLINE = '\033[4m'
+
</ins><span class="cx"> class SummarizingMixin(object):
</span><span class="cx"> 
</span><span class="cx">     def printHeader(self, output, fields):
</span><span class="lines">@@ -87,3 +105,496 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for method, data in perOperationTimes:
</span><span class="cx">             self._printRow(output, formats, self._summarizeData(method, data))
</span><ins>+
+
+class MessageLogger(object):
+    def observe(self, event):
+        if event.get(&quot;type&quot;) == &quot;log&quot;:
+            import random
+            identifier = random.random()
+            print(TerminalColors.WARNING + str(identifier) + '/' + event.get('val') + ':' + event.get('text') + TerminalColors.ENDC)
+
+    def report(self, output):
+        pass
+
+
+    def failures(self):
+        return []
+
+
+class EverythingLogger(object):
+    def observe(self, event):
+        # if event.get(&quot;type&quot;) == &quot;response&quot;:
+        #     from pprint import pprint
+        #     pprint(event)
+        pass
+
+    def report(self, output):
+        pass
+
+
+    def failures(self):
+        return []
+
+
+class RequestLogger(object):
+    format = u&quot;%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s&quot;
+    success = u&quot;\N{CHECK MARK}&quot;
+    failure = u&quot;\N{BALLOT X}&quot;
+
+    def observe(self, event):
+        if event.get(&quot;type&quot;) == &quot;response&quot;:
+            formatArgs = dict(
+                user=event['user'],
+                method=event['method'],
+                url=urlunparse(('', '') + urlparse(event['url'])[2:]),
+                code=event['code'],
+                duration=event['duration'],
+            )
+
+            if event['success']:
+                formatArgs['success'] = self.success
+                start = TerminalColors.OKGREEN
+            else:
+                formatArgs['success'] = self.failure
+                start = TerminalColors.FAIL
+            print(start + (self.format % formatArgs).encode('utf-8') + &quot;from Logger w/ id: &quot; + str(id(self)) + TerminalColors.ENDC)
+
+
+    def report(self, output):
+        pass
+
+
+    def failures(self):
+        return []
+
+
+
+class OperationLogger(SummarizingMixin):
+    &quot;&quot;&quot;
+    Profiles will initiate operations which may span multiple requests.  Start
+    and stop log messages are emitted for these operations and logged by this
+    logger.
+    &quot;&quot;&quot;
+    formats = {
+        u&quot;start&quot; : u&quot;%(user)s - - - - - - - - - - - %(label)8s BEGIN %(lag)s&quot;,
+        u&quot;end&quot;   : u&quot;%(user)s - - - - - - - - - - - %(label)8s END [%(duration)5.2f s]&quot;,
+        u&quot;failed&quot;: u&quot;%(user)s x x x x x x x x x x x %(label)8s FAILED %(reason)s&quot;,
+    }
+
+    lagFormat = u'{lag %5.2f ms}'
+
+    # the response time thresholds to display together with failing % count threshold
+    _thresholds_default = {
+        &quot;operations&quot;: {
+            &quot;limits&quot;: [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
+            &quot;thresholds&quot;: {
+                &quot;default&quot;: [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0],
+            }
+        }
+    }
+    _lag_cut_off = 1.0      # Maximum allowed median scheduling latency, seconds
+    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure
+
+    _fields_init = [
+        ('operation', -25, '%-25s'),
+        ('count', 8, '%8s'),
+        ('failed', 8, '%8s'),
+    ]
+
+    _fields_extend = [
+        ('mean', 8, '%8.4f'),
+        ('median', 8, '%8.4f'),
+        ('stddev', 8, '%8.4f'),
+        ('avglag (ms)', 12, '%12.4f'),
+        ('STATUS', 8, '%8s'),
+    ]
+
+    def __init__(self, outfile=None, **params):
+        self._perOperationTimes = {}
+        self._perOperationLags = {}
+        if outfile is None:
+            outfile = sys.stdout
+        self._outfile = outfile
+
+        # Load parameters from config
+        if &quot;thresholdsPath&quot; in params:
+            jsondata = json.load(open(params[&quot;thresholdsPath&quot;]))
+        elif &quot;thresholds&quot; in params:
+            jsondata = params[&quot;thresholds&quot;]
+        else:
+            jsondata = self._thresholds_default
+        self._thresholds = [[limit, {}] for limit in jsondata[&quot;operations&quot;][&quot;limits&quot;]]
+        for ctr, item in enumerate(self._thresholds):
+            for k, v in jsondata[&quot;operations&quot;][&quot;thresholds&quot;].items():
+                item[1][k] = v[ctr]
+
+        self._fields = self._fields_init[:]
+        for threshold, _ignore_fail_at in self._thresholds:
+            self._fields.append(('&gt;%g sec' % (threshold,), 10, '%10s'))
+        self._fields.extend(self._fields_extend)
+
+        if &quot;lagCutoff&quot; in params:
+            self._lag_cut_off = params[&quot;lagCutoff&quot;]
+
+        if &quot;failCutoff&quot; in params:
+            self._fail_cut_off = params[&quot;failCutoff&quot;]
+
+
+    def observe(self, event):
+        if event.get(&quot;type&quot;) == &quot;operation&quot;:
+            event = event.copy()
+            lag = event.get('lag')
+            if lag is None:
+                event['lag'] = ''
+            else:
+                event['lag'] = self.lagFormat % (lag * 1000.0,)
+
+            self._outfile.write(
+                TerminalColors.LIGHTBLUE +
+                (self.formats[event[u'phase']] % event).encode('utf-8') + TerminalColors.ENDC + '\n')
+
+            if event[u'phase'] == u'end':
+                dataset = self._perOperationTimes.setdefault(event[u'label'], [])
+                dataset.append((event[u'success'], event[u'duration']))
+            elif lag is not None:
+                dataset = self._perOperationLags.setdefault(event[u'label'], [])
+                dataset.append(lag)
+
+
+    def _summarizeData(self, operation, data):
+        avglag = mean(self._perOperationLags.get(operation, [0.0])) * 1000.0
+        data = SummarizingMixin._summarizeData(self, operation, data)
+        return data[:-1] + (avglag,) + data[-1:]
+
+
+    def report(self, output):
+        output.write(&quot;\n&quot;)
+        self.printHeader(output, [
+            (label, width)
+            for (label, width, _ignore_fmt) in self._fields
+        ])
+        self.printData(
+            output,
+            [fmt for (label, width, fmt) in self._fields],
+            sorted(self._perOperationTimes.items())
+        )
+
+    _LATENCY_REASON = &quot;Median %(operation)s scheduling lag greater than %(cutoff)sms&quot;
+    _FAILED_REASON = &quot;Greater than %(cutoff).0f%% %(operation)s failed&quot;
+
+    def failures(self):
+        reasons = []
+
+        for operation, lags in self._perOperationLags.iteritems():
+            if median(lags) &gt; self._lag_cut_off:
+                reasons.append(self._LATENCY_REASON % dict(
+                    operation=operation.upper(), cutoff=self._lag_cut_off * 1000))
+
+        for operation, times in self._perOperationTimes.iteritems():
+            failures = len([success for (success, _ignore_duration) in times if not success])
+            if failures * 100.0 / len(times) &gt; self._fail_cut_off:
+                reasons.append(self._FAILED_REASON % dict(
+                    operation=operation.upper(), cutoff=self._fail_cut_off))
+
+        return reasons
+
+
+
+class StatisticsBase(object):
+    def observe(self, event):
+        if event.get('type') == 'response':
+            self.eventReceived(event)
+        elif event.get('type') == 'client-failure':
+            self.clientFailure(event)
+        elif event.get('type') == 'sim-failure':
+            self.simFailure(event)
+
+
+    def report(self, output):
+        pass
+
+
+    def failures(self):
+        return []
+
+
+
+class SimpleStatistics(StatisticsBase):
+    def __init__(self):
+        self._times = []
+        self._failures = collections.defaultdict(int)
+        self._simFailures = collections.defaultdict(int)
+
+
+    def eventReceived(self, event):
+        self._times.append(event['duration'])
+        if len(self._times) == 200:
+            print('mean:', mean(self._times))
+            print('median:', median(self._times))
+            print('stddev:', stddev(self._times))
+            print('mad:', mad(self._times))
+            del self._times[:100]
+
+
+    def clientFailure(self, event):
+        self._failures[event] += 1
+
+
+    def simFailure(self, event):
+        self._simFailures[event] += 1
+
+
+
+class ReportStatistics(StatisticsBase, SummarizingMixin):
+    &quot;&quot;&quot;
+
+    @ivar _users: A C{set} containing all user UIDs which have been observed in
+        events.  When generating the final report, the size of this set is
+        reported as the number of users in the simulation.
+
+    &quot;&quot;&quot;
+
+    # the response time thresholds to display together with failing % count threshold
+    _thresholds_default = {
+        &quot;requests&quot;: {
+            &quot;limits&quot;: [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
+            &quot;thresholds&quot;: {
+                &quot;default&quot;: [100.0, 100.0, 100.0, 5.0, 1.0, 0.5, 0.0],
+            }
+        }
+    }
+    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure
+
+    _fields_init = [
+        ('request', -25, '%-25s'),
+        ('count', 8, '%8s'),
+        ('failed', 8, '%8s'),
+    ]
+
+    _fields_extend = [
+        ('mean', 8, '%8.4f'),
+        ('median', 8, '%8.4f'),
+        ('stddev', 8, '%8.4f'),
+        ('QoS', 8, '%8.4f'),
+        ('STATUS', 8, '%8s'),
+    ]
+
+    def __init__(self, **params):
+        self._perMethodTimes = {}
+        self._users = set()
+        self._clients = set()
+        self._failed_clients = []
+        self._failed_sim = collections.defaultdict(int)
+        self._startTime = datetime.now()
+        self._expired_data = None
+
+        # Load parameters from config
+        if &quot;thresholdsPath&quot; in params:
+            jsondata = json.load(open(params[&quot;thresholdsPath&quot;]))
+        elif &quot;thresholds&quot; in params:
+            jsondata = params[&quot;thresholds&quot;]
+        else:
+            jsondata = self._thresholds_default
+        self._thresholds = [[limit, {}] for limit in jsondata[&quot;requests&quot;][&quot;limits&quot;]]
+        for ctr, item in enumerate(self._thresholds):
+            for k, v in jsondata[&quot;requests&quot;][&quot;thresholds&quot;].items():
+                item[1][k] = v[ctr]
+
+        self._fields = self._fields_init[:]
+        for threshold, _ignore_fail_at in self._thresholds:
+            self._fields.append(('&gt;%g sec' % (threshold,), 10, '%10s'))
+        self._fields.extend(self._fields_extend)
+
+        if &quot;benchmarksPath&quot; in params:
+            self.benchmarks = json.load(open(params[&quot;benchmarksPath&quot;]))
+        else:
+            self.benchmarks = {}
+
+        if &quot;failCutoff&quot; in params:
+            self._fail_cut_off = params[&quot;failCutoff&quot;]
+
+
+    def observe(self, event):
+        if event.get('type') == 'sim-expired':
+            self.simExpired(event)
+        else:
+            super(ReportStatistics, self).observe(event)
+
+
+    def countUsers(self):
+        return len(self._users)
+
+
+    def countClients(self):
+        return len(self._clients)
+
+
+    def countClientFailures(self):
+        return len(self._failed_clients)
+
+
+    def countSimFailures(self):
+        return len(self._failed_sim)
+
+
+    def eventReceived(self, event):
+        dataset = self._perMethodTimes.setdefault(event['method'], [])
+        dataset.append((event['success'], event['duration']))
+        self._users.add(event['user'])
+        self._clients.add(event['client_id'])
+
+
+    def clientFailure(self, event):
+        self._failed_clients.append(event['reason'])
+
+
+    def simFailure(self, event):
+        self._failed_sim[event['reason']] += 1
+
+
+    def simExpired(self, event):
+        self._expired_data = event['reason']
+
+
+    def printMiscellaneous(self, output, items):
+        maxColumnWidth = str(len(max(items.iterkeys(), key=len)))
+        fmt = &quot;%&quot; + maxColumnWidth + &quot;s : %-s\n&quot;
+        for k in sorted(items.iterkeys()):
+            output.write(fmt % (k.title(), items[k],))
+
+
+    def qos(self):
+        &quot;&quot;&quot;
+        Determine a &quot;quality of service&quot; value that can be used for comparisons between runs. This value
+        is based on the percentage deviation of means of each request from a set of &quot;benchmarks&quot; for each
+        type of request.
+        &quot;&quot;&quot;
+
+        # Get means for each type of method
+        means = {}
+        for method, results in self._perMethodTimes.items():
+            means[method] = mean([duration for success, duration in results if success])
+
+        # Determine percentage differences with weighting
+        differences = []
+        for method, value in means.items():
+            result = self.qos_value(method, value)
+            if result is not None:
+                differences.append(result)
+
+        return (&quot;%-8.4f&quot; % mean(differences)) if differences else &quot;None&quot;
+
+
+    def qos_value(self, method, value):
+        benchmark = self.benchmarks.get(method)
+        if benchmark is None:
+            return None
+        test_mean, weight = (benchmark[&quot;mean&quot;], benchmark[&quot;weight&quot;],)
+        return ((value / test_mean) - 1.0) * weight + 1.0
+
+
+    def _summarizeData(self, operation, data):
+        data = SummarizingMixin._summarizeData(self, operation, data)
+        value = self.qos_value(operation, data[-4])
+        if value is None:
+            value = 0.0
+        return data[:-1] + (value,) + data[-1:]
+
+
+    def report(self, output):
+        output.write(&quot;\n&quot;)
+        output.write(&quot;** REPORT **\n&quot;)
+        output.write(&quot;\n&quot;)
+        runtime = datetime.now() - self._startTime
+        cpu = os.times()
+        cpuUser = cpu[0] + cpu[2]
+        cpuSys = cpu[1] + cpu[3]
+        cpuTotal = cpuUser + cpuSys
+        runHours, remainder = divmod(runtime.seconds, 3600)
+        runMinutes, runSeconds = divmod(remainder, 60)
+        cpuHours, remainder = divmod(cpuTotal, 3600)
+        cpuMinutes, cpuSeconds = divmod(remainder, 60)
+        items = {
+            'Users': self.countUsers(),
+            'Clients': self.countClients(),
+            'Start time': self._startTime.strftime('%m/%d %H:%M:%S'),
+            'Run time': &quot;%02d:%02d:%02d&quot; % (runHours, runMinutes, runSeconds),
+            'CPU Time': &quot;user %-5.2f sys %-5.2f total %02d:%02d:%02d&quot; % (cpuUser, cpuSys, cpuHours, cpuMinutes, cpuSeconds,),
+            'QoS': self.qos(),
+        }
+        if self.countClientFailures() &gt; 0:
+            items['Failed clients'] = self.countClientFailures()
+            for ctr, reason in enumerate(self._failed_clients, 1):
+                items['Failure #%d' % (ctr,)] = reason
+        if self.countSimFailures() &gt; 0:
+            for reason, count in self._failed_sim.items():
+                items['Failed operation'] = &quot;%s : %d times&quot; % (reason, count,)
+        output.write(&quot;* Client\n&quot;)
+        self.printMiscellaneous(output, items)
+        output.write(&quot;\n&quot;)
+
+        if self._expired_data is not None:
+            items = {
+                &quot;Req/sec&quot; : &quot;%.1f&quot; % (self._expired_data[0],),
+                &quot;Response&quot;: &quot;%.1f (ms)&quot; % (self._expired_data[1],),
+                &quot;Slots&quot;: &quot;%.2f&quot; % (self._expired_data[2],),
+                &quot;CPU&quot;: &quot;%.1f%%&quot; % (self._expired_data[3],),
+            }
+            output.write(&quot;* Server (Last 5 minutes)\n&quot;)
+            self.printMiscellaneous(output, items)
+            output.write(&quot;\n&quot;)
+        output.write(&quot;* Details\n&quot;)
+
+        self.printHeader(output, [
+            (label, width)
+            for (label, width, _ignore_fmt)
+            in self._fields
+        ])
+        self.printData(
+            output,
+            [fmt for (label, width, fmt) in self._fields],
+            sorted(self._perMethodTimes.items())
+        )
+
+    _FAILED_REASON = &quot;Greater than %(cutoff)g%% %(method)s failed&quot;
+
+    _REASON_1 = &quot;Greater than %(cutoff)g%% %(method)s exceeded &quot;
+    _REASON_2 = &quot;%g second response time&quot;
+
+    def failures(self):
+        # TODO
+        reasons = []
+
+        for (method, times) in self._perMethodTimes.iteritems():
+            failures = 0
+            overDurations = [0] * len(self._thresholds)
+
+            for success, duration in times:
+                if not success:
+                    failures += 1
+                for ctr, item in enumerate(self._thresholds):
+                    threshold, _ignore_fail_at = item
+                    if duration &gt; threshold:
+                        overDurations[ctr] += 1
+
+            checks = [
+                (failures, self._fail_cut_off, self._FAILED_REASON),
+            ]
+
+            for ctr, item in enumerate(self._thresholds):
+                threshold, fail_at = item
+                fail_at = fail_at.get(method, fail_at[&quot;default&quot;])
+                checks.append(
+                    (overDurations[ctr], fail_at, self._REASON_1 + self._REASON_2 % (threshold,))
+                )
+
+            for count, cutoff, reason in checks:
+                if count * 100.0 / len(times) &gt; cutoff:
+                    reasons.append(reason % dict(method=method, cutoff=cutoff))
+
+        if self.countClientFailures() != 0:
+            reasons.append(&quot;Client failures: %d&quot; % (self.countClientFailures(),))
+        if self.countSimFailures() != 0:
+            reasons.append(&quot;Overall failures: %d&quot; % (self.countSimFailures(),))
+        return reasons
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestpopulationpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/population.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/population.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/population.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -25,13 +25,10 @@
</span><span class="cx"> 
</span><span class="cx"> from tempfile import mkdtemp
</span><span class="cx"> from itertools import izip
</span><del>-from datetime import datetime
</del><ins>+
</ins><span class="cx"> from urllib2 import HTTPBasicAuthHandler
</span><span class="cx"> from urllib2 import HTTPDigestAuthHandler
</span><span class="cx"> from urllib2 import HTTPPasswordMgrWithDefaultRealm
</span><del>-import collections
-import json
-import os
</del><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import DeferredList
</span><span class="cx"> from twisted.python.failure import Failure
</span><span class="lines">@@ -41,54 +38,59 @@
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.timezones import TimezoneCache
</span><span class="cx"> 
</span><del>-from contrib.performance.stats import mean, median, stddev, mad
</del><span class="cx"> from contrib.performance.loadtest.trafficlogger import loggedReactor
</span><del>-from contrib.performance.loadtest.logger import SummarizingMixin
-from contrib.performance.loadtest.ical import OS_X_10_6, RequestLogger
</del><ins>+
</ins><span class="cx"> from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class ProfileType(object, FancyEqMixin):
-    &quot;&quot;&quot;
-    @ivar profileType: A L{ProfileBase} subclass, or an L{ICalendarUserProfile}
-        implementation.
</del><ins>+# class ProfileType(object, FancyEqMixin):
+#     &quot;&quot;&quot;
+#     @ivar profileType: A L{ProfileBase} subclass
+#     @type profileType: C{type}
</ins><span class="cx"> 
</span><del>-    @ivar params: A C{dict} which will be passed to C{profileType} as keyword
-        arguments to create a new profile instance.
-    &quot;&quot;&quot;
-    compareAttributes = (&quot;profileType&quot;, &quot;params&quot;)
</del><ins>+#     @ivar params: A C{dict} which will be passed to C{profileType} as keyword
+#         arguments to create a new profile instance.
+#     &quot;&quot;&quot;
+#     compareAttributes = (&quot;profileType&quot;, &quot;params&quot;)
</ins><span class="cx"> 
</span><del>-    def __init__(self, profileType, params):
-        self.profileType = profileType
-        self.params = params
</del><ins>+#     def __init__(self, profileType, params):
+#         self.profileType = profileType
+#         self.params = params
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __call__(self, reactor, simulator, client, number):
-        return self.profileType(reactor, simulator, client, number, **self.params)
</del><ins>+#     def __call__(self, reactor, simulator, client, number):
+#         base = self.profileType(**self.params)
+#         base.setUp(reactor, simulator, client, number)
+#         return base
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+#     def __repr__(self):
+#         return &quot;ProfileType(%s, params=%s)&quot; % (self.profileType.__name__, self.params)
</ins><span class="cx"> 
</span><del>-class ClientType(object, FancyEqMixin):
</del><ins>+
+
+class ClientFactory(object, FancyEqMixin):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    @ivar clientType: An L{ICalendarClient} implementation
</del><ins>+    @ivar clientType: An L{BaseAppleClient} subclass
+    @ivar params: A C{dict} which will be passed to C{clientType} as keyword
+        arguements to create a new client
</ins><span class="cx">     @ivar profileTypes: A list of L{ProfileType} instances
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     compareAttributes = (&quot;clientType&quot;, &quot;profileTypes&quot;)
</span><span class="cx"> 
</span><del>-    def __init__(self, clientType, clientParams, profileTypes):
</del><ins>+    def __init__(self, clientType, clientParams, profiles):
</ins><span class="cx">         self.clientType = clientType
</span><span class="cx">         self.clientParams = clientParams
</span><del>-        self.profileTypes = profileTypes
</del><ins>+        self.profiles = profiles
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def new(self, reactor, serverAddress, principalPathTemplate, serializationPath, userRecord, authInfo):
</del><ins>+    def new(self, reactor, serverAddress, serializationPath, userRecord, authInfo):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a new instance of this client type.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self.clientType(
</span><del>-            reactor, serverAddress, principalPathTemplate,
-            serializationPath, userRecord, authInfo,
-            **self.clientParams
</del><ins>+            reactor, serverAddress, serializationPath,
+            userRecord, authInfo, **self.clientParams
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -118,66 +120,74 @@
</span><span class="cx">         self.clients.append((weight, clientType))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def clientTypes(self):
-        &quot;&quot;&quot;
-        Return a list of two-tuples giving the weights and types of
-        clients in the population.
-        &quot;&quot;&quot;
-        return self.clients
</del><ins>+    def clientGenerator(self):
+        while True:
+            for (weight, clientFactory) in self.clients:
+                for _ignore_i in xrange(weight):
+                    yield clientFactory
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    # def clientTypes(self):
+    #     &quot;&quot;&quot;
+    #     Return a list of two-tuples giving the weights and types of
+    #     clients in the population.
+    #     &quot;&quot;&quot;
+    #     return self.clients
</ins><span class="cx"> 
</span><del>-class Populator(object):
-    &quot;&quot;&quot;
-    @ivar userPattern: A C{str} giving a formatting pattern to use to
-        construct usernames.  The string will be interpolated with a
-        single integer, the incrementing counter of how many users
-        have thus far been &quot;used&quot;.
</del><span class="cx"> 
</span><del>-    @ivar passwordPattern: Similar to C{userPattern}, but for
-        passwords.
-    &quot;&quot;&quot;
-    def __init__(self, random):
-        self._random = random
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _cycle(self, elements):
-        while True:
-            for (weight, value) in elements:
-                for _ignore_i in range(weight):
-                    yield value
</del><span class="cx"> 
</span><ins>+# class Populator(object):
+#     &quot;&quot;&quot;
+#     &quot;&quot;&quot;
+#     def __init__(self):
+#         self._random = random
</ins><span class="cx"> 
</span><del>-    def populate(self, parameters):
-        &quot;&quot;&quot;
-        Generate individuals such as might be randomly selected from a
-        population with the given parameters.
</del><span class="cx"> 
</span><del>-        @type parameters: L{PopulationParameters}
-        @rtype: generator of L{ClientType} instances
-        &quot;&quot;&quot;
-        for (clientType,) in izip(self._cycle(parameters.clientTypes())):
-            yield clientType
</del><ins>+#     def _cycle(self, elements):
+#         while True:
+#             for (weight, value) in elements:
+#                 for _ignore_i in range(weight):
+#                     yield value
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+#     def populate(self, parameters):
+#         &quot;&quot;&quot;
+#         Generate individuals such as might be randomly selected from a
+#         population with the given parameters.
</ins><span class="cx"> 
</span><ins>+#         @type parameters: L{PopulationParameters}
+#         @rtype: generator of L{ClientType} instances
+#         &quot;&quot;&quot;
+#         for (clientType,) in izip(self._cycle(parameters.clientTypes())):
+#             yield clientType
+
+
+
</ins><span class="cx"> class CalendarClientSimulator(object):
</span><del>-    def __init__(self, records, populator, parameters, reactor, server,
-                 principalPathTemplate, serializationPath, workerIndex=0, workerCount=1):
</del><ins>+    def __init__(self, records, parameters, reactor, server,
+                 serializationPath, workerIndex=0, workerCount=1):
+        import random
+        # i = random.randint(0, 1000)
+        # with open('log%d.txt'.format(i), 'a') as f:
+        #     f.write('wtf')
+        val = random.random()
+        msg(type=&quot;log&quot;, text=&quot;In create client sim&quot;, val=str(val))
+        # from pprint import pprint
+        # pprint(records)
</ins><span class="cx">         self._records = records
</span><del>-        self.populator = populator
</del><span class="cx">         self.reactor = reactor
</span><span class="cx">         self.server = server
</span><del>-        self.principalPathTemplate = principalPathTemplate
</del><span class="cx">         self.serializationPath = serializationPath
</span><del>-        self._pop = self.populator.populate(parameters)
</del><ins>+        self._populator = parameters.clientGenerator()
</ins><span class="cx">         self._user = 0
</span><span class="cx">         self._stopped = False
</span><span class="cx">         self.workerIndex = workerIndex
</span><span class="cx">         self.workerCount = workerCount
</span><span class="cx">         self.clients = []
</span><span class="cx"> 
</span><del>-        TimezoneCache.create()
</del><ins>+        # TimezoneCache.create()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getUserRecord(self, index):
</span><span class="lines">@@ -225,47 +235,71 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def add(self, numClients, clientsPerUser):
</span><del>-        for _ignore_n in range(numClients):
-            number = self._nextUserNumber()
</del><ins>+        # for _ignore_n in range(numClients):
+        #     number = self._nextUserNumber()
</ins><span class="cx"> 
</span><del>-            for _ignore_peruser in range(clientsPerUser):
-                clientType = self._pop.next()
</del><ins>+        #     for _ignore_peruser in range(clientsPerUser):
+        #         clientType = self._populator.next()
+        #         if (number % self.workerCount) != self.workerIndex:
+        #             # If we're in a distributed work scenario and we are worker N,
+        #             # we have to skip all but every Nth request (since every node
+        #             # runs the same arrival policy).
+        #             continue
+
+        #         _ignore_user, auth = self._createUser(number)
+
+        #         reactor = loggedReactor(self.reactor)
+        #         client = clientType.new(
+        #             reactor,
+        #             self.server,
+        #             self.serializationPath,
+        #             self.getUserRecord(number),
+        #             auth,
+        #         )
+        #         self.clients.append(client)
+        #         d = client.run()
+        #         d.addErrback(self._clientFailure, reactor)
+
+        #         for profileType in clientType.profileTypes:
+        #             print(profileType)
+        #             profile = profileType(reactor, self, client, number)
+        #             if profile.enabled:
+        #                 d = profile.initialize()
+        #                 def _run(result):
+        #                     d2 = profile.run()
+        #                     d2.addErrback(self._profileFailure, profileType, reactor)
+        #                     return d2
+        #                 d.addCallback(_run)
+
+        for i in range(numClients):
+            number = self._nextUserNumber()
+            # What user are we representing?
+            for j in range(clientsPerUser):
</ins><span class="cx">                 if (number % self.workerCount) != self.workerIndex:
</span><span class="cx">                     # If we're in a distributed work scenario and we are worker N,
</span><span class="cx">                     # we have to skip all but every Nth request (since every node
</span><span class="cx">                     # runs the same arrival policy).
</span><span class="cx">                     continue
</span><ins>+                clientFactory = self._populator.next()
</ins><span class="cx"> 
</span><span class="cx">                 _ignore_user, auth = self._createUser(number)
</span><del>-
</del><span class="cx">                 reactor = loggedReactor(self.reactor)
</span><del>-                client = clientType.new(
-                    reactor,
</del><ins>+
+                client = clientFactory.new(
+                    self.reactor,
</ins><span class="cx">                     self.server,
</span><del>-                    self.principalPathTemplate,
</del><span class="cx">                     self.serializationPath,
</span><span class="cx">                     self.getUserRecord(number),
</span><del>-                    auth,
</del><ins>+                    auth
</ins><span class="cx">                 )
</span><span class="cx">                 self.clients.append(client)
</span><del>-                d = client.run()
-                d.addErrback(self._clientFailure, reactor)
</del><ins>+                client.run().addErrback(self._clientFailure, reactor)
+                for profileTemplate in clientFactory.profiles:
+                    profile = profileTemplate.duplicate()
+                    profile.setUp(self.reactor, self, client, number)
+                    profile.run().addErrback(self._profileFailure, reactor)
</ins><span class="cx"> 
</span><del>-                for profileType in clientType.profileTypes:
-                    profile = profileType(reactor, self, client, number)
-                    if profile.enabled:
-                        d = profile.initialize()
-                        def _run(result):
-                            d2 = profile.run()
-                            d2.addErrback(self._profileFailure, profileType, reactor)
-                            return d2
-                        d.addCallback(_run)
</del><span class="cx"> 
</span><del>-        # XXX this status message is prone to be slightly inaccurate, but isn't
-        # really used by much anyway.
-        msg(type=&quot;status&quot;, clientCount=self._user - 1)
-
-
</del><span class="cx">     def _dumpLogs(self, loggingReactor, reason):
</span><span class="cx">         path = FilePath(mkdtemp())
</span><span class="cx">         logstate = loggingReactor.getLogFiles()
</span><span class="lines">@@ -278,6 +312,13 @@
</span><span class="cx">         return path
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _profileFailure(self, reason, reactor):
+        if not self._stopped:
+            where = self._dumpLogs(reactor, reason)
+            err(reason, &quot;Profile stopped with error; recent traffic in %r&quot; % (
+                where.path,))
+
+
</ins><span class="cx">     def _clientFailure(self, reason, reactor):
</span><span class="cx">         if not self._stopped:
</span><span class="cx">             where = self._dumpLogs(reactor, reason)
</span><span class="lines">@@ -288,13 +329,6 @@
</span><span class="cx">             msg(type=&quot;client-failure&quot;, reason=&quot;%s: %s&quot; % (reason.type, reason.value,))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _profileFailure(self, reason, profileType, reactor):
-        if not self._stopped:
-            where = self._dumpLogs(reactor, reason)
-            err(reason, &quot;Profile stopped with error; recent traffic in %r&quot; % (
-                where.path,))
-
-
</del><span class="cx">     def _simFailure(self, reason, reactor):
</span><span class="cx">         if not self._stopped:
</span><span class="cx">             msg(type=&quot;sim-failure&quot;, reason=reason)
</span><span class="lines">@@ -302,321 +336,20 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class SmoothRampUp(object):
</span><del>-    def __init__(self, reactor, groups, groupSize, interval, clientsPerUser):
-        self.reactor = reactor
</del><ins>+    def __init__(self, groups, groupSize, interval, clientsPerUser):
</ins><span class="cx">         self.groups = groups
</span><span class="cx">         self.groupSize = groupSize
</span><span class="cx">         self.interval = interval
</span><span class="cx">         self.clientsPerUser = clientsPerUser
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def run(self, simulator):
</del><ins>+    def run(self, reactor, simulator):
</ins><span class="cx">         for i in range(self.groups):
</span><del>-            self.reactor.callLater(
</del><ins>+            reactor.callLater(
</ins><span class="cx">                 self.interval * i, simulator.add, self.groupSize, self.clientsPerUser)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class StatisticsBase(object):
-    def observe(self, event):
-        if event.get('type') == 'response':
-            self.eventReceived(event)
-        elif event.get('type') == 'client-failure':
-            self.clientFailure(event)
-        elif event.get('type') == 'sim-failure':
-            self.simFailure(event)
-
-
-    def report(self, output):
-        pass
-
-
-    def failures(self):
-        return []
-
-
-
-class SimpleStatistics(StatisticsBase):
-    def __init__(self):
-        self._times = []
-        self._failures = collections.defaultdict(int)
-        self._simFailures = collections.defaultdict(int)
-
-
-    def eventReceived(self, event):
-        self._times.append(event['duration'])
-        if len(self._times) == 200:
-            print('mean:', mean(self._times))
-            print('median:', median(self._times))
-            print('stddev:', stddev(self._times))
-            print('mad:', mad(self._times))
-            del self._times[:100]
-
-
-    def clientFailure(self, event):
-        self._failures[event] += 1
-
-
-    def simFailure(self, event):
-        self._simFailures[event] += 1
-
-
-
-class ReportStatistics(StatisticsBase, SummarizingMixin):
-    &quot;&quot;&quot;
-
-    @ivar _users: A C{set} containing all user UIDs which have been observed in
-        events.  When generating the final report, the size of this set is
-        reported as the number of users in the simulation.
-
-    &quot;&quot;&quot;
-
-    # the response time thresholds to display together with failing % count threshold
-    _thresholds_default = {
-        &quot;requests&quot;: {
-            &quot;limits&quot;: [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
-            &quot;thresholds&quot;: {
-                &quot;default&quot;: [100.0, 100.0, 100.0, 5.0, 1.0, 0.5, 0.0],
-            }
-        }
-    }
-    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure
-
-    _fields_init = [
-        ('request', -25, '%-25s'),
-        ('count', 8, '%8s'),
-        ('failed', 8, '%8s'),
-    ]
-
-    _fields_extend = [
-        ('mean', 8, '%8.4f'),
-        ('median', 8, '%8.4f'),
-        ('stddev', 8, '%8.4f'),
-        ('QoS', 8, '%8.4f'),
-        ('STATUS', 8, '%8s'),
-    ]
-
-    def __init__(self, **params):
-        self._perMethodTimes = {}
-        self._users = set()
-        self._clients = set()
-        self._failed_clients = []
-        self._failed_sim = collections.defaultdict(int)
-        self._startTime = datetime.now()
-        self._expired_data = None
-
-        # Load parameters from config
-        if &quot;thresholdsPath&quot; in params:
-            jsondata = json.load(open(params[&quot;thresholdsPath&quot;]))
-        elif &quot;thresholds&quot; in params:
-            jsondata = params[&quot;thresholds&quot;]
-        else:
-            jsondata = self._thresholds_default
-        self._thresholds = [[limit, {}] for limit in jsondata[&quot;requests&quot;][&quot;limits&quot;]]
-        for ctr, item in enumerate(self._thresholds):
-            for k, v in jsondata[&quot;requests&quot;][&quot;thresholds&quot;].items():
-                item[1][k] = v[ctr]
-
-        self._fields = self._fields_init[:]
-        for threshold, _ignore_fail_at in self._thresholds:
-            self._fields.append(('&gt;%g sec' % (threshold,), 10, '%10s'))
-        self._fields.extend(self._fields_extend)
-
-        if &quot;benchmarksPath&quot; in params:
-            self.benchmarks = json.load(open(params[&quot;benchmarksPath&quot;]))
-        else:
-            self.benchmarks = {}
-
-        if &quot;failCutoff&quot; in params:
-            self._fail_cut_off = params[&quot;failCutoff&quot;]
-
-
-    def observe(self, event):
-        if event.get('type') == 'sim-expired':
-            self.simExpired(event)
-        else:
-            super(ReportStatistics, self).observe(event)
-
-
-    def countUsers(self):
-        return len(self._users)
-
-
-    def countClients(self):
-        return len(self._clients)
-
-
-    def countClientFailures(self):
-        return len(self._failed_clients)
-
-
-    def countSimFailures(self):
-        return len(self._failed_sim)
-
-
-    def eventReceived(self, event):
-        dataset = self._perMethodTimes.setdefault(event['method'], [])
-        dataset.append((event['success'], event['duration']))
-        self._users.add(event['user'])
-        self._clients.add(event['client_id'])
-
-
-    def clientFailure(self, event):
-        self._failed_clients.append(event['reason'])
-
-
-    def simFailure(self, event):
-        self._failed_sim[event['reason']] += 1
-
-
-    def simExpired(self, event):
-        self._expired_data = event['reason']
-
-
-    def printMiscellaneous(self, output, items):
-        maxColumnWidth = str(len(max(items.iterkeys(), key=len)))
-        fmt = &quot;%&quot; + maxColumnWidth + &quot;s : %-s\n&quot;
-        for k in sorted(items.iterkeys()):
-            output.write(fmt % (k.title(), items[k],))
-
-
-    def qos(self):
-        &quot;&quot;&quot;
-        Determine a &quot;quality of service&quot; value that can be used for comparisons between runs. This value
-        is based on the percentage deviation of means of each request from a set of &quot;benchmarks&quot; for each
-        type of request.
-        &quot;&quot;&quot;
-
-        # Get means for each type of method
-        means = {}
-        for method, results in self._perMethodTimes.items():
-            means[method] = mean([duration for success, duration in results if success])
-
-        # Determine percentage differences with weighting
-        differences = []
-        for method, value in means.items():
-            result = self.qos_value(method, value)
-            if result is not None:
-                differences.append(result)
-
-        return (&quot;%-8.4f&quot; % mean(differences)) if differences else &quot;None&quot;
-
-
-    def qos_value(self, method, value):
-        benchmark = self.benchmarks.get(method)
-        if benchmark is None:
-            return None
-        test_mean, weight = (benchmark[&quot;mean&quot;], benchmark[&quot;weight&quot;],)
-        return ((value / test_mean) - 1.0) * weight + 1.0
-
-
-    def _summarizeData(self, operation, data):
-        data = SummarizingMixin._summarizeData(self, operation, data)
-        value = self.qos_value(operation, data[-4])
-        if value is None:
-            value = 0.0
-        return data[:-1] + (value,) + data[-1:]
-
-
-    def report(self, output):
-        output.write(&quot;\n&quot;)
-        output.write(&quot;** REPORT **\n&quot;)
-        output.write(&quot;\n&quot;)
-        runtime = datetime.now() - self._startTime
-        cpu = os.times()
-        cpuUser = cpu[0] + cpu[2]
-        cpuSys = cpu[1] + cpu[3]
-        cpuTotal = cpuUser + cpuSys
-        runHours, remainder = divmod(runtime.seconds, 3600)
-        runMinutes, runSeconds = divmod(remainder, 60)
-        cpuHours, remainder = divmod(cpuTotal, 3600)
-        cpuMinutes, cpuSeconds = divmod(remainder, 60)
-        items = {
-            'Users': self.countUsers(),
-            'Clients': self.countClients(),
-            'Start time': self._startTime.strftime('%m/%d %H:%M:%S'),
-            'Run time': &quot;%02d:%02d:%02d&quot; % (runHours, runMinutes, runSeconds),
-            'CPU Time': &quot;user %-5.2f sys %-5.2f total %02d:%02d:%02d&quot; % (cpuUser, cpuSys, cpuHours, cpuMinutes, cpuSeconds,),
-            'QoS': self.qos(),
-        }
-        if self.countClientFailures() &gt; 0:
-            items['Failed clients'] = self.countClientFailures()
-            for ctr, reason in enumerate(self._failed_clients, 1):
-                items['Failure #%d' % (ctr,)] = reason
-        if self.countSimFailures() &gt; 0:
-            for reason, count in self._failed_sim.items():
-                items['Failed operation'] = &quot;%s : %d times&quot; % (reason, count,)
-        output.write(&quot;* Client\n&quot;)
-        self.printMiscellaneous(output, items)
-        output.write(&quot;\n&quot;)
-
-        if self._expired_data is not None:
-            items = {
-                &quot;Req/sec&quot; : &quot;%.1f&quot; % (self._expired_data[0],),
-                &quot;Response&quot;: &quot;%.1f (ms)&quot; % (self._expired_data[1],),
-                &quot;Slots&quot;: &quot;%.2f&quot; % (self._expired_data[2],),
-                &quot;CPU&quot;: &quot;%.1f%%&quot; % (self._expired_data[3],),
-            }
-            output.write(&quot;* Server (Last 5 minutes)\n&quot;)
-            self.printMiscellaneous(output, items)
-            output.write(&quot;\n&quot;)
-        output.write(&quot;* Details\n&quot;)
-
-        self.printHeader(output, [
-            (label, width)
-            for (label, width, _ignore_fmt)
-            in self._fields
-        ])
-        self.printData(
-            output,
-            [fmt for (label, width, fmt) in self._fields],
-            sorted(self._perMethodTimes.items())
-        )
-
-    _FAILED_REASON = &quot;Greater than %(cutoff)g%% %(method)s failed&quot;
-
-    _REASON_1 = &quot;Greater than %(cutoff)g%% %(method)s exceeded &quot;
-    _REASON_2 = &quot;%g second response time&quot;
-
-    def failures(self):
-        # TODO
-        reasons = []
-
-        for (method, times) in self._perMethodTimes.iteritems():
-            failures = 0
-            overDurations = [0] * len(self._thresholds)
-
-            for success, duration in times:
-                if not success:
-                    failures += 1
-                for ctr, item in enumerate(self._thresholds):
-                    threshold, _ignore_fail_at = item
-                    if duration &gt; threshold:
-                        overDurations[ctr] += 1
-
-            checks = [
-                (failures, self._fail_cut_off, self._FAILED_REASON),
-            ]
-
-            for ctr, item in enumerate(self._thresholds):
-                threshold, fail_at = item
-                fail_at = fail_at.get(method, fail_at[&quot;default&quot;])
-                checks.append(
-                    (overDurations[ctr], fail_at, self._REASON_1 + self._REASON_2 % (threshold,))
-                )
-
-            for count, cutoff, reason in checks:
-                if count * 100.0 / len(times) &gt; cutoff:
-                    reasons.append(reason % dict(method=method, cutoff=cutoff))
-
-        if self.countClientFailures() != 0:
-            reasons.append(&quot;Client failures: %d&quot; % (self.countClientFailures(),))
-        if self.countSimFailures() != 0:
-            reasons.append(&quot;Overall failures: %d&quot; % (self.countSimFailures(),))
-        return reasons
-
-
-
</del><span class="cx"> def main():
</span><span class="cx">     import random
</span><span class="cx"> 
</span><span class="lines">@@ -626,6 +359,9 @@
</span><span class="cx">     from twisted.python.failure import startDebugMode
</span><span class="cx">     startDebugMode()
</span><span class="cx"> 
</span><ins>+    from contrib.performance.loadtest.clients import OS_X_10_6
+    from contrib.performance.loadtest.logger import ReportStatistics, SimpleStatistics, RequestLogger
+
</ins><span class="cx">     report = ReportStatistics()
</span><span class="cx">     addObserver(SimpleStatistics().observe)
</span><span class="cx">     addObserver(report.observe)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestprofilespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/profiles.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/profiles.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/profiles.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -21,30 +21,50 @@
</span><span class="cx"> 
</span><span class="cx"> from __future__ import division
</span><span class="cx"> 
</span><del>-import json
</del><span class="cx"> import random
</span><del>-import sys
</del><span class="cx"> from uuid import uuid4
</span><ins>+from numbers import Number
</ins><span class="cx"> 
</span><span class="cx"> from caldavclientlibrary.protocol.caldav.definitions import caldavxml
</span><span class="cx"> 
</span><span class="cx"> from twisted.python import context
</span><span class="cx"> from twisted.python.log import msg
</span><span class="cx"> from twisted.python.failure import Failure
</span><del>-from twisted.internet.defer import Deferred, succeed, fail
</del><ins>+from twisted.internet.defer import Deferred, DeferredList, succeed, fail
</ins><span class="cx"> from twisted.internet.task import LoopingCall
</span><span class="cx"> from twisted.web.http import PRECONDITION_FAILED
</span><span class="cx"> 
</span><del>-from twistedcaldav.ical import Property, Component
</del><ins>+from twistedcaldav.ical import Property
</ins><span class="cx"> 
</span><del>-from contrib.performance.stats import NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, mean, median
-from contrib.performance.stats import LogNormalDistribution, RecurrenceDistribution
-from contrib.performance.loadtest.logger import SummarizingMixin
</del><ins>+from contrib.performance.loadtest.distributions import (
+    NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, BernoulliDistribution,
+    LogNormalDistribution, RecurrenceDistribution, FixedDistribution
+)
</ins><span class="cx"> from contrib.performance.loadtest.ical import IncorrectResponseCode
</span><ins>+from contrib.performance.loadtest.resources import Calendar, Event
+from contrib.performance.loadtest.templates import eventTemplate, alarmTemplate, taskTemplate
</ins><span class="cx"> 
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> from pycalendar.duration import Duration
</span><ins>+from pycalendar.value import Value
</ins><span class="cx"> 
</span><ins>+def _loopWithDistribution(reactor, distribution, function):
+    result = Deferred()
+    # print(distribution)
+
+    def repeat(ignored):
+        reactor.callLater(distribution.sample(), iterate)
+
+    def iterate():
+        d = function()
+        if d is not None:
+            d.addCallbacks(repeat, result.errback)
+        else:
+            repeat(None)
+
+    repeat(None)
+    return result
+
</ins><span class="cx"> class ProfileBase(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Base class which provides some conveniences for profile
</span><span class="lines">@@ -52,27 +72,38 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     random = random
</span><span class="cx"> 
</span><del>-    def __init__(self, reactor, simulator, client, userNumber, **params):
</del><ins>+    def __init__(self, enabled, interval, **params):
+        self.enabled = enabled
+        if isinstance(interval, Number):
+            interval = FixedDistribution(interval)
+        self._interval = interval
+        self._params = params
+        self.setDistributions(**params)
+        self._initialized = False
+
+    def duplicate(self):
+        return type(self)(enabled=self.enabled, interval=self._interval, **self._params)
+
+    def setUp(self, reactor, simulator, client, record):
</ins><span class="cx">         self._reactor = reactor
</span><span class="cx">         self._sim = simulator
</span><span class="cx">         self._client = client
</span><del>-        self._number = userNumber
-        self.setParameters(**params)
</del><ins>+        self._record = record
+        self._initialized = True
</ins><span class="cx"> 
</span><del>-
-    def setParameters(self):
</del><ins>+    def setDistributions(self):
</ins><span class="cx">         pass
</span><span class="cx"> 
</span><ins>+    def _wrapper(self):
+        if not self.enabled:
+            return succeed(None)
+        if not self._client.started:
+            return succeed(None)
+        return self.action()
</ins><span class="cx"> 
</span><del>-    def initialize(self):
-        &quot;&quot;&quot;
-        Called before the profile runs for real. Can be used to initialize client state.
</del><ins>+    def run(self):
+        return _loopWithDistribution(self._reactor, self._interval, self._wrapper)
</ins><span class="cx"> 
</span><del>-        @return: a L{Deferred} that fires when initialization is done
-        &quot;&quot;&quot;
-        return succeed(None)
-
-
</del><span class="cx">     def _calendarsOfType(self, calendarType, componentType):
</span><span class="cx">         return [
</span><span class="cx">             cal
</span><span class="lines">@@ -90,6 +121,46 @@
</span><span class="cx">         return attendee.parameterValue('EMAIL') == self._client.email[len('mailto:'):]
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _getRandomCalendarOfType(self, component_type):
+        &quot;&quot;&quot;
+        Return a random L{Calendar} object from the current user
+        or C{None} if there are no calendars to work with
+        &quot;&quot;&quot;
+        calendars = self._calendarsOfType(caldavxml.calendar, component_type)
+        if not calendars: # Oh no! There are no calendars to play with
+            return None
+        # Choose a random calendar
+        calendar = self.random.choice(calendars)
+        return calendar
+
+
+    def _getRandomEventOfType(self, component_type):
+        &quot;&quot;&quot;
+        Return a random L{Event} object from the current user
+        or C{None} if there are no events to work with
+        &quot;&quot;&quot;
+        calendars = self._calendarsOfType(caldavxml.calendar, component_type)
+        while calendars:
+            calendar = self.random.choice(calendars)
+            calendars.remove(calendar)
+            if not calendar.events:
+                continue
+
+            events = calendar.events.keys()
+            while events:
+                href = self.random.choice(events)
+                events.remove(href)
+                event = calendar.events[href]
+                if not event.component:
+                    continue
+                return event
+        return None
+
+
+    def _getRandomLocation(self):
+        pass
+
+
</ins><span class="cx">     def _newOperation(self, label, deferred):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Helper to emit a log event when a new operation is started and
</span><span class="lines">@@ -156,46 +227,336 @@
</span><span class="cx">     pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+#####################
+# Eventer Hierarchy #
+# ----------------- #
+# EventBase         #
+#   Eventer         #
+#   EventUpdaterBase#
+#     Titler        #
+#     Noter         #
+#     Linker        #
+#     Relocater     #
+#     Repeater      #
+#     Rescheduler   #
+#     ~Alerter~     #
+#     Attacher      #
+#     InviterBase   #
+#       Inviter     #
+#       Relocater   #
+#   EventDeleter    #
+#####################
</ins><span class="cx"> 
</span><del>-def loopWithDistribution(reactor, distribution, function):
-    result = Deferred()
</del><ins>+class EventBase(ProfileBase):
+    &quot;&quot;&quot;
+    Base profile for a calendar user who interacts with events
+    &quot;&quot;&quot;
+    def _getRandomCalendar(self):
+        return self._getRandomCalendarOfType('VEVENT')
</ins><span class="cx"> 
</span><del>-    def repeat(ignored):
-        reactor.callLater(distribution.sample(), iterate)
</del><ins>+    def _getRandomEvent(self):
+        return self._getRandomEventOfType('VEVENT')
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def iterate():
-        d = function()
-        if d is not None:
-            d.addCallbacks(repeat, result.errback)
-        else:
-            repeat(None)
</del><ins>+class Eventer(EventBase):
+    &quot;&quot;&quot;
+    A Calendar user who creates new events.
+    &quot;&quot;&quot;
+    def setDistributions(
+        self,
+        eventStartDistribution=NearFutureDistribution(),
+        eventDurationDistribution=UniformDiscreteDistribution([
+            15 * 60, 30 * 60,
+            45 * 60, 60 * 60,
+            120 * 60
+        ])
+    ):
+        &quot;&quot;&quot;
+        @param eventStartDistribution: Generates datetimes at which an event starts
+        @param eventDurationDistribution: Generates length of event (in seconds)
+        &quot;&quot;&quot;
+        self._eventStart = eventStartDistribution
+        self._eventDuration = eventDurationDistribution
</ins><span class="cx"> 
</span><del>-    repeat(None)
-    return result
</del><ins>+    def _addEvent(self):
+        # Choose a random calendar on which to add an event
+        calendar = self._getRandomCalendar()
+        if not calendar:
+            return succeed(None)
+        # print &quot;Going to add an event&quot;
</ins><span class="cx"> 
</span><ins>+        # Form a new event by modifying fields of the template event
+        vcalendar = eventTemplate.duplicate()
+        vevent = vcalendar.mainComponent()
+        uid = str(uuid4())
+        dtstart = self._eventStart.sample()
+        dtend = dtstart + Duration(seconds=self._eventDuration.sample())
</ins><span class="cx"> 
</span><ins>+        vevent.replaceProperty(Property(&quot;UID&quot;, uid))
+        vevent.replaceProperty(Property(&quot;CREATED&quot;, DateTime.getNowUTC()))
+        vevent.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
+        vevent.replaceProperty(Property(&quot;DTSTART&quot;, dtstart))
+        vevent.replaceProperty(Property(&quot;DTEND&quot;, dtend))
</ins><span class="cx"> 
</span><del>-class Inviter(ProfileBase):
</del><ins>+        href = '%s%s.ics' % (calendar.url, uid)
+        # print(&quot;Vcalendar is&quot;, vcalendar)
+        event = Event(self._client.serializeLocation(), href, None, component=vcalendar)
+        # print(&quot;ABout to add event&quot;, event.component)
+        d = self._client.addEvent(href, event)
+        return self._newOperation(&quot;create{event}&quot;, d)
+
+    action = _addEvent
+
+# Could have better handling for not changing events once they're modified
+# esp re: repeating
+class EventUpdaterBase(EventBase):
+    &quot;&quot;&quot;Superclass of all event mixins.
+    Accepts two parameters
+    enabled: bool on or off
+    interval: distibution that generates integers representing delays
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    A Calendar user who invites and de-invites other users to events.
-    &quot;&quot;&quot;
-    def setParameters(
</del><ins>+    def action(self):
+        event = self._getRandomEvent()
+        if not event:
+            return succeed(None)
+        component = event.component
+        vevent = component.mainComponent()
+
+        label = self.modifyEvent(event.url, vevent)
+        vevent.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
+
+        event.component = component
+        return self._client.updateEvent(event)
+        # d.addCallback(finish)
+
+        return self._newOperation(label, d)
+
+    def modifyEvent(self, href, vevent):
+        &quot;&quot;&quot;Overriden by subclasses&quot;&quot;&quot;
+        pass
+
+class Titler(EventUpdaterBase):
+    def setDistributions(
</ins><span class="cx">         self,
</span><del>-        enabled=True,
-        sendInvitationDistribution=NormalDistribution(600, 60),
-        inviteeDistribution=UniformDiscreteDistribution(range(-10, 11))
</del><ins>+        titleLengthDistribution=NormalDistribution(10, 2)
</ins><span class="cx">     ):
</span><del>-        self.enabled = enabled
-        self._sendInvitationDistribution = sendInvitationDistribution
-        self._inviteeDistribution = inviteeDistribution
</del><ins>+        self._titleLength = titleLengthDistribution
</ins><span class="cx"> 
</span><ins>+    def modifyEvent(self, _ignore_href, vevent):
+        length = max(5, int(self._titleLength.sample()))
+        vevent.replaceProperty(Property(&quot;SUMMARY&quot;, &quot;Event&quot; + &quot;.&quot; * (length - 5)))
+        return &quot;update{title}&quot;
</ins><span class="cx"> 
</span><del>-    def run(self):
-        return loopWithDistribution(
-            self._reactor, self._sendInvitationDistribution, self._invite)
</del><ins>+class Transparenter(EventUpdaterBase):
+    def setDistributions(
+        self,
+        transparentLikelihoodDistribution=BernoulliDistribution(0.95)
+    ):
+        self._transparentLikelihood = transparentLikelihoodDistribution
</ins><span class="cx"> 
</span><ins>+    def modifyEvent(self, _ignore_href, vevent):
+        if self._transparentLikelihood.sample():
+            transparency = &quot;TRANSPARENT&quot;
+        else:
+            transparency = &quot;OPAQUE&quot;
+        vevent.replaceProperty(Property(&quot;TRANSP&quot;, transparency))
+        return &quot;update{transp}&quot;
</ins><span class="cx"> 
</span><ins>+class Hider(EventUpdaterBase):
+    def setDistributions(
+        self,
+        publicLikelihoodDistribution=BernoulliDistribution(0.95)
+    ):
+        self._publicLikelihood = publicLikelihoodDistribution
+
+    def modifyEvent(self, _ignore_href, vevent):
+        if self._publicLikelihood.sample():
+            privacy = &quot;PUBLIC&quot;
+        else:
+            privacy = &quot;CONFIDENTIAL&quot;
+        vevent.replaceProperty(Property(&quot;X-CALENDARSERVER-ACCESS&quot;, privacy))
+        return &quot;update{privacy}&quot;
+
+class Noter(EventUpdaterBase):
+    def setDistributions(
+        self,
+        noteLengthDistribution=NormalDistribution(10, 2)
+    ):
+        self._noteLength = noteLengthDistribution
+
+    def modifyEvent(self, _ignore_href, vevent):
+        length = max(5, int(self._noteLength.sample()))
+        vevent.replaceProperty(Property(&quot;DESCRIPTION&quot;, &quot;.&quot; * length))
+        return &quot;update{notes}&quot;
+
+class Linker(EventUpdaterBase):
+    def setDistributions(
+        self,
+        urlLengthDistribution=NormalDistribution(10, 2)
+    ):
+        self._urlLength = urlLengthDistribution
+
+    def modifyEvent(self, _ignore_href, vevent):
+        length = max(5, int(self._urlLength.sample()))
+        vevent.replaceProperty(Property(&quot;URL&quot;, 'https://bit.ly/' + '*' * length, valuetype=Value.VALUETYPE_URI))
+        return &quot;update{url}&quot;
+
+class Repeater(EventUpdaterBase):
+    def setDistributions(
+        self,
+        recurrenceDistribution=RecurrenceDistribution(False)
+    ):
+        self._recurrence = recurrenceDistribution
+
+    def modifyEvent(self, _ignore_href, vevent):
+        rrule = self._recurrence.sample()
+        if rrule is not None:
+            vevent.replaceProperty(Property(None, None, None, pycalendar=rrule))
+        return &quot;update{rrule}&quot;
+
+class Rescheduler(EventUpdaterBase):
+    def setDistributions(
+        self,
+        eventStartDistribution=NearFutureDistribution(),
+        eventDurationDistribution=UniformDiscreteDistribution([
+            15 * 60, 30 * 60,
+            45 * 60, 60 * 60,
+            120 * 60
+        ])
+    ):
+        self._eventStart = eventStartDistribution
+        self._eventDuration = eventDurationDistribution
+
+    def modifyEvent(self, _ignore_href, vevent):
+        dtstart = self._eventStart.sample()
+        dtend = dtstart + Duration(seconds=self._eventDuration.sample())
+        vevent.replaceProperty(Property(&quot;DTSTART&quot;, dtstart))
+        vevent.replaceProperty(Property(&quot;DTEND&quot;, dtend))
+        return &quot;reschedule{event}&quot;
+
+# class Alerter(EventUpdaterBase):
+# component.replaceProperty(Property(&quot;ACKNOWLEDGED&quot;, DateTime.getNowUTC()))
+#     pass
+
+class Attacher(EventUpdaterBase):
+    def setDistributions(
+        self,
+        filesizeDistribution=NormalDistribution(24, 3),
+        numAttachmentsDistribution=LogNormalDistribution(2, 1),
+        attachLikelihoodDistribution=BernoulliDistribution(0.9),
+
+    ):
+        self._filesize = filesizeDistribution
+        # self._numAttachments = numAttachmentsDistribution
+        # self._attachLikelihood = attachLikelihoodDistribution
+        # pass
+
+    def modifyEvent(self, href, vevent):
+        d = self._client.postAttachment(href, 'x' * 1024)
+        return &quot;attach{files}&quot;
+
+    def handleAttachments(self):
+        pass
+
+        # if True: # attachLikelihoodDistribution.sample():
+        #     # size = max(0, int(self._filesize.sample()))
+        #     numAttachments()
+        #     self.attachFiles(event, filesizeDistribution.sample())
+        # else:
+        #     pass
+
+    def attachFile(self, event):
+        # PUT new event information (nothing has actually changed)
+        # POST attachment (with Content-Disposition header, and response location)
+        # GET updated event
+        pass
+
+    def unattachFile(self):
+        pass
+
+class InviterBase(EventUpdaterBase):
+    def setDistributions(
+        self,
+        numInviteesDistribution=NormalDistribution(7, 2)
+    ):
+        self._numInvitees = numInviteesDistribution
+
+    def _findUninvitedRecord(self, vevent):
+        pass
+
+    def _addAttendee(self, some_id):
+        attendeeProp = self._buildAttendee()
+        self._client.attendeeAutocomplete
+
+    # def _didSelfOrganize(self, vevent):
+
+    # TODO handle alternate roles
+    def _buildAttendee(self, commonname, cuaddr, isIndividual=True, isRequired=False):
+        return Property(
+            name=u'ATTENDEE',
+            value=cuaddr.encode(&quot;utf-8&quot;),
+            params={
+                'CN': commonname,
+                'CUTYPE': 'INDIVIDUAL' if isIndividual else 'ROOM',
+                'PARTSTAT': 'NEEDS-ACTION',
+                'ROLE': 'REQ-PARTICIPANT',
+                'RSVP': 'TRUE',
+            },
+        )
+
+    def _getAttendees(self, vevent):
+        return vevent.properties('ATTENDEE')
+
+    def _invite():
+        raise NotImplementedError
+
+    def _addAttendee():
+        raise NotImplementedError
+
+class Inviter(InviterBase):
+    def modifyEvent(self, href, vevent):
+        # print(&quot;*&quot; * 16)
+        numToInvite = max(0, int(self._numInvitees.sample()))
+        deferreds = []
+        for _ignore_i in xrange(numToInvite):
+            number = random.randint(1, 50)
+            record = self._sim.getUserRecord(number)
+            attendee = self._buildAttendee(record.commonName, record.email)
+            deferreds.append(self._client.addEventAttendee(href, attendee))
+            vevent.addProperty(attendee)
+        # d = self._client.addInvite(event)
+        # deferreds.append(d)
+        return DeferredList(deferreds)
+
+
+    def test(self):
+        event = self._getRandomEvent()
+        if not event:
+            return succeed(None)
+        href = event.url
+
+        attendee = Property(
+            name=u'ATTENDEE',
+            value='urn:uuid:30000000-0000-0000-0000-000000000002',
+            params={
+                'CN': 'Location 02',
+                'CUTYPE': 'ROOM',
+                'PARTSTAT': 'NEEDS-ACTION',
+                'ROLE': 'REQ-PARTICIPANT',
+                'RSVP': 'TRUE',
+            },
+        )
+
+        d = self._client.addEventAttendee(href, attendee)
+
+        component = event.component
+        component.mainComponent().addProperty(attendee)
+        event.component = component
+
+        d2 = self._client.addInvite(event)
+        return self._newOperation(&quot;add attendee&quot;, DeferredList([d, d2]))
+
+
</ins><span class="cx">     def _addAttendee(self, event, attendees):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a new attendee to add to the list of attendees for the
</span><span class="lines">@@ -280,7 +641,8 @@
</span><span class="cx">                 # Find out who might attend
</span><span class="cx">                 attendees = tuple(component.properties('ATTENDEE'))
</span><span class="cx"> 
</span><del>-                d = self._addAttendee(event, attendees)
</del><ins>+                # d = self._addAttendee(event, attendees)
+                d = self._addLocation(event, &quot;Location 05&quot;, &quot;urn:uuid:30000000-0000-0000-0000-000000000005&quot;)
</ins><span class="cx">                 d.addCallbacks(
</span><span class="cx">                     lambda attendee:
</span><span class="cx">                         self._client.addEventAttendee(
</span><span class="lines">@@ -291,173 +653,248 @@
</span><span class="cx">         # Oops, either no events or no calendars to play with.
</span><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><ins>+    # action = invite
</ins><span class="cx"> 
</span><ins>+class Relocater(InviterBase):
+    def setDistributions(
+        self,
+    ):
+        pass
</ins><span class="cx"> 
</span><del>-class RealisticInviter(ProfileBase):
</del><ins>+class EventDeleter(EventBase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    A Calendar user who invites other users to new events.
</del><ins>+    A calendar user who deletes events at random
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    _eventTemplate = Component.fromString(&quot;&quot;&quot;\
-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
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;))
</del><ins>+    def _deleteEvent(self, event):
+        # if self organized
+        d = self._client.deleteEvent(event.url)
+        return self._newOperation(&quot;delete{event}&quot;, d)
+        # if am attendee
</ins><span class="cx"> 
</span><ins>+    def _deleteRandomEvent(self):
+        event = self._getRandomEvent()
+        if event is None:
+            return succeed(None)
+        return self._deleteEvent(event)
</ins><span class="cx"> 
</span><del>-    def setParameters(
-        self,
-        enabled=True,
-        sendInvitationDistribution=NormalDistribution(600, 60),
-        inviteeDistribution=UniformDiscreteDistribution(range(-10, 11)),
-        inviteeClumping=True,
-        inviteeCountDistribution=LogNormalDistribution(1.2, 1.2),
-        eventStartDistribution=NearFutureDistribution(),
-        eventDurationDistribution=UniformDiscreteDistribution([
-            15 * 60, 30 * 60,
-            45 * 60, 60 * 60,
-            120 * 60
-        ]),
-        recurrenceDistribution=RecurrenceDistribution(False),
-    ):
-        self.enabled = enabled
-        self._sendInvitationDistribution = sendInvitationDistribution
-        self._inviteeDistribution = inviteeDistribution
-        self._inviteeClumping = inviteeClumping
-        self._inviteeCountDistribution = inviteeCountDistribution
-        self._eventStartDistribution = eventStartDistribution
-        self._eventDurationDistribution = eventDurationDistribution
-        self._recurrenceDistribution = recurrenceDistribution
</del><ins>+    action = _deleteRandomEvent
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class Emptier(EventDeleter):
+    &quot;&quot;&quot;
+    Behavior that keep events underneath capacity
+    &quot;&quot;&quot;
+    MAX_RESOURCES_PER_COLLECTION = 10
+    MAX_PERCENT_FULL = 0.9
+
</ins><span class="cx">     def run(self):
</span><del>-        return loopWithDistribution(
-            self._reactor, self._sendInvitationDistribution, self._invite)
</del><ins>+        deferreds = []
+        for calendar in self._client._calendars.values():
+            deferreds = []
+            numToDelete = max(0, int(len(calendar.events) - self.MAX_PERCENT_FULL * self.MAX_RESOURCES_PER_COLLECTION))
+            events = calendar.events.values()
+            print(events)
+            eventsToDelete = events[:numToDelete]
+            if eventsToDelete:
+                print(&quot;*&quot; * 16)
+                print(&quot;Deleting an event because of capacity&quot;)
+                for event in eventsToDelete:
+                    d = self._deleteEvent(event)
+                    deferreds.append(d)
+        if not deferreds:
+            return succeed(None)
+        return self._newOperation(&quot;empty{calendar}&quot;, DeferredList(deferreds))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _addAttendee(self, event, attendees):
-        &quot;&quot;&quot;
-        Create a new attendee to add to the list of attendees for the
-        given event.
-        &quot;&quot;&quot;
-        selfRecord = self._sim.getUserRecord(self._number)
-        invitees = set([u'mailto:%s' % (selfRecord.email,)])
-        for att in attendees:
-            invitees.add(att.value())
</del><span class="cx"> 
</span><del>-        for _ignore_i in range(10):
</del><span class="cx"> 
</span><del>-            sample = self._inviteeDistribution.sample()
-            if self._inviteeClumping:
-                sample = self._number + sample
-            invitee = max(0, sample)
</del><ins>+&quot;&quot;&quot; TEST &quot;&quot;&quot;
+# class Intern(object):
+#     def __init__(self):
+#         self.behaviors = [
+#             Eventer(asdfjadsf),
+#             Attacher(asjadsfjasdf),
+#             Inviter(enabled=True, **params)
+#         ]
</ins><span class="cx"> 
</span><del>-            try:
-                record = self._sim.getUserRecord(invitee)
-            except IndexError:
-                continue
-            cuaddr = u'mailto:%s' % (record.email,)
-            if cuaddr not in invitees:
-                break
-        else:
-            raise CannotAddAttendee(&quot;Can't find uninvited user to invite.&quot;)
</del><ins>+#     def run(self):
+#         deferreds = []
+#         for behavior in self.behaviors:
+#             deferreds.append(behavior.run())
+#         return DeferredList(deferreds)
</ins><span class="cx"> 
</span><del>-        attendee = Property(
-            name=u'ATTENDEE',
-            value=cuaddr.encode(&quot;utf-8&quot;),
-            params={
-                'CN': record.commonName,
-                'CUTYPE': 'INDIVIDUAL',
-                'PARTSTAT': 'NEEDS-ACTION',
-                'ROLE': 'REQ-PARTICIPANT',
-                'RSVP': 'TRUE',
-            },
-        )
</del><span class="cx"> 
</span><del>-        event.addProperty(attendee)
-        attendees.append(attendee)
</del><span class="cx"> 
</span><ins>+####################
+# Tasker Hierarchy #
+# ---------------- #
+# TaskBase         #
+#   Tasker         #
+#   TaskDeleter    #
+#   TaskUpdaterBase#
+#     Titler       #
+#     Noter        #
+#     Prioritizer  #
+#     Completer    #
+#     Alerter      #
+####################
</ins><span class="cx"> 
</span><del>-    def _invite(self):
-        &quot;&quot;&quot;
-        Try to add a new event, or perhaps remove an
-        existing attendee from an event.
</del><span class="cx"> 
</span><del>-        @return: C{None} if there are no events to play with,
-            otherwise a L{Deferred} which fires when the attendee
-            change has been made.
-        &quot;&quot;&quot;
</del><ins>+class TaskBase(ProfileBase):
+    &quot;&quot;&quot;
+    Base profile for a calendar user who interacts with tasks
+    &quot;&quot;&quot;
+    def _getRandomCalendar(self):
+        return self._getRandomCalendarOfType('VTODO')
</ins><span class="cx"> 
</span><del>-        if not self._client.started:
</del><ins>+    def _getRandomEvent(self):
+        return self._getRandomEventOfType('VTODO')
+
+
+class Tasker(TaskBase):
+    &quot;&quot;&quot;
+    A Calendar user who creates new tasks.
+    &quot;&quot;&quot;
+    def _addTask(self):
+        calendar = self._getRandomCalendar()
+        if not calendar:
</ins><span class="cx">             return succeed(None)
</span><span class="cx"> 
</span><del>-        # Find calendars which are eligible for invites
-        calendars = self._calendarsOfType(caldavxml.calendar, &quot;VEVENT&quot;)
</del><ins>+        # Form a new event by modifying fields of the template event
+        vcalendar = taskTemplate.duplicate()
+        vtodo = vcalendar.mainComponent()
+        uid = str(uuid4())
</ins><span class="cx"> 
</span><del>-        while calendars:
-            # Pick one at random from which to try to create an event
-            # to modify.
-            calendar = self.random.choice(calendars)
-            calendars.remove(calendar)
</del><ins>+        vtodo.replaceProperty(Property(&quot;UID&quot;, uid))
+        vtodo.replaceProperty(Property(&quot;CREATED&quot;, DateTime.getNowUTC()))
+        vtodo.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
</ins><span class="cx"> 
</span><del>-            # 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 + Duration(seconds=self._eventDurationDistribution.sample())
-            vevent.replaceProperty(Property(&quot;CREATED&quot;, DateTime.getNowUTC()))
-            vevent.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
-            vevent.replaceProperty(Property(&quot;DTSTART&quot;, dtstart))
-            vevent.replaceProperty(Property(&quot;DTEND&quot;, dtend))
-            vevent.replaceProperty(Property(&quot;UID&quot;, uid))
</del><ins>+        href = '%s%s.ics' % (calendar.url, uid)
+        event = Event(self._client.serializeLocation(), href, None, component=vcalendar)
+        d = self._client.addEvent(href, event)
+        return self._newOperation(&quot;create{task}&quot;, d)
</ins><span class="cx"> 
</span><del>-            rrule = self._recurrenceDistribution.sample()
-            if rrule is not None:
-                vevent.addProperty(Property(None, None, None, pycalendar=rrule))
</del><ins>+    action = _addTask
</ins><span class="cx"> 
</span><del>-            vevent.addProperty(self._client._makeSelfOrganizer())
-            vevent.addProperty(self._client._makeSelfAttendee())
</del><ins>+class TaskDeleter(TaskBase):
+    def _deleteTask(self):
+        event = self._getRandomEvent()
+        if event is None:
+            return succeed(None)
</ins><span class="cx"> 
</span><del>-            attendees = list(vevent.properties('ATTENDEE'))
-            for _ignore in range(int(self._inviteeCountDistribution.sample())):
-                try:
-                    self._addAttendee(vevent, attendees)
-                except CannotAddAttendee:
-                    self._failedOperation(&quot;invite&quot;, &quot;Cannot add attendee&quot;)
-                    return succeed(None)
</del><ins>+        d = self._client.deleteEvent(event.url)
+        return self._newOperation(&quot;delete{task}&quot;, d)
</ins><span class="cx"> 
</span><del>-            href = '%s%s.ics' % (calendar.url, uid)
-            d = self._client.addInvite(href, vcalendar)
-            return self._newOperation(&quot;invite&quot;, d)
</del><ins>+    action = _deleteTask
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class TaskUpdaterBase(TaskBase):
+    def action(self):
+        task = self._getRandomEvent()
+        if not task:
+            return succeed(None)
+        component = task.component
+        vtodo = component.mainComponent()
</ins><span class="cx"> 
</span><ins>+        label = self.modifyEvent(task.url, vtodo)
+        vtodo.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
+
+        task.component = component
+        d = self._client.updateEvent(task, method_label=&quot;update{task}&quot;)
+        return self._newOperation(label, d)
+
+    def modifyEvent(self, href, vtodo):
+        &quot;&quot;&quot;Overriden by subclasses&quot;&quot;&quot;
+        pass
+
+
+class TaskTitler(TaskUpdaterBase, Titler):
+    &quot;&quot;&quot;
+    Changes the SUMMARY of a random VTODO
+    &quot;&quot;&quot;
+    def modifyEvent(self, _ignore_href, vtodo):
+        vtodo.replaceProperty(Property(&quot;SUMMARY&quot;, &quot;.&quot; * 5))
+        return &quot;update{title}&quot;
+
+class TaskNoter(TaskUpdaterBase, Noter):
+    &quot;&quot;&quot;
+    Changes the NOTES of a random VTODO
+    &quot;&quot;&quot;
+    def modifyEvent(self, _ignore_href, vtodo):
+        vtodo.replaceProperty(Property(&quot;DESCRIPTION&quot;, &quot;.&quot; * 5))
+        return &quot;update{notes}&quot;
+
+
+# class TaskAlerterMixin = AlerterMixin (alarm AND due)
+# self._taskStartDistribution = taskDueDistribution
+# vtodo.replaceProperty(Property(&quot;DUE&quot;, due))
+
+
+class Prioritizer(TaskUpdaterBase):
+    PRIORITY_NONE = 0
+    PRIORITY_HIGH = 1
+    PRIORITY_MEDIUM = 5
+    PRIORITY_LOW = 9
+
+    def setDistributions(
+        self,
+        priorityDistribution=UniformDiscreteDistribution([
+            PRIORITY_NONE, PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH
+        ])
+    ):
+        self._priority = priorityDistribution
+
+    def modifyEvent(self, _ignore_href, vtodo):
+        self._setPriority(vtodo, self._priority.sample())
+
+
+    def _setPriority(self, vtodo, priority):
+        &quot;&quot;&quot; Set the PRIORITY of a VTODO &quot;&quot;&quot;
+        vtodo.replaceProperty(Property(&quot;PRIORITY&quot;, priority))
+
+class Completer(TaskUpdaterBase):
+    def setDistributions(
+        self,
+        completeLikelihood=BernoulliDistribution(0.9)
+    ):
+        self._complete = completeLikelihood
+
+    def modifyEvent(self, _ignore_href, vtodo):
+        if self._complete.sample():
+            self._markTaskComplete(vtodo)
+        else:
+            self._markTaskIncomplete(vtodo)
+
+    def _markTaskComplete(self, vtodo):
+        &quot;&quot;&quot; Mark a VTODO as complete &quot;&quot;&quot;
+        vtodo.replaceProperty(Property(&quot;COMPLETED&quot;, DateTime.getNowUTC()))
+        vtodo.replaceProperty(Property(&quot;PERCENT-COMPLETE&quot;, 100))
+        vtodo.replaceProperty(Property(&quot;STATUS&quot;, &quot;COMPLETED&quot;))
+
+    def _markTaskIncomplete(self, vtodo):
+        &quot;&quot;&quot; Mark a VTODO as incomplete &quot;&quot;&quot;
+        vtodo.removeProperty(&quot;COMPLETED&quot;)
+        vtodo.removeProperty(&quot;PERCENT-COMPLETE&quot;)
+        vtodo.replaceProperty(Property(&quot;STATUS&quot;, &quot;NEEDS-ACTION&quot;))
+
+
+##########################
+# Notification Behaviors #
+##########################
</ins><span class="cx"> class Accepter(ProfileBase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     A Calendar user who accepts invitations to events. As well as accepting requests, this
</span><span class="cx">     will also remove cancels and replies.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    def setParameters(
</del><ins>+    def setDistributions(
</ins><span class="cx">         self,
</span><del>-        enabled=True,
-        acceptDelayDistribution=NormalDistribution(1200, 60)
</del><ins>+        acceptDelayDistribution=NormalDistribution(1200, 60),
+        acceptLikelihoodDistribution=BernoulliDistribution(1),
</ins><span class="cx">     ):
</span><del>-        self.enabled = enabled
</del><span class="cx">         self._accepting = set()
</span><span class="cx">         self._acceptDelayDistribution = acceptDelayDistribution
</span><ins>+        self._acceptLikelihood = acceptLikelihoodDistribution
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def run(self):
</span><span class="lines">@@ -474,13 +911,14 @@
</span><span class="cx">         except KeyError:
</span><span class="cx">             return
</span><span class="cx"> 
</span><del>-        if calendar.resourceType == caldavxml.schedule_inbox:
-            # Handle inbox differently
-            self.inboxEventChanged(calendar, href)
-        elif calendar.resourceType == caldavxml.calendar:
-            self.calendarEventChanged(calendar, href)
-        else:
-            return
</del><ins>+        if self._acceptLikelihood.sample():
+            if calendar.resourceType == caldavxml.schedule_inbox:
+                # Handle inbox differently
+                self.inboxEventChanged(calendar, href)
+            elif calendar.resourceType == caldavxml.calendar:
+                self.calendarEventChanged(calendar, href)
+            else:
+                return
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def calendarEventChanged(self, calendar, href):
</span><span class="lines">@@ -533,7 +971,7 @@
</span><span class="cx"> 
</span><span class="cx">             # Download the event again and attempt to make the change
</span><span class="cx">             # to the attendee list again.
</span><del>-            d = self._client.updateEvent(href)
</del><ins>+            d = self._client._refreshEvent(href)
</ins><span class="cx">             def cbUpdated(ignored):
</span><span class="cx">                 d = change()
</span><span class="cx">                 d.addErrback(scheduleError)
</span><span class="lines">@@ -597,399 +1035,162 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class Eventer(ProfileBase):
</del><ins>+######################
+# Calendar Behaviors #
+######################
+class CalendarBase(ProfileBase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    A Calendar user who creates new events.
</del><ins>+    A calendar user who interacts with calendars
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    _eventTemplate = Component.fromString(&quot;&quot;&quot;\
-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
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;))
</del><ins>+    # def initialize(self):
+    #     self.action = lambda: None
+    #     return succeed(None)
</ins><span class="cx"> 
</span><del>-    def setParameters(
-        self,
-        enabled=True,
-        interval=25,
-        eventStartDistribution=NearFutureDistribution(),
-        eventDurationDistribution=UniformDiscreteDistribution([
-            15 * 60, 30 * 60,
-            45 * 60, 60 * 60,
-            120 * 60
-        ]),
-        recurrenceDistribution=RecurrenceDistribution(False),
-    ):
-        self.enabled = enabled
-        self._interval = interval
-        self._eventStartDistribution = eventStartDistribution
-        self._eventDurationDistribution = eventDurationDistribution
-        self._recurrenceDistribution = recurrenceDistribution
</del><span class="cx"> 
</span><ins>+    # def setDistributions(self, enabled=True, interval=25):
+    #     self.enabled = enabled
+    #     self._interval = interval
</ins><span class="cx"> 
</span><del>-    def run(self):
-        self._call = LoopingCall(self._addEvent)
-        self._call.clock = self._reactor
-        return self._call.start(self._interval)
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _addEvent(self):
-        if not self._client.started:
-            return succeed(None)
</del><ins>+class CalendarMaker(CalendarBase):
+    &quot;&quot;&quot; A Calendar user who adds new Calendars &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-        calendars = self._calendarsOfType(caldavxml.calendar, &quot;VEVENT&quot;)
</del><ins>+    def _addCalendar(self):
+        print &quot;Adding a calendar&quot;
+        # if not self._client.started:
+        #     return None
</ins><span class="cx"> 
</span><del>-        while calendars:
-            calendar = self.random.choice(calendars)
-            calendars.remove(calendar)
</del><ins>+        # uid = str(uuid4())
</ins><span class="cx"> 
</span><del>-            # 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 + Duration(seconds=self._eventDurationDistribution.sample())
-            vevent.replaceProperty(Property(&quot;CREATED&quot;, DateTime.getNowUTC()))
-            vevent.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
-            vevent.replaceProperty(Property(&quot;DTSTART&quot;, dtstart))
-            vevent.replaceProperty(Property(&quot;DTEND&quot;, dtend))
-            vevent.replaceProperty(Property(&quot;UID&quot;, uid))
</del><ins>+        # body = Calendar.buildCalendarXML(order=0, component_type=&quot;VEVENT&quot;, rgba_color='FB524FFF', name='Sample Calendar')
+        # print(&quot;Making new calendar with uid: &quot; + uid)
+        # # XXX Just for testing! remove this soon
+        # path = &quot;/calendars/__uids__/&quot; + self._client.record.guid + &quot;/&quot; + uid + &quot;/&quot;
+        # d = self._client.addCalendar(path, body)
+        d = succeed('calendar created')
+        return self._newOperation(&quot;create&quot;, d)
</ins><span class="cx"> 
</span><del>-            rrule = self._recurrenceDistribution.sample()
-            if rrule is not None:
-                vevent.addProperty(Property(None, None, None, pycalendar=rrule))
</del><ins>+    action = _addCalendar
</ins><span class="cx"> 
</span><del>-            href = '%s%s.ics' % (calendar.url, uid)
-            d = self._client.addEvent(href, vcalendar)
-            return self._newOperation(&quot;create&quot;, d)
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-class EventUpdater(ProfileBase):
</del><ins>+class CalendarUpdater(CalendarBase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    A Calendar user who creates a new event, and then updates its alarm.
</del><ins>+    A calendar user who updates random calendars
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    _eventTemplate = Component.fromString(&quot;&quot;&quot;\
-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
-BEGIN:VALARM
-X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
-UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
-DESCRIPTION:Event reminder
-TRIGGER:-PT8M
-ACTION:DISPLAY
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;))
</del><ins>+    def initialize(self):
+        from collections import defaultdict
+        self.action = self._updateCalendar
+        self._calendarModCount = defaultdict(int) # Map from calendar href to count of modifications
+        return succeed(None)
</ins><span class="cx"> 
</span><del>-    def setParameters(
-        self,
-        enabled=True,
-        interval=25,
-        eventStartDistribution=NearFutureDistribution(),
-        eventDurationDistribution=UniformDiscreteDistribution([
-            15 * 60, 30 * 60,
-            45 * 60, 60 * 60,
-            120 * 60
-        ]),
-        recurrenceDistribution=RecurrenceDistribution(False),
-    ):
-        self.enabled = enabled
-        self._interval = interval
-        self._eventStartDistribution = eventStartDistribution
-        self._eventDurationDistribution = eventDurationDistribution
-        self._recurrenceDistribution = recurrenceDistribution
</del><ins>+    def _updateCalendar(self):
+        if not self._client.started:
+            return None
</ins><span class="cx"> 
</span><ins>+        calendar = self._getRandomCalendar()
+        if not calendar:
+            return None
</ins><span class="cx"> 
</span><del>-    def initialize(self):
-        &quot;&quot;&quot;
-        Called before the profile runs for real. Can be used to initialize client state.
</del><ins>+        self._calendarModCount[calendar.url] += 1
+        modcount = self._calendarModCount[calendar.url]
</ins><span class="cx"> 
</span><del>-        @return: a L{Deferred} that fires when initialization is done
-        &quot;&quot;&quot;
-        return self._initEvent()
</del><ins>+        colors = [
+            &quot;#800000FF&quot;, # maroon
+            &quot;#FF0000FF&quot;, # red
+            &quot;#008000FF&quot;, # green
+            &quot;#00FF00FF&quot;, # line
+            &quot;#000080FF&quot;, # navy
+            &quot;#0000FFFF&quot;, # blue
+        ]
+        color = colors[modcount % len(colors)]
+        self._client.setCalendarDisplayName(calendar, &quot;Calendar ({mods})&quot;.format(mods=modcount))
+        self._client.setCalendarColor(calendar, color)
+        # choice = self.random.randint(0, 4)
+        # if choice == 0:
+        #     self._client._
+        # return succeed(None)
</ins><span class="cx"> 
</span><ins>+    def randomUpdate(self):
+        pass
</ins><span class="cx"> 
</span><del>-    def run(self):
-        self._call = LoopingCall(self._updateEvent)
-        self._call.clock = self._reactor
-        return self._call.start(self._interval)
</del><ins>+class CalendarSharer(CalendarBase, InviterBase):
+    &quot;&quot;&quot;
+    A calendar user who shares random calendars.
+    Even though the real client allows batch requests (e.g. 10 shares in one HTTP request),
+    we simplify life (TODO: keep it real) by having each HTTP request only add or remove one sharee.
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+    def initialize(self):
+        self.action = self._shareCalendar
+        return succeed(None)
</ins><span class="cx"> 
</span><del>-    def _initEvent(self):
</del><ins>+    def _shareCalendar(self):
</ins><span class="cx">         if not self._client.started:
</span><span class="cx">             return succeed(None)
</span><span class="cx"> 
</span><del>-        # If it already exists, don't re-create
-        calendar = self._calendarsOfType(caldavxml.calendar, &quot;VEVENT&quot;)[0]
-        if calendar.events:
-            events = [event for event in calendar.events.values() if event.url.endswith(&quot;event_to_update.ics&quot;)]
-            if events:
-                return succeed(None)
</del><ins>+        calendar = self._getRandomCalendar()
+        if not calendar:
+            return None
</ins><span class="cx"> 
</span><del>-        # 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 + Duration(seconds=self._eventDurationDistribution.sample())
-        vevent.replaceProperty(Property(&quot;CREATED&quot;, DateTime.getNowUTC()))
-        vevent.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
-        vevent.replaceProperty(Property(&quot;DTSTART&quot;, dtstart))
-        vevent.replaceProperty(Property(&quot;DTEND&quot;, dtend))
-        vevent.replaceProperty(Property(&quot;UID&quot;, uid))
</del><ins>+        # The decision of who to invite / uninvite should be made here
+        inv = random.randint(0, 1)
+        rem = random.randint(0, 1)
</ins><span class="cx"> 
</span><del>-        rrule = self._recurrenceDistribution.sample()
-        if rrule is not None:
-            vevent.addProperty(Property(None, None, None, pycalendar=rrule))
</del><ins>+        invRecord = self._sim.getUserRecord(inv)
+        remRecord = self._sim.getUserRecord(rem)
</ins><span class="cx"> 
</span><del>-        href = '%s%s' % (calendar.url, &quot;event_to_update.ics&quot;)
-        d = self._client.addEvent(href, vcalendar)
-        return self._newOperation(&quot;create&quot;, d)
</del><ins>+        print(&quot;Sharing &quot; + calendar.url)
+        self._inviteUser(calendar, invRecord)
+        # self._removeUser(calendar, remRecord)
</ins><span class="cx"> 
</span><ins>+        return succeed(None)
</ins><span class="cx"> 
</span><del>-    def _updateEvent(self):
-        &quot;&quot;&quot;
-        Try to add a new attendee to an event, or perhaps remove an
-        existing attendee from an event.
</del><ins>+    def _inviteUser(self, calendar, userRecord):
+        mailto = &quot;mailto:{}&quot;.format(userRecord.email)
+        body = Calendar.addInviteeXML(mailto, calendar.name, readwrite=True)
+        d = self._client.postXML(calendar.url, body)
+        # print(body)
</ins><span class="cx"> 
</span><del>-        @return: C{None} if there are no events to play with,
-            otherwise a L{Deferred} which fires when the attendee
-            change has been made.
-        &quot;&quot;&quot;
</del><ins>+    def _removeUser(self, calendar, userRecord):
+        mailto = &quot;mailto:{}&quot;.format(userRecord.email)
</ins><span class="cx"> 
</span><del>-        if not self._client.started:
-            return succeed(None)
</del><ins>+        body = Calendar.removeInviteeXML(mailto)
</ins><span class="cx"> 
</span><del>-        # If it does not exist, try to create it
-        calendar = self._calendarsOfType(caldavxml.calendar, &quot;VEVENT&quot;)[0]
-        if not calendar.events:
-            return self._initEvent()
-        events = [event for event in calendar.events.values() if event.url.endswith(&quot;event_to_update.ics&quot;)]
-        if not events:
-            return self._initEvent()
-        event = events[0]
</del><ins>+        d = self._client.postXML(calendar.url, body)
+        # print(body)
</ins><span class="cx"> 
</span><del>-        # Add/update the ACKNOWLEDGED property
-        component = event.component.mainComponent()
-        component.replaceProperty(Property(&quot;ACKNOWLEDGED&quot;, DateTime.getNowUTC()))
-        d = self._client.changeEvent(event.url)
-        return self._newOperation(&quot;update&quot;, d)
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-class Tasker(ProfileBase):
</del><ins>+class CalendarDeleter(CalendarBase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    A Calendar user who creates new tasks.
</del><ins>+    A calendar user who deletes entire calendars
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    _taskTemplate = Component.fromString(&quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTODO
-CREATED:20101018T155431Z
-UID:C98AD237-55AD-4F7D-9009-0D355D835822
-SUMMARY:Simple task
-DUE;TZID=America/New_York:20101021T120000
-DTSTAMP:20101018T155438Z
-END:VTODO
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;))
</del><ins>+    def initialize(self):
+        self.action = self._deleteCalendar
+        return succeed(None)
</ins><span class="cx"> 
</span><del>-    def setParameters(
-        self,
-        enabled=True,
-        interval=25,
-        taskDueDistribution=NearFutureDistribution(),
-    ):
-        self.enabled = enabled
-        self._interval = interval
-        self._taskStartDistribution = taskDueDistribution
-
-
-    def run(self):
-        self._call = LoopingCall(self._addTask)
-        self._call.clock = self._reactor
-        return self._call.start(self._interval)
-
-
-    def _addTask(self):
</del><ins>+    def _deleteCalendar(self):
</ins><span class="cx">         if not self._client.started:
</span><span class="cx">             return succeed(None)
</span><span class="cx"> 
</span><del>-        calendars = self._calendarsOfType(caldavxml.calendar, &quot;VTODO&quot;)
</del><ins>+        calendar = self._getRandomCalendar()
+        if not calendar:
+            return None
+        print(&quot;Deleting &quot; + calendar.url)
+        d = self._client.deleteCalendar(calendar.url)
+        return self._newOperation(&quot;delete&quot;, d)
</ins><span class="cx"> 
</span><del>-        while calendars:
-            calendar = self.random.choice(calendars)
-            calendars.remove(calendar)
</del><ins>+if __name__ == '__main__':
+    class TestProfile(ProfileBase):
+        def sayHello(self):
+            print(&quot;Hello!&quot;)
+        action = sayHello
</ins><span class="cx"> 
</span><del>-            # Copy the template task and fill in some of its fields
-            # to make a new task to create on the calendar.
-            vcalendar = self._taskTemplate.duplicate()
-            vtodo = vcalendar.mainComponent()
-            uid = str(uuid4())
-            due = self._taskStartDistribution.sample()
-            vtodo.replaceProperty(Property(&quot;CREATED&quot;, DateTime.getNowUTC()))
-            vtodo.replaceProperty(Property(&quot;DTSTAMP&quot;, DateTime.getNowUTC()))
-            vtodo.replaceProperty(Property(&quot;DUE&quot;, due))
-            vtodo.replaceProperty(Property(&quot;UID&quot;, uid))
</del><ins>+    from twisted.internet import reactor
</ins><span class="cx"> 
</span><del>-            href = '%s%s.ics' % (calendar.url, uid)
-            d = self._client.addEvent(href, vcalendar)
-            return self._newOperation(&quot;create&quot;, d)
-
-
-
-class OperationLogger(SummarizingMixin):
-    &quot;&quot;&quot;
-    Profiles will initiate operations which may span multiple requests.  Start
-    and stop log messages are emitted for these operations and logged by this
-    logger.
-    &quot;&quot;&quot;
-    formats = {
-        u&quot;start&quot; : u&quot;%(user)s - - - - - - - - - - - %(label)8s BEGIN %(lag)s&quot;,
-        u&quot;end&quot;   : u&quot;%(user)s - - - - - - - - - - - %(label)8s END [%(duration)5.2f s]&quot;,
-        u&quot;failed&quot;: u&quot;%(user)s x x x x x x x x x x x %(label)8s FAILED %(reason)s&quot;,
-    }
-
-    lagFormat = u'{lag %5.2f ms}'
-
-    # the response time thresholds to display together with failing % count threshold
-    _thresholds_default = {
-        &quot;operations&quot;: {
-            &quot;limits&quot;: [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
-            &quot;thresholds&quot;: {
-                &quot;default&quot;: [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0],
-            }
-        }
-    }
-    _lag_cut_off = 1.0      # Maximum allowed median scheduling latency, seconds
-    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure
-
-    _fields_init = [
-        ('operation', -25, '%-25s'),
-        ('count', 8, '%8s'),
-        ('failed', 8, '%8s'),
-    ]
-
-    _fields_extend = [
-        ('mean', 8, '%8.4f'),
-        ('median', 8, '%8.4f'),
-        ('stddev', 8, '%8.4f'),
-        ('avglag (ms)', 12, '%12.4f'),
-        ('STATUS', 8, '%8s'),
-    ]
-
-    def __init__(self, outfile=None, **params):
-        self._perOperationTimes = {}
-        self._perOperationLags = {}
-        if outfile is None:
-            outfile = sys.stdout
-        self._outfile = outfile
-
-        # Load parameters from config
-        if &quot;thresholdsPath&quot; in params:
-            jsondata = json.load(open(params[&quot;thresholdsPath&quot;]))
-        elif &quot;thresholds&quot; in params:
-            jsondata = params[&quot;thresholds&quot;]
-        else:
-            jsondata = self._thresholds_default
-        self._thresholds = [[limit, {}] for limit in jsondata[&quot;operations&quot;][&quot;limits&quot;]]
-        for ctr, item in enumerate(self._thresholds):
-            for k, v in jsondata[&quot;operations&quot;][&quot;thresholds&quot;].items():
-                item[1][k] = v[ctr]
-
-        self._fields = self._fields_init[:]
-        for threshold, _ignore_fail_at in self._thresholds:
-            self._fields.append(('&gt;%g sec' % (threshold,), 10, '%10s'))
-        self._fields.extend(self._fields_extend)
-
-        if &quot;lagCutoff&quot; in params:
-            self._lag_cut_off = params[&quot;lagCutoff&quot;]
-
-        if &quot;failCutoff&quot; in params:
-            self._fail_cut_off = params[&quot;failCutoff&quot;]
-
-
-    def observe(self, event):
-        if event.get(&quot;type&quot;) == &quot;operation&quot;:
-            event = event.copy()
-            lag = event.get('lag')
-            if lag is None:
-                event['lag'] = ''
-            else:
-                event['lag'] = self.lagFormat % (lag * 1000.0,)
-
-            self._outfile.write(
-                (self.formats[event[u'phase']] % event).encode('utf-8') + '\n')
-
-            if event[u'phase'] == u'end':
-                dataset = self._perOperationTimes.setdefault(event[u'label'], [])
-                dataset.append((event[u'success'], event[u'duration']))
-            elif lag is not None:
-                dataset = self._perOperationLags.setdefault(event[u'label'], [])
-                dataset.append(lag)
-
-
-    def _summarizeData(self, operation, data):
-        avglag = mean(self._perOperationLags.get(operation, [0.0])) * 1000.0
-        data = SummarizingMixin._summarizeData(self, operation, data)
-        return data[:-1] + (avglag,) + data[-1:]
-
-
-    def report(self, output):
-        output.write(&quot;\n&quot;)
-        self.printHeader(output, [
-            (label, width)
-            for (label, width, _ignore_fmt) in self._fields
-        ])
-        self.printData(
-            output,
-            [fmt for (label, width, fmt) in self._fields],
-            sorted(self._perOperationTimes.items())
-        )
-
-    _LATENCY_REASON = &quot;Median %(operation)s scheduling lag greater than %(cutoff)sms&quot;
-    _FAILED_REASON = &quot;Greater than %(cutoff).0f%% %(operation)s failed&quot;
-
-    def failures(self):
-        reasons = []
-
-        for operation, lags in self._perOperationLags.iteritems():
-            if median(lags) &gt; self._lag_cut_off:
-                reasons.append(self._LATENCY_REASON % dict(
-                    operation=operation.upper(), cutoff=self._lag_cut_off * 1000))
-
-        for operation, times in self._perOperationTimes.iteritems():
-            failures = len([success for (success, _ignore_duration) in times if not success])
-            if failures * 100.0 / len(times) &gt; self._fail_cut_off:
-                reasons.append(self._FAILED_REASON % dict(
-                    operation=operation.upper(), cutoff=self._fail_cut_off))
-
-        return reasons
</del><ins>+    profile = TestProfile(enabled=True, interval=1)
+    profile.setUp(reactor, None, None, None)
+    profile.run()
+    reactor.run()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestpubsubpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/pubsub.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/pubsub.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/pubsub.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,48 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+#
+##
+&quot;&quot;&quot;
+Publisher-Subscription model
+&quot;&quot;&quot;
+class _Subscription(object):
+    def __init__(self, publisher, subscriber):
+        self.publisher = publisher
+        self.subscriber = subscriber
+
+
+    def cancel(self):
+        self.publisher.subscriptions.remove(self)
+
+
+    def issue(self, issue):
+        self.subscriber(issue)
+
+
+
+class Publisher(object):
+    def __init__(self):
+        self.subscriptions = []
+
+
+    def subscribe(self, who):
+        subscription = _Subscription(self, who)
+        self.subscriptions.append(subscription)
+        return subscription
+
+
+    def issue(self, issue):
+        for subscr in self.subscriptions:
+            subscr.issue(issue)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestpushpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/push.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/push.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/push.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,133 @@
</span><ins>+import uuid
+
+from twisted.internet.endpoints import TCP4ClientEndpoint
+from twisted.internet.defer import inlineCallbacks, succeed
+
+from calendarserver.push.amppush import SubscribeToID, UnsubscribeFromID, AMPPushClientFactory
+
+class PushMonitor(object):
+    &quot;&quot;&quot;
+    Watchguard that monitors push notifications (AMP Push)
+    &quot;&quot;&quot;
+
+    def __init__(
+        self,
+        reactor,
+        ampPushHost,
+        ampPushPort,
+        callback
+    ):
+        &quot;&quot;&quot;
+        @param reactor: Twisted reactor
+        @type reactor: twisted.web.reactor
+        @param ampPushHost: AMP host to connect to (e.g. 'localhost')
+        @type ampPushHost: string
+        @param ampPushPort: AMP port to connect to (e.g. 62311)
+        @type ampPushPort: integer
+        @param callback: a one-argument function that is fired
+            with a calendar href upon receipt of a push notification
+            for that resource
+        @type callback: one-argument callable
+        &quot;&quot;&quot;
+
+        if reactor is None:
+            from twisted.internet import reactor
+
+        self._reactor = reactor
+        self._ampPushHost = ampPushHost
+        self._ampPushPort = ampPushPort
+
+        # Keep track of AMP parameters for calendar homes we encounter.  This
+        # dictionary has pushkeys as keys and calendar home URLs as values.
+        self._ampPushkeys = {}
+
+        self._callback = callback
+
+        self._token = str(uuid.uuid4()) # Unique token for this monitor
+        self._endpoint = TCP4ClientEndpoint(self._reactor, self._ampPushHost, self._ampPushPort)
+        self._factory = AMPPushClientFactory(self._receivedAMPPush)
+        self._connected = False
+
+    @inlineCallbacks
+    def begin(self):
+        &quot;&quot;&quot;
+        Start monitoring for AMP-based push notifications
+        &quot;&quot;&quot;
+        self._protocol = yield self._endpoint.connect(self._factory)
+        self._connected = True
+        pushkeys = self._ampPushkeys.keys()
+        yield self._subscribeToPushkeys(pushkeys)
+
+    @inlineCallbacks
+    def end(self):
+        &quot;&quot;&quot;
+        Finish monitoring push notifications.
+        &quot;&quot;&quot;
+        pushkeys = self._ampPushkeys.keys()
+        self._ampPushkeys = {}
+        yield self._unsubscribeFromPushkeys(pushkeys)
+
+        # Close the connection between client and server
+        yield self._protocol.transport.loseConnection()
+        self._connected = False
+
+
+    def addPushkey(self, pushkey, href):
+        &quot;&quot;&quot;
+        Register a pushkey associated with a specific calendar href.
+
+        @param pushkey: AMP pushkey returned by the server, used to listen to notifications
+        @type pushkey: C{str}
+        @param href: href of calendar home set. When the server triggers a push for the
+            associated pushkey, the callback will be fired with this href
+        @type href: C{str}
+
+        Example Usage:
+            monitor.addPushkey('/CalDAV/localhost/&lt;uid&gt;', '/calendars/__uids__/&lt;uid&gt;')
+        &quot;&quot;&quot;
+        self._ampPushkeys[pushkey] = href
+        return self._subscribeToPushkey(pushkey)
+
+    def removePushkey(self, pushkey):
+        &quot;&quot;&quot;
+        Unregister the calendar home associated with the specified pushkey
+        &quot;&quot;&quot;
+        if pushkey in self._ampPushkeys:
+            del self._ampPushkeys[pushkey]
+        return self._unsubscribeFromPushkey(pushkey)
+
+    def isSubscribedTo(self, href):
+        &quot;&quot;&quot;
+        Returns true if and only if the given calendar href is actively being monitored
+        &quot;&quot;&quot;
+        return href in self._ampPushkeys.itervalues()
+
+    @inlineCallbacks
+    def _subscribeToPushkeys(self, pushkeys):
+        for pushkey in pushkeys:
+            yield self._subscribeToPushkey(pushkey)
+
+    @inlineCallbacks
+    def _unsubscribeFromPushkeys(self, pushkeys):
+        for pushkey in pushkeys:
+            yield self._unsubscribeFromPushkey(pushkey)
+
+    def _subscribeToPushkey(self, pushkey):
+        if not self._connected:
+            return succeed(None)
+        return self._protocol.callRemote(SubscribeToID, token=self._token, id=pushkey)
+
+    def _unsubscribeFromPushkey(self, pushkey):
+        if not self._connected:
+            return succeed(None)
+        return self._protocol.callRemote(UnsubscribeFromID, id=pushkey)
+
+
+    def _receivedAMPPush(self, inboundID, dataChangedTimestamp, priority=5):
+        if inboundID in self._ampPushkeys:
+            # Only react if we're tracking this pushkey
+            href = self._ampPushkeys[inboundID]
+            self._callback(href)
+        else:
+            # Somehow we are not subscribed to this pushkey
+            pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrecordspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/records.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/records.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/records.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,36 @@
</span><ins>+from twisted.python.filepath import FilePath
+
+class DirectoryRecord(object):
+    def __init__(self, uid, password, commonName, email, guid):
+        self.uid = uid
+        self.password = password
+        self.commonName = commonName
+        self.email = email
+        self.guid = guid
+
+    def __repr__(self):
+        return &quot;Record(%s:%s %s %s %s)&quot; % (self.uid, self.password, self.commonName, self.email, self.guid)
+
+# def generateRecords(
+#     count, uidPattern=&quot;user%d&quot;, passwordPattern=&quot;user%d&quot;,
+#     namePattern=&quot;User %d&quot;, emailPattern=&quot;user%d@example.com&quot;,
+#     guidPattern=&quot;user%d&quot;
+# ):
+#     for i in xrange(count):
+#         i += 1
+#         uid = uidPattern % (i,)
+#         password = passwordPattern % (i,)
+#         name = namePattern % (i,)
+#         email = emailPattern % (i,)
+#         guid = guidPattern % (i,)
+#         yield DirectoryRecord(uid, password, name, email, guid)
+
+def recordsFromCSVFile(path):
+    if path:
+        pathObj = FilePath(path)
+    else:
+        pathObj = FilePath(__file__).sibling(&quot;accounts.csv&quot;)
+    return [
+        DirectoryRecord(*line.decode('utf-8').split(u','))
+        for line
+        in pathObj.getContent().splitlines()]
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11Profile"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/Profile (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/Profile                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/Profile        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,267 @@
</span><ins>+PROPFIND ./well-known/caldav                                -&gt;        /principals/
+        current-user-principal
+        principal-URL
+        resourcetype
+
+PROPFIND /principals/                                                -&gt;
+        current-user-principal                                                /principals/__uids__/&lt;uid&gt;
+        principal-URL                                                                ----
+        resourcetype                                                                collection
+
+OPTIONS /principals/__uids__/&lt;uid&gt;/
+
+PROPFIND /principals/__uids__/&lt;uid&gt;/
+        calendar-home-set                                                        /calendars/__uids__/&lt;uid&gt;/
+        calendar-user-address-set                                        mailto:user#@example.com
+                                                                                                urn:uuid:&lt;uid&gt;
+                                                                                                urn:x-uid:&lt;uid&gt;
+        current-user-principal                                                /principals/__uids__/&lt;uid&gt;/
+        displayname                                                                        User #
+        dropbox-home-URL                                                        /calendars/__uids__/&lt;uid&gt;/dropbox/
+        email-address-set                                                        user#@example.com
+        notification-URL                                                        /calendars/__uids__/&lt;uid&gt;/notification/
+        principal-collection-set                                        /principals/
+        principal-URL                                                                /principals/__uids__/&lt;uid&gt;/
+        resource-id                                                                        urn:x-uid:&lt;uid&gt;
+        schedule-inbox-URL                                                        /calendars/__uids__/&lt;uid&gt;/inbox/
+        schedule-outbox-URL                                                        /calendars/__uids__/&lt;uid&gt;/outbox/
+        supported-report-set                                                acl-principal-prop-set
+                                                                                                principal-match
+                                                                                                principal-property-search
+                                                                                                expand-property
+                                                                                                calendarserver-principal-search
+
+OPTIONS /principals/__uids__/&lt;uid&gt;
+
+REPORT /principals/                                                        -&gt; 
+        principal-search-property-set                                displayname
+                                                                                                email-address-set
+                                                                                                calendar-user-address-set
+                                                                                                calendar-user-type 
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/inbox/        -&gt;        
+        calendar-availability                                                ???
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/
+Depth 1
+        add-member                                                                        
+        allowed-sharing-modes                                                                        
+        autoprovisioned                                                                        
+        bulk-requests                                                                        
+        calendar-alarm                                                                        
+        calendar-color                                                                        
+        calendar-description                                                                        
+        calendar-free-busy-set                                                                        
+        calendar-order                                                                        
+        calendar-timezone                                                                        
+        current-user-privilege-set                                        all/read/read-free-busy/write/write-properties/write-content/bind/unbind/unlock/read-acl/write-acl/read-current-user-privilege-set                                
+        default-alarm-vevent-date                                                                        
+        default-alarm-vevent-datetime                                                                        
+        displayname                                                                        User #
+        getctag                                                                        
+        invite                                                                        
+        language-code                                                                        
+        location-code                                                                        
+        owner                                                                                /principals/__uids__/&lt;uid&gt;/
+        pre-publish-url                                                                        
+        publish-url                                                                        
+        push-transports                                                                        
+        pushkey                                                                                /CalDAV/localhost/&lt;uid&gt;/
+        quota-available-bytes                                                104857600
+        quota-used-bytes                                                        0
+        refreshrate                                                                        
+        resource-id                                                                        
+        resourcetype                                                                collection        
+        schedule-calendar-transp                                                                        
+        schedule-default-calendar-URL                                                                        
+        source                                                                        
+        subscribed-strip-alarms                                                                        
+        subscribed-strip-attachments                                                                        
+        subscribed-strip-todos                                                                        
+        supported-calendar-component-set                        VEVENT/VTODO                                                
+        supported-calendar-component-sets                                                                        
+        supported-report-set                                                acl-principal-prop-set/principal-match/principal-property-search/expand-property/calendarserver-principal-search/calendar-query/calendar-multiget/free-busy-query/addressbook-query/addressbook-multiget/sync-collection                        
+        sync-token                                                                        data:,36_58/&lt;hex&gt;
+         ** and more **
+
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/                -&gt;                default-alarm-vevent-date
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/                -&gt;                default-alarm-vevent-datetime
+
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        -&gt;                calendar-order
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        -&gt;                displayname
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        -&gt;                calendar-color
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        -&gt;                calendar-order
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        -&gt;                calendar-timezone
+
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;                calendar-order
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;                displayname
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;                calendar-color
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;                calendar-order
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;                calendar-timezone
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/calendar/-&gt;
+        getctag                                                                                37_63
+        sync-token                                                                        data:,37_63/&lt;hex&gt;
+
+REPORT /calendars/__uids__/&lt;uid&gt;/calendar/         -&gt;
+        getcontenttype
+        getetag
+REPORT /calendar/__uids__/&lt;uid&gt;/calendar/
+        getcontenttype
+        getetag
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/                        -&gt;
+        checksum-versions                                                        ???
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/calendar/        -&gt;
+        getctag                                                                                
+        sync-token                                                                                
+PROPFIND /calendars/__uids__/&lt;uid&gt;/calendar/
+        getcontenttype                                                                httpd/unix-directory
+        getetag                                                                                &quot;&lt;hex&gt;&quot;
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/                        -&gt; (again?) 
+        checksum-versions
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;
+        getctag
+        sync-token
+PROPFIND /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;
+        getcontenttype
+        getetag
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/inbox/        -&gt;
+        getctag
+        sync-token
+PROPFIND /calendars/__uids__/&lt;uid&gt;/inbox/        -&gt;
+        getcontenttype
+        getetag
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;
+        getctag
+        sync-token
+PROPFIND /calendars/__uids__/&lt;uid&gt;/tasks/        -&gt;
+        getcontenttype
+        getetag
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/notification/        -&gt;
+        getctag
+        sync-token
+PROPFIND /calendars/__uids__/&lt;uid&gt;/notification/        -&gt;
+        notificationtype
+        getetag
+
+REPORT /principals/__uids__/&lt;uid&gt;/
+        calendar-proxy-write-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+        calendar-proxy-read-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+
+REPORT /calendars/__uids__/&lt;uid&gt;/
+        sync-collection
+                sync-token
+                sync-level
+                *lots of properties*
+
+PROPFIND /calendars/__uids__/&lt;uid&gt;/inbox/
+        getctag
+        sync-token
+
+PROPFIND /principals/__uids__/&lt;uid&gt;/
+        calendar-proxy-write-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+        calendar-proxy-read-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+
+----------------------------------------------------------------
+Deep Refresh (CMD + SHIFT + R)
+
+PROPFIND /principals/__uids__/&lt;uid&gt;/
+        &lt;B:calendar-home-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:calendar-user-address-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:current-user-principal/&gt;
+    &lt;A:displayname/&gt;
+    &lt;C:dropbox-home-URL xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:email-address-set xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:notification-URL xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:principal-collection-set/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;A:resource-id/&gt;
+    &lt;B:schedule-inbox-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:schedule-outbox-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:supported-report-set/&gt;
+
+OPTIONS /principals/__uids__/10000000-0000-0000-0000-000000000001/
+
+REPORT /principals/
+        principal-search-property-set
+
+PROPFIND /calendars/__uids__/10000000-0000-0000-0000-000000000001/inbox/
+        calendar-availability
+
+PROPFIND /calendars/__uids__/10000000-0000-0000-0000-000000000001/
+Depth 1
+        &lt;A:add-member/&gt;
+    &lt;C:allowed-sharing-modes xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;D:autoprovisioned xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;E:bulk-requests xmlns:E=&quot;http://me.com/_namespace/&quot;/&gt;
+    &lt;B:calendar-alarm xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;D:calendar-color xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;B:calendar-description xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:calendar-free-busy-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;D:calendar-order xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;B:calendar-timezone xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:current-user-privilege-set/&gt;
+    &lt;B:default-alarm-vevent-date xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:default-alarm-vevent-datetime xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:displayname/&gt;
+    &lt;C:getctag xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:invite xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;D:language-code xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;D:location-code xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;A:owner/&gt;
+    &lt;C:pre-publish-url xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:publish-url xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:push-transports xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:pushkey xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:quota-available-bytes/&gt;
+    &lt;A:quota-used-bytes/&gt;
+    &lt;D:refreshrate xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;A:resource-id/&gt;
+    &lt;A:resourcetype/&gt;
+    &lt;B:schedule-calendar-transp xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:schedule-default-calendar-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;C:source xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:subscribed-strip-alarms xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:subscribed-strip-attachments xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:subscribed-strip-todos xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;B:supported-calendar-component-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:supported-calendar-component-sets xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:supported-report-set/&gt;
+    &lt;A:sync-token/&gt;
+
+PROPFIND on calendar/tasks/inbox/notifications as before
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+                                                                        
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11StartupProfile"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/StartupProfile        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,71 @@
</span><ins>+PROPFIND  ./well-known/caldav                                        - startup_well_known_propfind
+
+PROPFIND  /principals/                                                        - startup_principal_initial_propfind
+
+PROPFIND  /principals/__uids__/&lt;uid&gt;/                        - startup_principal_propfind
+
+REPORT    /principals/                                                        - startup_principals_report
+
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/inbox/                - ???
+        calendar-availability                                                
+
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/                        - poll_calendar_home_depth1_propfind
+
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/                        - startup_calendarhome_default_alarm_date_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/                        - startup_calendarhome_default_alarm_datetime_proppatch
+
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        - startup_calendar_order_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        - startup_calendar_displayname_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        - startup_calendar_color_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/calendar/        - startup_calendar_timezone_proppatch
+
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/                - startup_calendar_order_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/                - startup_calendar_displayname_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/                - startup_calendar_color_proppatch
+PROPPATCH /calendars/__uids__/&lt;uid&gt;/tasks/                - startup_calendar_timezone_proppatch
+
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/calendar/        - poll_calendar_propfind
+
+REPORT   /calendars/__uids__/&lt;uid&gt;/calendar/         - startup_query_events_depth1_report.request
+
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/calendar/        - poll_calendar_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/calendar/        - poll_calendar_depth1_propfind
+
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/tasks/                - poll_calendar_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/tasks/                - poll_calendar_depth1_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/inbox/                - poll_calendar_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/inbox/                - poll_calendar_depth1_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/tasks/                - poll_calendar_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/tasks/                - poll_calendar_depth1_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/notification/        - poll_calendar_propfind
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/notification/        - poll_notification_depth1_propfind
+
+REPORT    /principals/__uids__/&lt;uid&gt;/
+        calendar-proxy-write-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+        calendar-proxy-read-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+
+REPORT    /calendars/__uids__/&lt;uid&gt;/
+        sync-collection
+                sync-token
+                sync-level
+                *lots of properties*
+
+PROPFIND  /calendars/__uids__/&lt;uid&gt;/inbox/
+        getctag
+        sync-token
+
+PROPFIND  /principals/__uids__/&lt;uid&gt;/
+        calendar-proxy-write-for
+                calendar-user-address-set
+                email-address-set
+                displayname
+        calendar-proxy-read-for
+                calendar-user-address-set
+                email-address-set
+                displayname
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_calendar_depth1_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;A:getcontenttype/&gt;
+    &lt;A:getetag/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_calendar_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendar_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;C:getctag xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:sync-token/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_calendarhome_depth1_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,43 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;A:add-member/&gt;
+    &lt;C:allowed-sharing-modes xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;D:autoprovisioned xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;E:bulk-requests xmlns:E=&quot;http://me.com/_namespace/&quot;/&gt;
+    &lt;B:calendar-alarm xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;D:calendar-color xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;B:calendar-description xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:calendar-free-busy-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;D:calendar-order xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;B:calendar-timezone xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:current-user-privilege-set/&gt;
+    &lt;B:default-alarm-vevent-date xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:default-alarm-vevent-datetime xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:displayname/&gt;
+    &lt;C:getctag xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:invite xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;D:language-code xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;D:location-code xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;A:owner/&gt;
+    &lt;C:pre-publish-url xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:publish-url xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:push-transports xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:pushkey xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:quota-available-bytes/&gt;
+    &lt;A:quota-used-bytes/&gt;
+    &lt;D:refreshrate xmlns:D=&quot;http://apple.com/ns/ical/&quot;/&gt;
+    &lt;A:resource-id/&gt;
+    &lt;A:resourcetype/&gt;
+    &lt;B:schedule-calendar-transp xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:schedule-default-calendar-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;C:source xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:subscribed-strip-alarms xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:subscribed-strip-attachments xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:subscribed-strip-todos xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;B:supported-calendar-component-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:supported-calendar-component-sets xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:supported-report-set/&gt;
+    &lt;A:sync-token/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11poll_notification_depth1_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/poll_notification_depth1_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;A:getetag/&gt;
+    &lt;C:notificationtype xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11post_freebusyrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/post_freebusy.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,13 @@
</span><ins>+BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VFREEBUSY
+UID:4288F0F3-5C5B-4DF4-9AD8-B1E5FE3F5B97
+DTSTART:20150804T211500Z
+DTEND:20150804T231500Z
+ATTENDEE:urn:uuid:30000000-0000-0000-0000-000000000005
+DTSTAMP:20150727T203410Z
+ORGANIZER:mailto:user01@example.com
+END:VFREEBUSY
+END:VCALENDAR
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11principal_search_reportrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/principal_search_report.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,14 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;C:calendarserver-principal-search xmlns:C=&quot;http://calendarserver.org/ns/&quot; context=&quot;{context}&quot;&gt;
+  {searchTokens}
+  &lt;A:prop xmlns:A=&quot;DAV:&quot;&gt;
+    &lt;C:email-address-set/&gt;
+    &lt;B:calendar-user-type xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:calendar-user-address-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;C:last-name/&gt;
+    &lt;C:record-type/&gt;
+    &lt;A:displayname/&gt;
+    &lt;C:first-name/&gt;
+  &lt;/A:prop&gt;
+&lt;/C:calendarserver-principal-search&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11report_principal_searchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/report_principal_search.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,14 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;C:calendarserver-principal-search xmlns:C=&quot;http://calendarserver.org/ns/&quot; context=&quot;attendee&quot;&gt;
+  &lt;C:search-token&gt;%(search)s&lt;/C:search-token&gt;
+  &lt;A:prop xmlns:A=&quot;DAV:&quot;&gt;
+    &lt;B:calendar-user-type xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;C:email-address-set/&gt;
+    &lt;A:displayname/&gt;
+    &lt;C:first-name/&gt;
+    &lt;C:last-name/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;C:record-type/&gt;
+    &lt;B:calendar-user-address-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+  &lt;/A:prop&gt;
+&lt;/C:calendarserver-principal-search&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_color_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_color_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;D:calendar-color xmlns:D=&quot;http://apple.com/ns/ical/&quot; symbolic-color=&quot;orange&quot;&gt;#FD8208FF&lt;/D:calendar-color&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_description_proppatchrequestxml"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;B:calendar-description xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;some description&lt;/B:calendar-description&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_displayname_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;A:displayname&gt;calendar&lt;/A:displayname&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_order_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_order_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;D:calendar-order xmlns:D=&quot;http://apple.com/ns/ical/&quot;&gt;1&lt;/D:calendar-order&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_timezone_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,24 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;B:calendar-timezone xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;BEGIN:VCALENDAR&amp;#13;
+VERSION:2.0&amp;#13;
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN&amp;#13;
+CALSCALE:GREGORIAN&amp;#13;
+BEGIN:VTIMEZONE&amp;#13;
+TZID:America/Los_Angeles&amp;#13;
+BEGIN:DAYLIGHT&amp;#13;
+TZOFFSETFROM:-0800&amp;#13;
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU&amp;#13;
+DTSTART:20070311T020000&amp;#13;
+TZNAME:PDT&amp;#13;
+TZOFFSETTO:-0700&amp;#13;
+END:DAYLIGHT&amp;#13;
+BEGIN:STANDARD&amp;#13;
+TZOFFSETFROM:-0700&amp;#13;
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU&amp;#13;
+DTSTART:20071104T020000&amp;#13;
+TZNAME:PST&amp;#13;
+TZOFFSETTO:-0800&amp;#13;
+END:STANDARD&amp;#13;
+END:VTIMEZONE&amp;#13;
+END:VCALENDAR&amp;#13;
+&lt;/B:calendar-timezone&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendar_transparent_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,5 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;B:schedule-calendar-transp xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;&lt;B:transparent/&gt;&lt;/B:schedule-calendar-transp&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
+
+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;B:schedule-calendar-transp xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;&lt;B:opaque/&gt;&lt;/B:schedule-calendar-transp&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendarhome_default_alarm_date_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,9 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;B:default-alarm-vevent-date xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;BEGIN:VALARM&amp;#13;
+X-WR-ALARMUID:49F29226-D2D7-4464-AE22-0147EDEFB2B4&amp;#13;
+UID:49F29226-D2D7-4464-AE22-0147EDEFB2B4&amp;#13;
+TRIGGER:-PT15H&amp;#13;
+ATTACH;VALUE=URI:Basso&amp;#13;
+ACTION:AUDIO&amp;#13;
+END:VALARM&amp;#13;
+&lt;/B:default-alarm-vevent-date&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_calendarhome_default_alarm_datetime_proppatchrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propertyupdate xmlns:A=&quot;DAV:&quot;&gt;&lt;A:set&gt;&lt;A:prop&gt;&lt;B:default-alarm-vevent-datetime xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;BEGIN:VALARM&amp;#13;
+X-WR-ALARMUID:4AD03A33-54A6-42BE-A157-47273DD60803&amp;#13;
+UID:4AD03A33-54A6-42BE-A157-47273DD60803&amp;#13;
+TRIGGER;VALUE=DATE-TIME:19760401T005545Z&amp;#13;
+ACTION:NONE&amp;#13;
+END:VALARM&amp;#13;
+&lt;/B:default-alarm-vevent-datetime&gt;&lt;/A:prop&gt;&lt;/A:set&gt;&lt;/A:propertyupdate&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_create_calendarrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_create_calendar.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,39 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;B:mkcalendar xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;
+  &lt;A:set xmlns:A=&quot;DAV:&quot;&gt;
+    &lt;A:prop&gt;
+      &lt;D:calendar-order xmlns:D=&quot;http://apple.com/ns/ical/&quot;&gt;{order}&lt;/D:calendar-order&gt;
+      &lt;B:supported-calendar-component-set&gt;
+        &lt;B:comp name=&quot;{component_type}&quot;/&gt;
+      &lt;/B:supported-calendar-component-set&gt;
+      &lt;D:calendar-color xmlns:D=&quot;http://apple.com/ns/ical/&quot; symbolic-color=&quot;custom&quot;&gt;#{color}&lt;/D:calendar-color&gt;
+      &lt;B:calendar-timezone&gt;BEGIN:VCALENDAR&amp;#13;
+VERSION:2.0&amp;#13;
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN&amp;#13;
+CALSCALE:GREGORIAN&amp;#13;
+BEGIN:VTIMEZONE&amp;#13;
+TZID:America/Los_Angeles&amp;#13;
+BEGIN:DAYLIGHT&amp;#13;
+TZOFFSETFROM:-0800&amp;#13;
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU&amp;#13;
+DTSTART:20070311T020000&amp;#13;
+TZNAME:PDT&amp;#13;
+TZOFFSETTO:-0700&amp;#13;
+END:DAYLIGHT&amp;#13;
+BEGIN:STANDARD&amp;#13;
+TZOFFSETFROM:-0700&amp;#13;
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU&amp;#13;
+DTSTART:20071104T020000&amp;#13;
+TZNAME:PST&amp;#13;
+TZOFFSETTO:-0800&amp;#13;
+END:STANDARD&amp;#13;
+END:VTIMEZONE&amp;#13;
+END:VCALENDAR&amp;#13;
+&lt;/B:calendar-timezone&gt;
+      &lt;A:displayname&gt;{name}&lt;/A:displayname&gt;
+      &lt;B:schedule-calendar-transp&gt;
+        &lt;B:opaque/&gt;
+      &lt;/B:schedule-calendar-transp&gt;
+    &lt;/A:prop&gt;
+  &lt;/A:set&gt;
+&lt;/B:mkcalendar&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_delegate_principal_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_delegate_principal_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,19 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;B:allowed-calendar-component-set xmlns:B=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:calendar-home-set xmlns:C=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;C:calendar-user-address-set xmlns:C=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:current-user-principal/&gt;
+    &lt;A:displayname/&gt;
+    &lt;B:dropbox-home-URL xmlns:B=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;B:email-address-set xmlns:B=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;B:notification-URL xmlns:B=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:principal-collection-set/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;A:resource-id/&gt;
+    &lt;C:schedule-inbox-URL xmlns:C=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;C:schedule-outbox-URL xmlns:C=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:supported-report-set/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principal_expandrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_expand.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,13 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:expand-property xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:property name=&quot;calendar-proxy-read-for&quot; namespace=&quot;http://calendarserver.org/ns/&quot;&gt;
+    &lt;A:property name=&quot;calendar-user-address-set&quot; namespace=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:property name=&quot;email-address-set&quot; namespace=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:property name=&quot;displayname&quot; namespace=&quot;DAV:&quot;/&gt;
+  &lt;/A:property&gt;
+  &lt;A:property name=&quot;calendar-proxy-write-for&quot; namespace=&quot;http://calendarserver.org/ns/&quot;&gt;
+    &lt;A:property name=&quot;calendar-user-address-set&quot; namespace=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:property name=&quot;email-address-set&quot; namespace=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:property name=&quot;displayname&quot; namespace=&quot;DAV:&quot;/&gt;
+  &lt;/A:property&gt;
+&lt;/A:expand-property&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principal_initial_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_initial_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;A:current-user-principal/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;A:resourcetype/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principal_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principal_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,18 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;B:calendar-home-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:calendar-user-address-set xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:current-user-principal/&gt;
+    &lt;A:displayname/&gt;
+    &lt;C:dropbox-home-URL xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:email-address-set xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;C:notification-URL xmlns:C=&quot;http://calendarserver.org/ns/&quot;/&gt;
+    &lt;A:principal-collection-set/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;A:resource-id/&gt;
+    &lt;B:schedule-inbox-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;B:schedule-outbox-URL xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;/&gt;
+    &lt;A:supported-report-set/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_principals_reportrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_principals_report.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:principal-search-property-set xmlns:A=&quot;DAV:&quot;/&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_query_events_depth1_reportrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_query_events_depth1_report.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,14 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;B:calendar-query xmlns:B=&quot;urn:ietf:params:xml:ns:caldav&quot;&gt;
+  &lt;A:prop xmlns:A=&quot;DAV:&quot;&gt;
+    &lt;A:getetag/&gt;
+    &lt;A:getcontenttype/&gt;
+  &lt;/A:prop&gt;
+  &lt;B:filter&gt;
+    &lt;B:comp-filter name=&quot;VCALENDAR&quot;&gt;
+      &lt;B:comp-filter name=&quot;VEVENT&quot;&gt;
+        &lt;B:time-range start=&quot;20150630T010101Z&quot; end=&quot;20150721T010101Z&quot;/&gt;
+      &lt;/B:comp-filter&gt;
+    &lt;/B:comp-filter&gt;
+  &lt;/B:filter&gt;
+&lt;/B:calendar-query&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequestdataOS_X_10_11startup_well_known_propfindrequest"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/request-data/OS_X_10_11/startup_well_known_propfind.request        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;A:propfind xmlns:A=&quot;DAV:&quot;&gt;
+  &lt;A:prop&gt;
+    &lt;A:current-user-principal/&gt;
+    &lt;A:principal-URL/&gt;
+    &lt;A:resourcetype/&gt;
+  &lt;/A:prop&gt;
+&lt;/A:propfind&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestrequesterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/requester.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/requester.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/requester.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,307 @@
</span><ins>+from caldavclientlibrary.protocol.webdav.propfindparser import PropFindParser
+
+from contrib.performance.httpauth import AuthHandlerAgent
+from contrib.performance.httpclient import StringProducer, readBody
+
+from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT
+from twisted.web.http_headers import Headers
+from twisted.web.client import Agent, ContentDecoderAgent, GzipDecoder, \
+    _DeprecatedToCurrentPolicyForHTTPS
+
+from twisted.python.log import msg
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.ssl import ClientContextFactory
+
+&quot;&quot;&quot;
+TODO
+Finish the comment table
+THink about better ways to do default headers
+Try to log messages in a more intelligent way
+
+&quot;&quot;&quot;
+
+class Requester(object):
+    &quot;&quot;&quot;
+    Utility to create requests on behalf of a client. Public methods are:
+    method     url     body     headers     status     method_label
+    ------------------------------------------------------------------------
+    GET        req     ---
+    POST       req     req
+    PUT        req     req*
+    DELETE     req     ---
+    PROPFIND   req
+    PROPPATCH  req
+    REPORT     req
+    MKCALENDAR req
+
+    req: required
+    opt: optional
+    ---: disallowed
+    All of these rely on a private method _request
+
+    &quot;&quot;&quot;
+
+    def __init__(
+        self,
+        root,
+        headers,
+        title,
+        uid,
+        client_id,
+        auth,
+        reactor
+    ):
+        self._root = root
+        self._headers = headers
+        self._title = title
+        self._uid = uid
+        self._client_id = client_id
+
+        self._reactor = reactor
+
+        # The server might use gzip encoding
+        agent = Agent(
+            self._reactor,
+            contextFactory=_DeprecatedToCurrentPolicyForHTTPS(WebClientContextFactory()),
+        )
+        agent = ContentDecoderAgent(agent, [(&quot;gzip&quot;, GzipDecoder)])
+        self._agent = AuthHandlerAgent(agent, auth)
+
+    def _addDefaultHeaders(self, headers):
+        &quot;&quot;&quot;
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        &quot;&quot;&quot;
+        for k, v in self._headers.iteritems():
+            headers.setRawHeaders(k, v)
+
+    @inlineCallbacks
+    def _request(self, method, url, expectedResponseCodes, headers=None, body=None, method_label=None):
+        &quot;&quot;&quot;
+        Execute a request and check against the expected response codes.
+        &quot;&quot;&quot;
+        if type(expectedResponseCodes) is int:
+            expectedResponseCodes = (expectedResponseCodes,)
+        if not method_label:
+            method_label = method
+        if headers is None:
+            headers = Headers({})
+        self._addDefaultHeaders(headers)
+        url = self._root + url.encode('utf-8')
+
+        msg(
+            type=&quot;request&quot;,
+            method=method_label,
+            url=url,
+            user=self._uid,
+            client_type=self._title,
+            client_id=self._client_id,
+        )
+
+
+        before = self._reactor.seconds()
+        response = yield self._agent.request(method, url, headers, StringProducer(body) if body else None)
+
+        # XXX This is time to receive response headers, not time
+        # to receive full response.  Should measure the latter, if
+        # not both.
+        after = self._reactor.seconds()
+
+        success = response.code in expectedResponseCodes
+
+        msg(
+            type=&quot;response&quot;,
+            success=success,
+            method=method_label,
+            headers=headers,
+            body=body,
+            code=response.code,
+            user=self._uid,
+            client_type=self._title,
+            client_id=self._client_id,
+            duration=(after - before),
+            url=url,
+        )
+
+        if success:
+            returnValue(response)
+
+        raise IncorrectResponseCode(expectedResponseCodes, response)
+
+
+    @inlineCallbacks
+    def get(self, url, method_label=None):
+        response = yield self._request(
+            'GET',
+            url,
+            (OK,),
+            method_label=method_label
+        )
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def post(self, url, body, headers=None, method_label=None):
+        response = yield self._request(
+            'POST',
+            url,
+            (OK, CREATED, MULTI_STATUS),
+            headers=headers,
+            body=body,
+            method_label=method_label
+        )
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def put(self, expectedResponseCodes, url, component, headers=None, method_label=None):
+        response = yield self._request(
+            'PUT',
+            url,
+            expectedResponseCodes,
+            headers=headers,
+            body=component.getTextWithTimezones(includeTimezones=True),
+            method_label=method_label
+        )
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def delete(self, url, method_label=None):
+        response = yield self._request(
+            'DELETE',
+            url,
+            (NO_CONTENT,),
+            method_label=method_label
+        )
+        returnValue(response)
+
+
+    def _parseMultiStatus(self, response, otherTokens=False):
+        &quot;&quot;&quot;
+        Parse a &lt;multistatus&gt; - might need to return other top-level elements
+        in the response - e.g. DAV:sync-token
+        I{PROPFIND} request for the principal URL.
+
+        @type response: C{str}
+        @rtype: C{cls}
+        &quot;&quot;&quot;
+        parser = PropFindParser()
+        parser.parseData(response)
+        if otherTokens:
+            return (parser.getResults(), parser.getOthers(),)
+        else:
+            return parser.getResults()
+
+    @inlineCallbacks
+    def propfind(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), method_label=None):
+        &quot;&quot;&quot;
+        Issue a PROPFIND on the chosen URL
+        &quot;&quot;&quot;
+        hdrs = Headers({'content-type': ['text/xml']})
+        if depth is not None:
+            hdrs.addRawHeader('depth', depth)
+        response = yield self._request(
+            'PROPFIND',
+            url,
+            allowedStatus,
+            headers=hdrs,
+            body=body,
+            method_label=method_label,
+        )
+
+        body = yield readBody(response)
+        result = self._parseMultiStatus(body) if response.code == MULTI_STATUS else None
+
+        returnValue((response, result,))
+
+    @inlineCallbacks
+    def proppatch(self, url, body, method_label=None):
+        &quot;&quot;&quot;
+        Issue a PROPPATCH on the chosen URL
+        &quot;&quot;&quot;
+        hdrs = Headers({'content-type': ['text/xml']})
+        response = yield self._request(
+            'PROPPATCH',
+            url,
+            (OK, MULTI_STATUS,),
+            headers=hdrs,
+            body=body,
+            method_label=method_label,
+        )
+        if response.code == MULTI_STATUS:
+            body = yield readBody(response)
+            result = self._parseMultiStatus(body)
+            returnValue(result)
+        else:
+            returnValue(None)
+
+    @inlineCallbacks
+    def report(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), otherTokens=False, method_label=None):
+        &quot;&quot;&quot;
+        Issue a REPORT on the chosen URL
+        &quot;&quot;&quot;
+        hdrs = Headers({'content-type': ['text/xml']})
+        if depth is not None:
+            hdrs.addRawHeader('depth', depth)
+        response = yield self._request(
+            'REPORT',
+            url,
+            allowedStatus,
+            headers=hdrs,
+            body=body,
+            method_label=method_label,
+        )
+
+        body = yield readBody(response)
+        result = self._parseMultiStatus(body, otherTokens) if response.code == MULTI_STATUS else None
+
+        returnValue(result)
+
+    @inlineCallbacks
+    def mkcalendar(self, url, body, method_label=None):
+        &quot;&quot;&quot;
+        Issue a MKCALENDAR on the chosen URL with the given body
+        url: an href like /calendars/__uids__/&lt;user-uid&gt;/&lt;calendar-uid&gt;/
+        body: the XML body of the request
+        &quot;&quot;&quot;
+        headers = Headers({'content-type': ['text/xml']})
+        response = yield self._request(
+            'MKCALENDAR',
+            url,
+            (CREATED,),
+            headers=headers,
+            body=body,
+            method_label=method_label
+        )
+        body = yield readBody(response)
+        returnValue(body)
+
+
+class IncorrectResponseCode(Exception):
+    &quot;&quot;&quot;
+    Raised when a response has a code other than the one expected.
+
+    @ivar expected: The response codes which was expected.
+    @type expected: C{tuple} of C{int}
+
+    @ivar response: The response which was received
+    @type response: L{twisted.web.client.Response}
+    &quot;&quot;&quot;
+    def __init__(self, expected, response):
+        self.expected = expected
+        self.response = response
+
+    def __repr__(self):
+        return &quot;%s &quot;
+
+
+class WebClientContextFactory(ClientContextFactory):
+    &quot;&quot;&quot;
+    A web context factory which ignores the hostname and port and does no
+    certificate verification.
+    &quot;&quot;&quot;
+    def getContext(self, hostname, port):
+        return ClientContextFactory.getContext(self)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestresourcespy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/resources.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/resources.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/resources.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,214 @@
</span><ins>+from twistedcaldav.ical import Component
+
+from urlparse import urljoin
+
+from caldavclientlibrary.protocol.caldav.definitions import caldavxml
+
+from caldavclientlibrary.protocol.calendarserver.invite import AddInvitees, RemoveInvitee, InviteUser
+from caldavclientlibrary.protocol.webdav.proppatch import PropPatch
+
+import os
+
+from xml.etree import ElementTree
+
+def u2str(data):
+    return data.encode(&quot;utf-8&quot;) if type(data) is unicode else data
+
+
+class Event(object):
+    def __init__(self, serializeBasePath, url, etag, component=None):
+        self.serializeBasePath = serializeBasePath
+        self.url = url
+        self.etag = etag
+        self.scheduleTag = None
+        if component is not None:
+            self.component = component
+        self.uid = component.resourceUID() if component is not None else None
+
+    def getUID(self):
+        &quot;&quot;&quot;
+        Return the UID of the calendar resource.
+        &quot;&quot;&quot;
+        return self.uid
+
+
+    def serializePath(self):
+        if self.serializeBasePath:
+            calendar = os.path.join(self.serializeBasePath, self.url.split(&quot;/&quot;)[-2])
+            if not os.path.exists(calendar):
+                os.makedirs(calendar)
+            return os.path.join(calendar, self.url.split(&quot;/&quot;)[-1])
+        else:
+            return None
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a dict of the data so we can serialize as JSON.
+        &quot;&quot;&quot;
+
+        result = {}
+        for attr in (&quot;url&quot;, &quot;etag&quot;, &quot;scheduleTag&quot;, &quot;uid&quot;,):
+            result[attr] = getattr(self, attr)
+        return result
+
+
+    @staticmethod
+    def deserialize(serializeLocation, data):
+        &quot;&quot;&quot;
+        Convert dict (deserialized from JSON) into an L{Event}.
+        &quot;&quot;&quot;
+
+        event = Event(serializeLocation, None, None)
+        for attr in (&quot;url&quot;, &quot;etag&quot;, &quot;scheduleTag&quot;, &quot;uid&quot;,):
+            setattr(event, attr, u2str(data[attr]))
+        return event
+
+
+    @property
+    def component(self):
+        &quot;&quot;&quot;
+        Data always read from disk - never cached in the object.
+        &quot;&quot;&quot;
+        path = self.serializePath()
+        if path and os.path.exists(path):
+            f = open(path)
+            comp = Component.fromString(f.read())
+            f.close()
+            return comp
+        else:
+            return None
+
+
+    @component.setter
+    def component(self, component):
+        &quot;&quot;&quot;
+        Data always written to disk - never cached on the object.
+        &quot;&quot;&quot;
+        path = self.serializePath()
+        if path:
+            if component is None:
+                os.remove(path)
+            else:
+                f = open(path, &quot;w&quot;)
+                f.write(str(component))
+                f.close()
+        self.uid = component.resourceUID() if component is not None else None
+
+
+    def removed(self):
+        &quot;&quot;&quot;
+        Resource no longer exists on the server - remove associated data.
+        &quot;&quot;&quot;
+        path = self.serializePath()
+        if path and os.path.exists(path):
+            os.remove(path)
+
+
+
+class Calendar(object):
+    def __init__(self, resourceType, componentTypes, name, url, changeToken):
+        self.resourceType = resourceType
+        self.componentTypes = componentTypes
+        self.name = name
+        self.url = url
+        self.changeToken = changeToken
+        self.events = {}
+        if self.name is None and self.url is not None:
+            self.name = self.url.rstrip(&quot;/&quot;).split(&quot;/&quot;)[-1]
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a dict of the data so we can serialize as JSON.
+        &quot;&quot;&quot;
+
+        result = {}
+        for attr in (&quot;resourceType&quot;, &quot;name&quot;, &quot;url&quot;, &quot;changeToken&quot;):
+            result[attr] = getattr(self, attr)
+        result[&quot;componentTypes&quot;] = list(sorted(self.componentTypes))
+        result[&quot;events&quot;] = sorted(self.events.keys())
+        return result
+
+
+    @staticmethod
+    def deserialize(data, events):
+        &quot;&quot;&quot;
+        Convert dict (deserialized from JSON) into an L{Calendar}.
+        &quot;&quot;&quot;
+
+        calendar = Calendar(None, None, None, None, None)
+        for attr in (&quot;resourceType&quot;, &quot;name&quot;, &quot;url&quot;, &quot;changeToken&quot;):
+            setattr(calendar, attr, u2str(data[attr]))
+        calendar.componentTypes = set(map(u2str, data[&quot;componentTypes&quot;]))
+
+        for event in data[&quot;events&quot;]:
+            url = urljoin(calendar.url, event)
+            if url in events:
+                calendar.events[event] = events[url]
+            else:
+                # Ughh - an event is missing - force changeToken to empty to trigger full resync
+                calendar.changeToken = &quot;&quot;
+        return calendar
+
+
+    @staticmethod
+    def addInviteeXML(uid, summary, readwrite=True):
+        return AddInvitees(None, '/', [uid], readwrite, summary=summary).request_data.text
+
+
+    @staticmethod
+    def removeInviteeXML(uid):
+        invitee = InviteUser()
+        # Usually an InviteUser is populated through .parseFromUser, but we only care about a uid
+        invitee.user_uid = uid
+        return RemoveInvitee(None, '/', invitee).request_data.text
+
+
+    @staticmethod
+    def _buildPropPatchXML(element):
+        &quot;&quot;&quot;
+        Change the specified element on the calendar given by href.
+        &quot;&quot;&quot;
+        return PropPatch(None, '/', [element]).request_data.text
+
+
+    # def setCalendarDisplayName(self, calendar, displayname):
+    #     self._calendars[calendar.url].displayname = displayname # Update the cached copy
+    #     qn = davxml.displayname
+    #     el = ElementTree.Element(qn)
+    #     el.text = displayname
+    #     yield self._property_update(el)
+
+    @staticmethod
+    def setCalendarDescriptionXML(calendar, description):
+        qn = caldavxml.calendar_description
+        el = ElementTree.Element(qn)
+        el.text = description
+        return Calendar._buildPropPatchXML(el)
+
+
+    @staticmethod
+    def setCalendarTransparencyXML(calendar, isTransparent):
+        qn = caldavxml.schedule_calendar_transp
+        el = ElementTree.Element(qn)
+        transp_qn = caldavxml.transparent if isTransparent else caldavxml.opaque
+        ElementTree.SubElement(el, transp_qn)
+        return Calendar._buildPropPatchXML(el)
+
+
+    @staticmethod
+    def setCalendarColorXML(calendar, color):
+        &quot;&quot;&quot; color is an RGBA string, e.g. &quot;#FF0088FF&quot; &quot;&quot;&quot;
+        qn = ElementTree.QName('http://apple.com/ns/ical/', 'calendar-color')
+        el = ElementTree.Element(qn)
+        el.text = color
+        el.set('symbolic-color', 'custom')
+        return Calendar._buildPropPatchXML(el)
+
+
+    @staticmethod
+    def setCalendarOrder(self, calendar, order):
+        qn = ElementTree.QName('http://apple.com/ns/ical/', 'calendar-order')
+        el = ElementTree.Element(qn)
+        el.text = order
+        return Calendar._buildPropPatchXML(el)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsandboxpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sandbox.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sandbox.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sandbox.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,33 @@
</span><ins>+import numpy as np
+from scipy.optimize import curve_fit
+from scipy import stats
+
+
+values = [(0,16704),(1,36939),(2,13483),(3,6779),(4,4325),(5,2803),(6,2088),(7,1697),(8,1283),(9,976),(10,829),(11,623),(12,512),(13,449),(14,368),(15,300),(16,257),(17,236),(18,238),(19,181),(20,174),(21,171),(22,145),(23,122),(24,104),(25,105),(26,85),(27,66),(28,56),(29,58),(30,58),(31,64),(32,81),(33,37),(34,45),(35,37),(36,33),(37,35),(38,36),(39,17),(40,22),(41,19),(42,16),(43,32),(44,20),(45,15),(46,18),(47,11),(48,8),(49,24),(50,10),(51,22),(52,12),(53,12),(54,12),(55,16),(56,17),(57,12),(58,9),(59,6),(60,2),(61,7),(62,9),(63,5),(64,9),(65,15),(66,10),(67,4),(68,3),(69,4),(70,5),(71,13),(72,5),(73,4),(74,2),(75,5),(76,7),(77,6),(78,4),(79,3),(80,2),(81,2),(82,1),(84,3),(85,2),(86,1),(90,4),(91,1),(92,3),(93,1),(94,3),(95,1),(96,4),(97,2),(98,1),(100,2),(101,1),(104,1),(105,3),(106,1),(108,3),(109,1),(110,1),(113,1),(114,2),(115,2),(116,10),(117,1),(118,1),(119,1)]
+
+def toDistribution(values):
+    &quot;&quot;&quot;
+    Converts an array of (x,y) pairs to a distribution object
+    &quot;&quot;&quot;
+    xdata, ydata = map(np.array(zip(*values)))
+
+    popt, pcov = curve_fit(normalPDF, xdata, ydata)
+    return popt, pcov
+
+
+def normalPDF(xdata, mu, sigma):
+    rv = stats.norm(mu, sigma)
+    return rv.pdf(xdata)
+
+def lognormPDF(xdata, mu, sigma):
+    &quot;&quot;&quot;
+    If log(x) is normally distributed with mean mu and variance sigma**2
+    then x is log-normally distributed with shape parameter sigma and scale parameter exp(mu).
+    &quot;&quot;&quot;
+    rv = stats.lognorm(sigma, 0, np.exp(mu))
+    return rv.pdf(xdata)
+
+line = np.linspace(1, 10, 100)
+realnorm = lognormPDF(line, 1, 3)
+
+popt, pcov = toDistribution(np.array(zip(line, realnorm)))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsREADMEmd"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/README.md (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/README.md                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/README.md        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,3 @@
</span><ins>+# Settings
+
+Documentation about how to use and manipulate the settings files go here
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettings__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/__init__.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/__init__.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,16 @@
</span><ins>+##
+# Copyright (c) 2012-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistcalendarsonlyplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/calendars-only.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/calendars-only.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/calendars-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,138 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+                        &lt;dict&gt;
+                                &lt;!-- Here is a El Capitan iCal simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_11&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.11&lt;/string&gt;
+
+                                        &lt;!-- Client can poll the calendar home at some interval. This is
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;5&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS X 10.11 can wait for notifications
+                                                about calendar home changes instead of polling for them periodically. If
+                                                this option is true, then look for the server advertisement for xmpp push
+                                                and use it if possible. Still fall back to polling if there is no xmpp push
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;true /&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an calendar-creating profile, which will periodically create a new calendar --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.CalendarMaker&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true /&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new calendar. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;15&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically change something about the event. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.CalendarUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true /&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to update an existing calendar. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile randomly shares calendars. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.CalendarSharer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false /&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to share an existing calendar. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;30&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile randomly deletes calendars. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.CalendarDeleter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true /&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to delete an existing calendar. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;30&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistclientsoldplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients-old.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients-old.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients-old.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,527 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a OS X client simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.clients.OS_X_10_7&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the OS_X_10_7 instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.7&lt;/string&gt;
+        
+                                        &lt;!-- OS_X_10_7 can poll the calendar home at some interval. This is
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;30&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications
+                                                about calendar home changes instead of polling for them periodically. If
+                                                this option is true, then look for the server advertisement for xmpp push
+                                                and use it if possible. Still fall back to polling if there is no xmpp push
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;true/&gt;
+                                        &lt;key&gt;ampPushHost&lt;/key&gt;
+                                        &lt;string&gt;localhost&lt;/string&gt;
+                                        &lt;key&gt;ampPushPort&lt;/key&gt;
+                                        &lt;integer&gt;62311&lt;/integer&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an event-creating profile, which will periodically create
+                                                new events at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
+                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.NormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
+                                                                        &lt;key&gt;mu&lt;/key&gt;
+                                                                        &lt;integer&gt;60&lt;/integer&gt;
+
+                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
+                                                                        &lt;key&gt;sigma&lt;/key&gt;
+                                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define the distribution of who will be invited to an event.
+                                                        
+                                                                When inviteeClumping is turned on each invitee is based on a sample of
+                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
+                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
+                                                                those larger numbers will simply fail (the sim will report that situation).
+                                                                
+                                                                When inviteeClumping is off invitees will be sampled across an entire
+                                                                range of account indexes. In this case the distribution ought to be a
+                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
+                                                        --&gt;
+                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.UniformIntegerDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;min&lt;/key&gt;
+                                                                        &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;max&lt;/key&gt;
+                                                                        &lt;integer&gt;99&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
+                                                        
+                                                                LogNormal is the best fit to observed data.
+
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
+                                                                mode should typically be 1, and mean whatever matches the user behavior.
+                                                                Our typical mean is 6.                                                         
+                                                             --&gt;
+                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.LogNormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mode - peak--&gt;
+                                                                        &lt;key&gt;mode&lt;/key&gt;
+                                                                        &lt;integer&gt;1&lt;/integer&gt;
+                                                                        &lt;!-- mean - average--&gt;
+                                                                        &lt;key&gt;median&lt;/key&gt;
+                                                                        &lt;integer&gt;6&lt;/integer&gt;
+                                                                        &lt;!-- maximum --&gt;
+                                                                        &lt;key&gt;maximum&lt;/key&gt;
+                                                                        &lt;real&gt;60&lt;/real&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
+                                             handles replies received. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define how long to wait after seeing a new invitation before
+                                                                accepting it.
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
+                                                                (i.e., half of the user have accepted by that time).                                                                
+                                                        --&gt;
+                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.LogNormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mode - peak--&gt;
+                                                                        &lt;key&gt;mode&lt;/key&gt;
+                                                                        &lt;integer&gt;300&lt;/integer&gt;
+                                                                        &lt;!-- median - 50% done--&gt;
+                                                                        &lt;key&gt;median&lt;/key&gt;
+                                                                        &lt;integer&gt;1800&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- A task-creating profile, which will periodically create
+                                                new tasks at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new task. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- A task-updating profile, which will periodically create
+                                                new tasks at a random time on a random calendar and then
+                                                update them in some way --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.TaskUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new task. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;60&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistclientsplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,89 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a OS X client simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.clients.OS_X_10_11&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the OS_X_10_7 instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.11&lt;/string&gt;
+
+                                        &lt;!-- OS_X_10_7 can poll the calendar home at some interval. This is
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;30&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises AMP push, the client can wait for notifications about calendar home changes in addition to polling them periodically. If this option is true, look for the server advertisement for AMP push and use it if possible
+                                                about calendar home changes instead of polling for them periodically. If
+                                                this option is true, then look for the server advertisement for xmpp push
+                                                and use it if possible. Still fall back to polling if there is no xmpp push
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;true/&gt;
+                                        &lt;key&gt;ampPushHost&lt;/key&gt;
+                                        &lt;string&gt;localhost&lt;/string&gt;
+                                        &lt;key&gt;ampPushPort&lt;/key&gt;
+                                        &lt;integer&gt;62311&lt;/integer&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;2&lt;/integer&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistconfigdistplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.dist.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.dist.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.dist.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,184 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- This is a distributed orchestrator configuration; 'workers' is a list of
+                                                        shell commands to run sub-processes.
+                                                        --&gt;
+                &lt;key&gt;workers&lt;/key&gt;
+                &lt;array&gt;
+                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
+                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
+                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
+                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
+                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
+                        &lt;string&gt;./bin/python contrib/performance/loadtest/ampsim.py&lt;/string&gt;
+                &lt;/array&gt;
+
+                &lt;!-- Identify the server to be load tested. --&gt;
+                &lt;key&gt;server&lt;/key&gt;
+                &lt;string&gt;https://127.0.0.1:8443&lt;/string&gt;
+
+                &lt;!-- Configure Admin Web UI. --&gt;
+                &lt;key&gt;webadmin&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;key&gt;enabled&lt;/key&gt;
+                        &lt;true/&gt;
+
+                        &lt;key&gt;HTTPPort&lt;/key&gt;
+                        &lt;integer&gt;8080&lt;/integer&gt;
+                &lt;/dict&gt;
+
+                &lt;!--  Define whether server supports stats socket. --&gt;
+                &lt;key&gt;serverStats&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;key&gt;enabled&lt;/key&gt;
+                        &lt;true/&gt;
+                        &lt;key&gt;Port&lt;/key&gt;
+                        &lt;integer&gt;8100&lt;/integer&gt;
+                &lt;/dict&gt;
+
+                &lt;!--  Define whether client data should be re-used. It will always be saved to the specified path.--&gt;
+                &lt;key&gt;clientDataSerialization&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;key&gt;UseOldData&lt;/key&gt;
+                        &lt;true/&gt;
+                        &lt;key&gt;Path&lt;/key&gt;
+                        &lt;string&gt;/tmp/sim&lt;/string&gt;
+                &lt;/dict&gt;
+
+                &lt;!-- Define the credentials of the clients which will be used to load test
+                        the server. These credentials must already be valid on the server. --&gt;
+                &lt;key&gt;accounts&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;!-- The loader is the fully-qualified Python name of a callable which
+                                returns a list of directory service records defining all of the client accounts
+                                to use. contrib.performance.loadtest.sim.recordsFromCSVFile reads username,
+                                password, mailto triples from a CSV file and returns them as a list of faked
+                                directory service records. --&gt;
+                        &lt;key&gt;loader&lt;/key&gt;
+                        &lt;string&gt;contrib.performance.loadtest.sim.recordsFromCSVFile&lt;/string&gt;
+
+                        &lt;!-- Keyword arguments may be passed to the loader. --&gt;
+                        &lt;key&gt;params&lt;/key&gt;
+                        &lt;dict&gt;
+                                &lt;!-- recordsFromCSVFile interprets the path relative to the config.plist,
+                                        to make it independent of the script's working directory while still allowing
+                                        a relative path. This isn't a great solution. --&gt;
+                                &lt;key&gt;path&lt;/key&gt;
+                                &lt;string&gt;contrib/performance/loadtest/accounts.csv&lt;/string&gt;
+                        &lt;/dict&gt;
+                &lt;/dict&gt;
+
+                &lt;!-- Define how many clients will participate in the load test and how
+                        they will show up. --&gt;
+                &lt;key&gt;arrival&lt;/key&gt;
+                &lt;dict&gt;
+
+                        &lt;!-- Specify a class which creates new clients and introduces them into
+                                the test. contrib.performance.loadtest.population.SmoothRampUp introduces
+                                groups of new clients at fixed intervals up to a maximum. The size of the
+                                group, interval, and maximum are configured by the parameters below. The
+                                total number of clients is groups * groupSize, which needs to be no larger
+                                than the number of credentials created in the accounts section. --&gt;
+                        &lt;key&gt;factory&lt;/key&gt;
+                        &lt;string&gt;contrib.performance.loadtest.population.SmoothRampUp&lt;/string&gt;
+
+                        &lt;key&gt;params&lt;/key&gt;
+                        &lt;dict&gt;
+                                &lt;!-- groups gives the total number of groups of clients to introduce. --&gt;
+                                &lt;key&gt;groups&lt;/key&gt;
+                                &lt;integer&gt;99&lt;/integer&gt;
+
+                                &lt;!-- groupSize is the number of clients in each group of clients. It's
+                                        really only a &quot;smooth&quot; ramp up if this is pretty small. --&gt;
+                                &lt;key&gt;groupSize&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+
+                                &lt;!-- Number of seconds between the introduction of each group. --&gt;
+                                &lt;key&gt;interval&lt;/key&gt;
+                                &lt;integer&gt;3&lt;/integer&gt;
+
+                                &lt;!-- Number of clients each user is assigned to. --&gt;
+                                &lt;!-- Set weight of clients to 1 if this is &gt; 1. Number of clients must match this value if &gt; 1. --&gt;
+                                &lt;key&gt;clientsPerUser&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+
+                &lt;/dict&gt;
+
+                &lt;!-- Define some log observers to report on the load test. --&gt;
+                &lt;key&gt;observers&lt;/key&gt;
+                &lt;array&gt;
+                        &lt;!-- ReportStatistics generates an end-of-run summary of the HTTP requests
+                                made, their timings, and their results. --&gt;
+                        &lt;dict&gt;
+                                &lt;key&gt;type&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.logger.ReportStatistics&lt;/string&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- The thresholds for each request type --&gt;
+                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
+                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
+
+                                        &lt;!-- The benchmarks for overall QoS --&gt;
+                                        &lt;key&gt;benchmarksPath&lt;/key&gt;
+                                        &lt;string&gt;contrib/performance/loadtest/benchmarks.json&lt;/string&gt;
+
+                                        &lt;!-- The % of failures that constitute a failed test --&gt;
+                                        &lt;key&gt;failCutoff&lt;/key&gt;
+                                        &lt;real&gt;1.0&lt;/real&gt;
+                                &lt;/dict&gt;
+                        &lt;/dict&gt;
+
+                        &lt;!-- RequestLogger generates a realtime log of all HTTP requests made
+                                during the load test. --&gt;
+                        &lt;dict&gt;
+                                &lt;key&gt;type&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.logger.RequestLogger&lt;/string&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                &lt;/dict&gt;
+                        &lt;/dict&gt;
+
+                        &lt;!-- OperationLogger generates an end-of-run summary of the gross operations
+                                performed (logical operations which may span more than one HTTP request,
+                                such as inviting an attendee to an event). --&gt;
+                        &lt;dict&gt;
+                                &lt;key&gt;type&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.logger.OperationLogger&lt;/string&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- The thresholds for each operation type --&gt;
+                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
+                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
+
+                                        &lt;!-- The % of operations beyond the lag cut-off that constitute a failed test --&gt;
+                                        &lt;key&gt;lagCutoff&lt;/key&gt;
+                                        &lt;real&gt;1.0&lt;/real&gt;
+
+                                        &lt;!-- The % of failures that constitute a failed test --&gt;
+                                        &lt;key&gt;failCutoff&lt;/key&gt;
+                                        &lt;real&gt;1.0&lt;/real&gt;
+                                &lt;/dict&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistconfigplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/config.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,171 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Identify the server to be load tested. --&gt;
+                &lt;key&gt;server&lt;/key&gt;
+                &lt;string&gt;https://127.0.0.1:8443&lt;/string&gt;
+
+                &lt;!-- Configure Admin Web UI. --&gt;
+                &lt;key&gt;webadmin&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;key&gt;enabled&lt;/key&gt;
+                        &lt;true/&gt;
+
+                        &lt;key&gt;HTTPPort&lt;/key&gt;
+                        &lt;integer&gt;8080&lt;/integer&gt;
+                &lt;/dict&gt;
+
+                &lt;!--  Define whether server supports stats socket. --&gt;
+                &lt;key&gt;serverStats&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;key&gt;enabled&lt;/key&gt;
+                        &lt;true/&gt;
+                        &lt;key&gt;Port&lt;/key&gt;
+                        &lt;integer&gt;8100&lt;/integer&gt;
+                &lt;/dict&gt;
+
+                &lt;!--  Define whether client data should be re-used. It will always be saved to the specified path.--&gt;
+                &lt;key&gt;clientDataSerialization&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;key&gt;UseOldData&lt;/key&gt;
+                        &lt;true/&gt;
+                        &lt;key&gt;Path&lt;/key&gt;
+                        &lt;string&gt;/tmp/sim&lt;/string&gt;
+                &lt;/dict&gt;
+
+                &lt;!-- Define the credentials of the clients which will be used to load test
+                        the server. These credentials must already be valid on the server. --&gt;
+                &lt;key&gt;accounts&lt;/key&gt;
+                &lt;dict&gt;
+                        &lt;!-- The loader is the fully-qualified Python name of a callable which
+                                returns a list of directory service records defining all of the client accounts
+                                to use. contrib.performance.loadtest.sim.recordsFromCSVFile reads username,
+                                password, mailto triples from a CSV file and returns them as a list of faked
+                                directory service records. --&gt;
+                        &lt;key&gt;loader&lt;/key&gt;
+                        &lt;string&gt;contrib.performance.loadtest.records.recordsFromCSVFile&lt;/string&gt;
+
+                        &lt;!-- Keyword arguments may be passed to the loader. --&gt;
+                        &lt;key&gt;params&lt;/key&gt;
+                        &lt;dict&gt;
+                                &lt;!-- recordsFromCSVFile interprets the path relative to the config.plist,
+                                        to make it independent of the script's working directory while still allowing
+                                        a relative path. This isn't a great solution. --&gt;
+                                &lt;key&gt;path&lt;/key&gt;
+                                &lt;string&gt;contrib/performance/loadtest/accounts.csv&lt;/string&gt;
+                        &lt;/dict&gt;
+                &lt;/dict&gt;
+
+                &lt;!-- Define how many clients will participate in the load test and how
+                        they will show up. --&gt;
+                &lt;key&gt;arrival&lt;/key&gt;
+                &lt;dict&gt;
+
+                        &lt;!-- Specify a class which creates new clients and introduces them into
+                                the test. contrib.performance.loadtest.population.SmoothRampUp introduces
+                                groups of new clients at fixed intervals up to a maximum. The size of the
+                                group, interval, and maximum are configured by the parameters below. The
+                                total number of clients is groups * groupSize, which needs to be no larger
+                                than the number of credentials created in the accounts section. --&gt;
+                        &lt;key&gt;factory&lt;/key&gt;
+                        &lt;string&gt;contrib.performance.loadtest.population.SmoothRampUp&lt;/string&gt;
+
+                        &lt;key&gt;params&lt;/key&gt;
+                        &lt;dict&gt;
+                                &lt;!-- groups gives the total number of groups of clients to introduce. --&gt;
+                                &lt;key&gt;groups&lt;/key&gt;
+                                &lt;integer&gt;3&lt;/integer&gt;
+
+                                &lt;!-- groupSize is the number of clients in each group of clients. It's
+                                        really only a &quot;smooth&quot; ramp up if this is pretty small. --&gt;
+                                &lt;key&gt;groupSize&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+
+                                &lt;!-- Number of seconds between the introduction of each group. --&gt;
+                                &lt;key&gt;interval&lt;/key&gt;
+                                &lt;integer&gt;15&lt;/integer&gt;
+
+                                &lt;!-- Number of clients each user is assigned to. --&gt;
+                                &lt;!-- Set weight of clients to 1 if this is &gt; 1. Number of clients must match this value if &gt; 1. --&gt;
+                                &lt;key&gt;clientsPerUser&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+
+                &lt;/dict&gt;
+
+                &lt;!-- Define some log observers to report on the load test. --&gt;
+                &lt;key&gt;observers&lt;/key&gt;
+                &lt;array&gt;
+                        &lt;!-- ReportStatistics generates an end-of-run summary of the HTTP requests
+                                made, their timings, and their results. --&gt;
+                        &lt;dict&gt;
+                                &lt;key&gt;type&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.logger.ReportStatistics&lt;/string&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- The thresholds for each request type --&gt;
+                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
+                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
+
+                                        &lt;!-- The benchmarks for overall QoS --&gt;
+                                        &lt;key&gt;benchmarksPath&lt;/key&gt;
+                                        &lt;string&gt;contrib/performance/loadtest/benchmarks.json&lt;/string&gt;
+
+                                        &lt;!-- The % of failures that constitute a failed test --&gt;
+                                        &lt;key&gt;failCutoff&lt;/key&gt;
+                                        &lt;real&gt;1.0&lt;/real&gt;
+                                &lt;/dict&gt;
+                        &lt;/dict&gt;
+
+                        &lt;!-- RequestLogger generates a realtime log of all HTTP requests made
+                                during the load test. --&gt;
+                        &lt;dict&gt;
+                                &lt;key&gt;type&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.logger.RequestLogger&lt;/string&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                &lt;/dict&gt;
+                        &lt;/dict&gt;
+
+                        &lt;!-- OperationLogger generates an end-of-run summary of the gross operations
+                                performed (logical operations which may span more than one HTTP request,
+                                such as inviting an attendee to an event). --&gt;
+                        &lt;dict&gt;
+                                &lt;key&gt;type&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.logger.OperationLogger&lt;/string&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- The thresholds for each operation type --&gt;
+                                        &lt;key&gt;thresholdsPath&lt;/key&gt;
+                                        &lt;string&gt;contrib/performance/loadtest/thresholds.json&lt;/string&gt;
+
+                                        &lt;!-- The % of operations beyond the lag cut-off that constitute a failed test --&gt;
+                                        &lt;key&gt;lagCutoff&lt;/key&gt;
+                                        &lt;real&gt;1.0&lt;/real&gt;
+
+                                        &lt;!-- The % of failures that constitute a failed test --&gt;
+                                        &lt;key&gt;failCutoff&lt;/key&gt;
+                                        &lt;real&gt;1.0&lt;/real&gt;
+                                &lt;/dict&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistdemoclientsplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/demo-clients.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/demo-clients.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/demo-clients.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,166 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+                &lt;array&gt;
+                        &lt;dict&gt;
+                                &lt;!-- Here is a OS X client simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.clients.OS_X_10_11&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the OS_X_10_7 instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.11&lt;/string&gt;
+
+                                        &lt;!-- OS_X_10_11 can poll the calendar home at some interval. This is
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;30&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises AMP push, the client can wait for notifications about calendar home changes in addition to polling them periodically. If this option is true, look for the server advertisement for AMP push and use it if possible. Still fall back to polling if there is no AMP push                                                advertised. --&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;true/&gt;
+                                        &lt;key&gt;ampPushHost&lt;/key&gt;
+                                        &lt;string&gt;localhost&lt;/string&gt;
+                                        &lt;key&gt;ampPushPort&lt;/key&gt;
+                                        &lt;integer&gt;62311&lt;/integer&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;1&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Rescheduler&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplisteventupdatesonlyplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/event-updates-only.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/event-updates-only.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/event-updates-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,543 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a Lion iCal simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.7&lt;/string&gt;
+
+                                        &lt;!-- Client can poll the calendar home at some interval. This is 
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;300000&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
+                                                about calendar home changes instead of polling for them periodically. If 
+                                                this option is true, then look for the server advertisement for xmpp push 
+                                                and use it if possible. Still fall back to polling if there is no xmpp push 
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the 
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an event-creating profile, which will periodically create 
+                                                new events at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;20&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
+                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.NormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
+                                                                        &lt;key&gt;mu&lt;/key&gt;
+                                                                        &lt;integer&gt;10&lt;/integer&gt;
+
+                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
+                                                                        &lt;key&gt;sigma&lt;/key&gt;
+                                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define the distribution of who will be invited to an event.
+                                                        
+                                                                When inviteeClumping is turned on each invitee is based on a sample of
+                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
+                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
+                                                                those larger numbers will simply fail (the sim will report that situation).
+                                                                
+                                                                When inviteeClumping is off invitees will be sampled across an entire
+                                                                range of account indexes. In this case the distribution ought to be a
+                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
+                                                        --&gt;
+                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.UniformIntegerDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;min&lt;/key&gt;
+                                                                        &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;max&lt;/key&gt;
+                                                                        &lt;integer&gt;99&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
+                                                        
+                                                                LogNormal is the best fit to observed data.
+
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
+                                                                mode should typically be 1, and mean whatever matches the user behavior.
+                                                                Our typical mean is 6.                                                         
+                                                             --&gt;
+                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.LogNormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mode - peak--&gt;
+                                                                        &lt;key&gt;mode&lt;/key&gt;
+                                                                        &lt;integer&gt;1&lt;/integer&gt;
+                                                                        &lt;!-- mean - average--&gt;
+                                                                        &lt;key&gt;median&lt;/key&gt;
+                                                                        &lt;integer&gt;6&lt;/integer&gt;
+                                                                        &lt;!-- maximum --&gt;
+                                                                        &lt;key&gt;maximum&lt;/key&gt;
+                                                                        &lt;real&gt;100&lt;/real&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
+                                             handles replies received. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define how long to wait after seeing a new invitation before
+                                                                accepting it.
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
+                                                                (i.e., half of the user have accepted by that time).                                                                
+                                                        --&gt;
+                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.LogNormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mode - peak--&gt;
+                                                                        &lt;key&gt;mode&lt;/key&gt;
+                                                                        &lt;integer&gt;300&lt;/integer&gt;
+                                                                        &lt;!-- median - 50% done--&gt;
+                                                                        &lt;key&gt;median&lt;/key&gt;
+                                                                        &lt;integer&gt;1800&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- A task-creating profile, which will periodically create 
+                                                new tasks at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new task. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+
+                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will 
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplisteventsonlyplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/events-only.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/events-only.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/events-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,280 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is an El Captian iCal simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_11&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.11&lt;/string&gt;
+
+                                        &lt;!-- Client can poll the calendar home at some interval. This is 
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;30&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS X 10.11 can wait for notifications 
+                                                about calendar home changes instead of polling for them periodically. If 
+                                                this option is true, then look for the server advertisement for xmpp push 
+                                                and use it if possible. Still fall back to polling if there is no xmpp push 
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the 
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an event-creating profile, which will periodically create 
+                                                new events at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;20&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will 
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistinvitesacceptsplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-accepts.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-accepts.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-accepts.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,522 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a Lion iCal simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.7&lt;/string&gt;
+
+                                        &lt;!-- Client can poll the calendar home at some interval. This is 
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;300000&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
+                                                about calendar home changes instead of polling for them periodically. If 
+                                                this option is true, then look for the server advertisement for xmpp push 
+                                                and use it if possible. Still fall back to polling if there is no xmpp push 
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;true /&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the 
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an event-creating profile, which will periodically create 
+                                                new events at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;20&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
+                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.FixedDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- interval (in seconds). --&gt;
+                                                                        &lt;key&gt;value&lt;/key&gt;
+                                                                        &lt;integer&gt;150&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define the distribution of who will be invited to an event.
+                                                        
+                                                                When inviteeClumping is turned on each invitee is based on a sample of
+                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
+                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
+                                                                those larger numbers will simply fail (the sim will report that situation).
+                                                                
+                                                                When inviteeClumping is off invitees will be sampled across an entire
+                                                                range of account indexes. In this case the distribution ought to be a
+                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
+                                                        --&gt;
+                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.UniformIntegerDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;min&lt;/key&gt;
+                                                                        &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;max&lt;/key&gt;
+                                                                        &lt;integer&gt;99&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
+                                                        
+                                                                LogNormal is the best fit to observed data.
+
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
+                                                                mode should typically be 1, and mean whatever matches the user behavior.
+                                                                Our typical mean is 6.                                                         
+                                                             --&gt;
+                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.FixedDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- Number of attendees. --&gt;
+                                                                        &lt;key&gt;value&lt;/key&gt;
+                                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;100&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
+                                             handles replies received. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define how long to wait after seeing a new invitation before
+                                                                accepting it.
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
+                                                                (i.e., half of the user have accepted by that time).                                                                
+                                                        --&gt;
+                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.UniformDiscreteDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- Set of values to use - will be chosen in random order. --&gt;
+                                                                        &lt;key&gt;values&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;integer&gt;15&lt;/integer&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;integer&gt;30&lt;/integer&gt;
+                                                                        &lt;/array&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- A task-creating profile, which will periodically create 
+                                                new tasks at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new task. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+
+                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will 
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+
+                &lt;!-- Determine the interval between client creation. --&gt;
+                &lt;key&gt;arrivalInterval&lt;/key&gt;
+                &lt;integer&gt;5&lt;/integer&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistinvitesonlyrecurringplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only-recurring.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only-recurring.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only-recurring.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,517 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a Lion iCal simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.7&lt;/string&gt;
+
+                                        &lt;!-- Client can poll the calendar home at some interval. This is 
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;300000&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
+                                                about calendar home changes instead of polling for them periodically. If 
+                                                this option is true, then look for the server advertisement for xmpp push 
+                                                and use it if possible. Still fall back to polling if there is no xmpp push 
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the 
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an event-creating profile, which will periodically create 
+                                                new events at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;20&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
+                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.FixedDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- interval (in seconds). --&gt;
+                                                                        &lt;key&gt;value&lt;/key&gt;
+                                                                        &lt;integer&gt;120&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define the distribution of who will be invited to an event.
+                                                        
+                                                                When inviteeClumping is turned on each invitee is based on a sample of
+                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
+                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
+                                                                those larger numbers will simply fail (the sim will report that situation).
+                                                                
+                                                                When inviteeClumping is off invitees will be sampled across an entire
+                                                                range of account indexes. In this case the distribution ought to be a
+                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
+                                                        --&gt;
+                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.UniformIntegerDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;min&lt;/key&gt;
+                                                                        &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;max&lt;/key&gt;
+                                                                        &lt;integer&gt;99&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
+                                                        
+                                                                LogNormal is the best fit to observed data.
+
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
+                                                                mode should typically be 1, and mean whatever matches the user behavior.
+                                                                Our typical mean is 6.                                                         
+                                                             --&gt;
+                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.FixedDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- Number of attendees. --&gt;
+                                                                        &lt;key&gt;value&lt;/key&gt;
+                                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;100&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
+                                             handles replies received. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define how long to wait after seeing a new invitation before
+                                                                accepting it.
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
+                                                                (i.e., half of the user have accepted by that time).                                                                
+                                                        --&gt;
+                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.LogNormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mode - peak--&gt;
+                                                                        &lt;key&gt;mode&lt;/key&gt;
+                                                                        &lt;integer&gt;300&lt;/integer&gt;
+                                                                        &lt;!-- median - 50% done--&gt;
+                                                                        &lt;key&gt;median&lt;/key&gt;
+                                                                        &lt;integer&gt;1800&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- A task-creating profile, which will periodically create 
+                                                new tasks at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new task. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+
+                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will 
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+
+                &lt;!-- Determine the interval between client creation. --&gt;
+                &lt;key&gt;arrivalInterval&lt;/key&gt;
+                &lt;integer&gt;4&lt;/integer&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsaltsettingsplistinvitesonlyplist"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only.plist (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only.plist                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/invites-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,533 @@
</span><ins>+&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+
+&lt;!--
+    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+    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 &quot;AS IS&quot; 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.
+  --&gt;
+
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+        &lt;dict&gt;
+                &lt;!-- Define the kinds of software and user behavior the load simulation
+                        will simulate. --&gt;
+                &lt;key&gt;clients&lt;/key&gt;
+
+                &lt;!-- Have as many different kinds of software and user behavior configurations
+                        as you want. Each is a dict --&gt;
+                &lt;array&gt;
+
+                        &lt;dict&gt;
+
+                                &lt;!-- Here is a Lion iCal simulator. --&gt;
+                                &lt;key&gt;software&lt;/key&gt;
+                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
+
+                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
+                                &lt;key&gt;params&lt;/key&gt;
+                                &lt;dict&gt;
+                                        &lt;!-- Name that appears in logs. --&gt;
+                                        &lt;key&gt;title&lt;/key&gt;
+                                        &lt;string&gt;10.7&lt;/string&gt;
+
+                                        &lt;!-- Client can poll the calendar home at some interval. This is 
+                                                in seconds. --&gt;
+                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
+                                        &lt;integer&gt;300000&lt;/integer&gt;
+
+                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
+                                                about calendar home changes instead of polling for them periodically. If 
+                                                this option is true, then look for the server advertisement for xmpp push 
+                                                and use it if possible. Still fall back to polling if there is no xmpp push 
+                                                advertised. --&gt;
+                                        &lt;key&gt;supportPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
+                                        &lt;false /&gt;
+                                &lt;/dict&gt;
+
+                                &lt;!-- The profiles define certain types of user behavior on top of the 
+                                        client software being simulated. --&gt;
+                                &lt;key&gt;profiles&lt;/key&gt;
+                                &lt;array&gt;
+
+                                        &lt;!-- First an event-creating profile, which will periodically create 
+                                                new events at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;20&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
+                                                                its client to create a new event. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;5&lt;/integer&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;true/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;25&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
+                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.FixedDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- interval (in seconds). --&gt;
+                                                                        &lt;key&gt;value&lt;/key&gt;
+                                                                        &lt;integer&gt;120&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define the distribution of who will be invited to an event.
+                                                        
+                                                                When inviteeClumping is turned on each invitee is based on a sample of
+                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
+                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
+                                                                those larger numbers will simply fail (the sim will report that situation).
+                                                                
+                                                                When inviteeClumping is off invitees will be sampled across an entire
+                                                                range of account indexes. In this case the distribution ought to be a
+                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
+                                                        --&gt;
+                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.UniformIntegerDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;min&lt;/key&gt;
+                                                                        &lt;integer&gt;0&lt;/integer&gt;
+                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
+                                                                        &lt;key&gt;max&lt;/key&gt;
+                                                                        &lt;integer&gt;99&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
+                                                        &lt;true/&gt;
+
+                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
+                                                        
+                                                                LogNormal is the best fit to observed data.
+
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
+                                                                mode should typically be 1, and mean whatever matches the user behavior.
+                                                                Our typical mean is 6.                                                         
+                                                             --&gt;
+                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.FixedDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- Number of attendees. --&gt;
+                                                                        &lt;key&gt;value&lt;/key&gt;
+                                                                        &lt;integer&gt;5&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+
+                                                        &lt;!-- Define how recurrences are created. --&gt;
+                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
+                                                                     RRULEs defined for this distribution and pick each based on a
+                                                                     weight. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.RecurrenceDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- False to disable RRULEs --&gt;
+                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
+                                                                        &lt;false/&gt;
+
+                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
+                                                                        &lt;key&gt;weights&lt;/key&gt;
+                                                                        &lt;dict&gt;
+                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
+                                                                                &lt;key&gt;none&lt;/key&gt;
+                                                                                &lt;integer&gt;50&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
+                                                                                &lt;key&gt;daily&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                                &lt;key&gt;weekly&lt;/key&gt;
+                                                                                &lt;integer&gt;20&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
+                                                                                &lt;key&gt;monthly&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;yearly&lt;/key&gt;
+                                                                                &lt;integer&gt;1&lt;/integer&gt;
+                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;2&lt;/integer&gt;
+                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
+                                                                                &lt;integer&gt;5&lt;/integer&gt;
+                                                                                
+                                                                                &lt;!-- Work days pretty common --&gt;
+                                                                                &lt;key&gt;workdays&lt;/key&gt;
+                                                                                &lt;integer&gt;10&lt;/integer&gt;
+                                                                        &lt;/dict&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
+                                             handles replies received. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define how long to wait after seeing a new invitation before
+                                                                accepting it.
+
+                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
+                                                                (i.e., half of the user have accepted by that time).                                                                
+                                                        --&gt;
+                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.LogNormalDistribution&lt;/string&gt;
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- mode - peak--&gt;
+                                                                        &lt;key&gt;mode&lt;/key&gt;
+                                                                        &lt;integer&gt;300&lt;/integer&gt;
+                                                                        &lt;!-- median - 50% done--&gt;
+                                                                        &lt;key&gt;median&lt;/key&gt;
+                                                                        &lt;integer&gt;1800&lt;/integer&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                        &lt;!-- A task-creating profile, which will periodically create 
+                                                new tasks at a random time on a random calendar. --&gt;
+                                        &lt;dict&gt;
+                                                &lt;key&gt;class&lt;/key&gt;
+                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
+
+                                                &lt;key&gt;params&lt;/key&gt;
+                                                &lt;dict&gt;
+                                                        &lt;key&gt;enabled&lt;/key&gt;
+                                                        &lt;false/&gt;
+
+                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
+                                                                its client to create a new task. --&gt;
+                                                        &lt;key&gt;interval&lt;/key&gt;
+                                                        &lt;integer&gt;300&lt;/integer&gt;
+
+                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
+                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
+                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
+                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
+                                                        &lt;dict&gt;
+
+                                                                &lt;!-- 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. --&gt;
+                                                                &lt;key&gt;type&lt;/key&gt;
+                                                                &lt;string&gt;contrib.performance.loadtest.distributions.WorkDistribution&lt;/string&gt;
+
+                                                                &lt;key&gt;params&lt;/key&gt;
+                                                                &lt;dict&gt;
+                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
+                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
+                                                                        &lt;array&gt;
+                                                                                &lt;string&gt;mon&lt;/string&gt;
+                                                                                &lt;string&gt;tue&lt;/string&gt;
+                                                                                &lt;string&gt;wed&lt;/string&gt;
+                                                                                &lt;string&gt;thu&lt;/string&gt;
+                                                                                &lt;string&gt;fri&lt;/string&gt;
+                                                                        &lt;/array&gt;
+
+                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
+                                                                        &lt;key&gt;beginHour&lt;/key&gt;
+                                                                        &lt;integer&gt;8&lt;/integer&gt;
+
+                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
+                                                                                to begin!). --&gt;
+                                                                        &lt;key&gt;endHour&lt;/key&gt;
+                                                                        &lt;integer&gt;16&lt;/integer&gt;
+
+                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
+                                                                                really work right?) --&gt;
+                                                                        &lt;key&gt;tzname&lt;/key&gt;
+                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
+                                                                &lt;/dict&gt;
+                                                        &lt;/dict&gt;
+                                                &lt;/dict&gt;
+                                        &lt;/dict&gt;
+
+                                &lt;/array&gt;
+
+                                &lt;!-- Determine the frequency at which this client configuration will 
+                                        appear in the clients which are created by the load tester. --&gt;
+                                &lt;key&gt;weight&lt;/key&gt;
+                                &lt;integer&gt;1&lt;/integer&gt;
+                        &lt;/dict&gt;
+                &lt;/array&gt;
+        &lt;/dict&gt;
+&lt;/plist&gt;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsclientspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/clients.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/clients.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/clients.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,115 @@
</span><ins>+from contrib.performance.loadtest.clients import iOS_5, OS_X_10_6, OS_X_10_7, OS_X_10_11
+from contrib.performance.loadtest.profiles import (
+    Eventer, EventDeleter,
+    Titler,
+    Inviter, Emptier,
+    Tasker, TaskDeleter,
+    TaskTitler, TaskNoter, Completer, Prioritizer,
+
+    CalendarMaker, CalendarUpdater, CalendarSharer, CalendarDeleter
+)
+from contrib.performance.loadtest.distributions import FixedDistribution, BernoulliDistribution, NormalDistribution
+
+
+from preset_distributions import STANDARD_WORK_DISTRIBUTION, LOW_RECURRENCE_DISTRIBUTION, MEDIUM_RECURRENCE_DISTRIBUTION
+
+config = [
+    {
+        &quot;software&quot;: OS_X_10_11,
+        #     title=&quot;10.11&quot;,
+        #     calendarHomePollInterval=5,
+        #     supportAmpPush=True,
+        #     ampPushHost=&quot;localhost&quot;,
+        #     ampPushPort62311
+        # )
+        &quot;params&quot;: {
+            &quot;title&quot;: &quot;10.11&quot;,
+            &quot;calendarHomePollInterval&quot;: 5,
+            &quot;supportAmpPush&quot;: True,
+            &quot;ampPushHost&quot;: &quot;127.0.0.1&quot;,
+            &quot;ampPushPort&quot;: 62311
+        },
+        &quot;profiles&quot;: [
+            Eventer(enabled=True, interval=60, eventStartDistribution=STANDARD_WORK_DISTRIBUTION),
+            # Emptier(enabled=True, interval=30),
+            # Titler(enabled=True, interval=1, titleLengthDistribution=FixedDistribution(10)),
+            Inviter(enabled=True, interval=60, numInviteesDistribution=NormalDistribution(7, 2)),
+
+            # Tasker(enabled=False, interval=1),
+            # Completer(enabled=True, interval=0.5, completeLikelihood=BernoulliDistribution(0.5)),
+            # Prioritizer(enabled=True, interval=0.1),
+            # TaskTitler(enabled=True, interval=1),
+            # TaskNoter(enabled=True, interval=1),
+            # TaskDeleter(enabled=True, interval=1),
+
+            # CalendarMaker(enabled=True, interval=1),
+            # CalendarUpdater(enabled=True, interval=5),
+            # CalendarSharer(enabled=True, interval=30),
+            # CalendarDeleter(false=True, interval=30)
+        ],
+        &quot;weight&quot;: 3
+    }
+]
+
+# # TBD what about multiple weights?
+# calendars_only_ideal = [
+#     OS_X_10_11(
+#         title=&quot;10.11&quot;,
+#         calendarHomePollInterval=5,
+#         supportAmpPush=True,
+#         ampPushHost=&quot;localhost&quot;,
+#         ampPushPort=62311,
+#         profiles=[
+#             CalendarMaker(enabled=True, interval=15),
+#             # CalendarUpdater(enabled=True, interval=5),
+#             # CalendarSharer(enabled=False, interval=30),
+#             # CalendarDeleter(enabled=False, interval=30)
+#         ]
+#     )
+# ]
+
+# event_updates_only = [
+#     {
+#         &quot;software&quot;: OS_X_10_11,
+#         &quot;params&quot;: {
+#             &quot;title&quot;: &quot;10.11&quot;,
+#             &quot;calendarHomePollInterval&quot;: 5,
+#             &quot;supportAmpPush&quot;: True,
+#             &quot;ampPushHost&quot;: &quot;localhost&quot;,
+#             &quot;ampPushPort&quot;: 62311
+#         },
+#         &quot;profiles&quot;: [
+#             ProfileType(Eventer, dict(
+#                 enabled=False,
+#                 interval=20,
+#                 eventStartDistribution=STANDARD_WORK_DISTRIBUTION,
+#                 recurrenceDistribution=MEDIUM_RECURRENCE_DISTRIBUTION
+#             )),
+#             ProfileType(EventerUpdater, dict(
+#                 enabled=True,
+#                 interval=5,
+#                 eventStartDistribution=STANDARD_WORK_DISTRIBUTION,
+#                 recurrenceDistribution=LOW_RECURRENCE_DISTRIBUTION
+#             )),
+#             ProfileType(RealisticInviter, dict(
+#                 enabled=False,
+#                 sendInvitationDistribution=LogNormalDistribution(mu=10, sigma=5),
+#                 inviteeDistribution=UniformIntegerDistribution(0, 99),
+#                 inviteeClumping=True,
+#                 inviteeCountDistribution=LogNormalDistribution(mode=1, median=6, maximum=100),
+#                 eventStartDistribution=STANDARD_WORK_DISTRIBUTION,
+#                 recurrenceDistribution=MEDIUM_RECURRENCE_DISTRIBUTION
+#             )),
+#             ProfileType(Accepter, dict(
+#                 enabled=False,
+#                 acceptDelayDistribution=LogNormalDistribution(mode=300, median=1800)
+#             )),
+#             ProfileType(Tasker, dict(
+#                 enabled=False,
+#                 interval=300,
+#                 taskDueDistribution=STANDARD_WORK_DISTRIBUTION
+#             ))
+#         ],
+#         &quot;weight&quot;: 1
+#     }
+# ]
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsconfigdistpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config-dist.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config-dist.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config-dist.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,16 @@
</span><ins>+from contrib.performance.loadtest.settings.defaults import arrival, requestLogger, operationLogger, statisticsReporter, accounts
+
+from contrib.performance.loadtest.logger import EverythingLogger, MessageLogger
+
+NUM_WORKERS = 1
+
+config = {
+    &quot;server&quot;: 'https://127.0.0.1:8443',
+    &quot;webadminPort&quot;: 8080,
+    &quot;serverStatsPort&quot;: 8100,
+    &quot;serializationPath&quot;: '/tmp/sim',
+    &quot;arrival&quot;: arrival,
+    &quot;observers&quot;: [requestLogger, operationLogger, statisticsReporter, EverythingLogger(), MessageLogger()],
+    &quot;records&quot;: accounts,
+    &quot;workers&quot;: [&quot;./bin/python contrib/performance/loadtest/ampsim.py&quot;] * NUM_WORKERS,
+}
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsconfigpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/config.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,13 @@
</span><ins>+from contrib.performance.loadtest.settings.defaults import arrival, requestLogger, operationLogger, statisticsReporter, accounts
+
+from contrib.performance.loadtest.logger import EverythingLogger, MessageLogger
+
+config = {
+    &quot;server&quot;: 'https://127.0.0.1:8443',
+    &quot;webadminPort&quot;: 8080,
+    &quot;serverStatsPort&quot;: {'server': 'localhost', 'Port': 8100},
+    &quot;serializationPath&quot;: '/tmp/sim',
+    &quot;arrival&quot;: arrival,
+    &quot;observers&quot;: [requestLogger, operationLogger, statisticsReporter, EverythingLogger(), MessageLogger()],
+    &quot;records&quot;: accounts
+}
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingsdefaultspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/defaults.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/defaults.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/defaults.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,24 @@
</span><ins>+from contrib.performance.loadtest.logger import ReportStatistics, RequestLogger, OperationLogger
+from contrib.performance.loadtest.records import recordsFromCSVFile
+from contrib.performance.loadtest.population import SmoothRampUp
+
+requestLogger = RequestLogger()
+operationLogger = OperationLogger(
+    thresholdsPath=&quot;contrib/performance/loadtest/thresholds.json&quot;,
+    lagCutoff=1.0,
+    failCutoff=1.0
+)
+statisticsReporter = ReportStatistics(
+    thresholdsPath=&quot;contrib/performance/loadtest/thresholds.json&quot;,
+    benchmarksPath=&quot;contrib/performance/loadtest/benchmarks.json&quot;,
+    failCutoff=1.0
+)
+
+arrival = SmoothRampUp(
+    groups=30,
+    groupSize=1,
+    interval=3,
+    clientsPerUser=1
+)
+
+accounts = recordsFromCSVFile(&quot;contrib/performance/loadtest/accounts.csv&quot;)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsettingspreset_distributionspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/preset_distributions.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/preset_distributions.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/settings/preset_distributions.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,36 @@
</span><ins>+from contrib.performance.loadtest.distributions import WorkDistribution, RecurrenceDistribution
+
+STANDARD_WORK_DISTRIBUTION = WorkDistribution(
+    daysOfWeek=[&quot;mon&quot;, &quot;tue&quot;, &quot;wed&quot;, &quot;thu&quot;, &quot;fri&quot;],
+    beginHour=8,
+    endHour=16,
+    tzname=&quot;America/Los_Angeles&quot;
+)
+
+LOW_RECURRENCE_DISTRIBUTION = RecurrenceDistribution(
+    allowRecurrence=True,
+    weights={
+        &quot;none&quot;: 50,
+        &quot;daily&quot;: 25,
+        &quot;weekly&quot;: 25,
+        &quot;monthly&quot;: 0,
+        &quot;yearly&quot;: 0,
+        &quot;dailylimit&quot;: 0,
+        &quot;weeklylimit&quot;: 0,
+        &quot;workdays&quot;: 0
+    }
+)
+
+MEDIUM_RECURRENCE_DISTRIBUTION = RecurrenceDistribution(
+    allowRecurrence=True,
+    weights={
+        &quot;none&quot;: 50,
+        &quot;daily&quot;: 10,
+        &quot;weekly&quot;: 20,
+        &quot;monthly&quot;: 2,
+        &quot;yearly&quot;: 1,
+        &quot;dailylimit&quot;: 2,
+        &quot;weeklylimit&quot;: 5,
+        &quot;workdays&quot;: 10
+    }
+)
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsimpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sim.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sim.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/sim.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -45,58 +45,17 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.web.server import Site
</span><span class="cx"> 
</span><del>-from contrib.performance.loadtest.ical import OS_X_10_6
</del><ins>+from contrib.performance.loadtest.clients import OS_X_10_6
</ins><span class="cx"> from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
</span><span class="cx"> from contrib.performance.loadtest.population import (
</span><del>-    Populator, ProfileType, ClientType, PopulationParameters, SmoothRampUp,
</del><ins>+    ClientFactory, PopulationParameters, SmoothRampUp,
</ins><span class="cx">     CalendarClientSimulator)
</span><ins>+from contrib.performance.loadtest.config import Config
</ins><span class="cx"> from contrib.performance.loadtest.webadmin import LoadSimAdminResource
</span><span class="cx"> 
</span><del>-
-
-class _DirectoryRecord(object):
-    def __init__(self, uid, password, commonName, email, guid):
-        self.uid = uid
-        self.password = password
-        self.commonName = commonName
-        self.email = email
-        self.guid = guid
-
-
-
</del><span class="cx"> def safeDivision(value, total, factor=1):
</span><span class="cx">     return value * factor / total if total else 0
</span><span class="cx"> 
</span><del>-
-
-def generateRecords(
-    count, uidPattern=&quot;user%d&quot;, passwordPattern=&quot;user%d&quot;,
-    namePattern=&quot;User %d&quot;, emailPattern=&quot;user%d@example.com&quot;,
-    guidPattern=&quot;user%d&quot;
-):
-    for i in xrange(count):
-        i += 1
-        uid = uidPattern % (i,)
-        password = passwordPattern % (i,)
-        name = namePattern % (i,)
-        email = emailPattern % (i,)
-        guid = guidPattern % (i,)
-        yield _DirectoryRecord(uid, password, name, email, guid)
-
-
-
-def recordsFromCSVFile(path):
-    if path:
-        pathObj = FilePath(path)
-    else:
-        pathObj = FilePath(__file__).sibling(&quot;accounts.csv&quot;)
-    return [
-        _DirectoryRecord(*line.decode('utf-8').split(u','))
-        for line
-        in pathObj.getContent().splitlines()]
-
-
-
</del><span class="cx"> class LagTrackingReactor(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     This reactor wraps another reactor and proxies all attribute
</span><span class="lines">@@ -129,19 +88,31 @@
</span><span class="cx">     Command line configuration options for the load simulator.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     config = None
</span><del>-    _defaultConfig = FilePath(__file__).sibling(&quot;config.plist&quot;)
-    _defaultClients = FilePath(__file__).sibling(&quot;clients.plist&quot;)
</del><ins>+    settings = FilePath(__file__).sibling(&quot;settings&quot;)
+    # plists = settings.child('alt-settings').child('plist')
+    # _defaultConfig = plists.child(&quot;config.plist&quot;)
+    # _defaultClients = plists.child(&quot;clients.plist&quot;)
+    # _defaultConfig = settings.child('settings.config')
+    # _defaultClients = settings.child('settings.clients')
+    _defaultConfig = 'contrib.performance.loadtest.settings.config'
+    _defaultClients = 'contrib.performance.loadtest.settings.clients'
</ins><span class="cx"> 
</span><ins>+    optFlags = [
+        (&quot;debug&quot;, &quot;d&quot;, &quot;Enable Deferred and Failure debugging&quot;),
+        (&quot;debug-deferred&quot;, None, &quot;Enable only Deferred debugging&quot;),
+        (&quot;debug-failure&quot;, None, &quot;Enable only Failure debugging&quot;),
+        (&quot;use-plist&quot;, None, &quot;Interpret configuration files as XML property lists (default false)&quot;)
+    ]
+
</ins><span class="cx">     optParameters = [
</span><span class="cx">         (&quot;runtime&quot;, &quot;t&quot;, None,
</span><span class="cx">          &quot;Specify the limit (seconds) on the time to run the simulation.&quot;,
</span><span class="cx">          int),
</span><span class="cx">         (&quot;config&quot;, None, _defaultConfig,
</span><del>-         &quot;Configuration plist file name from which to read simulation parameters.&quot;,
-         FilePath),
</del><ins>+         &quot;Configuration plist file name from which to read simulation parameters.&quot;),
</ins><span class="cx">         (&quot;clients&quot;, None, _defaultClients,
</span><del>-         &quot;Configuration plist file name from which to read client parameters.&quot;,
-         FilePath),
</del><ins>+         &quot;Configuration plist file name from which to read client parameters.&quot;),
+        (&quot;logfile&quot;, &quot;l&quot;, '-', FilePath)
</ins><span class="cx">     ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -181,36 +152,48 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def postOptions(self):
</span><del>-        try:
-            configFile = self['config'].open()
-        except IOError, e:
-            raise UsageError(&quot;--config %s: %s&quot; % (
-                self['config'].path, e.strerror))
-        try:
</del><ins>+        &quot;&quot;&quot;
+        Convert the given configuration files to dictionaries, respectively in
+        self.config and self.clients
+        &quot;&quot;&quot;
+        configPath = self[&quot;config&quot;]
+        clientsPath = self[&quot;clients&quot;]
+        if self['use-plist']:
</ins><span class="cx">             try:
</span><del>-                self.config = readPlist(configFile)
</del><ins>+                with open(configPath) as configFile: # Could raise an IOError
+                    self.config = readPlist(configFile) # Could raise an ExpatError
+            except IOError, e:
+                raise UsageError(&quot;--config %s: %s&quot; % (configPath.path, e.strerror))
</ins><span class="cx">             except ExpatError, e:
</span><del>-                raise UsageError(&quot;--config %s: %s&quot; % (self['config'].path, e))
-        finally:
-            configFile.close()
</del><ins>+                raise UsageError(&quot;--config %s: %s&quot; % (configPath.path, e))
</ins><span class="cx"> 
</span><del>-        try:
-            clientFile = self['clients'].open()
-        except IOError, e:
-            raise UsageError(&quot;--clients %s: %s&quot; % (
-                self['clients'].path, e.strerror))
-        try:
</del><span class="cx">             try:
</span><del>-                client_config = readPlist(clientFile)
-                self.config[&quot;clients&quot;] = client_config[&quot;clients&quot;]
-                if &quot;arrivalInterval&quot; in client_config:
-                    self.config[&quot;arrival&quot;][&quot;params&quot;][&quot;interval&quot;] = client_config[&quot;arrivalInterval&quot;]
</del><ins>+                with open(clientsPath) as clientFile: # Could raise an IOError
+                    self.clients = readPlist(clientFile) # Could raise an ExpatError
+            except IOError, e:
+                raise UsageError(&quot;--clients %s: %s&quot; % (clientsPath.path, e.strerror))
</ins><span class="cx">             except ExpatError, e:
</span><del>-                raise UsageError(&quot;--clients %s: %s&quot; % (self['clients'].path, e))
-        finally:
-            clientFile.close()
</del><ins>+                raise UsageError(&quot;--clients %s: %s&quot; % (clientsPath.path, e))
</ins><span class="cx"> 
</span><ins>+            self.config[&quot;clients&quot;] = self.clients[&quot;clients&quot;]
</ins><span class="cx"> 
</span><ins>+        else:
+            from importlib import import_module
+            try:
+                config = import_module(configPath) # Could raise ImportError
+                self.config = config.config # Could raise attribute error
+            except (ImportError, AttributeError), e:
+                raise UsageError(&quot;--config %s: %s&quot; % (configPath, e))
+            try:
+                clients = import_module(clientsPath) # Could raise ImportError
+                self.clients = clients.config # Could raise attribute error
+            except (ImportError, AttributeError), e:
+                raise UsageError(&quot;--clients %s: %s&quot; % (clientsPath, e))
+
+        self.configObj = Config()
+        self.configObj.populateFrom(self.config, self.clients)
+
+
</ins><span class="cx"> Arrival = namedtuple('Arrival', 'factory parameters')
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -224,17 +207,18 @@
</span><span class="cx">     @type arrival: L{Arrival}
</span><span class="cx">     @type parameters: L{PopulationParameters}
</span><span class="cx"> 
</span><del>-    @ivar records: A C{list} of L{_DirectoryRecord} instances giving
</del><ins>+    @ivar records: A C{list} of L{DirectoryRecord} instances giving
</ins><span class="cx">         user information about the accounts on the server being put
</span><span class="cx">         under load.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    def __init__(self, server, principalPathTemplate, webadminPort, serverStats, serializationPath, arrival, parameters, observers=None,
</del><ins>+    def __init__(self, server, webadminPort, serverStats, serializationPath, arrival, parameters, observers=None,
</ins><span class="cx">                  records=None, reactor=None, runtime=None, workers=None,
</span><span class="cx">                  configTemplate=None, workerID=None, workerCount=1):
</span><ins>+        if configTemplate == {}:
+            raise Exception('Got here!')
</ins><span class="cx">         if reactor is None:
</span><span class="cx">             from twisted.internet import reactor
</span><span class="cx">         self.server = server
</span><del>-        self.principalPathTemplate = principalPathTemplate
</del><span class="cx">         self.webadminPort = webadminPort
</span><span class="cx">         self.serverStats = serverStats
</span><span class="cx">         self.serializationPath = serializationPath
</span><span class="lines">@@ -262,7 +246,7 @@
</span><span class="cx">         except UsageError, e:
</span><span class="cx">             raise SystemExit(str(e))
</span><span class="cx"> 
</span><del>-        return cls.fromConfig(options.config, options['runtime'], output)
</del><ins>+        return cls.fromConfigObject(options.configObj, options['runtime'], output)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -270,6 +254,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a L{LoadSimulator} from a parsed instance of a configuration
</span><span class="cx">         property list.
</span><ins>+
+        @type{config} L{Config} object
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         workers = config.get(&quot;workers&quot;)
</span><span class="lines">@@ -279,7 +265,6 @@
</span><span class="cx">             workerCount = config.get(&quot;workerCount&quot;, 1)
</span><span class="cx">             configTemplate = None
</span><span class="cx">             server = config.get('server', 'http://127.0.0.1:8008')
</span><del>-            principalPathTemplate = config.get('principalPathTemplate', '/principals/users/%s/')
</del><span class="cx">             serializationPath = None
</span><span class="cx"> 
</span><span class="cx">             if 'clientDataSerialization' in config:
</span><span class="lines">@@ -306,6 +291,14 @@
</span><span class="cx">             parameters = PopulationParameters()
</span><span class="cx">             if 'clients' in config:
</span><span class="cx">                 for clientConfig in config['clients']:
</span><ins>+                    # parameters.addClient(
+                    #     clientConfig[&quot;weight&quot;],
+                    #     ClientType(
+                    #         clientConfig[&quot;software&quot;],
+                    #         clientConfig[&quot;params&quot;],
+                    #         clientConfig[&quot;profiles&quot;]
+                    #     )
+                    # )
</ins><span class="cx">                     parameters.addClient(
</span><span class="cx">                         clientConfig[&quot;weight&quot;],
</span><span class="cx">                         ClientType(
</span><span class="lines">@@ -316,7 +309,8 @@
</span><span class="cx">                                     namedAny(profile[&quot;class&quot;]),
</span><span class="cx">                                     cls._convertParams(profile[&quot;params&quot;])
</span><span class="cx">                                 ) for profile in clientConfig[&quot;profiles&quot;]
</span><del>-                            ]))
</del><ins>+                            ])),
+                        
</ins><span class="cx">             if not parameters.clients:
</span><span class="cx">                 parameters.addClient(1,
</span><span class="cx">                                      ClientType(OS_X_10_6, {},
</span><span class="lines">@@ -324,7 +318,6 @@
</span><span class="cx">         else:
</span><span class="cx">             # Manager / observer process.
</span><span class="cx">             server = ''
</span><del>-            principalPathTemplate = ''
</del><span class="cx">             serializationPath = None
</span><span class="cx">             arrival = None
</span><span class="cx">             parameters = None
</span><span class="lines">@@ -359,7 +352,6 @@
</span><span class="cx"> 
</span><span class="cx">         return cls(
</span><span class="cx">             server,
</span><del>-            principalPathTemplate,
</del><span class="cx">             webadminPort,
</span><span class="cx">             serverStats,
</span><span class="cx">             serializationPath,
</span><span class="lines">@@ -368,7 +360,6 @@
</span><span class="cx">             observers=observers,
</span><span class="cx">             records=records,
</span><span class="cx">             runtime=runtime,
</span><del>-            reactor=reactor,
</del><span class="cx">             workers=workers,
</span><span class="cx">             configTemplate=configTemplate,
</span><span class="cx">             workerID=workerID,
</span><span class="lines">@@ -377,30 +368,39 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def _convertParams(cls, params):
-        &quot;&quot;&quot;
-        Find parameter values which should be more structured than plistlib is
-        capable of constructing and replace them with the more structured form.
</del><ins>+    def fromConfigObject(cls, config, runtime=None, output=stdout):
+        # if config.isManaging:
+        #     observers = 
</ins><span class="cx"> 
</span><del>-        Specifically, find keys that end with C{&quot;Distribution&quot;} and convert
-        them into some kind of distribution object using the associated
-        dictionary of keyword arguments.
-        &quot;&quot;&quot;
-        for k, v in params.iteritems():
-            if k.endswith('Distribution'):
-                params[k] = cls._convertDistribution(v)
-        return params
</del><ins>+        # if config.isWorking:
+        #     simulator = CalendarClientSimulator(
+        #         config.records,
+        #         config.parameters,
+        #         config.reactor,
+        #         config.server,
+        #         config.serializationPath,
+        #         config.workerID,
+        #         config.workerCount,
+        #     )
+        #     arrival = config.arrival
</ins><span class="cx"> 
</span><ins>+        return cls(
+            config.server,
+            config.webadminPort,
+            config.serverStats,
+            config.serializationPath,
+            config.arrival,
+            config.parameters,
+            observers=config.observers,
+            records=config.records,
+            runtime=runtime,
+            configTemplate=config.serializeForWorker,
+            workers=config.workers,
+            workerID=config.workerID,
+            workerCount=config.workerCount,
+        )
</ins><span class="cx"> 
</span><del>-    @classmethod
-    def _convertDistribution(cls, value):
-        &quot;&quot;&quot;
-        Construct and return a new distribution object using the type and
-        params specified by C{value}.
-        &quot;&quot;&quot;
-        return namedAny(value['type'])(**value['params'])
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @classmethod
</span><span class="cx">     def main(cls, args=None):
</span><span class="cx">         simulator = cls.fromCommandLine(args)
</span><span class="lines">@@ -408,14 +408,11 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def createSimulator(self):
</span><del>-        populator = Populator(Random())
</del><span class="cx">         return CalendarClientSimulator(
</span><span class="cx">             self.records,
</span><del>-            populator,
</del><span class="cx">             self.parameters,
</span><span class="cx">             self.reactor,
</span><span class="cx">             self.server,
</span><del>-            self.principalPathTemplate,
</del><span class="cx">             self.serializationPath,
</span><span class="cx">             self.workerID,
</span><span class="cx">             self.workerCount,
</span><span class="lines">@@ -423,7 +420,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def createArrivalPolicy(self):
</span><del>-        return self.arrival.factory(self.reactor, **self.arrival.parameters)
</del><ins>+        # print(self.arrival.parameters)
+        # return zory(self.reactor, **self.arrival.parameters)
+        return self.arrival
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def serviceClasses(self):
</span><span class="lines">@@ -431,16 +430,15 @@
</span><span class="cx">         Return a list of L{SimService} subclasses for C{attachServices} to
</span><span class="cx">         instantiate and attach to the reactor.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if self.workers is not None:
-            return [
-                ObserverService,
-                WorkerSpawnerService,
-                ReporterService,
-            ]
</del><ins>+        if self.workers:
+            PrimaryService = WorkerSpawnerService
+        else:
+            PrimaryService = SimulatorService
+
</ins><span class="cx">         return [
</span><span class="cx">             ObserverService,
</span><del>-            SimulatorService,
</del><span class="cx">             ReporterService,
</span><ins>+            PrimaryService
</ins><span class="cx">         ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -509,6 +507,7 @@
</span><span class="cx">             data = &quot;&quot;
</span><span class="cx">             while not data.endswith(&quot;\n&quot;):
</span><span class="cx">                 d = s.recv(1024)
</span><ins>+                print(&quot;Received data from stats socket: &quot;, d)
</ins><span class="cx">                 if d:
</span><span class="cx">                     data += d
</span><span class="cx">                 else:
</span><span class="lines">@@ -556,6 +555,9 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         super(ObserverService, self).startService()
</span><span class="cx">         for obs in self.loadsim.observers:
</span><ins>+            import random
+            val = random.random()
+            msg(type='log', text='Adding observer: ' + obs.__class__.__name__, val=str(val))
</ins><span class="cx">             addObserver(obs.observe)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -573,16 +575,19 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     def startService(self):
</span><ins>+        print(&quot;Starting simulator service&quot;)
</ins><span class="cx">         super(SimulatorService, self).startService()
</span><span class="cx">         self.clientsim = self.loadsim.createSimulator()
</span><span class="cx">         arrivalPolicy = self.loadsim.createArrivalPolicy()
</span><del>-        arrivalPolicy.run(self.clientsim)
</del><ins>+        arrivalPolicy.run(self.loadsim.reactor, self.clientsim)
+        # self.loadsim.arrival.run(self.loadsim.reactor, self.loadsim.simulator)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def stopService(self):
</span><span class="cx">         yield super(SimulatorService, self).stopService()
</span><span class="cx">         yield self.clientsim.stop()
</span><ins>+        # yield self.loadsim.simulator.stop()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -596,6 +601,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Start observing.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        print(&quot;Starting reporter service&quot;)
</ins><span class="cx">         super(ReporterService, self).startService()
</span><span class="cx">         self.loadsim.reporter = self
</span><span class="cx"> 
</span><span class="lines">@@ -670,6 +676,7 @@
</span><span class="cx">         super(WorkerSpawnerService, self).startService()
</span><span class="cx">         self.bridges = []
</span><span class="cx">         for workerID, worker in enumerate(self.loadsim.workers):
</span><ins>+            print(&quot;Building bridge for #&quot; + str(workerID))
</ins><span class="cx">             bridge = ProcessProtocolBridge(
</span><span class="cx">                 self, Manager(self.loadsim, workerID, len(self.loadsim.workers),
</span><span class="cx">                               self.output)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigseventupdatesonlyplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/event-updates-only.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/event-updates-only.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/event-updates-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,543 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Define the kinds of software and user behavior the load simulation
-                        will simulate. --&gt;
-                &lt;key&gt;clients&lt;/key&gt;
-
-                &lt;!-- Have as many different kinds of software and user behavior configurations
-                        as you want. Each is a dict --&gt;
-                &lt;array&gt;
-
-                        &lt;dict&gt;
-
-                                &lt;!-- Here is a Lion iCal simulator. --&gt;
-                                &lt;key&gt;software&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
-
-                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- Name that appears in logs. --&gt;
-                                        &lt;key&gt;title&lt;/key&gt;
-                                        &lt;string&gt;10.7&lt;/string&gt;
-
-                                        &lt;!-- Client can poll the calendar home at some interval. This is 
-                                                in seconds. --&gt;
-                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
-                                        &lt;integer&gt;300000&lt;/integer&gt;
-
-                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
-                                                about calendar home changes instead of polling for them periodically. If 
-                                                this option is true, then look for the server advertisement for xmpp push 
-                                                and use it if possible. Still fall back to polling if there is no xmpp push 
-                                                advertised. --&gt;
-                                        &lt;key&gt;supportPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                &lt;/dict&gt;
-
-                                &lt;!-- The profiles define certain types of user behavior on top of the 
-                                        client software being simulated. --&gt;
-                                &lt;key&gt;profiles&lt;/key&gt;
-                                &lt;array&gt;
-
-                                        &lt;!-- First an event-creating profile, which will periodically create 
-                                                new events at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;20&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;5&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
-                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.NormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
-                                                                        &lt;key&gt;mu&lt;/key&gt;
-                                                                        &lt;integer&gt;10&lt;/integer&gt;
-
-                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
-                                                                        &lt;key&gt;sigma&lt;/key&gt;
-                                                                        &lt;integer&gt;5&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define the distribution of who will be invited to an event.
-                                                        
-                                                                When inviteeClumping is turned on each invitee is based on a sample of
-                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
-                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
-                                                                those larger numbers will simply fail (the sim will report that situation).
-                                                                
-                                                                When inviteeClumping is off invitees will be sampled across an entire
-                                                                range of account indexes. In this case the distribution ought to be a
-                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
-                                                        --&gt;
-                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformIntegerDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;min&lt;/key&gt;
-                                                                        &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;max&lt;/key&gt;
-                                                                        &lt;integer&gt;99&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
-                                                        
-                                                                LogNormal is the best fit to observed data.
-
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
-                                                                mode should typically be 1, and mean whatever matches the user behavior.
-                                                                Our typical mean is 6.                                                         
-                                                             --&gt;
-                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;1&lt;/integer&gt;
-                                                                        &lt;!-- mean - average--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;6&lt;/integer&gt;
-                                                                        &lt;!-- maximum --&gt;
-                                                                        &lt;key&gt;maximum&lt;/key&gt;
-                                                                        &lt;real&gt;100&lt;/real&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
-                                             handles replies received. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define how long to wait after seeing a new invitation before
-                                                                accepting it.
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
-                                                                (i.e., half of the user have accepted by that time).                                                                
-                                                        --&gt;
-                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;300&lt;/integer&gt;
-                                                                        &lt;!-- median - 50% done--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;1800&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- A task-creating profile, which will periodically create 
-                                                new tasks at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new task. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;300&lt;/integer&gt;
-
-                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                &lt;/array&gt;
-
-                                &lt;!-- Determine the frequency at which this client configuration will 
-                                        appear in the clients which are created by the load tester. --&gt;
-                                &lt;key&gt;weight&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigseventsonlyplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/events-only.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/events-only.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/events-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,543 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Define the kinds of software and user behavior the load simulation
-                        will simulate. --&gt;
-                &lt;key&gt;clients&lt;/key&gt;
-
-                &lt;!-- Have as many different kinds of software and user behavior configurations
-                        as you want. Each is a dict --&gt;
-                &lt;array&gt;
-
-                        &lt;dict&gt;
-
-                                &lt;!-- Here is a Lion iCal simulator. --&gt;
-                                &lt;key&gt;software&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
-
-                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- Name that appears in logs. --&gt;
-                                        &lt;key&gt;title&lt;/key&gt;
-                                        &lt;string&gt;10.7&lt;/string&gt;
-
-                                        &lt;!-- Client can poll the calendar home at some interval. This is 
-                                                in seconds. --&gt;
-                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
-                                        &lt;integer&gt;300000&lt;/integer&gt;
-
-                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
-                                                about calendar home changes instead of polling for them periodically. If 
-                                                this option is true, then look for the server advertisement for xmpp push 
-                                                and use it if possible. Still fall back to polling if there is no xmpp push 
-                                                advertised. --&gt;
-                                        &lt;key&gt;supportPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                &lt;/dict&gt;
-
-                                &lt;!-- The profiles define certain types of user behavior on top of the 
-                                        client software being simulated. --&gt;
-                                &lt;key&gt;profiles&lt;/key&gt;
-                                &lt;array&gt;
-
-                                        &lt;!-- First an event-creating profile, which will periodically create 
-                                                new events at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;20&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;5&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
-                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.NormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mu gives the mean of the normal distribution (in seconds). --&gt;
-                                                                        &lt;key&gt;mu&lt;/key&gt;
-                                                                        &lt;integer&gt;10&lt;/integer&gt;
-
-                                                                        &lt;!-- and sigma gives its standard deviation. --&gt;
-                                                                        &lt;key&gt;sigma&lt;/key&gt;
-                                                                        &lt;integer&gt;5&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define the distribution of who will be invited to an event.
-                                                        
-                                                                When inviteeClumping is turned on each invitee is based on a sample of
-                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
-                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
-                                                                those larger numbers will simply fail (the sim will report that situation).
-                                                                
-                                                                When inviteeClumping is off invitees will be sampled across an entire
-                                                                range of account indexes. In this case the distribution ought to be a
-                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
-                                                        --&gt;
-                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformIntegerDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;min&lt;/key&gt;
-                                                                        &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;max&lt;/key&gt;
-                                                                        &lt;integer&gt;99&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
-                                                        
-                                                                LogNormal is the best fit to observed data.
-
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
-                                                                mode should typically be 1, and mean whatever matches the user behavior.
-                                                                Our typical mean is 6.                                                         
-                                                             --&gt;
-                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;1&lt;/integer&gt;
-                                                                        &lt;!-- mean - average--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;6&lt;/integer&gt;
-                                                                        &lt;!-- maximum --&gt;
-                                                                        &lt;key&gt;maximum&lt;/key&gt;
-                                                                        &lt;real&gt;100&lt;/real&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
-                                             handles replies received. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define how long to wait after seeing a new invitation before
-                                                                accepting it.
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
-                                                                (i.e., half of the user have accepted by that time).                                                                
-                                                        --&gt;
-                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;300&lt;/integer&gt;
-                                                                        &lt;!-- median - 50% done--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;1800&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- A task-creating profile, which will periodically create 
-                                                new tasks at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new task. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;300&lt;/integer&gt;
-
-                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                &lt;/array&gt;
-
-                                &lt;!-- Determine the frequency at which this client configuration will 
-                                        appear in the clients which are created by the load tester. --&gt;
-                                &lt;key&gt;weight&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigsinvitesacceptsplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-accepts.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-accepts.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-accepts.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,522 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Define the kinds of software and user behavior the load simulation
-                        will simulate. --&gt;
-                &lt;key&gt;clients&lt;/key&gt;
-
-                &lt;!-- Have as many different kinds of software and user behavior configurations
-                        as you want. Each is a dict --&gt;
-                &lt;array&gt;
-
-                        &lt;dict&gt;
-
-                                &lt;!-- Here is a Lion iCal simulator. --&gt;
-                                &lt;key&gt;software&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
-
-                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- Name that appears in logs. --&gt;
-                                        &lt;key&gt;title&lt;/key&gt;
-                                        &lt;string&gt;10.7&lt;/string&gt;
-
-                                        &lt;!-- Client can poll the calendar home at some interval. This is 
-                                                in seconds. --&gt;
-                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
-                                        &lt;integer&gt;300000&lt;/integer&gt;
-
-                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
-                                                about calendar home changes instead of polling for them periodically. If 
-                                                this option is true, then look for the server advertisement for xmpp push 
-                                                and use it if possible. Still fall back to polling if there is no xmpp push 
-                                                advertised. --&gt;
-                                        &lt;key&gt;supportPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
-                                        &lt;true /&gt;
-                                &lt;/dict&gt;
-
-                                &lt;!-- The profiles define certain types of user behavior on top of the 
-                                        client software being simulated. --&gt;
-                                &lt;key&gt;profiles&lt;/key&gt;
-                                &lt;array&gt;
-
-                                        &lt;!-- First an event-creating profile, which will periodically create 
-                                                new events at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;20&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;5&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
-                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.FixedDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- interval (in seconds). --&gt;
-                                                                        &lt;key&gt;value&lt;/key&gt;
-                                                                        &lt;integer&gt;150&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define the distribution of who will be invited to an event.
-                                                        
-                                                                When inviteeClumping is turned on each invitee is based on a sample of
-                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
-                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
-                                                                those larger numbers will simply fail (the sim will report that situation).
-                                                                
-                                                                When inviteeClumping is off invitees will be sampled across an entire
-                                                                range of account indexes. In this case the distribution ought to be a
-                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
-                                                        --&gt;
-                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformIntegerDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;min&lt;/key&gt;
-                                                                        &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;max&lt;/key&gt;
-                                                                        &lt;integer&gt;99&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
-                                                        
-                                                                LogNormal is the best fit to observed data.
-
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
-                                                                mode should typically be 1, and mean whatever matches the user behavior.
-                                                                Our typical mean is 6.                                                         
-                                                             --&gt;
-                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.FixedDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- Number of attendees. --&gt;
-                                                                        &lt;key&gt;value&lt;/key&gt;
-                                                                        &lt;integer&gt;5&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;100&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
-                                             handles replies received. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define how long to wait after seeing a new invitation before
-                                                                accepting it.
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
-                                                                (i.e., half of the user have accepted by that time).                                                                
-                                                        --&gt;
-                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformDiscreteDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- Set of values to use - will be chosen in random order. --&gt;
-                                                                        &lt;key&gt;values&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;integer&gt;15&lt;/integer&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;integer&gt;30&lt;/integer&gt;
-                                                                        &lt;/array&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- A task-creating profile, which will periodically create 
-                                                new tasks at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new task. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;300&lt;/integer&gt;
-
-                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                &lt;/array&gt;
-
-                                &lt;!-- Determine the frequency at which this client configuration will 
-                                        appear in the clients which are created by the load tester. --&gt;
-                                &lt;key&gt;weight&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-
-                &lt;!-- Determine the interval between client creation. --&gt;
-                &lt;key&gt;arrivalInterval&lt;/key&gt;
-                &lt;integer&gt;5&lt;/integer&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigsinvitesonlyrecurringplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only-recurring.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only-recurring.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only-recurring.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,517 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Define the kinds of software and user behavior the load simulation
-                        will simulate. --&gt;
-                &lt;key&gt;clients&lt;/key&gt;
-
-                &lt;!-- Have as many different kinds of software and user behavior configurations
-                        as you want. Each is a dict --&gt;
-                &lt;array&gt;
-
-                        &lt;dict&gt;
-
-                                &lt;!-- Here is a Lion iCal simulator. --&gt;
-                                &lt;key&gt;software&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
-
-                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- Name that appears in logs. --&gt;
-                                        &lt;key&gt;title&lt;/key&gt;
-                                        &lt;string&gt;10.7&lt;/string&gt;
-
-                                        &lt;!-- Client can poll the calendar home at some interval. This is 
-                                                in seconds. --&gt;
-                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
-                                        &lt;integer&gt;300000&lt;/integer&gt;
-
-                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
-                                                about calendar home changes instead of polling for them periodically. If 
-                                                this option is true, then look for the server advertisement for xmpp push 
-                                                and use it if possible. Still fall back to polling if there is no xmpp push 
-                                                advertised. --&gt;
-                                        &lt;key&gt;supportPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                &lt;/dict&gt;
-
-                                &lt;!-- The profiles define certain types of user behavior on top of the 
-                                        client software being simulated. --&gt;
-                                &lt;key&gt;profiles&lt;/key&gt;
-                                &lt;array&gt;
-
-                                        &lt;!-- First an event-creating profile, which will periodically create 
-                                                new events at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;20&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;5&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
-                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.FixedDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- interval (in seconds). --&gt;
-                                                                        &lt;key&gt;value&lt;/key&gt;
-                                                                        &lt;integer&gt;120&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define the distribution of who will be invited to an event.
-                                                        
-                                                                When inviteeClumping is turned on each invitee is based on a sample of
-                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
-                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
-                                                                those larger numbers will simply fail (the sim will report that situation).
-                                                                
-                                                                When inviteeClumping is off invitees will be sampled across an entire
-                                                                range of account indexes. In this case the distribution ought to be a
-                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
-                                                        --&gt;
-                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformIntegerDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;min&lt;/key&gt;
-                                                                        &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;max&lt;/key&gt;
-                                                                        &lt;integer&gt;99&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
-                                                        
-                                                                LogNormal is the best fit to observed data.
-
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
-                                                                mode should typically be 1, and mean whatever matches the user behavior.
-                                                                Our typical mean is 6.                                                         
-                                                             --&gt;
-                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.FixedDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- Number of attendees. --&gt;
-                                                                        &lt;key&gt;value&lt;/key&gt;
-                                                                        &lt;integer&gt;5&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;100&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
-                                             handles replies received. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define how long to wait after seeing a new invitation before
-                                                                accepting it.
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
-                                                                (i.e., half of the user have accepted by that time).                                                                
-                                                        --&gt;
-                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;300&lt;/integer&gt;
-                                                                        &lt;!-- median - 50% done--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;1800&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- A task-creating profile, which will periodically create 
-                                                new tasks at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new task. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;300&lt;/integer&gt;
-
-                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                &lt;/array&gt;
-
-                                &lt;!-- Determine the frequency at which this client configuration will 
-                                        appear in the clients which are created by the load tester. --&gt;
-                                &lt;key&gt;weight&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-
-                &lt;!-- Determine the interval between client creation. --&gt;
-                &lt;key&gt;arrivalInterval&lt;/key&gt;
-                &lt;integer&gt;4&lt;/integer&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadteststandardconfigsinvitesonlyplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only.plist (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only.plist        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/standard-configs/invites-only.plist        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,533 +0,0 @@
</span><del>-&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
-
-&lt;!--
-    Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-
-    Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-    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 &quot;AS IS&quot; 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.
-  --&gt;
-
-&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
-&lt;plist version=&quot;1.0&quot;&gt;
-        &lt;dict&gt;
-                &lt;!-- Define the kinds of software and user behavior the load simulation
-                        will simulate. --&gt;
-                &lt;key&gt;clients&lt;/key&gt;
-
-                &lt;!-- Have as many different kinds of software and user behavior configurations
-                        as you want. Each is a dict --&gt;
-                &lt;array&gt;
-
-                        &lt;dict&gt;
-
-                                &lt;!-- Here is a Lion iCal simulator. --&gt;
-                                &lt;key&gt;software&lt;/key&gt;
-                                &lt;string&gt;contrib.performance.loadtest.ical.OS_X_10_7&lt;/string&gt;
-
-                                &lt;!-- Arguments to use to initialize the client instance. --&gt;
-                                &lt;key&gt;params&lt;/key&gt;
-                                &lt;dict&gt;
-                                        &lt;!-- Name that appears in logs. --&gt;
-                                        &lt;key&gt;title&lt;/key&gt;
-                                        &lt;string&gt;10.7&lt;/string&gt;
-
-                                        &lt;!-- Client can poll the calendar home at some interval. This is 
-                                                in seconds. --&gt;
-                                        &lt;key&gt;calendarHomePollInterval&lt;/key&gt;
-                                        &lt;integer&gt;300000&lt;/integer&gt;
-
-                                        &lt;!-- If the server advertises xmpp push, OS X 10.6 can wait for notifications 
-                                                about calendar home changes instead of polling for them periodically. If 
-                                                this option is true, then look for the server advertisement for xmpp push 
-                                                and use it if possible. Still fall back to polling if there is no xmpp push 
-                                                advertised. --&gt;
-                                        &lt;key&gt;supportPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                        &lt;key&gt;supportAmpPush&lt;/key&gt;
-                                        &lt;false /&gt;
-                                &lt;/dict&gt;
-
-                                &lt;!-- The profiles define certain types of user behavior on top of the 
-                                        client software being simulated. --&gt;
-                                &lt;key&gt;profiles&lt;/key&gt;
-                                &lt;array&gt;
-
-                                        &lt;!-- First an event-creating profile, which will periodically create 
-                                                new events at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Eventer&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;20&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.EventUpdater&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use
-                                                                its client to create a new event. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;5&lt;/integer&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;true/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;25&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile invites some number of new attendees to new events. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.RealisticInviter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the frequency at which new invitations will be sent out. --&gt;
-                                                        &lt;key&gt;sendInvitationDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.FixedDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- interval (in seconds). --&gt;
-                                                                        &lt;key&gt;value&lt;/key&gt;
-                                                                        &lt;integer&gt;120&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define the distribution of who will be invited to an event.
-                                                        
-                                                                When inviteeClumping is turned on each invitee is based on a sample of
-                                                                users &quot;close to&quot; the organizer based on account index. If the clumping
-                                                                is too &quot;tight&quot; for the requested number of attendees, then invites for
-                                                                those larger numbers will simply fail (the sim will report that situation).
-                                                                
-                                                                When inviteeClumping is off invitees will be sampled across an entire
-                                                                range of account indexes. In this case the distribution ought to be a
-                                                                UniformIntegerDistribution with min=0 and max set to the number of accounts.
-                                                        --&gt;
-                                                        &lt;key&gt;inviteeDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.UniformIntegerDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- The minimum value (inclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;min&lt;/key&gt;
-                                                                        &lt;integer&gt;0&lt;/integer&gt;
-                                                                        &lt;!-- The maximum value (exclusive) of the uniform distribution. --&gt;
-                                                                        &lt;key&gt;max&lt;/key&gt;
-                                                                        &lt;integer&gt;99&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;key&gt;inviteeClumping&lt;/key&gt;
-                                                        &lt;true/&gt;
-
-                                                        &lt;!-- Define the distribution of how many attendees will be invited to an event.
-                                                        
-                                                                LogNormal is the best fit to observed data.
-
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;mean&quot; is the mean value.        For invites,
-                                                                mode should typically be 1, and mean whatever matches the user behavior.
-                                                                Our typical mean is 6.                                                         
-                                                             --&gt;
-                                                        &lt;key&gt;inviteeCountDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.FixedDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- Number of attendees. --&gt;
-                                                                        &lt;key&gt;value&lt;/key&gt;
-                                                                        &lt;integer&gt;5&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how start times (DTSTART) for the randomly generated events 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;eventStartDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-
-                                                        &lt;!-- Define how recurrences are created. --&gt;
-                                                        &lt;key&gt;recurrenceDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- This distribution is pretty specialized.  We have a fixed set of
-                                                                     RRULEs defined for this distribution and pick each based on a
-                                                                     weight. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.RecurrenceDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- False to disable RRULEs --&gt;
-                                                                        &lt;key&gt;allowRecurrence&lt;/key&gt;
-                                                                        &lt;false/&gt;
-
-                                                                        &lt;!-- These are the weights for the specific set of RRULEs. --&gt;
-                                                                        &lt;key&gt;weights&lt;/key&gt;
-                                                                        &lt;dict&gt;
-                                                                                &lt;!-- Half of all events will be non-recurring --&gt;
-                                                                                &lt;key&gt;none&lt;/key&gt;
-                                                                                &lt;integer&gt;50&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Daily and weekly are pretty common --&gt;
-                                                                                &lt;key&gt;daily&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                                &lt;key&gt;weekly&lt;/key&gt;
-                                                                                &lt;integer&gt;20&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Monthly, yearly, daily &amp; weekly limit not so common --&gt;
-                                                                                &lt;key&gt;monthly&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;yearly&lt;/key&gt;
-                                                                                &lt;integer&gt;1&lt;/integer&gt;
-                                                                                &lt;key&gt;dailylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;2&lt;/integer&gt;
-                                                                                &lt;key&gt;weeklylimit&lt;/key&gt;
-                                                                                &lt;integer&gt;5&lt;/integer&gt;
-                                                                                
-                                                                                &lt;!-- Work days pretty common --&gt;
-                                                                                &lt;key&gt;workdays&lt;/key&gt;
-                                                                                &lt;integer&gt;10&lt;/integer&gt;
-                                                                        &lt;/dict&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- This profile accepts invitations to events, handles cancels, and
-                                             handles replies received. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Accepter&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define how long to wait after seeing a new invitation before
-                                                                accepting it.
-
-                                                                For LogNormal &quot;mode&quot; is the peak, &quot;median&quot; is the 50% cummulative value
-                                                                (i.e., half of the user have accepted by that time).                                                                
-                                                        --&gt;
-                                                        &lt;key&gt;acceptDelayDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.LogNormalDistribution&lt;/string&gt;
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- mode - peak--&gt;
-                                                                        &lt;key&gt;mode&lt;/key&gt;
-                                                                        &lt;integer&gt;300&lt;/integer&gt;
-                                                                        &lt;!-- median - 50% done--&gt;
-                                                                        &lt;key&gt;median&lt;/key&gt;
-                                                                        &lt;integer&gt;1800&lt;/integer&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                        &lt;!-- A task-creating profile, which will periodically create 
-                                                new tasks at a random time on a random calendar. --&gt;
-                                        &lt;dict&gt;
-                                                &lt;key&gt;class&lt;/key&gt;
-                                                &lt;string&gt;contrib.performance.loadtest.profiles.Tasker&lt;/string&gt;
-
-                                                &lt;key&gt;params&lt;/key&gt;
-                                                &lt;dict&gt;
-                                                        &lt;key&gt;enabled&lt;/key&gt;
-                                                        &lt;false/&gt;
-
-                                                        &lt;!-- Define the interval (in seconds) at which this profile will use 
-                                                                its client to create a new task. --&gt;
-                                                        &lt;key&gt;interval&lt;/key&gt;
-                                                        &lt;integer&gt;300&lt;/integer&gt;
-
-                                                        &lt;!-- Define how due times (DUE) for the randomly generated tasks 
-                                                                will be selected. This is an example of a &quot;Distribution&quot; parameter. The value 
-                                                                for most &quot;Distribution&quot; parameters are interchangeable and extensible. --&gt;
-                                                        &lt;key&gt;taskDueDistribution&lt;/key&gt;
-                                                        &lt;dict&gt;
-
-                                                                &lt;!-- 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. --&gt;
-                                                                &lt;key&gt;type&lt;/key&gt;
-                                                                &lt;string&gt;contrib.performance.stats.WorkDistribution&lt;/string&gt;
-
-                                                                &lt;key&gt;params&lt;/key&gt;
-                                                                &lt;dict&gt;
-                                                                        &lt;!-- These are the days of the week the distribution will use. --&gt;
-                                                                        &lt;key&gt;daysOfWeek&lt;/key&gt;
-                                                                        &lt;array&gt;
-                                                                                &lt;string&gt;mon&lt;/string&gt;
-                                                                                &lt;string&gt;tue&lt;/string&gt;
-                                                                                &lt;string&gt;wed&lt;/string&gt;
-                                                                                &lt;string&gt;thu&lt;/string&gt;
-                                                                                &lt;string&gt;fri&lt;/string&gt;
-                                                                        &lt;/array&gt;
-
-                                                                        &lt;!-- The earliest hour of a day at which an event might be scheduled. --&gt;
-                                                                        &lt;key&gt;beginHour&lt;/key&gt;
-                                                                        &lt;integer&gt;8&lt;/integer&gt;
-
-                                                                        &lt;!-- And the latest hour of a day (at which an event will be scheduled 
-                                                                                to begin!). --&gt;
-                                                                        &lt;key&gt;endHour&lt;/key&gt;
-                                                                        &lt;integer&gt;16&lt;/integer&gt;
-
-                                                                        &lt;!-- The timezone in which the event is scheduled. (XXX Does this 
-                                                                                really work right?) --&gt;
-                                                                        &lt;key&gt;tzname&lt;/key&gt;
-                                                                        &lt;string&gt;America/Los_Angeles&lt;/string&gt;
-                                                                &lt;/dict&gt;
-                                                        &lt;/dict&gt;
-                                                &lt;/dict&gt;
-                                        &lt;/dict&gt;
-
-                                &lt;/array&gt;
-
-                                &lt;!-- Determine the frequency at which this client configuration will 
-                                        appear in the clients which are created by the load tester. --&gt;
-                                &lt;key&gt;weight&lt;/key&gt;
-                                &lt;integer&gt;1&lt;/integer&gt;
-                        &lt;/dict&gt;
-                &lt;/array&gt;
-        &lt;/dict&gt;
-&lt;/plist&gt;
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestsubscribepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/subscribe.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/subscribe.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/subscribe.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,30 +0,0 @@
</span><del>-
-class Subscription(object):
-    def __init__(self, periodical, subscriber):
-        self.periodical = periodical
-        self.subscriber = subscriber
-
-
-    def cancel(self):
-        self.periodical.subscriptions.remove(self)
-
-
-    def issue(self, issue):
-        self.subscriber(issue)
-
-
-
-class Periodical(object):
-    def __init__(self):
-        self.subscriptions = []
-
-
-    def subscribe(self, who):
-        subscription = Subscription(self, who)
-        self.subscriptions.append(subscription)
-        return subscription
-
-
-    def issue(self, issue):
-        for subscr in self.subscriptions:
-            subscr.issue(issue)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttemplatespy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/templates.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/templates.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/templates.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,72 @@
</span><ins>+##
+# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+#
+##
+&quot;&quot;&quot;
+A list of predefined component templates for use in the client sim
+&quot;&quot;&quot;
+
+from twistedcaldav.ical import Component
+
+
+# Default Event
+eventTemplate = Component.fromString(&quot;&quot;&quot;
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+TRANSP:OPAQUE
+SUMMARY:Sample Event
+UID:00000000-0000-0000-0000-000000000000
+CREATED:00000000T000000Z
+DTSTAMP:00000000T000000Z
+DTSTART:00000000T000000
+DTEND:00000000T000000
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+
+
+# Default Task
+taskTemplate = Component.fromString(&quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN
+CALSCALE:GREGORIAN
+BEGIN:VTODO
+SUMMARY:Sample Task
+UID:00000000-0000-0000-0000-000000000000
+CREATED:00000000T000000Z
+DTSTAMP:00000000T000000Z
+END:VTODO
+END:VCALENDAR
+&quot;&quot;&quot;)
+
+
+# Default Alarm
+alarmTemplate = Component.fromString(&quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN
+CALSCALE:GREGORIAN
+BEGIN:VALARM
+ACTION:DISPLAY
+TRIGGER:-PT5M
+UID:00000000-0000-0000-0000-000000000000
+DESCRIPTION:Sample Alarm
+END:VALARM
+END:VCALENDAR
+&quot;&quot;&quot;)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_icalpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_ical.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_ical.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_ical.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,2022 +0,0 @@
</span><del>-##
-# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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 caldavclientlibrary.protocol.caldav.definitions import caldavxml
-from caldavclientlibrary.protocol.caldav.definitions import csxml
-from caldavclientlibrary.protocol.url import URL
-from caldavclientlibrary.protocol.webdav.definitions import davxml
-
-from contrib.performance.httpclient import MemoryConsumer, StringProducer
-from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, OS_X_10_6
-from contrib.performance.loadtest.sim import _DirectoryRecord
-
-from pycalendar.datetime import DateTime
-from pycalendar.timezone import Timezone
-
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
-from twisted.internet.protocol import ProtocolToConsumerAdapter
-from twisted.python.failure import Failure
-from twisted.trial.unittest import TestCase
-from twisted.web.client import ResponseDone
-from twisted.web.http import OK, NO_CONTENT, CREATED, MULTI_STATUS
-from twisted.web.http_headers import Headers
-
-from twistedcaldav.ical import Component
-from twistedcaldav.timezones import TimezoneCache
-
-import json
-import os
-
-EVENT_UID = 'D94F247D-7433-43AF-B84B-ADD684D023B0'
-
-EVENT = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VEVENT
-CREATED:20101018T155454Z
-UID:%(UID)s
-DTEND;TZID=America/New_York:20101028T130000
-ATTENDEE;CN=&quot;User 03&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user03@example.com&quot;;PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03@example.co
- m
-ATTENDEE;CN=&quot;User 01&quot;;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
- example.com
-TRANSP:OPAQUE
-SUMMARY:Attended Event
-DTSTART;TZID=America/New_York:20101028T120000
-DTSTAMP:20101018T155513Z
-ORGANIZER;CN=&quot;User 01&quot;:mailto:user01@example.com
-SEQUENCE:3
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;) % {'UID': EVENT_UID}
-
-EVENT_INVITE = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-X-LIC-LOCATION:America/New_York
-BEGIN:STANDARD
-DTSTART:18831118T120358
-RDATE:18831118T120358
-TZNAME:EST
-TZOFFSETFROM:-045602
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19180331T020000
-RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19181027T020000
-RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19200101T000000
-RDATE:19200101T000000
-RDATE:19420101T000000
-RDATE:19460101T000000
-RDATE:19670101T000000
-TZNAME:EST
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19200328T020000
-RDATE:19200328T020000
-RDATE:19740106T020000
-RDATE:19750223T020000
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19201031T020000
-RDATE:19201031T020000
-RDATE:19450930T020000
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19210424T020000
-RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19210925T020000
-RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19420209T020000
-RDATE:19420209T020000
-TZNAME:EWT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19450814T190000
-RDATE:19450814T190000
-TZNAME:EPT
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19460428T020000
-RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19460929T020000
-RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19551030T020000
-RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19670430T020000
-RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19671029T020000
-RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19760425T020000
-RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19870405T020000
-RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20101018T155454Z
-UID:%(UID)s
-DTEND;TZID=America/New_York:20101028T130000
-ATTENDEE;CN=&quot;User 02&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user02@example.com&quot;;PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user02@example.co
- m
-ATTENDEE;CN=&quot;User 03&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user03@example.com&quot;;PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03@example.co
- m
-ATTENDEE;CN=&quot;User 01&quot;;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:user01
-TRANSP:OPAQUE
-SUMMARY:Attended Event
-DTSTART;TZID=America/New_York:20101028T120000
-DTSTAMP:20101018T155513Z
-ORGANIZER;CN=&quot;User 01&quot;:urn:uuid:user01
-SEQUENCE:3
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;) % {'UID': EVENT_UID}
-
-EVENT_AND_TIMEZONE = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-X-LIC-LOCATION:America/New_York
-BEGIN:STANDARD
-DTSTART:18831118T120358
-RDATE:18831118T120358
-TZNAME:EST
-TZOFFSETFROM:-045602
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19180331T020000
-RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19181027T020000
-RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19200101T000000
-RDATE:19200101T000000
-RDATE:19420101T000000
-RDATE:19460101T000000
-RDATE:19670101T000000
-TZNAME:EST
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19200328T020000
-RDATE:19200328T020000
-RDATE:19740106T020000
-RDATE:19750223T020000
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19201031T020000
-RDATE:19201031T020000
-RDATE:19450930T020000
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19210424T020000
-RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19210925T020000
-RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19420209T020000
-RDATE:19420209T020000
-TZNAME:EWT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19450814T190000
-RDATE:19450814T190000
-TZNAME:EPT
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19460428T020000
-RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19460929T020000
-RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:STANDARD
-DTSTART:19551030T020000
-RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19670430T020000
-RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:19671029T020000
-RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:19760425T020000
-RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:19870405T020000
-RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20101018T155454Z
-UID:%(UID)s
-DTEND;TZID=America/New_York:20101028T130000
-ATTENDEE;CN=&quot;User 03&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user03@example.com&quot;;PARTS
- TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03@example.co
- m
-ATTENDEE;CN=&quot;User 01&quot;;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
- example.com
-TRANSP:OPAQUE
-SUMMARY:Attended Event
-DTSTART;TZID=America/New_York:20101028T120000
-DTSTAMP:20101018T155513Z
-ORGANIZER;CN=&quot;User 01&quot;:mailto:user01@example.com
-SEQUENCE:3
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;) % {'UID': EVENT_UID}
-
-
-
-class EventTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{Event}.
-    &quot;&quot;&quot;
-    def test_uid(self):
-        &quot;&quot;&quot;
-        When the C{vevent} attribute of an L{Event} instance is set,
-        L{Event.getUID} returns the UID value from it.
-        &quot;&quot;&quot;
-        event = Event(None, u'/foo/bar', u'etag', Component.fromString(EVENT))
-        self.assertEquals(event.getUID(), EVENT_UID)
-
-
-    def test_withoutUID(self):
-        &quot;&quot;&quot;
-        When an L{Event} has a C{vevent} attribute set to C{None},
-        L{Event.getUID} returns C{None}.
-        &quot;&quot;&quot;
-        event = Event(None, u'/bar/baz', u'etag')
-        self.assertIdentical(event.getUID(), None)
-
-
-
-PRINCIPAL_PROPFIND_RESPONSE = &quot;&quot;&quot;\
-&lt;?xml version='1.0' encoding='UTF-8'?&gt;
-&lt;multistatus xmlns='DAV:'&gt;
-  &lt;response&gt;
-    &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;principal-collection-set&gt;
-          &lt;href&gt;/principals/&lt;/href&gt;
-        &lt;/principal-collection-set&gt;
-        &lt;calendar-home-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01&lt;/href&gt;
-        &lt;/calendar-home-set&gt;
-        &lt;calendar-user-address-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;href xmlns='DAV:'&gt;/principals/__uids__/user01/&lt;/href&gt;
-          &lt;href xmlns='DAV:'&gt;/principals/users/user01/&lt;/href&gt;
-        &lt;/calendar-user-address-set&gt;
-        &lt;schedule-inbox-URL xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/inbox/&lt;/href&gt;
-        &lt;/schedule-inbox-URL&gt;
-        &lt;schedule-outbox-URL xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/outbox/&lt;/href&gt;
-        &lt;/schedule-outbox-URL&gt;
-        &lt;dropbox-home-URL xmlns='http://calendarserver.org/ns/'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/dropbox/&lt;/href&gt;
-        &lt;/dropbox-home-URL&gt;
-        &lt;notification-URL xmlns='http://calendarserver.org/ns/'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/notification/&lt;/href&gt;
-        &lt;/notification-URL&gt;
-        &lt;displayname&gt;User 01&lt;/displayname&gt;
-        &lt;principal-URL&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/principal-URL&gt;
-        &lt;supported-report-set&gt;
-          &lt;supported-report&gt;
-            &lt;report&gt;
-              &lt;acl-principal-prop-set/&gt;
-            &lt;/report&gt;
-          &lt;/supported-report&gt;
-          &lt;supported-report&gt;
-            &lt;report&gt;
-              &lt;principal-match/&gt;
-            &lt;/report&gt;
-          &lt;/supported-report&gt;
-          &lt;supported-report&gt;
-            &lt;report&gt;
-              &lt;principal-property-search/&gt;
-            &lt;/report&gt;
-          &lt;/supported-report&gt;
-          &lt;supported-report&gt;
-            &lt;report&gt;
-              &lt;expand-property/&gt;
-            &lt;/report&gt;
-          &lt;/supported-report&gt;
-        &lt;/supported-report-set&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-&lt;/multistatus&gt;
-&quot;&quot;&quot;
-
-_CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE = &quot;&quot;&quot;\
-&lt;?xml version='1.0' encoding='UTF-8'?&gt;
-&lt;multistatus xmlns='DAV:'&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        %(xmpp)s
-        &lt;displayname&gt;User 01&lt;/displayname&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'&gt;/Some/Unique/Value&lt;/pushkey&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/notification/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;displayname&gt;notification&lt;/displayname&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-          &lt;notification xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/dropbox/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-          &lt;dropbox-home xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;displayname/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/calendar/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'&gt;c2696540-4c4c-4a31-adaf-c99630776828#3&lt;/getctag&gt;
-        &lt;displayname&gt;calendar&lt;/displayname&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'&gt;#0252D4FF&lt;/calendar-color&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'&gt;1&lt;/calendar-order&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;comp name='VEVENT'/&gt;
-          &lt;comp name='VTODO'/&gt;
-          &lt;comp name='VTIMEZONE'/&gt;
-          &lt;comp name='VFREEBUSY'/&gt;
-        &lt;/supported-calendar-component-set&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-          &lt;calendar xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;opaque/&gt;
-        &lt;/schedule-calendar-transp&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:DAYLIGHT
-TZOFFSETFROM:-0500
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-DTSTART:20070311T020000
-TZNAME:EDT
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:-0400
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-DTSTART:20071104T020000
-TZNAME:EST
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-END:VCALENDAR
-]]&gt;&lt;/calendar-timezone&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/outbox/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;comp name='VEVENT'/&gt;
-          &lt;comp name='VTODO'/&gt;
-          &lt;comp name='VTIMEZONE'/&gt;
-          &lt;comp name='VFREEBUSY'/&gt;
-        &lt;/supported-calendar-component-set&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-          &lt;schedule-outbox xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;schedule-send xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;schedule xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;displayname/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/freebusy&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;resourcetype&gt;
-          &lt;free-busy-url xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;schedule-deliver xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;schedule xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;displayname/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/calendars/__uids__/user01/inbox/&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;getctag xmlns='http://calendarserver.org/ns/'&gt;a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0&lt;/getctag&gt;
-        &lt;displayname&gt;inbox&lt;/displayname&gt;
-        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;comp name='VEVENT'/&gt;
-          &lt;comp name='VTODO'/&gt;
-          &lt;comp name='VTIMEZONE'/&gt;
-          &lt;comp name='VFREEBUSY'/&gt;
-        &lt;/supported-calendar-component-set&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-          &lt;schedule-inbox xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;/resourcetype&gt;
-        &lt;owner&gt;
-          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
-        &lt;/owner&gt;
-        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/calendar&lt;/href&gt;
-        &lt;/calendar-free-busy-set&gt;
-        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'&gt;
-          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/calendar&lt;/href&gt;
-        &lt;/schedule-default-calendar-URL&gt;
-        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
-        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
-        &lt;current-user-privilege-set&gt;
-          &lt;privilege&gt;
-            &lt;schedule-deliver xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;schedule xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;all/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-properties/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-content/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;bind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unbind/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;unlock/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;write-acl/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-current-user-privilege-set/&gt;
-          &lt;/privilege&gt;
-          &lt;privilege&gt;
-            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-          &lt;/privilege&gt;
-        &lt;/current-user-privilege-set&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
-        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
-        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-&lt;/multistatus&gt;
-&quot;&quot;&quot;
-
-CALENDAR_HOME_PROPFIND_RESPONSE = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {
-    &quot;xmpp&quot;: &quot;&quot;&quot;\
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;&quot;&quot;&quot;,
-}
-
-CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {
-    &quot;xmpp&quot;: &quot;&quot;&quot;\
-        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'&gt;xmpp.example.invalid:1952&lt;/xmpp-server&gt;
-        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'&gt;xmpp:pubsub.xmpp.example.invalid?pubsub;node=/CalDAV/another.example.invalid/user01/&lt;/xmpp-uri&gt;&quot;&quot;&quot;,
-}
-
-CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {&quot;xmpp&quot;: &quot;&quot;}
-
-
-
-class MemoryResponse(object):
-    def __init__(self, version, code, phrase, headers, bodyProducer):
-        self.version = version
-        self.code = code
-        self.phrase = phrase
-        self.headers = headers
-        self.length = bodyProducer.length
-        self._bodyProducer = bodyProducer
-
-
-    def deliverBody(self, protocol):
-        protocol.makeConnection(self._bodyProducer)
-        d = self._bodyProducer.startProducing(ProtocolToConsumerAdapter(protocol))
-        d.addCallback(lambda ignored: protocol.connectionLost(Failure(ResponseDone())))
-
-
-
-class OS_X_10_6Mixin:
-    &quot;&quot;&quot;
-    Mixin for L{TestCase}s for L{OS_X_10_6}.
-    &quot;&quot;&quot;
-    def setUp(self):
-        TimezoneCache.create()
-        self.record = _DirectoryRecord(
-            u&quot;user91&quot;, u&quot;user91&quot;, u&quot;User 91&quot;, u&quot;user91@example.org&quot;, u&quot;user91&quot;,
-        )
-        serializePath = self.mktemp()
-        os.mkdir(serializePath)
-        self.client = OS_X_10_6(
-            None,
-            &quot;http://127.0.0.1&quot;,
-            &quot;/principals/users/%s/&quot;,
-            serializePath,
-            self.record,
-            None,
-        )
-
-
-    def interceptRequests(self):
-        requests = []
-        def request(*args, **kwargs):
-            result = Deferred()
-            requests.append((result, args))
-            return result
-        self.client._request = request
-        return requests
-
-
-
-class OS_X_10_6Tests(OS_X_10_6Mixin, TestCase):
-    &quot;&quot;&quot;
-    Tests for L{OS_X_10_6}.
-    &quot;&quot;&quot;
-    def test_parsePrincipalPROPFINDResponse(self):
-        &quot;&quot;&quot;
-        L{Principal._parsePROPFINDResponse} accepts an XML document
-        like the one in the response to a I{PROPFIND} request for
-        I{/principals/__uids__/&lt;uid&gt;/} and returns a C{PropFindResult}
-        representing the data from it.
-        &quot;&quot;&quot;
-        principals = self.client._parseMultiStatus(PRINCIPAL_PROPFIND_RESPONSE)
-        principal = principals['/principals/__uids__/user01/']
-        self.assertEquals(
-            principal.getHrefProperties(),
-            {
-                davxml.principal_collection_set: URL(path='/principals/'),
-                caldavxml.calendar_home_set: URL(path='/calendars/__uids__/user01'),
-                caldavxml.calendar_user_address_set: (
-                    URL(path='/principals/__uids__/user01/'),
-                    URL(path='/principals/users/user01/'),
-                ),
-                caldavxml.schedule_inbox_URL: URL(path='/calendars/__uids__/user01/inbox/'),
-                caldavxml.schedule_outbox_URL: URL(path='/calendars/__uids__/user01/outbox/'),
-                csxml.dropbox_home_URL: URL(path='/calendars/__uids__/user01/dropbox/'),
-                csxml.notification_URL: URL(path='/calendars/__uids__/user01/notification/'),
-                davxml.principal_URL: URL(path='/principals/__uids__/user01/'),
-            }
-        )
-        self.assertEquals(
-            principal.getTextProperties(),
-            {davxml.displayname: 'User 01'})
-
-#         self.assertEquals(
-#             principal.getSomething(),
-#             {SUPPORTED_REPORT_SET: (
-#                     '{DAV:}acl-principal-prop-set',
-#                     '{DAV:}principal-match',
-#                     '{DAV:}principal-property-search',
-#                     '{DAV:}expand-property',
-#                     )})
-
-
-    def test_extractCalendars(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6._extractCalendars} accepts a calendar home
-        PROPFIND response body and returns a list of calendar objects
-        constructed from the data extracted from the response.
-        &quot;&quot;&quot;
-        home = &quot;/calendars/__uids__/user01/&quot;
-        calendars = self.client._extractCalendars(
-            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE), home)
-        calendars.sort(key=lambda cal: cal.resourceType)
-        calendar, inbox = calendars
-
-        self.assertEquals(calendar.resourceType, caldavxml.calendar)
-        self.assertEquals(calendar.name, &quot;calendar&quot;)
-        self.assertEquals(calendar.url, &quot;/calendars/__uids__/user01/calendar/&quot;)
-        self.assertEquals(calendar.changeToken, &quot;c2696540-4c4c-4a31-adaf-c99630776828#3&quot;)
-
-        self.assertEquals(inbox.resourceType, caldavxml.schedule_inbox)
-        self.assertEquals(inbox.name, &quot;inbox&quot;)
-        self.assertEquals(inbox.url, &quot;/calendars/__uids__/user01/inbox/&quot;)
-        self.assertEquals(inbox.changeToken, &quot;a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0&quot;)
-
-        self.assertEqual({}, self.client.xmpp)
-
-
-    def test_extractCalendarsXMPP(self):
-        &quot;&quot;&quot;
-        If there is XMPP push information in a calendar home PROPFIND response,
-        L{OS_X_10_6._extractCalendars} finds it and records it.
-        &quot;&quot;&quot;
-        home = &quot;/calendars/__uids__/user01/&quot;
-        self.client._extractCalendars(
-            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP),
-            home
-        )
-        self.assertEqual({
-            home: XMPPPush(
-                &quot;xmpp.example.invalid:1952&quot;,
-                &quot;xmpp:pubsub.xmpp.example.invalid?pubsub;node=/CalDAV/another.example.invalid/user01/&quot;,
-                &quot;/Some/Unique/Value&quot;
-            )},
-            self.client.xmpp
-        )
-
-
-    def test_handleMissingXMPP(self):
-        home = &quot;/calendars/__uids__/user01/&quot;
-        self.client._extractCalendars(
-            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING), home)
-        self.assertEqual({}, self.client.xmpp)
-
-
-    @inlineCallbacks
-    def test_changeEventAttendee(self):
-        &quot;&quot;&quot;
-        OS_X_10_6.changeEventAttendee removes one attendee from an
-        existing event and appends another.
-        &quot;&quot;&quot;
-        requests = self.interceptRequests()
-
-        vevent = Component.fromString(EVENT)
-        attendees = tuple(vevent.mainComponent().properties(&quot;ATTENDEE&quot;))
-        old = attendees[0]
-        new = old.duplicate()
-        new.setParameter('CN', 'Some Other Guy')
-        event = Event(self.client.serializeLocation(), u'/some/calendar/1234.ics', None, vevent)
-        self.client._events[event.url] = event
-        self.client.changeEventAttendee(event.url, old, new)
-
-        _ignore_result, req = requests.pop(0)
-
-        # iCal PUTs the new VCALENDAR object.
-        _ignore_expectedResponseCode, method, url, headers, body = req
-        self.assertEquals(method, 'PUT')
-        self.assertEquals(url, 'http://127.0.0.1' + event.url)
-        self.assertIsInstance(url, str)
-        self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
-
-        consumer = MemoryConsumer()
-        yield body.startProducing(consumer)
-        vevent = Component.fromString(consumer.value())
-        attendees = tuple(vevent.mainComponent().properties(&quot;ATTENDEE&quot;))
-        self.assertEquals(len(attendees), 2)
-        self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
-        self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
-
-
-    def test_addEvent(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6.addEvent} PUTs the event passed to it to the
-        server and updates local state to reflect its existence.
-        &quot;&quot;&quot;
-        requests = self.interceptRequests()
-
-        calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/mumble/', None)
-        self.client._calendars[calendar.url] = calendar
-
-        vcalendar = Component.fromString(EVENT)
-        d = self.client.addEvent(u'/mumble/frotz.ics', vcalendar)
-
-        result, req = requests.pop(0)
-
-        # iCal PUTs the new VCALENDAR object.
-        expectedResponseCode, method, url, headers, body = req
-        self.assertEqual(expectedResponseCode, CREATED)
-        self.assertEqual(method, 'PUT')
-        self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
-        self.assertIsInstance(url, str)
-        self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
-        consumer = MemoryConsumer()
-        finished = body.startProducing(consumer)
-        def cbFinished(ignored):
-            self.assertEqual(
-                Component.fromString(consumer.value()),
-                Component.fromString(EVENT_AND_TIMEZONE))
-        finished.addCallback(cbFinished)
-
-        def requested(ignored):
-            response = MemoryResponse(
-                ('HTTP', '1', '1'), CREATED, &quot;Created&quot;, Headers({}),
-                StringProducer(&quot;&quot;))
-            result.callback(response)
-        finished.addCallback(requested)
-
-        return d
-
-
-    @inlineCallbacks
-    def test_addInvite(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6.addInvite} PUTs the event passed to it to the
-        server and updates local state to reflect its existence, but
-        it also does attendee auto-complete and free-busy checks before
-        the PUT.
-        &quot;&quot;&quot;
-
-        calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/mumble/', None)
-        self.client._calendars[calendar.url] = calendar
-
-        vcalendar = Component.fromString(EVENT_INVITE)
-
-        self.client.uuid = u'urn:uuid:user01'
-        self.client.email = u'mailto:user01@example.com'
-        self.client.principalCollection = &quot;/principals/&quot;
-        self.client.outbox = &quot;/calendars/__uids__/user01/outbox/&quot;
-
-        @inlineCallbacks
-        def _testReport(*args, **kwargs):
-            expectedResponseCode, method, url, headers, body = args
-            self.assertEqual(expectedResponseCode, (MULTI_STATUS,))
-            self.assertEqual(method, 'REPORT')
-            self.assertEqual(url, 'http://127.0.0.1/principals/')
-            self.assertIsInstance(url, str)
-            self.assertEqual(headers.getRawHeaders('content-type'), ['text/xml'])
-
-            consumer = MemoryConsumer()
-            yield body.startProducing(consumer)
-
-            response = MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;MultiStatus&quot;, Headers({}),
-                StringProducer(&quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;&lt;multistatus xmlns='DAV:' /&gt;&quot;))
-
-            returnValue(response)
-
-        @inlineCallbacks
-        def _testPost(*args, **kwargs):
-            expectedResponseCode, method, url, headers, body = args
-            self.assertEqual(expectedResponseCode, OK)
-            self.assertEqual(method, 'POST')
-            self.assertEqual(url, 'http://127.0.0.1/calendars/__uids__/user01/outbox/')
-            self.assertIsInstance(url, str)
-            self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
-            consumer = MemoryConsumer()
-            yield body.startProducing(consumer)
-            self.assertNotEqual(consumer.value().find(kwargs[&quot;attendee&quot;]), -1)
-
-            response = MemoryResponse(
-                ('HTTP', '1', '1'), OK, &quot;OK&quot;, Headers({}),
-                StringProducer(&quot;&quot;))
-
-            returnValue(response)
-
-        def _testPost02(*args, **kwargs):
-            return _testPost(*args, attendee=&quot;ATTENDEE:mailto:user02@example.com&quot;, **kwargs)
-
-        def _testPost03(*args, **kwargs):
-            return _testPost(*args, attendee=&quot;ATTENDEE:mailto:user03@example.com&quot;, **kwargs)
-
-        @inlineCallbacks
-        def _testPut(*args, **kwargs):
-            expectedResponseCode, method, url, headers, body = args
-            self.assertEqual(expectedResponseCode, CREATED)
-            self.assertEqual(method, 'PUT')
-            self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
-            self.assertIsInstance(url, str)
-            self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
-            consumer = MemoryConsumer()
-            yield body.startProducing(consumer)
-            self.assertEqual(
-                Component.fromString(consumer.value()),
-                Component.fromString(EVENT_INVITE))
-
-            response = MemoryResponse(
-                ('HTTP', '1', '1'), CREATED, &quot;Created&quot;, Headers({}),
-                StringProducer(&quot;&quot;))
-
-            returnValue(response)
-
-        requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut, ]
-
-        def _requestHandler(*args, **kwargs):
-            handler = requests.pop(0)
-            return handler(*args, **kwargs)
-        self.client._request = _requestHandler
-        yield self.client.addInvite('/mumble/frotz.ics', vcalendar)
-
-
-    def test_deleteEvent(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6.deleteEvent} DELETEs the event at the relative
-        URL passed to it and updates local state to reflect its
-        removal.
-        &quot;&quot;&quot;
-        requests = self.interceptRequests()
-
-        calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/foo/', None)
-        event = Event(None, calendar.url + u'bar.ics', None)
-        self.client._calendars[calendar.url] = calendar
-        self.client._setEvent(event.url, event)
-
-        d = self.client.deleteEvent(event.url)
-
-        result, req = requests.pop()
-
-        expectedResponseCode, method, url = req
-
-        self.assertEqual(expectedResponseCode, NO_CONTENT)
-        self.assertEqual(method, 'DELETE')
-        self.assertEqual(url, 'http://127.0.0.1' + event.url)
-        self.assertIsInstance(url, str)
-
-        self.assertNotIn(event.url, self.client._events)
-        self.assertNotIn(u'bar.ics', calendar.events)
-
-        response = MemoryResponse(
-            ('HTTP', '1', '1'), NO_CONTENT, &quot;No Content&quot;, None,
-            StringProducer(&quot;&quot;))
-        result.callback(response)
-        return d
-
-
-    def test_serialization(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6.serialize} properly generates a JSON document.
-        &quot;&quot;&quot;
-        clientPath = os.path.join(self.client.serializePath, &quot;user91-OS_X_10.6&quot;)
-        self.assertFalse(os.path.exists(clientPath))
-        indexPath = os.path.join(clientPath, &quot;index.json&quot;)
-        self.assertFalse(os.path.exists(indexPath))
-
-        cal1 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:004f8e41-b071-4b30-bb3b-6aada4adcc10
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        cal2 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:00a79cad-857b-418e-a54a-340b5686d747
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        events = (
-            Event(self.client.serializeLocation(), u'/home/calendar/1.ics', u'123.123', Component.fromString(cal1)),
-            Event(self.client.serializeLocation(), u'/home/inbox/i1.ics', u'123.123', Component.fromString(cal2)),
-        )
-        self.client._events.update(dict([[event.url, event] for event in events]))
-
-        calendars = (
-            Calendar(str(caldavxml.calendar), set(('VEVENT',)), u'calendar', u'/home/calendar/', &quot;123&quot;),
-            Calendar(str(caldavxml.calendar), set(('VTODO',)), u'tasks', u'/home/tasks/', &quot;456&quot;),
-            Calendar(str(caldavxml.schedule_inbox), set(('VEVENT', &quot;VTODO&quot;,)), u'calendar', u'/home/inbox/', &quot;789&quot;),
-        )
-        self.client._calendars.update(dict([[calendar.url, calendar] for calendar in calendars]))
-        self.client._calendars[&quot;/home/calendar/&quot;].events[&quot;1.ics&quot;] = events[0]
-        self.client._calendars[&quot;/home/inbox/&quot;].events[&quot;i1.ics&quot;] = events[1]
-
-        self.client.serialize()
-        self.assertTrue(os.path.exists(clientPath))
-        self.assertTrue(os.path.exists(indexPath))
-        def _normDict(d):
-            return dict([(k, sorted(v, key=lambda x: x[&quot;changeToken&quot; if k == &quot;calendars&quot; else &quot;url&quot;]) if v else None,) for k, v in d.items()])
-        self.assertEqual(_normDict(json.loads(open(indexPath).read())), _normDict(json.loads(&quot;&quot;&quot;{
-  &quot;calendars&quot;: [
-    {
-      &quot;changeToken&quot;: &quot;123&quot;,
-      &quot;name&quot;: &quot;calendar&quot;,
-      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
-      &quot;componentTypes&quot;: [
-        &quot;VEVENT&quot;
-      ],
-      &quot;url&quot;: &quot;/home/calendar/&quot;,
-      &quot;events&quot;: [
-        &quot;1.ics&quot;
-      ]
-    },
-    {
-      &quot;changeToken&quot;: &quot;789&quot;,
-      &quot;name&quot;: &quot;calendar&quot;,
-      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}schedule-inbox&quot;,
-      &quot;componentTypes&quot;: [
-        &quot;VEVENT&quot;,
-        &quot;VTODO&quot;
-      ],
-      &quot;url&quot;: &quot;/home/inbox/&quot;,
-      &quot;events&quot;: [
-        &quot;i1.ics&quot;
-      ]
-    },
-    {
-      &quot;changeToken&quot;: &quot;456&quot;,
-      &quot;name&quot;: &quot;tasks&quot;,
-      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
-      &quot;componentTypes&quot;: [
-        &quot;VTODO&quot;
-      ],
-      &quot;url&quot;: &quot;/home/tasks/&quot;,
-      &quot;events&quot;: []
-    }
-  ],
-  &quot;principalURL&quot;: null,
-  &quot;events&quot;: [
-    {
-      &quot;url&quot;: &quot;/home/calendar/1.ics&quot;,
-      &quot;scheduleTag&quot;: null,
-      &quot;etag&quot;: &quot;123.123&quot;,
-      &quot;uid&quot;: &quot;004f8e41-b071-4b30-bb3b-6aada4adcc10&quot;
-    },
-    {
-      &quot;url&quot;: &quot;/home/inbox/i1.ics&quot;,
-      &quot;scheduleTag&quot;: null,
-      &quot;etag&quot;: &quot;123.123&quot;,
-      &quot;uid&quot;: &quot;00a79cad-857b-418e-a54a-340b5686d747&quot;
-    }
-  ]
-}&quot;&quot;&quot;)))
-
-        event1Path = os.path.join(clientPath, &quot;calendar&quot;, &quot;1.ics&quot;)
-        self.assertTrue(os.path.exists(event1Path))
-        self.assertEqual(open(event1Path).read(), cal1)
-
-        event2Path = os.path.join(clientPath, &quot;inbox&quot;, &quot;i1.ics&quot;)
-        self.assertTrue(os.path.exists(event2Path))
-        self.assertEqual(open(event2Path).read(), cal2)
-
-
-    def test_deserialization(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6.deserailize} properly parses a JSON document.
-        &quot;&quot;&quot;
-
-        cal1 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:004f8e41-b071-4b30-bb3b-6aada4adcc10
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        cal2 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:00a79cad-857b-418e-a54a-340b5686d747
-DTSTART:20120817T113000
-DTEND:20120817T114500
-DTSTAMP:20120815T154420Z
-SEQUENCE:2
-SUMMARY:Simple event
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-
-        clientPath = os.path.join(self.client.serializePath, &quot;user91-OS_X_10.6&quot;)
-        os.mkdir(clientPath)
-        indexPath = os.path.join(clientPath, &quot;index.json&quot;)
-        open(indexPath, &quot;w&quot;).write(&quot;&quot;&quot;{
-  &quot;calendars&quot;: [
-    {
-      &quot;changeToken&quot;: &quot;321&quot;,
-      &quot;name&quot;: &quot;calendar&quot;,
-      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
-      &quot;componentTypes&quot;: [
-        &quot;VEVENT&quot;
-      ],
-      &quot;url&quot;: &quot;/home/calendar/&quot;,
-      &quot;events&quot;: [
-        &quot;2.ics&quot;
-      ]
-    },
-    {
-      &quot;changeToken&quot;: &quot;987&quot;,
-      &quot;name&quot;: &quot;calendar&quot;,
-      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}schedule-inbox&quot;,
-      &quot;componentTypes&quot;: [
-        &quot;VEVENT&quot;,
-        &quot;VTODO&quot;
-      ],
-      &quot;url&quot;: &quot;/home/inbox/&quot;,
-      &quot;events&quot;: [
-        &quot;i2.ics&quot;
-      ]
-    },
-    {
-      &quot;changeToken&quot;: &quot;654&quot;,
-      &quot;name&quot;: &quot;tasks&quot;,
-      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
-      &quot;componentTypes&quot;: [
-        &quot;VTODO&quot;
-      ],
-      &quot;url&quot;: &quot;/home/tasks/&quot;,
-      &quot;events&quot;: []
-    }
-  ],
-  &quot;principalURL&quot;: null,
-  &quot;events&quot;: [
-    {
-      &quot;url&quot;: &quot;/home/calendar/2.ics&quot;,
-      &quot;scheduleTag&quot;: null,
-      &quot;etag&quot;: &quot;321.321&quot;,
-      &quot;uid&quot;: &quot;004f8e41-b071-4b30-bb3b-6aada4adcc10&quot;
-    },
-    {
-      &quot;url&quot;: &quot;/home/inbox/i2.ics&quot;,
-      &quot;scheduleTag&quot;: null,
-      &quot;etag&quot;: &quot;987.987&quot;,
-      &quot;uid&quot;: &quot;00a79cad-857b-418e-a54a-340b5686d747&quot;
-    }
-  ]
-}&quot;&quot;&quot;)
-
-        os.mkdir(os.path.join(clientPath, &quot;calendar&quot;))
-        event1Path = os.path.join(clientPath, &quot;calendar&quot;, &quot;2.ics&quot;)
-        open(event1Path, &quot;w&quot;).write(cal1)
-        os.mkdir(os.path.join(clientPath, &quot;inbox&quot;))
-        event1Path = os.path.join(clientPath, &quot;inbox&quot;, &quot;i2.ics&quot;)
-        open(event1Path, &quot;w&quot;).write(cal2)
-
-        self.client.deserialize()
-
-        self.assertEqual(len(self.client._calendars), 3)
-        self.assertTrue(&quot;/home/calendar/&quot; in self.client._calendars)
-        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].changeToken, &quot;321&quot;)
-        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].name, &quot;calendar&quot;)
-        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].resourceType, &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;)
-        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].componentTypes, set((&quot;VEVENT&quot;,)))
-        self.assertTrue(&quot;/home/tasks/&quot; in self.client._calendars)
-        self.assertTrue(&quot;/home/inbox/&quot; in self.client._calendars)
-        self.assertEqual(self.client._calendars[&quot;/home/inbox/&quot;].componentTypes, set((&quot;VEVENT&quot;, &quot;VTODO&quot;,)))
-        self.assertEqual(len(self.client._events), 2)
-        self.assertTrue(&quot;/home/calendar/2.ics&quot; in self.client._events)
-        self.assertEqual(self.client._events[&quot;/home/calendar/2.ics&quot;].scheduleTag, None)
-        self.assertEqual(self.client._events[&quot;/home/calendar/2.ics&quot;].etag, &quot;321.321&quot;)
-        self.assertEqual(self.client._events[&quot;/home/calendar/2.ics&quot;].getUID(), &quot;004f8e41-b071-4b30-bb3b-6aada4adcc10&quot;)
-        self.assertEqual(str(self.client._events[&quot;/home/calendar/2.ics&quot;].component), cal1)
-        self.assertTrue(&quot;/home/inbox/i2.ics&quot; in self.client._events)
-        self.assertEqual(self.client._events[&quot;/home/inbox/i2.ics&quot;].scheduleTag, None)
-        self.assertEqual(self.client._events[&quot;/home/inbox/i2.ics&quot;].etag, &quot;987.987&quot;)
-        self.assertEqual(self.client._events[&quot;/home/inbox/i2.ics&quot;].getUID(), &quot;00a79cad-857b-418e-a54a-340b5686d747&quot;)
-        self.assertEqual(str(self.client._events[&quot;/home/inbox/i2.ics&quot;].component), cal2)
-
-
-
-class UpdateCalendarTests(OS_X_10_6Mixin, TestCase):
-    &quot;&quot;&quot;
-    Tests for L{OS_X_10_6._updateCalendar}.
-    &quot;&quot;&quot;
-
-    _CALENDAR_PROPFIND_RESPONSE_BODY = &quot;&quot;&quot;\
-&lt;?xml version='1.0' encoding='UTF-8'?&gt;
-&lt;multistatus xmlns='DAV:'&gt;
-  &lt;response&gt;
-    &lt;href&gt;/something/anotherthing.ics&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-        &lt;/resourcetype&gt;
-        &lt;getetag&gt;&quot;None&quot;&lt;/getetag&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/something/else.ics&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;resourcetype&gt;
-          &lt;collection/&gt;
-        &lt;/resourcetype&gt;
-        &lt;getetag&gt;&quot;None&quot;&lt;/getetag&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-   &lt;/response&gt;
-&lt;/multistatus&gt;
-&quot;&quot;&quot;
-    _CALENDAR_REPORT_RESPONSE_BODY = &quot;&quot;&quot;\
-&lt;?xml version='1.0' encoding='UTF-8'?&gt;
-&lt;multistatus xmlns='DAV:'&gt;
-  &lt;response&gt;
-    &lt;href&gt;/something/anotherthing.ics&lt;/href&gt;
-    &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
-  &lt;/response&gt;
-  &lt;response&gt;
-    &lt;href&gt;/something/else.ics&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;getetag&gt;&quot;ef70beb4cb7da4b2e2950350b09e9a01&quot;&lt;/getetag&gt;
-        &lt;calendar-data xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:CD54161A13AA8A4649D3781E@caldav.corp.apple.com
-DTSTART:20110715T140000Z
-DURATION:PT1H
-DTSTAMP:20110715T144217Z
-SUMMARY:Test2
-END:VEVENT
-END:VCALENDAR
-]]&gt;&lt;/calendar-data&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-&lt;/multistatus&gt;
-&quot;&quot;&quot;
-
-    _CALENDAR_REPORT_RESPONSE_BODY_1 = &quot;&quot;&quot;\
-&lt;?xml version='1.0' encoding='UTF-8'?&gt;
-&lt;multistatus xmlns='DAV:'&gt;
-  &lt;response&gt;
-    &lt;href&gt;/something/anotherthing.ics&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;getetag&gt;&quot;ef70beb4cb7da4b2e2950350b09e9a01&quot;&lt;/getetag&gt;
-        &lt;calendar-data xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:anotherthing@caldav.corp.apple.com
-DTSTART:20110715T140000Z
-DURATION:PT1H
-DTSTAMP:20110715T144217Z
-SUMMARY:Test1
-END:VEVENT
-END:VCALENDAR
-]]&gt;&lt;/calendar-data&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-&lt;/multistatus&gt;
-&quot;&quot;&quot;
-
-    _CALENDAR_REPORT_RESPONSE_BODY_2 = &quot;&quot;&quot;\
-&lt;?xml version='1.0' encoding='UTF-8'?&gt;
-&lt;multistatus xmlns='DAV:'&gt;
-  &lt;response&gt;
-    &lt;href&gt;/something/else.ics&lt;/href&gt;
-    &lt;propstat&gt;
-      &lt;prop&gt;
-        &lt;getetag&gt;&quot;ef70beb4cb7da4b2e2950350b09e9a01&quot;&lt;/getetag&gt;
-        &lt;calendar-data xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VEVENT
-UID:else@caldav.corp.apple.com
-DTSTART:20110715T140000Z
-DURATION:PT1H
-DTSTAMP:20110715T144217Z
-SUMMARY:Test2
-END:VEVENT
-END:VCALENDAR
-]]&gt;&lt;/calendar-data&gt;
-      &lt;/prop&gt;
-      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
-    &lt;/propstat&gt;
-  &lt;/response&gt;
-&lt;/multistatus&gt;
-&quot;&quot;&quot;
-
-    def test_eventMissing(self):
-        &quot;&quot;&quot;
-        If an event included in the calendar PROPFIND response no longer exists
-        by the time a REPORT is issued for that event, the 404 is handled and
-        the rest of the normal update logic for that event is skipped.
-        &quot;&quot;&quot;
-        requests = self.interceptRequests()
-
-        calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
-        self.client._calendars[calendar.url] = calendar
-        self.client._updateCalendar(calendar, &quot;1234&quot;)
-        result, req = requests.pop(0)
-        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('PROPFIND', method)
-        self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
-        result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
-
-        result, req = requests.pop(0)
-        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('REPORT', method)
-        self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
-        # Someone else comes along and gets rid of the event
-        del self.client._events[&quot;/something/anotherthing.ics&quot;]
-
-        result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)))
-
-        # Verify that processing proceeded to the response after the one with a
-        # 404 status.
-        self.assertIn('/something/else.ics', self.client._events)
-
-
-    def test_multigetBatch(self):
-        &quot;&quot;&quot;
-        If an event included in the calendar PROPFIND response no longer exists
-        by the time a REPORT is issued for that event, the 404 is handled and
-        the rest of the normal update logic for that event is skipped.
-        &quot;&quot;&quot;
-        requests = self.interceptRequests()
-
-        self.patch(self.client, &quot;MULTIGET_BATCH_SIZE&quot;, 1)
-
-        calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
-        self.client._calendars[calendar.url] = calendar
-        self.client._updateCalendar(calendar, &quot;1234&quot;)
-        result, req = requests.pop(0)
-        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('PROPFIND', method)
-        self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
-        result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
-
-        result, req = requests.pop(0)
-        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('REPORT', method)
-        self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
-        result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)))
-
-        self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
-        self.assertTrue(self.client._events['/something/else.ics'].etag is None)
-
-        result, req = requests.pop(0)
-        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
-        self.assertEqual('REPORT', method)
-        self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
-
-        result.callback(
-            MemoryResponse(
-                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
-                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)))
-
-        self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
-        self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
-
-
-
-class VFreeBusyTests(OS_X_10_6Mixin, TestCase):
-    &quot;&quot;&quot;
-    Tests for L{OS_X_10_6.requestAvailability}.
-    &quot;&quot;&quot;
-    def test_requestAvailability(self):
-        &quot;&quot;&quot;
-        L{OS_X_10_6.requestAvailability} accepts a date range and a set of
-        account uuids and issues a VFREEBUSY request.  It returns a Deferred
-        which fires with a dict mapping account uuids to availability range
-        information.
-        &quot;&quot;&quot;
-        self.client.uuid = u'urn:uuid:user01'
-        self.client.email = u'mailto:user01@example.com'
-        self.client.outbox = &quot;/calendars/__uids__/%s/outbox/&quot; % (self.record.uid,)
-        requests = self.interceptRequests()
-
-        start = DateTime(2011, 6, 10, 10, 45, 0, tzid=Timezone.UTCTimezone)
-        end = DateTime(2011, 6, 10, 11, 15, 0, tzid=Timezone.UTCTimezone)
-        d = self.client.requestAvailability(
-            start, end, [u&quot;urn:uuid:user05&quot;, u&quot;urn:uuid:user10&quot;])
-
-        result, req = requests.pop(0)
-        expectedResponseCode, method, url, headers, body = req
-
-        self.assertEqual(OK, expectedResponseCode)
-        self.assertEqual('POST', method)
-        self.assertEqual(
-            'http://127.0.0.1/calendars/__uids__/%s/outbox/' % (self.record.uid,),
-            url)
-
-        self.assertEqual(headers.getRawHeaders('originator'), ['mailto:user01@example.com'])
-        self.assertEqual(headers.getRawHeaders('recipient'), ['urn:uuid:user05, urn:uuid:user10'])
-        self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
-
-        consumer = MemoryConsumer()
-        finished = body.startProducing(consumer)
-        def cbFinished(ignored):
-            vevent = Component.fromString(consumer.value())
-            uid = vevent.resourceUID()
-            dtstamp = vevent.mainComponent().propertyValue(&quot;DTSTAMP&quot;)
-            dtstamp = dtstamp.getText()
-            self.assertEqual(&quot;&quot;&quot;BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VFREEBUSY
-UID:%(uid)s
-DTEND:20110611T000000Z
-ATTENDEE:urn:uuid:user05
-ATTENDEE:urn:uuid:user10
-DTSTART:20110610T000000Z
-DTSTAMP:%(dtstamp)s
-ORGANIZER:mailto:user01@example.com
-SUMMARY:Availability for urn:uuid:user05, urn:uuid:user10
-END:VFREEBUSY
-END:VCALENDAR
-&quot;&quot;&quot;.replace('\n', '\r\n') % {'uid': uid, 'dtstamp': dtstamp}, consumer.value())
-
-        finished.addCallback(cbFinished)
-
-        def requested(ignored):
-            response = MemoryResponse(
-                ('HTTP', '1', '1'), OK, &quot;Ok&quot;, Headers({}),
-                StringProducer(&quot;&quot;))
-            result.callback(response)
-        finished.addCallback(requested)
-
-        return d
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_populationpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_population.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_population.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_population.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,390 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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.
-#
-##
-
-&quot;&quot;&quot;
-Tests for some things in L{loadtest.population}.
-&quot;&quot;&quot;
-
-from twisted.trial.unittest import TestCase
-
-from contrib.performance.loadtest.population import ReportStatistics
-
-class ReportStatisticsTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{loadtest.population.ReportStatistics}.
-    &quot;&quot;&quot;
-    def test_countUsers(self):
-        &quot;&quot;&quot;
-        L{ReportStatistics.countUsers} returns the number of users observed to
-        have acted in the simulation.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        users = ['user01', 'user02', 'user03']
-        for user in users:
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=1.23, user=user, client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-        self.assertEqual(len(users), logger.countUsers())
-
-
-    def test_countClients(self):
-        &quot;&quot;&quot;
-        L{ReportStatistics.countClients} returns the number of clients observed to
-        have acted in the simulation.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        clients = ['c01', 'c02', 'c03']
-        for client in clients:
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=1.23, user=&quot;user01&quot;, client_type=&quot;test&quot;, client_id=client
-            ))
-        self.assertEqual(len(clients), logger.countClients())
-
-
-    def test_clientFailures(self):
-        &quot;&quot;&quot;
-        L{ReportStatistics.countClientFailures} returns the number of clients observed to
-        have failed in the simulation.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        clients = ['c01', 'c02', 'c03']
-        for client in clients:
-            logger.observe(dict(
-                type='client-failure', reason=&quot;testing %s&quot; % (client,)
-            ))
-        self.assertEqual(len(clients), logger.countClientFailures())
-
-
-    def test_simFailures(self):
-        &quot;&quot;&quot;
-        L{ReportStatistics.countSimFailures} returns the number of clients observed to
-        have caused an error in the simulation.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        clients = ['c01', 'c02', 'c03']
-        for client in clients:
-            logger.observe(dict(
-                type='sim-failure', reason=&quot;testing %s&quot; % (client,)
-            ))
-        self.assertEqual(len(clients), logger.countSimFailures())
-
-
-    def test_noFailures(self):
-        &quot;&quot;&quot;
-        If fewer than 1% of requests fail, fewer than 1% of requests take 5
-        seconds or more, and fewer than 5% of requests take 3 seconds or more,
-        L{ReportStatistics.failures} returns an empty list.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        logger.observe(dict(
-            type='response', method='GET', success=True,
-            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual([], logger.failures())
-
-
-    def test_requestFailures(self):
-        &quot;&quot;&quot;
-        If more than 1% of requests fail, L{ReportStatistics.failures} returns a
-        list containing a string describing this.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        for _ignore in range(98):
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-        logger.observe(dict(
-            type='response', method='GET', success=False,
-            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [&quot;Greater than 1% GET failed&quot;],
-            logger.failures())
-
-
-    def test_threeSecondFailure(self):
-        &quot;&quot;&quot;
-        If more than 5% of requests take longer than 3 seconds,
-        L{ReportStatistics.failures} returns a list containing a string
-        describing that.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        for _ignore in range(94):
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-        for _ignore in range(5):
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=3.5, user='user02', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-        self.assertEqual(
-            [&quot;Greater than 5% GET exceeded 3 second response time&quot;],
-            logger.failures())
-
-
-    def test_fiveSecondFailure(self):
-        &quot;&quot;&quot;
-        If more than 1% of requests take longer than 5 seconds,
-        L{ReportStatistics.failures} returns a list containing a string
-        describing that.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        for _ignore in range(98):
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-        logger.observe(dict(
-            type='response', method='GET', success=True,
-            duration=5.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [&quot;Greater than 1% GET exceeded 5 second response time&quot;],
-            logger.failures())
-
-
-    def test_methodsCountedSeparately(self):
-        &quot;&quot;&quot;
-        The counts for one method do not affect the results of another method.
-        &quot;&quot;&quot;
-        logger = ReportStatistics()
-        for _ignore in range(99):
-            logger.observe(dict(
-                type='response', method='GET', success=True,
-                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-            logger.observe(dict(
-                type='response', method='POST', success=True,
-                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-            ))
-
-        logger.observe(dict(
-            type='response', method='GET', success=False,
-            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='POST', success=False,
-            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-
-        self.assertEqual([], logger.failures())
-
-
-    def test_bucketRequest(self):
-        &quot;&quot;&quot;
-        PUT(xxx-huge/large/medium/small} have different thresholds. Test that requests straddling
-        each of those are correctly determined to be failures or not.
-        &quot;&quot;&quot;
-
-        _thresholds = {
-            &quot;requests&quot;: {
-                &quot;limits&quot;: [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
-                &quot;thresholds&quot;: {
-                    &quot;default&quot;: [100.0, 100.0, 100.0, 5.0, 1.0, 0.5, 0.0],
-                    &quot;PUT{organizer-small}&quot;: [100.0, 50.0, 25.0, 5.0, 1.0, 0.5, 0.0],
-                    &quot;PUT{organizer-medium}&quot;: [100.0, 100.0, 50.0, 25.0, 5.0, 1.0, 0.5],
-                    &quot;PUT{organizer-large}&quot;: [100.0, 100.0, 100.0, 50.0, 25.0, 5.0, 1.0],
-                    &quot;PUT{organizer-huge}&quot;: [100.0, 100.0, 100.0, 100.0, 100.0, 50.0, 25.0],
-                }
-            }
-        }
-
-        # -small below threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual([], logger.failures())
-
-        # -small above 0.5 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-small}', success=True,
-            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [&quot;Greater than 50% PUT{organizer-small} exceeded 0.5 second response time&quot;],
-            logger.failures()
-        )
-
-        # -medium below 0.5 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [],
-            logger.failures()
-        )
-
-        # -medium above 1.0 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-medium}', success=True,
-            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [&quot;Greater than 50% PUT{organizer-medium} exceeded 1 second response time&quot;],
-            logger.failures()
-        )
-
-        # -large below 1.0 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [],
-            logger.failures()
-        )
-
-        # -large above 3.0 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=3.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=3.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-large}', success=True,
-            duration=3.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [&quot;Greater than 50% PUT{organizer-large} exceeded 3 second response time&quot;],
-            logger.failures()
-        )
-
-        # -huge below 10.0 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=12.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=8, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=11.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=9.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [],
-            logger.failures()
-        )
-
-        # -huge above 10.0 threshold
-        logger = ReportStatistics(thresholds=_thresholds)
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=12.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=9.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=12.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        logger.observe(dict(
-            type='response', method='PUT{organizer-huge}', success=True,
-            duration=42.42, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
-        ))
-        self.assertEqual(
-            [&quot;Greater than 50% PUT{organizer-huge} exceeded 10 second response time&quot;],
-            logger.failures()
-        )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_profilespy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_profiles.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_profiles.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_profiles.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,1091 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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.
-#
-##
-
-&quot;&quot;&quot;
-Tests for loadtest.profiles.
-&quot;&quot;&quot;
-
-from StringIO import StringIO
-
-from caldavclientlibrary.protocol.caldav.definitions import caldavxml, csxml
-
-from twisted.trial.unittest import TestCase
-from twisted.internet.task import Clock
-from twisted.internet.defer import succeed, fail
-from twisted.web.http import NO_CONTENT, PRECONDITION_FAILED
-from twisted.web.client import Response
-
-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
-
-import os
-
-SIMPLE_EVENT = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:DAYLIGHT
-TZOFFSETFROM:-0500
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-DTSTART:20070311T020000
-TZNAME:EDT
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:-0400
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-DTSTART:20071104T020000
-TZNAME:EST
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-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
-&quot;&quot;&quot;
-
-INVITED_EVENT = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-END:VTIMEZONE
-BEGIN:VEVENT
-UID:882C3D50-0DAE-45CB-A2E7-DA75DA9BE452
-DTSTART;TZID=America/New_York:20110131T130000
-DTEND;TZID=America/New_York:20110131T140000
-ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
- CEPTED:urn:uuid:user01
-ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02@example.com;PARTSTAT=NE
- EDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:urn:uuid:user02
-CREATED:20110124T170357Z
-DTSTAMP:20110124T170425Z
-ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01
-SEQUENCE:3
-SUMMARY:Some Event For You
-TRANSP:TRANSPARENT
-X-APPLE-NEEDS-REPLY:TRUE
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;
-
-ACCEPTED_EVENT = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:STANDARD
-DTSTART:20071104T020000
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20070311T020000
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-END:VTIMEZONE
-BEGIN:VEVENT
-UID:882C3D50-0DAE-45CB-A2E7-DA75DA9BE452
-DTSTART;TZID=America/New_York:20110131T130000
-DTEND;TZID=America/New_York:20110131T140000
-ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
- CEPTED:urn:uuid:user01
-ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02@example.com;PARTSTAT=AC
- CEPTED:urn:uuid:user02
-CREATED:20110124T170357Z
-DTSTAMP:20110124T170425Z
-ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01
-SEQUENCE:3
-SUMMARY:Some Event For You
-TRANSP:TRANSPARENT
-X-APPLE-NEEDS-REPLY:TRUE
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;
-
-INBOX_REPLY = &quot;&quot;&quot;\
-BEGIN:VCALENDAR
-METHOD:REPLY
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1@example.com
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;
-
-
-
-class AnyUser(object):
-    def __getitem__(self, index):
-        return _AnyRecord(index)
-
-
-
-class _AnyRecord(object):
-    def __init__(self, index):
-        self.uid = u&quot;user%02d&quot; % (index,)
-        self.password = u&quot;user%02d&quot; % (index,)
-        self.commonName = u&quot;User %02d&quot; % (index,)
-        self.email = u&quot;user%02d@example.com&quot; % (index,)
-        self.guid = u&quot;user%02d&quot; % (index,)
-
-
-
-class Deterministic(object):
-    def __init__(self, value=None):
-        self.value = value
-
-
-    def gauss(self, mean, stddev):
-        &quot;&quot;&quot;
-        Pretend to return a value from a gaussian distribution with mu
-        parameter C{mean} and sigma parameter C{stddev}.  But actually
-        always return C{mean + 1}.
-        &quot;&quot;&quot;
-        return mean + 1
-
-
-    def choice(self, sequence):
-        return sequence[0]
-
-
-    def sample(self):
-        return self.value
-
-
-
-class StubClient(BaseClient):
-    &quot;&quot;&quot;
-    Stand in for an iCalendar client.
-
-    @ivar rescheduled: A set of event URLs which will not allow
-        attendee changes due to a changed schedule tag.
-    @ivar _pendingFailures: dict mapping URLs to failure objects
-    &quot;&quot;&quot;
-    def __init__(self, number, serializePath):
-        self.serializePath = serializePath
-        os.mkdir(self.serializePath)
-        self.title = &quot;StubClient&quot;
-        self._events = {}
-        self._calendars = {}
-        self._pendingFailures = {}
-        self.record = _DirectoryRecord(
-            &quot;user%02d&quot; % (number,), &quot;user%02d&quot; % (number,),
-            &quot;User %02d&quot; % (number,), &quot;user%02d@example.org&quot; % (number,),
-            &quot;user%02d&quot; % (number,))
-        self.email = &quot;mailto:user%02d@example.com&quot; % (number,)
-        self.uuid = &quot;urn:uuid:user%02d&quot; % (number,)
-        self.rescheduled = set()
-        self.started = True
-
-
-    def _failDeleteWithObject(self, href, failureObject):
-        &quot;&quot;&quot;
-        Accessor for inserting intentional failures for deletes.
-        &quot;&quot;&quot;
-        self._pendingFailures[href] = failureObject
-
-
-    def serializeLocation(self):
-        &quot;&quot;&quot;
-        Return the path to the directory where data for this user is serialized.
-        &quot;&quot;&quot;
-        if self.serializePath is None or not os.path.isdir(self.serializePath):
-            return None
-
-        key = &quot;%s-%s&quot; % (self.record.uid, &quot;StubClient&quot;)
-        path = os.path.join(self.serializePath, key)
-        if not os.path.exists(path):
-            os.mkdir(path)
-        elif not os.path.isdir(path):
-            return None
-
-        return path
-
-
-    def addEvent(self, href, vevent):
-        self._events[href] = Event(self.serializePath, href, None, vevent)
-        return succeed(None)
-
-
-    def addInvite(self, href, vevent):
-        return self.addEvent(href, vevent)
-
-
-    def deleteEvent(self, href,):
-        del self._events[href]
-        calendar, uid = href.rsplit('/', 1)
-        del self._calendars[calendar + '/'].events[uid]
-        if href in self._pendingFailures:
-            failureObject = self._pendingFailures.pop(href)
-            return fail(failureObject)
-        else:
-            return succeed(None)
-
-
-    def updateEvent(self, href):
-        self.rescheduled.remove(href)
-        return succeed(None)
-
-
-    def addEventAttendee(self, href, attendee):
-        vevent = self._events[href].component
-        vevent.mainComponent().addProperty(attendee)
-        self._events[href].component = vevent
-
-
-    def changeEventAttendee(self, href, old, new):
-        if href in self.rescheduled:
-            return fail(IncorrectResponseCode(
-                NO_CONTENT,
-                Response(
-                    ('HTTP', 1, 1), PRECONDITION_FAILED,
-                    'Precondition Failed', None, None))
-            )
-
-        vevent = self._events[href].component
-        vevent.mainComponent().removeProperty(old)
-        vevent.mainComponent().addProperty(new)
-        self._events[href].component = vevent
-        return succeed(None)
-
-
-    def _makeSelfAttendee(self):
-        attendee = Property(
-            name=u'ATTENDEE',
-            value=self.email,
-            params={
-                'CN': self.record.commonName,
-                'CUTYPE': 'INDIVIDUAL',
-                'PARTSTAT': 'ACCEPTED',
-            },
-        )
-        return attendee
-
-
-    def _makeSelfOrganizer(self):
-        organizer = Property(
-            name=u'ORGANIZER',
-            value=self.email,
-            params={
-                'CN': self.record.commonName,
-            },
-        )
-        return organizer
-
-
-
-class SequentialDistribution(object):
-    def __init__(self, values):
-        self.values = values
-
-
-    def sample(self):
-        return self.values.pop(0)
-
-
-
-class InviterTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for loadtest.profiles.Inviter.
-    &quot;&quot;&quot;
-    def setUp(self):
-        self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
-
-
-    def _simpleAccount(self, userNumber, eventText):
-        client = StubClient(userNumber, self.mktemp())
-
-        vevent = Component.fromString(eventText)
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None)
-        client._calendars.update({calendar.url: calendar})
-
-        event = Event(client.serializeLocation(), calendar.url + u'1234.ics', None, vevent)
-
-        client._events.update({event.url: event})
-        calendar.events = {u'1234.ics': event}
-
-        return vevent, event, calendar, client
-
-
-    def test_enabled(self):
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-
-        inviter = Inviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
-        self.assertEqual(inviter.enabled, False)
-
-        inviter = Inviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: True})
-        self.assertEqual(inviter.enabled, True)
-
-
-    def test_doNotAddAttendeeToInbox(self):
-        &quot;&quot;&quot;
-        When the only calendar with any events is a schedule inbox, no
-        attempt is made to add attendees to an event on that calendar.
-        &quot;&quot;&quot;
-        userNumber = 10
-        vevent, _ignore_event, calendar, client = self._simpleAccount(
-            userNumber, SIMPLE_EVENT)
-        calendar.resourceType = caldavxml.schedule_inbox
-        inviter = Inviter(None, self.sim, client, userNumber)
-        inviter._invite()
-        self.assertFalse(vevent.mainComponent().hasProperty('ATTENDEE'))
-
-
-    def test_doNotAddAttendeeToNoCalendars(self):
-        &quot;&quot;&quot;
-        When there are no calendars and no events at all, the inviter
-        does nothing.
-        &quot;&quot;&quot;
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-        inviter = Inviter(None, self.sim, client, userNumber)
-        inviter._invite()
-        self.assertEquals(client._events, {})
-        self.assertEquals(client._calendars, {})
-
-
-    def test_doNotAddAttendeeToUninitializedEvent(self):
-        &quot;&quot;&quot;
-        When there is an L{Event} on a calendar but the details of the
-        event have not yet been retrieved, no attempt is made to add
-        invitees to that event.
-        &quot;&quot;&quot;
-        userNumber = 19
-        _ignore_vevent, event, calendar, client = self._simpleAccount(
-            userNumber, SIMPLE_EVENT)
-        event.component = event.etag = event.scheduleTag = None
-        inviter = Inviter(None, self.sim, client, userNumber)
-        inviter._invite()
-        self.assertEquals(client._events, {event.url: event})
-        self.assertEquals(client._calendars, {calendar.url: calendar})
-
-
-    def test_addAttendeeToEvent(self):
-        &quot;&quot;&quot;
-        When there is a normal calendar with an event, inviter adds an
-        attendee to it.
-        &quot;&quot;&quot;
-        userNumber = 16
-        _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount(
-            userNumber, SIMPLE_EVENT)
-        inviter = Inviter(Clock(), self.sim, client, userNumber)
-        inviter.setParameters(inviteeDistribution=Deterministic(1))
-        inviter._invite()
-        attendees = tuple(event.component.mainComponent().properties('ATTENDEE'))
-        self.assertEquals(len(attendees), 1)
-        for paramname, paramvalue in {
-            'CN': 'User %d' % (userNumber + 1,),
-            'CUTYPE': 'INDIVIDUAL',
-            'PARTSTAT': 'NEEDS-ACTION',
-            'ROLE': 'REQ-PARTICIPANT',
-            'RSVP': 'TRUE'
-        }.items():
-            self.assertTrue(attendees[0].hasParameter(paramname))
-            self.assertEqual(attendees[0].parameterValue(paramname), paramvalue)
-
-
-    def test_doNotAddSelfToEvent(self):
-        &quot;&quot;&quot;
-        If the inviter randomly selects its own user to be added to
-        the attendee list, a different user is added instead.
-        &quot;&quot;&quot;
-        selfNumber = 12
-        _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount(
-            selfNumber, SIMPLE_EVENT)
-
-        otherNumber = 20
-        values = [selfNumber - selfNumber, otherNumber - selfNumber]
-
-        inviter = Inviter(Clock(), self.sim, client, selfNumber)
-        inviter.setParameters(inviteeDistribution=SequentialDistribution(values))
-        inviter._invite()
-        attendees = tuple(event.component.mainComponent().properties('ATTENDEE'))
-        self.assertEquals(len(attendees), 1)
-        for paramname, paramvalue in {
-            'CN': 'User %d' % (otherNumber,),
-            'CUTYPE': 'INDIVIDUAL',
-            'PARTSTAT': 'NEEDS-ACTION',
-            'ROLE': 'REQ-PARTICIPANT',
-            'RSVP': 'TRUE'
-        }.items():
-            self.assertTrue(attendees[0].hasParameter(paramname))
-            self.assertEqual(attendees[0].parameterValue(paramname), paramvalue)
-
-
-    def test_doNotAddExistingToEvent(self):
-        &quot;&quot;&quot;
-        If the inviter randomly selects a user which is already an
-        invitee on the event, a different user is added instead.
-        &quot;&quot;&quot;
-        selfNumber = 1
-        _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount(
-            selfNumber, INVITED_EVENT)
-
-        invitee = tuple(event.component.mainComponent().properties('ATTENDEE'))[0]
-        inviteeNumber = int(invitee.parameterValue('CN').split()[1])
-        anotherNumber = inviteeNumber + 5
-        values = [inviteeNumber - selfNumber, anotherNumber - selfNumber]
-
-        inviter = Inviter(Clock(), self.sim, client, selfNumber)
-        inviter.setParameters(inviteeDistribution=SequentialDistribution(values))
-        inviter._invite()
-        attendees = tuple(event.component.mainComponent().properties('ATTENDEE'))
-        self.assertEquals(len(attendees), 3)
-        for paramname, paramvalue in {
-            'CN': 'User %02d' % (anotherNumber,),
-            'CUTYPE': 'INDIVIDUAL',
-            'PARTSTAT': 'NEEDS-ACTION',
-            'ROLE': 'REQ-PARTICIPANT',
-            'RSVP': 'TRUE'
-        }.items():
-            self.assertTrue(attendees[2].hasParameter(paramname))
-            self.assertEqual(attendees[2].parameterValue(paramname), paramvalue)
-
-
-    def test_everybodyInvitedAlready(self):
-        &quot;&quot;&quot;
-        If the first so-many randomly selected users we come across
-        are already attendees on the event, the invitation attempt is
-        abandoned.
-        &quot;&quot;&quot;
-        selfNumber = 1
-        vevent, _ignore_event, _ignore_calendar, client = self._simpleAccount(
-            selfNumber, INVITED_EVENT)
-        inviter = Inviter(Clock(), self.sim, client, selfNumber)
-        # Always return a user number which has already been invited.
-        inviter.setParameters(inviteeDistribution=Deterministic(2 - selfNumber))
-        inviter._invite()
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        self.assertEquals(len(attendees), 2)
-
-
-    def test_doNotInviteToSomeoneElsesEvent(self):
-        &quot;&quot;&quot;
-        If there are events on our calendar which are being organized
-        by someone else, the inviter does not attempt to invite new
-        users to them.
-        &quot;&quot;&quot;
-        selfNumber = 2
-        vevent, _ignore_event, _ignore_calendar, client = self._simpleAccount(
-            selfNumber, INVITED_EVENT)
-        inviter = Inviter(None, self.sim, client, selfNumber)
-        # Try to send an invitation, but with only one event on the
-        # calendar, of which we are not the organizer.  It should be
-        # unchanged afterwards.
-        inviter._invite()
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        self.assertEqual(len(attendees), 2)
-        self.assertEqual(attendees[0].parameterValue('CN'), 'User 01')
-        self.assertEqual(attendees[1].parameterValue('CN'), 'User 02')
-
-
-
-class RealisticInviterTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for loadtest.profiles.RealisticInviter.
-    &quot;&quot;&quot;
-    def setUp(self):
-        self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
-
-
-    def _simpleAccount(self, userNumber, eventText):
-        client = StubClient(userNumber, self.mktemp())
-        vevent = Component.fromString(eventText)
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None)
-        event = Event(client.serializeLocation(), calendar.url + u'1234.ics', None, vevent)
-        calendar.events = {u'1234.ics': event}
-        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, self.mktemp())
-
-        inviter = RealisticInviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
-        self.assertEqual(inviter.enabled, False)
-
-        inviter = RealisticInviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: True})
-        self.assertEqual(inviter.enabled, True)
-
-
-    def test_doNotAddInviteToInbox(self):
-        &quot;&quot;&quot;
-        When the only calendar with any events is a schedule inbox, no
-        attempt is made to add attendees to that calendar.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None)
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars.update({calendar.url: calendar})
-
-        inviter = RealisticInviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
-        inviter._invite()
-
-        self.assertEquals(client._events, {})
-
-
-    def test_doNotAddInviteToNoCalendars(self):
-        &quot;&quot;&quot;
-        When there are no calendars and no events at all, the inviter
-        does nothing.
-        &quot;&quot;&quot;
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-        inviter = RealisticInviter(None, self.sim, client, userNumber)
-        inviter._invite()
-        self.assertEquals(client._events, {})
-        self.assertEquals(client._calendars, {})
-
-
-    def test_addInvite(self):
-        &quot;&quot;&quot;
-        When there is a normal calendar, inviter adds an invite to it.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
-        userNumber = 16
-        serializePath = self.mktemp()
-        os.mkdir(serializePath)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars.update({calendar.url: calendar})
-        inviter = RealisticInviter(Clock(), self.sim, client, userNumber)
-        inviter.setParameters(
-            inviteeDistribution=Deterministic(1),
-            inviteeCountDistribution=Deterministic(1)
-        )
-        inviter._invite()
-        self.assertEquals(len(client._events), 1)
-        attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE'))
-        expected = set((&quot;mailto:user%02d@example.com&quot; % (userNumber,), &quot;mailto:user%02d@example.com&quot; % (userNumber + 1,),))
-        for attendee in attendees:
-            expected.remove(attendee.value())
-        self.assertEqual(len(expected), 0)
-
-
-    def test_doNotAddSelfToEvent(self):
-        &quot;&quot;&quot;
-        If the inviter randomly selects its own user to be added to
-        the attendee list, a different user is added instead.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
-        selfNumber = 12
-        client = StubClient(selfNumber, self.mktemp())
-        client._calendars.update({calendar.url: calendar})
-
-        otherNumber = 20
-        values = [selfNumber - selfNumber, otherNumber - selfNumber]
-
-        inviter = RealisticInviter(Clock(), self.sim, client, selfNumber)
-        inviter.setParameters(
-            inviteeDistribution=SequentialDistribution(values),
-            inviteeCountDistribution=Deterministic(1)
-        )
-        inviter._invite()
-        self.assertEquals(len(client._events), 1)
-        attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE'))
-        expected = set((&quot;mailto:user%02d@example.com&quot; % (selfNumber,), &quot;mailto:user%02d@example.com&quot; % (otherNumber,),))
-        for attendee in attendees:
-            expected.remove(attendee.value())
-        self.assertEqual(len(expected), 0)
-
-
-    def test_doNotAddExistingToEvent(self):
-        &quot;&quot;&quot;
-        If the inviter randomly selects a user which is already an
-        invitee on the event, a different user is added instead.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
-        selfNumber = 1
-        client = StubClient(selfNumber, self.mktemp())
-        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(
-            inviteeDistribution=SequentialDistribution(values),
-            inviteeCountDistribution=Deterministic(2)
-        )
-        inviter._invite()
-        self.assertEquals(len(client._events), 1)
-        attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE'))
-        expected = set((
-            &quot;mailto:user%02d@example.com&quot; % (selfNumber,),
-            &quot;mailto:user%02d@example.com&quot; % (inviteeNumber,),
-            &quot;mailto:user%02d@example.com&quot; % (anotherNumber,),
-        ))
-        for attendee in attendees:
-            expected.remove(attendee.value())
-        self.assertEqual(len(expected), 0)
-
-
-    def test_everybodyInvitedAlready(self):
-        &quot;&quot;&quot;
-        If the first so-many randomly selected users we come across
-        are already attendees on the event, the invitation attempt is
-        abandoned.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
-        userNumber = 1
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars.update({calendar.url: calendar})
-        inviter = RealisticInviter(Clock(), self.sim, client, userNumber)
-        inviter.setParameters(
-            inviteeDistribution=Deterministic(1),
-            inviteeCountDistribution=Deterministic(2)
-        )
-        inviter._invite()
-        self.assertEquals(len(client._events), 0)
-
-
-
-class AccepterTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for loadtest.profiles.Accepter.
-    &quot;&quot;&quot;
-    def setUp(self):
-        self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
-
-
-    def test_enabled(self):
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-
-        accepter = Accepter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
-        self.assertEqual(accepter.enabled, False)
-
-        accepter = Accepter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: True})
-        self.assertEqual(accepter.enabled, True)
-
-
-    def test_ignoreEventOnUnknownCalendar(self):
-        &quot;&quot;&quot;
-        If an event on an unknown calendar changes, it is ignored.
-        &quot;&quot;&quot;
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-        accepter = Accepter(None, self.sim, client, userNumber)
-        accepter.eventChanged('/some/calendar/1234.ics')
-
-
-    def test_ignoreNonCalendar(self):
-        &quot;&quot;&quot;
-        If an event is on a calendar which is not of type
-        {CALDAV:}calendar, it is ignored.
-        &quot;&quot;&quot;
-        userNumber = 14
-        calendarURL = '/some/calendar/'
-        calendar = Calendar(
-            csxml.dropbox_home, set(), u'notification', calendarURL, None)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars[calendarURL] = calendar
-        accepter = Accepter(None, self.sim, client, userNumber)
-        accepter.eventChanged(calendarURL + '1234.ics')
-
-
-    def test_ignoreAccepted(self):
-        &quot;&quot;&quot;
-        If the client is an attendee on an event but the PARTSTAT is
-        not NEEDS-ACTION, the event is ignored.
-        &quot;&quot;&quot;
-        vevent = Component.fromString(ACCEPTED_EVENT)
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
-        calendarURL = '/some/calendar/'
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars[calendarURL] = calendar
-        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
-        client._events[event.url] = event
-        accepter = Accepter(None, self.sim, client, userNumber)
-        accepter.eventChanged(event.url)
-
-
-    def test_ignoreAlreadyAccepting(self):
-        &quot;&quot;&quot;
-        If the client sees an event change a second time before
-        responding to an invitation found on it during the first
-        change notification, the second change notification does not
-        generate another accept attempt.
-        &quot;&quot;&quot;
-        clock = Clock()
-        randomDelay = 7
-        vevent = Component.fromString(INVITED_EVENT)
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
-        calendarURL = '/some/calendar/'
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars[calendarURL] = calendar
-        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
-        client._events[event.url] = event
-        accepter = Accepter(clock, self.sim, client, userNumber)
-        accepter.random = Deterministic()
-
-        def _gauss(mu, sigma):
-            return randomDelay
-        accepter.random.gauss = _gauss
-        accepter.eventChanged(event.url)
-        accepter.eventChanged(event.url)
-        clock.advance(randomDelay)
-
-
-    def test_inboxReply(self):
-        &quot;&quot;&quot;
-        When an inbox item that contains a reply is seen by the client, it
-        deletes it immediately.
-        &quot;&quot;&quot;
-        userNumber = 1
-        clock = Clock()
-        inboxURL = '/some/inbox/'
-        vevent = Component.fromString(INBOX_REPLY)
-        inbox = Calendar(
-            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars[inboxURL] = inbox
-
-        inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent)
-        client._setEvent(inboxEvent.url, inboxEvent)
-        accepter = Accepter(clock, self.sim, client, userNumber)
-        accepter.eventChanged(inboxEvent.url)
-        clock.advance(3)
-        self.assertNotIn(inboxEvent.url, client._events)
-        self.assertNotIn('4321.ics', inbox.events)
-
-
-    def test_inboxReplyFailedDelete(self):
-        &quot;&quot;&quot;
-        When an inbox item that contains a reply is seen by the client, it
-        deletes it immediately.  If the delete fails, the appropriate response
-        code is returned.
-        &quot;&quot;&quot;
-        userNumber = 1
-        clock = Clock()
-        inboxURL = '/some/inbox/'
-        vevent = Component.fromString(INBOX_REPLY)
-        inbox = Calendar(
-            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars[inboxURL] = inbox
-
-        inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent)
-        client._setEvent(inboxEvent.url, inboxEvent)
-        client._failDeleteWithObject(inboxEvent.url, IncorrectResponseCode(
-            NO_CONTENT,
-            Response(
-                ('HTTP', 1, 1), PRECONDITION_FAILED,
-                'Precondition Failed', None, None))
-        )
-        accepter = Accepter(clock, self.sim, client, userNumber)
-        accepter.eventChanged(inboxEvent.url)
-        clock.advance(3)
-        self.assertNotIn(inboxEvent.url, client._events)
-        self.assertNotIn('4321.ics', inbox.events)
-
-
-    def test_acceptInvitation(self):
-        &quot;&quot;&quot;
-        If the client is an attendee on an event and the PARTSTAT is
-        NEEDS-ACTION, a response is generated which accepts the
-        invitation and the corresponding event in the
-        I{schedule-inbox} is deleted.
-        &quot;&quot;&quot;
-        clock = Clock()
-        randomDelay = 7
-        vevent = Component.fromString(INVITED_EVENT)
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
-        client = StubClient(userNumber, self.mktemp())
-
-        calendarURL = '/some/calendar/'
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
-        client._calendars[calendarURL] = calendar
-
-        inboxURL = '/some/inbox/'
-        inbox = Calendar(
-            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
-        client._calendars[inboxURL] = inbox
-
-        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
-        client._setEvent(event.url, event)
-
-        inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent)
-        client._setEvent(inboxEvent.url, inboxEvent)
-
-        accepter = Accepter(clock, self.sim, client, userNumber)
-        accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay))
-        accepter.eventChanged(event.url)
-        clock.advance(randomDelay)
-
-        vevent = client._events[event.url].component
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        self.assertEquals(len(attendees), 2)
-        self.assertEquals(
-            attendees[1].parameterValue('CN'), 'User %02d' % (userNumber,))
-        self.assertEquals(
-            attendees[1].parameterValue('PARTSTAT'), 'ACCEPTED')
-        self.assertFalse(attendees[1].hasParameter('RSVP'))
-
-        self.assertNotIn(inboxEvent.url, client._events)
-        self.assertNotIn('4321.ics', inbox.events)
-
-
-    def test_reacceptInvitation(self):
-        &quot;&quot;&quot;
-        If a client accepts an invitation on an event and then is
-        later re-invited to the same event, the invitation is again
-        accepted.
-        &quot;&quot;&quot;
-        clock = Clock()
-        randomDelay = 7
-        vevent = Component.fromString(INVITED_EVENT)
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
-        calendarURL = '/some/calendar/'
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
-        client = StubClient(userNumber, self.mktemp())
-        client._calendars[calendarURL] = calendar
-        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
-        client._events[event.url] = event
-        accepter = Accepter(clock, self.sim, client, userNumber)
-        accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay))
-        accepter.eventChanged(event.url)
-        clock.advance(randomDelay)
-
-        # Now re-set the event so it has to be accepted again
-        event.component = Component.fromString(INVITED_EVENT)
-
-        # And now re-deliver it
-        accepter.eventChanged(event.url)
-        clock.advance(randomDelay)
-
-        # And ensure that it was accepted again
-        vevent = client._events[event.url].component
-        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
-        self.assertEquals(len(attendees), 2)
-        self.assertEquals(
-            attendees[1].parameterValue('CN'), 'User %02d' % (userNumber,))
-        self.assertEquals(
-            attendees[1].parameterValue('PARTSTAT'), 'ACCEPTED')
-        self.assertFalse(attendees[1].hasParameter('RSVP'))
-
-
-    def test_changeEventAttendeePreconditionFailed(self):
-        &quot;&quot;&quot;
-        If the attempt to accept an invitation fails because of an
-        unmet precondition (412), the event is re-retrieved and the
-        PUT is re-issued with the new data.
-        &quot;&quot;&quot;
-        clock = Clock()
-        userNumber = 2
-        client = StubClient(userNumber, self.mktemp())
-        randomDelay = 3
-
-        calendarURL = '/some/calendar/'
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
-        client._calendars[calendarURL] = calendar
-
-        vevent = Component.fromString(INVITED_EVENT)
-        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
-        client._setEvent(event.url, event)
-
-        accepter = Accepter(clock, self.sim, client, userNumber)
-        accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay))
-
-        client.rescheduled.add(event.url)
-
-        accepter.eventChanged(event.url)
-        clock.advance(randomDelay)
-
-
-
-class EventerTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for loadtest.profiles.Eventer, a profile which adds new
-    events on calendars.
-    &quot;&quot;&quot;
-    def setUp(self):
-        self.sim = CalendarClientSimulator(
-            AnyUser(), Populator(None), None, None, None, None, None)
-
-
-    def test_enabled(self):
-        userNumber = 13
-        client = StubClient(userNumber, self.mktemp())
-
-        eventer = Eventer(None, self.sim, client, None, **{&quot;enabled&quot;: False})
-        self.assertEqual(eventer.enabled, False)
-
-        eventer = Eventer(None, self.sim, client, None, **{&quot;enabled&quot;: True})
-        self.assertEqual(eventer.enabled, True)
-
-
-    def test_doNotAddEventOnInbox(self):
-        &quot;&quot;&quot;
-        When the only calendar is a schedule inbox, no attempt is made
-        to add events on it.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None)
-        client = StubClient(21, self.mktemp())
-        client._calendars.update({calendar.url: calendar})
-
-        eventer = Eventer(None, self.sim, client, None)
-        eventer._addEvent()
-
-        self.assertEquals(client._events, {})
-
-
-    def test_addEvent(self):
-        &quot;&quot;&quot;
-        When there is a normal calendar to add events to,
-        L{Eventer._addEvent} adds an event to it.
-        &quot;&quot;&quot;
-        calendar = Calendar(
-            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
-        client = StubClient(31, self.mktemp())
-        client._calendars.update({calendar.url: calendar})
-
-        eventer = Eventer(Clock(), self.sim, client, None)
-        eventer._addEvent()
-
-        self.assertEquals(len(client._events), 1)
-
-        # XXX Vary the event period/interval and the uid
-
-
-
-class OperationLoggerTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{OperationLogger}.
-    &quot;&quot;&quot;
-    def test_noFailures(self):
-        &quot;&quot;&quot;
-        If the median lag is below 1 second and the failure rate is below 1%,
-        L{OperationLogger.failures} returns an empty list.
-        &quot;&quot;&quot;
-        logger = OperationLogger(outfile=StringIO())
-        logger.observe(dict(
-            type='operation', phase='start', user='user01',
-            label='testing', lag=0.5)
-        )
-        logger.observe(dict(
-            type='operation', phase='end', user='user01',
-            duration=0.35, label='testing', success=True)
-        )
-        self.assertEqual([], logger.failures())
-
-
-    def test_lagLimitExceeded(self):
-        &quot;&quot;&quot;
-        If the median scheduling lag for any operation in the simulation
-        exceeds 1 second, L{OperationLogger.failures} returns a list containing
-        a string describing that issue.
-        &quot;&quot;&quot;
-        logger = OperationLogger(outfile=StringIO())
-        for lag in [100.0, 1100.0, 1200.0]:
-            logger.observe(dict(
-                type='operation', phase='start', user='user01',
-                label='testing', lag=lag)
-            )
-        self.assertEqual(
-            [&quot;Median TESTING scheduling lag greater than 1000.0ms&quot;],
-            logger.failures())
-
-
-    def test_failureLimitExceeded(self):
-        &quot;&quot;&quot;
-        If the failure rate for any operation exceeds 1%,
-        L{OperationLogger.failures} returns a list containing a string
-        describing that issue.
-        &quot;&quot;&quot;
-        logger = OperationLogger(outfile=StringIO())
-        for _ignore in range(98):
-            logger.observe(dict(
-                type='operation', phase='end', user='user01',
-                duration=0.25, label='testing', success=True)
-            )
-        logger.observe(dict(
-            type='operation', phase='end', user='user01',
-            duration=0.25, label='testing', success=False)
-        )
-        self.assertEqual(
-            [&quot;Greater than 1% TESTING failed&quot;],
-            logger.failures())
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_simpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_sim.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_sim.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_sim.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,592 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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 cStringIO import StringIO
-
-from twisted.python.log import msg
-from twisted.python.usage import UsageError
-from twisted.python.filepath import FilePath
-from twisted.internet.defer import Deferred, succeed
-from twisted.trial.unittest import TestCase
-
-from contrib.performance.stats import NormalDistribution
-from contrib.performance.loadtest.ical import OS_X_10_6
-from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
-from contrib.performance.loadtest.population import (
-    SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
-    ProfileType, SimpleStatistics
-)
-from contrib.performance.loadtest.sim import (
-    Arrival, SimOptions, LoadSimulator, LagTrackingReactor,
-    _DirectoryRecord
-)
-
-
-VALID_CONFIG = {
-    'server': 'tcp:127.0.0.1:8008',
-    'webadmin': {
-        'enabled': True,
-        'HTTPPort': 8080,
-    },
-    'arrival': {
-        'factory': 'contrib.performance.loadtest.population.SmoothRampUp',
-        'params': {
-            'groups': 10,
-            'groupSize': 1,
-            'interval': 3,
-        },
-    },
-}
-
-VALID_CONFIG_PLIST = writePlistToString(VALID_CONFIG)
-
-
-
-class SimOptionsTests(TestCase):
-    def test_defaultConfig(self):
-        &quot;&quot;&quot;
-        If the I{config} option is not specified, the default config.plist in
-        the source tree is used.
-        &quot;&quot;&quot;
-        options = SimOptions()
-        self.assertEqual(options['config'], FilePath(__file__).sibling('config.plist'))
-
-
-    def test_configFileNotFound(self):
-        &quot;&quot;&quot;
-        If the filename given to the I{config} option is not found,
-        L{SimOptions.parseOptions} raises a L{UsageError} indicating
-        this.
-        &quot;&quot;&quot;
-        name = FilePath(self.mktemp())
-        options = SimOptions()
-        exc = self.assertRaises(
-            UsageError, options.parseOptions, ['--config', name.path])
-        self.assertEquals(
-            str(exc), &quot;--config %s: No such file or directory&quot; % (name.path,))
-
-
-    def test_configFileNotParseable(self):
-        &quot;&quot;&quot;
-        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.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(&quot;some random junk&quot;)
-        options = SimOptions()
-        exc = self.assertRaises(
-            UsageError, options.parseOptions, ['--config', config.path])
-        self.assertEquals(
-            str(exc),
-            &quot;--config %s: syntax error: line 1, column 0&quot; % (config.path,))
-
-
-
-class CalendarClientSimulatorTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{CalendarClientSimulator} which adds running clients to
-    a simulation.
-    &quot;&quot;&quot;
-    realmName = 'stub'
-
-    def _user(self, name):
-        password = 'password-' + name
-        email = name + &quot;@example.com&quot;
-        record = _DirectoryRecord(name, password, name, email, name)
-        return record
-
-
-    def test_createUser(self):
-        &quot;&quot;&quot;
-        Subsequent calls to L{CalendarClientSimulator._createUser}
-        with different user numbers return user details from different
-        directory records.
-        &quot;&quot;&quot;
-        calsim = CalendarClientSimulator(
-            [self._user('alice'), self._user('bob'), self._user('carol')],
-            Populator(None), None, None, 'http://example.org:1234/', None, None)
-        users = sorted([
-            calsim._createUser(0)[0],
-            calsim._createUser(1)[0],
-            calsim._createUser(2)[0],
-        ])
-        self.assertEqual(['alice', 'bob', 'carol'], users)
-
-
-    def test_createUserAuthInfo(self):
-        &quot;&quot;&quot;
-        The auth handler returned by L{CalendarClientSimulator._createUser}
-        includes the password taken from user's directory record.
-        &quot;&quot;&quot;
-        calsim = CalendarClientSimulator(
-            [self._user('alice')],
-            Populator(None), None, None, 'http://example.org:1234/', None, None)
-        user, auth = calsim._createUser(0)
-        self.assertEqual(
-            auth['basic'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
-            'password-' + user)
-        self.assertEqual(
-            auth['digest'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
-            'password-' + user)
-
-
-    def test_stop(self):
-        &quot;&quot;&quot;
-        After L{CalendarClientSimulator.stop} is called, failed clients and
-        profiles are not logged.
-        &quot;&quot;&quot;
-        class BrokenClient(object):
-            def __init__(self, reactor, serverAddress, principalPathTemplate, serializationPath, userInfo, auth, runResult):
-                self._runResult = runResult
-
-            def run(self):
-                return self._runResult
-
-            def stop(self):
-                return succeed(None)
-
-        class BrokenProfile(object):
-            def __init__(self, reactor, simulator, client, userNumber, runResult):
-                self._runResult = runResult
-                self.enabled = True
-
-            def initialize(self):
-                return succeed(None)
-
-            def run(self):
-                return self._runResult
-
-        clientRunResult = Deferred()
-        profileRunResult = Deferred()
-
-        params = PopulationParameters()
-        params.addClient(1, ClientType(
-            BrokenClient, {'runResult': clientRunResult},
-            [ProfileType(BrokenProfile, {'runResult': profileRunResult})])
-        )
-        sim = CalendarClientSimulator(
-            [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/', None, None)
-        sim.add(1, 1)
-        sim.stop()
-        clientRunResult.errback(RuntimeError(&quot;Some fictional client problem&quot;))
-        profileRunResult.errback(RuntimeError(&quot;Some fictional profile problem&quot;))
-
-        self.assertEqual([], self.flushLoggedErrors())
-
-
-
-class Reactor(object):
-    message = &quot;some event to be observed&quot;
-
-    def __init__(self):
-        self._triggers = []
-        self._whenRunning = []
-
-
-    def run(self):
-        for thunk in self._whenRunning:
-            thunk()
-        msg(thingo=self.message)
-        for _ignore_phase, event, thunk in self._triggers:
-            if event == 'shutdown':
-                thunk()
-
-
-    def callWhenRunning(self, thunk):
-        self._whenRunning.append(thunk)
-
-
-    def addSystemEventTrigger(self, phase, event, thunk):
-        self._triggers.append((phase, event, thunk))
-
-
-
-class Observer(object):
-    def __init__(self):
-        self.reported = False
-        self.events = []
-
-
-    def observe(self, event):
-        self.events.append(event)
-
-
-    def report(self, output):
-        self.reported = True
-
-
-    def failures(self):
-        return []
-
-
-
-class NullArrival(object):
-    def run(self, sim):
-        pass
-
-
-
-class StubSimulator(LoadSimulator):
-    def run(self):
-        return 3
-
-
-
-class LoadSimulatorTests(TestCase):
-    def test_main(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.main} raises L{SystemExit} with the result of
-        L{LoadSimulator.run}.
-        &quot;&quot;&quot;
-        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, None, None, None, None, None).run(),))
-
-
-    def test_createSimulator(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.createSimulator} creates a L{CalendarClientSimulator}
-        with its own reactor and host and port information from the
-        configuration file.
-        &quot;&quot;&quot;
-        server = 'http://127.0.0.7:1243/'
-        reactor = object()
-        sim = LoadSimulator(server, None, None, None, None, None, None, reactor=reactor)
-        calsim = sim.createSimulator()
-        self.assertIsInstance(calsim, CalendarClientSimulator)
-        self.assertIsInstance(calsim.reactor, LagTrackingReactor)
-        self.assertIdentical(calsim.reactor._reactor, reactor)
-        self.assertEquals(calsim.server, server)
-
-
-    def test_loadAccountsFromFile(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.fromCommandLine} takes an account loader from the
-        config file and uses it to create user records for use in the
-        simulation.
-        &quot;&quot;&quot;
-        accounts = FilePath(self.mktemp())
-        accounts.setContent(&quot;foo,bar,baz,quux,goo\nfoo2,bar2,baz2,quux2,goo2\n&quot;)
-        config = VALID_CONFIG.copy()
-        config[&quot;accounts&quot;] = {
-            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.recordsFromCSVFile&quot;,
-            &quot;params&quot;: {
-                &quot;path&quot;: accounts.path
-            },
-        }
-        configpath = FilePath(self.mktemp())
-        configpath.setContent(writePlistToString(config))
-        io = StringIO()
-        sim = LoadSimulator.fromCommandLine(['--config', configpath.path], io)
-        self.assertEquals(io.getvalue(), &quot;Loaded 2 accounts.\n&quot;)
-        self.assertEqual(2, len(sim.records))
-        self.assertEqual(sim.records[0].uid, 'foo')
-        self.assertEqual(sim.records[0].password, 'bar')
-        self.assertEqual(sim.records[0].commonName, 'baz')
-        self.assertEqual(sim.records[0].email, 'quux')
-        self.assertEqual(sim.records[1].uid, 'foo2')
-        self.assertEqual(sim.records[1].password, 'bar2')
-        self.assertEqual(sim.records[1].commonName, 'baz2')
-        self.assertEqual(sim.records[1].email, 'quux2')
-
-
-    def test_loadDefaultAccountsFromFile(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.fromCommandLine} takes an account loader (with
-        empty path)from the config file and uses it to create user
-        records for use in the simulation.
-        &quot;&quot;&quot;
-        config = VALID_CONFIG.copy()
-        config[&quot;accounts&quot;] = {
-            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.recordsFromCSVFile&quot;,
-            &quot;params&quot;: {
-                &quot;path&quot;: &quot;&quot;
-            },
-        }
-        configpath = FilePath(self.mktemp())
-        configpath.setContent(writePlistToString(config))
-        sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
-                                            StringIO())
-        self.assertEqual(99, len(sim.records))
-        self.assertEqual(sim.records[0].uid, 'user01')
-        self.assertEqual(sim.records[0].password, 'user01')
-        self.assertEqual(sim.records[0].commonName, 'User 01')
-        self.assertEqual(sim.records[0].email, 'user01@example.com')
-        self.assertEqual(sim.records[98].uid, 'user99')
-        self.assertEqual(sim.records[98].password, 'user99')
-        self.assertEqual(sim.records[98].commonName, 'User 99')
-        self.assertEqual(sim.records[98].email, 'user99@example.com')
-
-
-    def test_generateRecordsDefaultPatterns(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.fromCommandLine} takes an account loader from the
-        config file and uses it to generate user records for use in the
-        simulation.
-        &quot;&quot;&quot;
-        config = VALID_CONFIG.copy()
-        config[&quot;accounts&quot;] = {
-            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.generateRecords&quot;,
-            &quot;params&quot;: {
-                &quot;count&quot;: 2
-            },
-        }
-        configpath = FilePath(self.mktemp())
-        configpath.setContent(writePlistToString(config))
-        sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
-                                            StringIO())
-        self.assertEqual(2, len(sim.records))
-        self.assertEqual(sim.records[0].uid, 'user1')
-        self.assertEqual(sim.records[0].password, 'user1')
-        self.assertEqual(sim.records[0].commonName, 'User 1')
-        self.assertEqual(sim.records[0].email, 'user1@example.com')
-        self.assertEqual(sim.records[1].uid, 'user2')
-        self.assertEqual(sim.records[1].password, 'user2')
-        self.assertEqual(sim.records[1].commonName, 'User 2')
-        self.assertEqual(sim.records[1].email, 'user2@example.com')
-
-
-    def test_generateRecordsNonDefaultPatterns(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.fromCommandLine} takes an account loader from the
-        config file and uses it to generate user records for use in the
-        simulation.
-        &quot;&quot;&quot;
-        config = VALID_CONFIG.copy()
-        config[&quot;accounts&quot;] = {
-            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.generateRecords&quot;,
-            &quot;params&quot;: {
-                &quot;count&quot;: 3,
-                &quot;uidPattern&quot;: &quot;USER%03d&quot;,
-                &quot;passwordPattern&quot;: &quot;PASSWORD%03d&quot;,
-                &quot;namePattern&quot;: &quot;Test User %03d&quot;,
-                &quot;emailPattern&quot;: &quot;USER%03d@example2.com&quot;,
-            },
-        }
-        configpath = FilePath(self.mktemp())
-        configpath.setContent(writePlistToString(config))
-        sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
-                                            StringIO())
-        self.assertEqual(3, len(sim.records))
-        self.assertEqual(sim.records[0].uid, 'USER001')
-        self.assertEqual(sim.records[0].password, 'PASSWORD001')
-        self.assertEqual(sim.records[0].commonName, 'Test User 001')
-        self.assertEqual(sim.records[0].email, 'USER001@example2.com')
-        self.assertEqual(sim.records[2].uid, 'USER003')
-        self.assertEqual(sim.records[2].password, 'PASSWORD003')
-        self.assertEqual(sim.records[2].commonName, 'Test User 003')
-        self.assertEqual(sim.records[2].email, 'USER003@example2.com')
-
-
-    def test_specifyRuntime(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.fromCommandLine} recognizes the I{--runtime} option to
-        specify a limit on how long the simulation will run.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(VALID_CONFIG_PLIST)
-        sim = LoadSimulator.fromCommandLine(['--config', config.path, '--runtime', '123'])
-        self.assertEqual(123, sim.runtime)
-
-
-    def test_loadServerConfig(self):
-        &quot;&quot;&quot;
-        The Calendar Server host and port are loaded from the [server]
-        section of the configuration file specified.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(
-            writePlistToString({&quot;server&quot;: &quot;https://127.0.0.3:8432/&quot;})
-        )
-        sim = LoadSimulator.fromCommandLine(['--config', config.path])
-        self.assertEquals(sim.server, &quot;https://127.0.0.3:8432/&quot;)
-
-
-    def test_loadArrivalConfig(self):
-        &quot;&quot;&quot;
-        The arrival policy type and arguments are loaded from the
-        [arrival] section of the configuration file specified.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(
-            writePlistToString({
-                &quot;arrival&quot;: {
-                    &quot;factory&quot;: &quot;contrib.performance.loadtest.population.SmoothRampUp&quot;,
-                    &quot;params&quot;: {
-                        &quot;groups&quot;: 10,
-                        &quot;groupSize&quot;: 1,
-                        &quot;interval&quot;: 3,
-                    },
-                },
-            })
-        )
-        sim = LoadSimulator.fromCommandLine(['--config', config.path])
-        self.assertEquals(
-            sim.arrival,
-            Arrival(SmoothRampUp, dict(groups=10, groupSize=1, interval=3)))
-
-
-    def test_createArrivalPolicy(self):
-        &quot;&quot;&quot;
-        L{LoadSimulator.createArrivalPolicy} creates an arrival
-        policy based on the L{Arrival} passed to its initializer.
-        &quot;&quot;&quot;
-        class FakeArrival(object):
-            def __init__(self, reactor, x, y):
-                self.reactor = reactor
-                self.x = x
-                self.y = y
-
-        reactor = object()
-        sim = LoadSimulator(
-            None, None, None, None, None, Arrival(FakeArrival, {'x': 3, 'y': 2}), None, reactor=reactor)
-        arrival = sim.createArrivalPolicy()
-        self.assertIsInstance(arrival, FakeArrival)
-        self.assertIdentical(arrival.reactor, sim.reactor)
-        self.assertEquals(arrival.x, 3)
-        self.assertEquals(arrival.y, 2)
-
-
-    def test_loadPopulationParameters(self):
-        &quot;&quot;&quot;
-        Client weights and profiles are loaded from the [clients]
-        section of the configuration file specified.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(
-            writePlistToString(
-                {
-                    &quot;clients&quot;: [
-                        {
-                            &quot;software&quot;: &quot;contrib.performance.loadtest.ical.OS_X_10_6&quot;,
-                            &quot;params&quot;: {
-                                &quot;foo&quot;: &quot;bar&quot;
-                            },
-                            &quot;profiles&quot;: [
-                                {
-                                    &quot;params&quot;: {
-                                        &quot;interval&quot;: 25,
-                                        &quot;eventStartDistribution&quot;: {
-                                            &quot;type&quot;: &quot;contrib.performance.stats.NormalDistribution&quot;,
-                                            &quot;params&quot;: {
-                                                &quot;mu&quot;: 123,
-                                                &quot;sigma&quot;: 456,
-                                            }
-                                        }
-                                    },
-                                    &quot;class&quot;: &quot;contrib.performance.loadtest.profiles.Eventer&quot;
-                                }
-                            ],
-                            &quot;weight&quot;: 3,
-                        }
-                    ]
-                }
-            )
-        )
-
-        sim = LoadSimulator.fromCommandLine(
-            ['--config', config.path, '--clients', config.path]
-        )
-        expectedParameters = PopulationParameters()
-        expectedParameters.addClient(
-            3,
-            ClientType(
-                OS_X_10_6,
-                {&quot;foo&quot;: &quot;bar&quot;},
-                [
-                    ProfileType(
-                        Eventer, {
-                            &quot;interval&quot;: 25,
-                            &quot;eventStartDistribution&quot;: NormalDistribution(123, 456)
-                        }
-                    )
-                ]
-            )
-        )
-        self.assertEquals(sim.parameters, expectedParameters)
-
-
-    def test_requireClient(self):
-        &quot;&quot;&quot;
-        At least one client is required, so if a configuration with an
-        empty clients array is specified, a single default client type
-        is used.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(writePlistToString({&quot;clients&quot;: []}))
-        sim = LoadSimulator.fromCommandLine(
-            ['--config', config.path, '--clients', config.path]
-        )
-        expectedParameters = PopulationParameters()
-        expectedParameters.addClient(
-            1, ClientType(OS_X_10_6, {}, [Eventer, Inviter, Accepter]))
-        self.assertEquals(sim.parameters, expectedParameters)
-
-
-    def test_loadLogObservers(self):
-        &quot;&quot;&quot;
-        Log observers specified in the [observers] section of the
-        configuration file are added to the logging system.
-        &quot;&quot;&quot;
-        config = FilePath(self.mktemp())
-        config.setContent(
-            writePlistToString(
-                {
-                    &quot;observers&quot;: [
-                        {
-                            &quot;type&quot;: &quot;contrib.performance.loadtest.population.SimpleStatistics&quot;,
-                            &quot;params&quot;: {},
-                        },
-                    ]
-                }
-            )
-        )
-        sim = LoadSimulator.fromCommandLine(['--config', config.path])
-        self.assertEquals(len(sim.observers), 1)
-        self.assertIsInstance(sim.observers[0], SimpleStatistics)
-
-
-    def test_observeRunReport(self):
-        &quot;&quot;&quot;
-        Each log observer is added to the log publisher before the
-        simulation run is started and has its C{report} method called
-        after the simulation run completes.
-        &quot;&quot;&quot;
-        observers = [Observer()]
-        sim = LoadSimulator(
-            &quot;http://example.com:123/&quot;,
-            &quot;/principals/users/%s/&quot;,
-            None,
-            None,
-            None,
-            Arrival(lambda reactor: NullArrival(), {}),
-            None, observers, reactor=Reactor())
-        io = StringIO()
-        sim.run(io)
-        self.assertEquals(io.getvalue(), &quot;\n*** PASS\n&quot;)
-        self.assertTrue(observers[0].reported)
-        self.assertEquals(
-            [e for e in observers[0].events if &quot;thingo&quot; in e][0][&quot;thingo&quot;],
-            Reactor.message
-        )
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_trafficloggerpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_trafficlogger.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_trafficlogger.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_trafficlogger.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,210 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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 zope.interface import Interface, implements
-
-from twisted.internet.protocol import ClientFactory, Protocol
-from twisted.trial.unittest import TestCase
-from twisted.test.proto_helpers import StringTransport, MemoryReactor
-from twisted.protocols.wire import Discard
-
-from contrib.performance.loadtest.trafficlogger import _TrafficLoggingFactory, loggedReactor
-
-
-class IProbe(Interface):
-    &quot;&quot;&quot;
-    An interface which can be used to verify some interface-related behavior of
-    L{loggedReactor}.
-    &quot;&quot;&quot;
-    def probe(): #@NoSelf
-        pass
-
-
-
-class Probe(object):
-    implements(IProbe)
-
-    _probed = False
-
-    def __init__(self, result=None):
-        self._result = result
-
-
-    def probe(self):
-        self._probed = True
-        return self._result
-
-
-
-class TrafficLoggingReactorTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{loggedReactor}.
-    &quot;&quot;&quot;
-    def test_nothing(self):
-        &quot;&quot;&quot;
-        L{loggedReactor} returns the object passed to it, if the object passed
-        to it doesn't provide any interfaces.  This is mostly for testing
-        convenience rather than a particularly useful feature.
-        &quot;&quot;&quot;
-        probe = object()
-        self.assertIdentical(probe, loggedReactor(probe))
-
-
-    def test_interfaces(self):
-        &quot;&quot;&quot;
-        The object returned by L{loggedReactor} provides all of the interfaces
-        provided by the object passed to it.
-        &quot;&quot;&quot;
-        probe = Probe()
-        reactor = loggedReactor(probe)
-        self.assertTrue(IProbe.providedBy(reactor))
-
-
-    def test_passthrough(self):
-        &quot;&quot;&quot;
-        Methods on interfaces on the object passed to L{loggedReactor} can be
-        called by calling them on the object returned by L{loggedReactor}.
-        &quot;&quot;&quot;
-        expected = object()
-        probe = Probe(expected)
-        reactor = loggedReactor(probe)
-        result = reactor.probe()
-        self.assertTrue(probe._probed)
-        self.assertIdentical(expected, result)
-
-
-    def test_connectTCP(self):
-        &quot;&quot;&quot;
-        Called on the object returned by L{loggedReactor}, C{connectTCP} calls
-        the wrapped reactor's C{connectTCP} method with the original factory
-        wrapped in a L{_TrafficLoggingFactory}.
-        &quot;&quot;&quot;
-        class RecordDataProtocol(Protocol):
-            def dataReceived(self, data):
-                self.data = data
-        proto = RecordDataProtocol()
-        factory = ClientFactory()
-        factory.protocol = lambda: proto
-        reactor = MemoryReactor()
-        logged = loggedReactor(reactor)
-        logged.connectTCP('192.168.1.2', 1234, factory, 21, '127.0.0.2')
-        [(host, port, factory, timeout, bindAddress)] = reactor.tcpClients
-        self.assertEqual('192.168.1.2', host)
-        self.assertEqual(1234, port)
-        self.assertIsInstance(factory, _TrafficLoggingFactory)
-        self.assertEqual(21, timeout)
-        self.assertEqual('127.0.0.2', bindAddress)
-
-        # Verify that the factory and protocol specified are really being used
-        protocol = factory.buildProtocol(None)
-        protocol.makeConnection(None)
-        protocol.dataReceived(&quot;foo&quot;)
-        self.assertEqual(proto.data, &quot;foo&quot;)
-
-
-    def test_getLogFiles(self):
-        &quot;&quot;&quot;
-        The reactor returned by L{loggedReactor} has a C{getLogFiles} method
-        which returns a L{logstate} instance containing the active and
-        completed log files tracked by the logging wrapper.
-        &quot;&quot;&quot;
-        wrapped = ClientFactory()
-        wrapped.protocol = Discard
-        reactor = MemoryReactor()
-        logged = loggedReactor(reactor)
-        logged.connectTCP('127.0.0.1', 1234, wrapped)
-        factory = reactor.tcpClients[0][2]
-
-        finished = factory.buildProtocol(None)
-        finished.makeConnection(StringTransport())
-        finished.dataReceived('finished')
-        finished.connectionLost(None)
-
-        active = factory.buildProtocol(None)
-        active.makeConnection(StringTransport())
-        active.dataReceived('active')
-
-        logs = logged.getLogFiles()
-        self.assertEqual(1, len(logs.finished))
-        self.assertIn('finished', logs.finished[0].getvalue())
-        self.assertEqual(1, len(logs.active))
-        self.assertIn('active', logs.active[0].getvalue())
-
-
-
-class TrafficLoggingFactoryTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{_TrafficLoggingFactory}.
-    &quot;&quot;&quot;
-    def setUp(self):
-        self.wrapped = ClientFactory()
-        self.wrapped.protocol = Discard
-        self.factory = _TrafficLoggingFactory(self.wrapped)
-
-
-    def test_receivedBytesLogged(self):
-        &quot;&quot;&quot;
-        When bytes are delivered through a protocol created by
-        L{_TrafficLoggingFactory}, they are added to a log kept on that
-        factory.
-        &quot;&quot;&quot;
-        protocol = self.factory.buildProtocol(None)
-
-        # The factory should now have a new StringIO log file
-        self.assertEqual(1, len(self.factory.logs))
-
-        transport = StringTransport()
-        protocol.makeConnection(transport)
-
-        protocol.dataReceived(&quot;hello, world&quot;)
-        self.assertEqual(
-            &quot;*\nC 0: 'hello, world'\n&quot;, self.factory.logs[0].getvalue())
-
-
-    def test_finishedLogs(self):
-        &quot;&quot;&quot;
-        When connections are lost, the corresponding log files are moved into
-        C{_TrafficLoggingFactory.finishedLogs}.
-        &quot;&quot;&quot;
-        protocol = self.factory.buildProtocol(None)
-        transport = StringTransport()
-        protocol.makeConnection(transport)
-        logfile = self.factory.logs[0]
-        protocol.connectionLost(None)
-        self.assertEqual(0, len(self.factory.logs))
-        self.assertEqual([logfile], self.factory.finishedLogs)
-
-
-    def test_finishedLogsLimit(self):
-        &quot;&quot;&quot;
-        Only the most recent C{_TrafficLoggingFactory.LOGFILE_LIMIT} logfiles
-        are kept in C{_TrafficLoggingFactory.finishedLogs}.
-        &quot;&quot;&quot;
-        self.factory.LOGFILE_LIMIT = 2
-        first = self.factory.buildProtocol(None)
-        first.makeConnection(StringTransport())
-        second = self.factory.buildProtocol(None)
-        second.makeConnection(StringTransport())
-        third = self.factory.buildProtocol(None)
-        third.makeConnection(StringTransport())
-
-        second.connectionLost(None)
-        first.connectionLost(None)
-        third.connectionLost(None)
-
-        self.assertEqual(
-            [first.logfile, third.logfile], self.factory.finishedLogs)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttest_webadminpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_webadmin.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_webadmin.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/test_webadmin.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -1,144 +0,0 @@
</span><del>-##
-# Copyright (c) 2012-2015 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# 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 &quot;AS IS&quot; 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 twisted.trial.unittest import TestCase
-from contrib.performance.loadtest.webadmin import LoadSimAdminResource
-
-class WebAdminTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for L{LoadSimAdminResource}.
-    &quot;&quot;&quot;
-
-    class FakeReporter(object):
-
-        def generateReport(self, output):
-            output.write(&quot;FakeReporter&quot;)
-
-
-    class FakeReactor(object):
-
-        def __init__(self):
-            self.running = True
-
-        def stop(self):
-            self.running = False
-
-
-    class FakeLoadSim(object):
-
-        def __init__(self):
-            self.reactor = WebAdminTests.FakeReactor()
-            self.reporter = WebAdminTests.FakeReporter()
-            self.running = True
-
-        def stop(self):
-            self.running = False
-
-
-    class FakeRequest(object):
-
-        def __init__(self, **kwargs):
-            self.args = kwargs
-
-
-    def test_resourceGET(self):
-        &quot;&quot;&quot;
-        Test render_GET
-        &quot;&quot;&quot;
-
-        loadsim = WebAdminTests.FakeLoadSim()
-        resource = LoadSimAdminResource(loadsim)
-
-        response = resource.render_GET(WebAdminTests.FakeRequest())
-        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
-        self.assertTrue(response.find(resource.token) != -1)
-
-
-    def test_resourcePOST_Stop(self):
-        &quot;&quot;&quot;
-        Test render_POST when Stop button is clicked
-        &quot;&quot;&quot;
-
-        loadsim = WebAdminTests.FakeLoadSim()
-        resource = LoadSimAdminResource(loadsim)
-        self.assertTrue(loadsim.reactor.running)
-
-        response = resource.render_POST(WebAdminTests.FakeRequest(
-            token=(resource.token,),
-            stop=None,
-        ))
-        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
-        self.assertTrue(response.find(resource.token) == -1)
-        self.assertTrue(response.find(&quot;FakeReporter&quot;) != -1)
-        self.assertFalse(loadsim.running)
-
-
-    def test_resourcePOST_Stop_BadToken(self):
-        &quot;&quot;&quot;
-        Test render_POST when Stop button is clicked but token is wrong
-        &quot;&quot;&quot;
-
-        loadsim = WebAdminTests.FakeLoadSim()
-        resource = LoadSimAdminResource(loadsim)
-        self.assertTrue(loadsim.reactor.running)
-
-        response = resource.render_POST(WebAdminTests.FakeRequest(
-            token=(&quot;xyz&quot;,),
-            stop=None,
-        ))
-        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
-        self.assertTrue(response.find(resource.token) != -1)
-        self.assertTrue(response.find(&quot;FakeReporter&quot;) == -1)
-        self.assertTrue(loadsim.running)
-
-
-    def test_resourcePOST_Results(self):
-        &quot;&quot;&quot;
-        Test render_POST when Results button is clicked
-        &quot;&quot;&quot;
-
-        loadsim = WebAdminTests.FakeLoadSim()
-        resource = LoadSimAdminResource(loadsim)
-        self.assertTrue(loadsim.reactor.running)
-
-        response = resource.render_POST(WebAdminTests.FakeRequest(
-            token=(resource.token,),
-            results=None,
-        ))
-        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
-        self.assertTrue(response.find(resource.token) != -1)
-        self.assertTrue(response.find(&quot;FakeReporter&quot;) != -1)
-        self.assertTrue(loadsim.running)
-
-
-    def test_resourcePOST_Results_BadToken(self):
-        &quot;&quot;&quot;
-        Test render_POST when Results button is clicked and token is wrong
-        &quot;&quot;&quot;
-
-        loadsim = WebAdminTests.FakeLoadSim()
-        resource = LoadSimAdminResource(loadsim)
-        self.assertTrue(loadsim.reactor.running)
-
-        response = resource.render_POST(WebAdminTests.FakeRequest(
-            token=(&quot;xyz&quot;,),
-            results=None,
-        ))
-        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
-        self.assertTrue(response.find(resource.token) != -1)
-        self.assertTrue(response.find(&quot;FakeReporter&quot;) == -1)
-        self.assertTrue(loadsim.running)
</del></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtesttests__init__py"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/__init__.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/__init__.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/__init__.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,27 @@
</span><ins>+##
+# Copyright (c) 2012-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+
+&quot;&quot;&quot;
+Unit tests for load-testing tool
+Tests:
+  ical.py
+  logger.py
+  population.py
+  profiles.py
+  sim.py
+  trafficlogger.py
+  webadmin.py
+&quot;&quot;&quot;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_distributionspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_distributions.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_distributions.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_distributions.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,167 @@
</span><ins>+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.distributions import (
+    # Continuous distributions
+    LogNormalDistribution, NormalDistribution,
+    # Discrete distributions
+    UniformDiscreteDistribution, UniformIntegerDistribution,
+    BernoulliDistribution, BinomialDistribution, FixedDistribution,
+    # Calendar-specific distributions
+    WorkDistribution, RecurrenceDistribution,
+)
+
+from pycalendar.datetime import DateTime
+from pycalendar.timezone import Timezone
+
+from scipy import stats
+from scipy.optimize import curve_fit
+import itertools
+
+&quot;&quot;&quot;
+Disclaimer: These tests are nondeterministic, so be careful
+&quot;&quot;&quot;
+
+class DistributionTestBase(TestCase):
+    def get_n_samples(self, dist, n):
+        samples = []
+        for _ignore_i in xrange(n):
+            samples.append(dist.sample())
+        return samples
+
+class DiscreteDistributionTests(DistributionTestBase):
+    def test_bernoulli(self):
+        sample_count = 1000
+        proportions = [0, 0.1, 0.25, 0.5, 0.75, 0.9, 1]
+        for prop in proportions:
+            dist = BernoulliDistribution(proportion=prop)
+            samples = self.get_n_samples(dist, sample_count)
+            successes = samples.count(True)
+
+            # This representes the likelihood that we would see as many successes
+            # as we did given that the true proportion is prop
+            p_value = stats.binom_test(successes, n=sample_count, p=prop)
+            self.assertFalse(p_value &lt;= 0.01, &quot;%d/%d, expected %f&quot; % (successes, sample_count, prop))
+    test_bernoulli.skip = &quot;FIXME: Sometimes doesn't pass...  551/1000, expected 0.500000&quot;
+
+    def test_binomial(self):
+        sample_counts = [100, 1000, 10000]
+        proportions = [0, 0.1, 0.25, 0.5, 0.75, 0.9, 1]
+        for sample_count, prop in itertools.product(sample_counts, proportions):
+            dist = BinomialDistribution(p=prop, n=sample_count)
+            successes = dist.sample()
+
+            # This representes the likelihood that we would see as many successes
+            # as we did given that the true proportion is prop
+            p_value = stats.binom_test(successes, n=sample_count, p=prop)
+            self.assertFalse(p_value &lt;= 0.01, &quot;%d/%d, expected %f&quot; % (successes, sample_count, prop))
+
+    def test_fixed(self):
+        dist = FixedDistribution(4) # https://xkcd.com/221/
+        for _ignore_i in xrange(100):
+            self.assertEqual(dist.sample(), 4)
+
+    def test_uniformdiscrete(self):
+        population = [82, 101, 100, 109, 111, 110, 100]
+        counts = dict.fromkeys(population, 0)
+        dist = UniformDiscreteDistribution(population)
+        for _ignore_i in range(len(population) * 10):
+            counts[dist.sample()] += 1
+        self.assertEqual(dict.fromkeys(population, 10), counts)
+        # Do some chi squared stuff
+    test_uniformdiscrete.skip = &quot;FIXME: Investigate and fix&quot;
+
+    def test_uniform(self):
+        dist = UniformIntegerDistribution(-5, 10)
+        for _ignore_i in range(100):
+            value = dist.sample()
+            self.assertTrue(-5 &lt;= value &lt; 10)
+            self.assertIsInstance(value, int)
+
+class ContinuousDistributionTests(TestCase):
+    def is_fit(self, pdf, xdata, ydata, pexp):
+        &quot;&quot;&quot;
+        expected parameters
+        &quot;&quot;&quot;
+        popt, pcov = curve_fit(pdf, xdata, ydata)
+        print popt
+
+    def test_normal(self):
+        dist = NormalDistribution()
+        dist
+    test_normal.skip = &quot;FIXME: Finish writing this test&quot;
+
+    def test_lognormal(self):
+        dist = LogNormalDistribution(mu=1, sigma=1)
+        for _ignore_i in range(100):
+            value = dist.sample()
+            self.assertIsInstance(value, float)
+            self.assertTrue(value &gt;= 0.0, &quot;negative value %r&quot; % (value,))
+            self.assertTrue(value &lt;= 1000, &quot;implausibly high value %r&quot; % (value,))
+
+        dist = LogNormalDistribution(mode=1, median=2)
+        for _ignore_i in range(100):
+            value = dist.sample()
+            self.assertIsInstance(value, float)
+            self.assertTrue(value &gt;= 0.0, &quot;negative value %r&quot; % (value,))
+            self.assertTrue(value &lt;= 1000, &quot;implausibly high value %r&quot; % (value,))
+
+        dist = LogNormalDistribution(mode=1, mean=2)
+        for _ignore_i in range(100):
+            value = dist.sample()
+            self.assertIsInstance(value, float)
+            self.assertTrue(value &gt;= 0.0, &quot;negative value %r&quot; % (value,))
+            self.assertTrue(value &lt;= 1000, &quot;implausibly high value %r&quot; % (value,))
+
+        self.assertRaises(ValueError, LogNormalDistribution, mu=1)
+        self.assertRaises(ValueError, LogNormalDistribution, sigma=1)
+        self.assertRaises(ValueError, LogNormalDistribution, mode=1)
+        self.assertRaises(ValueError, LogNormalDistribution, mean=1)
+        self.assertRaises(ValueError, LogNormalDistribution, median=1)
+
+class CalendarDistributionTests(TestCase):
+
+    def test_workdistribution(self):
+        tzname = &quot;US/Eastern&quot;
+        dist = WorkDistribution([&quot;mon&quot;, &quot;wed&quot;, &quot;thu&quot;, &quot;sat&quot;], 10, 20, tzname)
+        dist._helperDistribution = UniformDiscreteDistribution([35 * 60 * 60 + 30 * 60])
+        dist.now = lambda tzname = None: DateTime(2011, 5, 29, 18, 5, 36, tzid=tzname)
+        value = dist.sample()
+        self.assertEqual(
+            # Move past three workdays - monday, wednesday, thursday - using 30
+            # of the hours, and then five and a half hours into the fourth
+            # workday, saturday.  Workday starts at 10am, so the sample value
+            # is 3:30pm, ie 1530 hours.
+            DateTime(2011, 6, 4, 15, 30, 0, tzid=Timezone(tzid=tzname)),
+            value
+        )
+
+        dist = WorkDistribution([&quot;mon&quot;, &quot;tue&quot;, &quot;wed&quot;, &quot;thu&quot;, &quot;fri&quot;], 10, 20, tzname)
+        dist._helperDistribution = UniformDiscreteDistribution([35 * 60 * 60 + 30 * 60])
+        value = dist.sample()
+        self.assertTrue(isinstance(value, DateTime))
+
+    # twisted.trial.unittest.FailTest: not equal:
+    # a = datetime.datetime(2011, 6, 4, 15, 30, tzinfo=&lt;DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD&gt;)
+    # b = datetime.datetime(2011, 6, 4, 19, 30, tzinfo=&lt;DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST&gt;)
+    # test_workdistribution.todo = &quot;Somehow timezones mess this up&quot;
+
+
+    def test_recurrencedistribution(self):
+        dist = RecurrenceDistribution(False)
+        for _ignore in range(100):
+            value = dist.sample()
+            self.assertTrue(value is None)
+
+        dist = RecurrenceDistribution(True, {&quot;daily&quot;: 1, &quot;none&quot;: 2, &quot;weekly&quot;: 1})
+        dist._helperDistribution = UniformDiscreteDistribution([0, 3, 2, 1, 0])
+        value = dist.sample()
+        self.assertTrue(value is not None)
+        value = dist.sample()
+        self.assertTrue(value is None)
+        value = dist.sample()
+        self.assertTrue(value is None)
+        value = dist.sample()
+        self.assertTrue(value is not None)
+        value = dist.sample()
+        self.assertTrue(value is not None)
+    test_recurrencedistribution.skip = &quot;FIXME: Investigate and fix&quot;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_icalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_ical.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_ical.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_ical.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,1974 @@
</span><ins>+##
+# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 caldavclientlibrary.protocol.caldav.definitions import caldavxml
+from caldavclientlibrary.protocol.caldav.definitions import csxml
+from caldavclientlibrary.protocol.url import URL
+from caldavclientlibrary.protocol.webdav.definitions import davxml
+
+from contrib.performance.httpclient import MemoryConsumer, StringProducer
+from contrib.performance.loadtest.ical import Event, Calendar
+from contrib.performance.loadtest.records import DirectoryRecord
+from contrib.performance.loadtest.clients import OS_X_10_6
+
+from pycalendar.datetime import DateTime
+from pycalendar.timezone import Timezone
+
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+from twisted.internet.protocol import ProtocolToConsumerAdapter
+from twisted.python.failure import Failure
+from twisted.trial.unittest import TestCase
+from twisted.web.client import ResponseDone
+from twisted.web.http import OK, NO_CONTENT, CREATED, MULTI_STATUS
+from twisted.web.http_headers import Headers
+
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE # FIXME: to get TimezoneCache to work
+from twistedcaldav.ical import Component
+from twistedcaldav.timezones import TimezoneCache
+
+import json
+import os
+
+EVENT_UID = 'D94F247D-7433-43AF-B84B-ADD684D023B0'
+
+EVENT = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20101018T155454Z
+UID:%(UID)s
+DTEND;TZID=America/New_York:20101028T130000
+ATTENDEE;CN=&quot;User 03&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user03@example.com&quot;;PARTS
+ TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03@example.co
+ m
+ATTENDEE;CN=&quot;User 01&quot;;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
+ example.com
+TRANSP:OPAQUE
+SUMMARY:Attended Event
+DTSTART;TZID=America/New_York:20101028T120000
+DTSTAMP:20101018T155513Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user01@example.com
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;) % {'UID': EVENT_UID}
+
+EVENT_INVITE = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:STANDARD
+DTSTART:18831118T120358
+RDATE:18831118T120358
+TZNAME:EST
+TZOFFSETFROM:-045602
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19180331T020000
+RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19181027T020000
+RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19200101T000000
+RDATE:19200101T000000
+RDATE:19420101T000000
+RDATE:19460101T000000
+RDATE:19670101T000000
+TZNAME:EST
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19200328T020000
+RDATE:19200328T020000
+RDATE:19740106T020000
+RDATE:19750223T020000
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19201031T020000
+RDATE:19201031T020000
+RDATE:19450930T020000
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19210424T020000
+RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19210925T020000
+RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19420209T020000
+RDATE:19420209T020000
+TZNAME:EWT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19450814T190000
+RDATE:19450814T190000
+TZNAME:EPT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19460428T020000
+RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19460929T020000
+RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19551030T020000
+RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19670430T020000
+RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19760425T020000
+RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20101018T155454Z
+UID:%(UID)s
+DTEND;TZID=America/New_York:20101028T130000
+ATTENDEE;CN=&quot;User 02&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user02@example.com&quot;;PARTS
+ TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user02@example.co
+ m
+ATTENDEE;CN=&quot;User 03&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user03@example.com&quot;;PARTS
+ TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03@example.co
+ m
+ATTENDEE;CN=&quot;User 01&quot;;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:user01
+TRANSP:OPAQUE
+SUMMARY:Attended Event
+DTSTART;TZID=America/New_York:20101028T120000
+DTSTAMP:20101018T155513Z
+ORGANIZER;CN=&quot;User 01&quot;:urn:uuid:user01
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;) % {'UID': EVENT_UID}
+
+EVENT_AND_TIMEZONE = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:STANDARD
+DTSTART:18831118T120358
+RDATE:18831118T120358
+TZNAME:EST
+TZOFFSETFROM:-045602
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19180331T020000
+RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19181027T020000
+RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19200101T000000
+RDATE:19200101T000000
+RDATE:19420101T000000
+RDATE:19460101T000000
+RDATE:19670101T000000
+TZNAME:EST
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19200328T020000
+RDATE:19200328T020000
+RDATE:19740106T020000
+RDATE:19750223T020000
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19201031T020000
+RDATE:19201031T020000
+RDATE:19450930T020000
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19210424T020000
+RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19210925T020000
+RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19420209T020000
+RDATE:19420209T020000
+TZNAME:EWT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19450814T190000
+RDATE:19450814T190000
+TZNAME:EPT
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19460428T020000
+RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19460929T020000
+RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:19551030T020000
+RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19670430T020000
+RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19671029T020000
+RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19760425T020000
+RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:19870405T020000
+RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20101018T155454Z
+UID:%(UID)s
+DTEND;TZID=America/New_York:20101028T130000
+ATTENDEE;CN=&quot;User 03&quot;;CUTYPE=INDIVIDUAL;EMAIL=&quot;user03@example.com&quot;;PARTS
+ TAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user03@example.co
+ m
+ATTENDEE;CN=&quot;User 01&quot;;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:user01@
+ example.com
+TRANSP:OPAQUE
+SUMMARY:Attended Event
+DTSTART;TZID=America/New_York:20101028T120000
+DTSTAMP:20101018T155513Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user01@example.com
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;) % {'UID': EVENT_UID}
+
+
+PRINCIPAL_PROPFIND_RESPONSE = &quot;&quot;&quot;\
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;multistatus xmlns='DAV:'&gt;
+  &lt;response&gt;
+    &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;principal-collection-set&gt;
+          &lt;href&gt;/principals/&lt;/href&gt;
+        &lt;/principal-collection-set&gt;
+        &lt;calendar-home-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01&lt;/href&gt;
+        &lt;/calendar-home-set&gt;
+        &lt;calendar-user-address-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;href xmlns='DAV:'&gt;/principals/__uids__/user01/&lt;/href&gt;
+          &lt;href xmlns='DAV:'&gt;/principals/users/user01/&lt;/href&gt;
+        &lt;/calendar-user-address-set&gt;
+        &lt;schedule-inbox-URL xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/inbox/&lt;/href&gt;
+        &lt;/schedule-inbox-URL&gt;
+        &lt;schedule-outbox-URL xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/outbox/&lt;/href&gt;
+        &lt;/schedule-outbox-URL&gt;
+        &lt;dropbox-home-URL xmlns='http://calendarserver.org/ns/'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/dropbox/&lt;/href&gt;
+        &lt;/dropbox-home-URL&gt;
+        &lt;notification-URL xmlns='http://calendarserver.org/ns/'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/notification/&lt;/href&gt;
+        &lt;/notification-URL&gt;
+        &lt;displayname&gt;User 01&lt;/displayname&gt;
+        &lt;principal-URL&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/principal-URL&gt;
+        &lt;supported-report-set&gt;
+          &lt;supported-report&gt;
+            &lt;report&gt;
+              &lt;acl-principal-prop-set/&gt;
+            &lt;/report&gt;
+          &lt;/supported-report&gt;
+          &lt;supported-report&gt;
+            &lt;report&gt;
+              &lt;principal-match/&gt;
+            &lt;/report&gt;
+          &lt;/supported-report&gt;
+          &lt;supported-report&gt;
+            &lt;report&gt;
+              &lt;principal-property-search/&gt;
+            &lt;/report&gt;
+          &lt;/supported-report&gt;
+          &lt;supported-report&gt;
+            &lt;report&gt;
+              &lt;expand-property/&gt;
+            &lt;/report&gt;
+          &lt;/supported-report&gt;
+        &lt;/supported-report-set&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+&lt;/multistatus&gt;
+&quot;&quot;&quot;
+
+_CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE = &quot;&quot;&quot;\
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;multistatus xmlns='DAV:'&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        %(xmpp)s
+        &lt;displayname&gt;User 01&lt;/displayname&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'&gt;/Some/Unique/Value&lt;/pushkey&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/notification/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;displayname&gt;notification&lt;/displayname&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+          &lt;notification xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/dropbox/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+          &lt;dropbox-home xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;displayname/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/calendar/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'&gt;c2696540-4c4c-4a31-adaf-c99630776828#3&lt;/getctag&gt;
+        &lt;displayname&gt;calendar&lt;/displayname&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'&gt;#0252D4FF&lt;/calendar-color&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'&gt;1&lt;/calendar-order&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;comp name='VEVENT'/&gt;
+          &lt;comp name='VTODO'/&gt;
+          &lt;comp name='VTIMEZONE'/&gt;
+          &lt;comp name='VFREEBUSY'/&gt;
+        &lt;/supported-calendar-component-set&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+          &lt;calendar xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;opaque/&gt;
+        &lt;/schedule-calendar-transp&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+]]&gt;&lt;/calendar-timezone&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/outbox/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;comp name='VEVENT'/&gt;
+          &lt;comp name='VTODO'/&gt;
+          &lt;comp name='VTIMEZONE'/&gt;
+          &lt;comp name='VFREEBUSY'/&gt;
+        &lt;/supported-calendar-component-set&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+          &lt;schedule-outbox xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;schedule-send xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;schedule xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;displayname/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/freebusy&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;resourcetype&gt;
+          &lt;free-busy-url xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;schedule-deliver xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;schedule xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;displayname/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/calendars/__uids__/user01/inbox/&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;getctag xmlns='http://calendarserver.org/ns/'&gt;a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0&lt;/getctag&gt;
+        &lt;displayname&gt;inbox&lt;/displayname&gt;
+        &lt;supported-calendar-component-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;comp name='VEVENT'/&gt;
+          &lt;comp name='VTODO'/&gt;
+          &lt;comp name='VTIMEZONE'/&gt;
+          &lt;comp name='VFREEBUSY'/&gt;
+        &lt;/supported-calendar-component-set&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+          &lt;schedule-inbox xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;/resourcetype&gt;
+        &lt;owner&gt;
+          &lt;href&gt;/principals/__uids__/user01/&lt;/href&gt;
+        &lt;/owner&gt;
+        &lt;calendar-free-busy-set xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/calendar&lt;/href&gt;
+        &lt;/calendar-free-busy-set&gt;
+        &lt;schedule-default-calendar-URL xmlns='urn:ietf:params:xml:ns:caldav'&gt;
+          &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/calendar&lt;/href&gt;
+        &lt;/schedule-default-calendar-URL&gt;
+        &lt;quota-available-bytes&gt;104855434&lt;/quota-available-bytes&gt;
+        &lt;quota-used-bytes&gt;2166&lt;/quota-used-bytes&gt;
+        &lt;current-user-privilege-set&gt;
+          &lt;privilege&gt;
+            &lt;schedule-deliver xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;schedule xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;all/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-properties/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-content/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;bind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unbind/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;unlock/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;write-acl/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-current-user-privilege-set/&gt;
+          &lt;/privilege&gt;
+          &lt;privilege&gt;
+            &lt;read-free-busy xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+          &lt;/privilege&gt;
+        &lt;/current-user-privilege-set&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;calendar-description xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-color xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;calendar-order xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;calendar-timezone xmlns='urn:ietf:params:xml:ns:caldav'/&gt;
+        &lt;source xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-alarms xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-attachments xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;subscribed-strip-todos xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;refreshrate xmlns='http://apple.com/ns/ical/'/&gt;
+        &lt;push-transports xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;pushkey xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;publish-url xmlns='http://calendarserver.org/ns/'/&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+&lt;/multistatus&gt;
+&quot;&quot;&quot;
+
+CALENDAR_HOME_PROPFIND_RESPONSE = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {
+    &quot;xmpp&quot;: &quot;&quot;&quot;\
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'/&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'/&gt;&quot;&quot;&quot;,
+}
+
+CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {
+    &quot;xmpp&quot;: &quot;&quot;&quot;\
+        &lt;xmpp-server xmlns='http://calendarserver.org/ns/'&gt;xmpp.example.invalid:1952&lt;/xmpp-server&gt;
+        &lt;xmpp-uri xmlns='http://calendarserver.org/ns/'&gt;xmpp:pubsub.xmpp.example.invalid?pubsub;node=/CalDAV/another.example.invalid/user01/&lt;/xmpp-uri&gt;&quot;&quot;&quot;,
+}
+
+CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING = _CALENDAR_HOME_PROPFIND_RESPONSE_TEMPLATE % {&quot;xmpp&quot;: &quot;&quot;}
+
+
+
+class MemoryResponse(object):
+    def __init__(self, version, code, phrase, headers, bodyProducer):
+        self.version = version
+        self.code = code
+        self.phrase = phrase
+        self.headers = headers
+        self.length = bodyProducer.length
+        self._bodyProducer = bodyProducer
+
+
+    def deliverBody(self, protocol):
+        protocol.makeConnection(self._bodyProducer)
+        d = self._bodyProducer.startProducing(ProtocolToConsumerAdapter(protocol))
+        d.addCallback(lambda ignored: protocol.connectionLost(Failure(ResponseDone())))
+
+
+
+class OS_X_10_6Mixin:
+    &quot;&quot;&quot;
+    Mixin for L{TestCase}s for L{OS_X_10_6}.
+    &quot;&quot;&quot;
+    def setUp(self):
+        TimezoneCache.create()
+        self.record = DirectoryRecord(
+            u&quot;user91&quot;, u&quot;user91&quot;, u&quot;User 91&quot;, u&quot;user91@example.org&quot;, u&quot;user91&quot;,
+        )
+        serializePath = self.mktemp()
+        os.mkdir(serializePath)
+        self.client = OS_X_10_6(
+            None,
+            &quot;http://127.0.0.1&quot;,
+            # &quot;/principals/users/%s/&quot;,
+            serializePath,
+            self.record,
+            None,
+        )
+
+
+    def interceptRequests(self):
+        requests = []
+        def request(*args, **kwargs):
+            result = Deferred()
+            requests.append((result, args))
+            return result
+        self.client._request = request
+        return requests
+
+
+
+class OS_X_10_6Tests(OS_X_10_6Mixin, TestCase):
+    &quot;&quot;&quot;
+    Tests for L{OS_X_10_6}.
+    &quot;&quot;&quot;
+    def test_parsePrincipalPROPFINDResponse(self):
+        &quot;&quot;&quot;
+        L{Principal._parsePROPFINDResponse} accepts an XML document
+        like the one in the response to a I{PROPFIND} request for
+        I{/principals/__uids__/&lt;uid&gt;/} and returns a C{PropFindResult}
+        representing the data from it.
+        &quot;&quot;&quot;
+        principals = self.client._parseMultiStatus(PRINCIPAL_PROPFIND_RESPONSE)
+        principal = principals['/principals/__uids__/user01/']
+        self.assertEquals(
+            principal.getHrefProperties(),
+            {
+                davxml.principal_collection_set: URL(path='/principals/'),
+                caldavxml.calendar_home_set: URL(path='/calendars/__uids__/user01'),
+                caldavxml.calendar_user_address_set: (
+                    URL(path='/principals/__uids__/user01/'),
+                    URL(path='/principals/users/user01/'),
+                ),
+                caldavxml.schedule_inbox_URL: URL(path='/calendars/__uids__/user01/inbox/'),
+                caldavxml.schedule_outbox_URL: URL(path='/calendars/__uids__/user01/outbox/'),
+                csxml.dropbox_home_URL: URL(path='/calendars/__uids__/user01/dropbox/'),
+                csxml.notification_URL: URL(path='/calendars/__uids__/user01/notification/'),
+                davxml.principal_URL: URL(path='/principals/__uids__/user01/'),
+            }
+        )
+        self.assertEquals(
+            principal.getTextProperties(),
+            {davxml.displayname: 'User 01'})
+
+#         self.assertEquals(
+#             principal.getSomething(),
+#             {SUPPORTED_REPORT_SET: (
+#                     '{DAV:}acl-principal-prop-set',
+#                     '{DAV:}principal-match',
+#                     '{DAV:}principal-property-search',
+#                     '{DAV:}expand-property',
+#                     )})
+
+
+    def test_extractCalendars(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6._extractCalendars} accepts a calendar home
+        PROPFIND response body and returns a list of calendar objects
+        constructed from the data extracted from the response.
+        &quot;&quot;&quot;
+        home = &quot;/calendars/__uids__/user01/&quot;
+        calendars = self.client._extractCalendars(
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE), home)
+        calendars.sort(key=lambda cal: cal.resourceType)
+        calendar, inbox = calendars
+
+        self.assertEquals(calendar.resourceType, caldavxml.calendar)
+        self.assertEquals(calendar.name, &quot;calendar&quot;)
+        self.assertEquals(calendar.url, &quot;/calendars/__uids__/user01/calendar/&quot;)
+        self.assertEquals(calendar.changeToken, &quot;c2696540-4c4c-4a31-adaf-c99630776828#3&quot;)
+
+        self.assertEquals(inbox.resourceType, caldavxml.schedule_inbox)
+        self.assertEquals(inbox.name, &quot;inbox&quot;)
+        self.assertEquals(inbox.url, &quot;/calendars/__uids__/user01/inbox/&quot;)
+        self.assertEquals(inbox.changeToken, &quot;a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0&quot;)
+
+        self.assertEqual({}, self.client.xmpp)
+
+
+
+    @inlineCallbacks
+    def test_changeEventAttendee(self):
+        &quot;&quot;&quot;
+        OS_X_10_6.changeEventAttendee removes one attendee from an
+        existing event and appends another.
+        &quot;&quot;&quot;
+        requests = self.interceptRequests()
+
+        vevent = Component.fromString(EVENT)
+        attendees = tuple(vevent.mainComponent().properties(&quot;ATTENDEE&quot;))
+        old = attendees[0]
+        new = old.duplicate()
+        new.setParameter('CN', 'Some Other Guy')
+        event = Event(self.client.serializeLocation(), u'/some/calendar/1234.ics', None, vevent)
+        self.client._events[event.url] = event
+        self.client.changeEventAttendee(event.url, old, new)
+
+        _ignore_result, req = requests.pop(0)
+
+        # iCal PUTs the new VCALENDAR object.
+        _ignore_expectedResponseCode, method, url, headers, body = req
+        self.assertEquals(method, 'PUT')
+        self.assertEquals(url, 'http://127.0.0.1' + event.url)
+        self.assertIsInstance(url, str)
+        self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+        consumer = MemoryConsumer()
+        yield body.startProducing(consumer)
+        vevent = Component.fromString(consumer.value())
+        attendees = tuple(vevent.mainComponent().properties(&quot;ATTENDEE&quot;))
+        self.assertEquals(len(attendees), 2)
+        self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
+        self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
+
+
+    def test_addEvent(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6.addEvent} PUTs the event passed to it to the
+        server and updates local state to reflect its existence.
+        &quot;&quot;&quot;
+        requests = self.interceptRequests()
+
+        calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/mumble/', None)
+        self.client._calendars[calendar.url] = calendar
+
+        vcalendar = Component.fromString(EVENT)
+        d = self.client.addEvent(u'/mumble/frotz.ics', vcalendar)
+
+        result, req = requests.pop(0)
+
+        # iCal PUTs the new VCALENDAR object.
+        expectedResponseCode, method, url, headers, body = req
+        self.assertEqual(expectedResponseCode, CREATED)
+        self.assertEqual(method, 'PUT')
+        self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
+        self.assertIsInstance(url, str)
+        self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+        consumer = MemoryConsumer()
+        finished = body.startProducing(consumer)
+        def cbFinished(ignored):
+            self.assertEqual(
+                Component.fromString(consumer.value()),
+                Component.fromString(EVENT_AND_TIMEZONE))
+        finished.addCallback(cbFinished)
+
+        def requested(ignored):
+            response = MemoryResponse(
+                ('HTTP', '1', '1'), CREATED, &quot;Created&quot;, Headers({}),
+                StringProducer(&quot;&quot;))
+            result.callback(response)
+        finished.addCallback(requested)
+
+        return d
+
+
+    @inlineCallbacks
+    def test_addInvite(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6.addInvite} PUTs the event passed to it to the
+        server and updates local state to reflect its existence, but
+        it also does attendee auto-complete and free-busy checks before
+        the PUT.
+        &quot;&quot;&quot;
+
+        calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/mumble/', None)
+        self.client._calendars[calendar.url] = calendar
+
+        vcalendar = Component.fromString(EVENT_INVITE)
+
+        self.client.uuid = u'urn:uuid:user01'
+        self.client.email = u'mailto:user01@example.com'
+        self.client.principalCollection = &quot;/principals/&quot;
+        self.client.outbox = &quot;/calendars/__uids__/user01/outbox/&quot;
+
+        @inlineCallbacks
+        def _testReport(*args, **kwargs):
+            expectedResponseCode, method, url, headers, body = args
+            self.assertEqual(expectedResponseCode, (MULTI_STATUS,))
+            self.assertEqual(method, 'REPORT')
+            self.assertEqual(url, 'http://127.0.0.1/principals/')
+            self.assertIsInstance(url, str)
+            self.assertEqual(headers.getRawHeaders('content-type'), ['text/xml'])
+
+            consumer = MemoryConsumer()
+            yield body.startProducing(consumer)
+
+            response = MemoryResponse(
+                ('HTTP', '1', '1'), MULTI_STATUS, &quot;MultiStatus&quot;, Headers({}),
+                StringProducer(&quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;&lt;multistatus xmlns='DAV:' /&gt;&quot;))
+
+            returnValue(response)
+
+        @inlineCallbacks
+        def _testPost(*args, **kwargs):
+            expectedResponseCode, method, url, headers, body = args
+            self.assertEqual(expectedResponseCode, OK)
+            self.assertEqual(method, 'POST')
+            self.assertEqual(url, 'http://127.0.0.1/calendars/__uids__/user01/outbox/')
+            self.assertIsInstance(url, str)
+            self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+            consumer = MemoryConsumer()
+            yield body.startProducing(consumer)
+            self.assertNotEqual(consumer.value().find(kwargs[&quot;attendee&quot;]), -1)
+
+            response = MemoryResponse(
+                ('HTTP', '1', '1'), OK, &quot;OK&quot;, Headers({}),
+                StringProducer(&quot;&quot;))
+
+            returnValue(response)
+
+        def _testPost02(*args, **kwargs):
+            return _testPost(*args, attendee=&quot;ATTENDEE:mailto:user02@example.com&quot;, **kwargs)
+
+        def _testPost03(*args, **kwargs):
+            return _testPost(*args, attendee=&quot;ATTENDEE:mailto:user03@example.com&quot;, **kwargs)
+
+        @inlineCallbacks
+        def _testPut(*args, **kwargs):
+            expectedResponseCode, method, url, headers, body = args
+            self.assertEqual(expectedResponseCode, CREATED)
+            self.assertEqual(method, 'PUT')
+            self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
+            self.assertIsInstance(url, str)
+            self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+            consumer = MemoryConsumer()
+            yield body.startProducing(consumer)
+            self.assertEqual(
+                Component.fromString(consumer.value()),
+                Component.fromString(EVENT_INVITE))
+
+            response = MemoryResponse(
+                ('HTTP', '1', '1'), CREATED, &quot;Created&quot;, Headers({}),
+                StringProducer(&quot;&quot;))
+
+            returnValue(response)
+
+        requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut, ]
+
+        def _requestHandler(*args, **kwargs):
+            handler = requests.pop(0)
+            return handler(*args, **kwargs)
+        self.client._request = _requestHandler
+        yield self.client.addInvite('/mumble/frotz.ics', vcalendar)
+
+
+    def test_deleteEvent(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6.deleteEvent} DELETEs the event at the relative
+        URL passed to it and updates local state to reflect its
+        removal.
+        &quot;&quot;&quot;
+        requests = self.interceptRequests()
+
+        calendar = Calendar(caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/foo/', None)
+        event = Event(None, calendar.url + u'bar.ics', None)
+        self.client._calendars[calendar.url] = calendar
+        self.client._setEvent(event.url, event)
+
+        d = self.client.deleteEvent(event.url)
+
+        result, req = requests.pop()
+
+        expectedResponseCode, method, url = req
+
+        self.assertEqual(expectedResponseCode, NO_CONTENT)
+        self.assertEqual(method, 'DELETE')
+        self.assertEqual(url, 'http://127.0.0.1' + event.url)
+        self.assertIsInstance(url, str)
+
+        self.assertNotIn(event.url, self.client._events)
+        self.assertNotIn(u'bar.ics', calendar.events)
+
+        response = MemoryResponse(
+            ('HTTP', '1', '1'), NO_CONTENT, &quot;No Content&quot;, None,
+            StringProducer(&quot;&quot;))
+        result.callback(response)
+        return d
+
+
+    def test_serialization(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6.serialize} properly generates a JSON document.
+        &quot;&quot;&quot;
+        clientPath = os.path.join(self.client.serializePath, &quot;user91-OS_X_10.6&quot;)
+        self.assertFalse(os.path.exists(clientPath))
+        indexPath = os.path.join(clientPath, &quot;index.json&quot;)
+        self.assertFalse(os.path.exists(indexPath))
+
+        cal1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:004f8e41-b071-4b30-bb3b-6aada4adcc10
+DTSTART:20120817T113000
+DTEND:20120817T114500
+DTSTAMP:20120815T154420Z
+SEQUENCE:2
+SUMMARY:Simple event
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        cal2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:00a79cad-857b-418e-a54a-340b5686d747
+DTSTART:20120817T113000
+DTEND:20120817T114500
+DTSTAMP:20120815T154420Z
+SEQUENCE:2
+SUMMARY:Simple event
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        events = (
+            Event(self.client.serializeLocation(), u'/home/calendar/1.ics', u'123.123', Component.fromString(cal1)),
+            Event(self.client.serializeLocation(), u'/home/inbox/i1.ics', u'123.123', Component.fromString(cal2)),
+        )
+        self.client._events.update(dict([[event.url, event] for event in events]))
+
+        calendars = (
+            Calendar(str(caldavxml.calendar), set(('VEVENT',)), u'calendar', u'/home/calendar/', &quot;123&quot;),
+            Calendar(str(caldavxml.calendar), set(('VTODO',)), u'tasks', u'/home/tasks/', &quot;456&quot;),
+            Calendar(str(caldavxml.schedule_inbox), set(('VEVENT', &quot;VTODO&quot;,)), u'calendar', u'/home/inbox/', &quot;789&quot;),
+        )
+        self.client._calendars.update(dict([[calendar.url, calendar] for calendar in calendars]))
+        self.client._calendars[&quot;/home/calendar/&quot;].events[&quot;1.ics&quot;] = events[0]
+        self.client._calendars[&quot;/home/inbox/&quot;].events[&quot;i1.ics&quot;] = events[1]
+
+        self.client.serialize()
+        self.assertTrue(os.path.exists(clientPath))
+        self.assertTrue(os.path.exists(indexPath))
+        def _normDict(d):
+            return dict([(k, sorted(v, key=lambda x: x[&quot;changeToken&quot; if k == &quot;calendars&quot; else &quot;url&quot;]) if v else None,) for k, v in d.items()])
+        self.assertEqual(_normDict(json.loads(open(indexPath).read())), _normDict(json.loads(&quot;&quot;&quot;{
+  &quot;calendars&quot;: [
+    {
+      &quot;changeToken&quot;: &quot;123&quot;,
+      &quot;name&quot;: &quot;calendar&quot;,
+      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
+      &quot;componentTypes&quot;: [
+        &quot;VEVENT&quot;
+      ],
+      &quot;url&quot;: &quot;/home/calendar/&quot;,
+      &quot;events&quot;: [
+        &quot;1.ics&quot;
+      ]
+    },
+    {
+      &quot;changeToken&quot;: &quot;789&quot;,
+      &quot;name&quot;: &quot;calendar&quot;,
+      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}schedule-inbox&quot;,
+      &quot;componentTypes&quot;: [
+        &quot;VEVENT&quot;,
+        &quot;VTODO&quot;
+      ],
+      &quot;url&quot;: &quot;/home/inbox/&quot;,
+      &quot;events&quot;: [
+        &quot;i1.ics&quot;
+      ]
+    },
+    {
+      &quot;changeToken&quot;: &quot;456&quot;,
+      &quot;name&quot;: &quot;tasks&quot;,
+      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
+      &quot;componentTypes&quot;: [
+        &quot;VTODO&quot;
+      ],
+      &quot;url&quot;: &quot;/home/tasks/&quot;,
+      &quot;events&quot;: []
+    }
+  ],
+  &quot;principalURL&quot;: null,
+  &quot;events&quot;: [
+    {
+      &quot;url&quot;: &quot;/home/calendar/1.ics&quot;,
+      &quot;scheduleTag&quot;: null,
+      &quot;etag&quot;: &quot;123.123&quot;,
+      &quot;uid&quot;: &quot;004f8e41-b071-4b30-bb3b-6aada4adcc10&quot;
+    },
+    {
+      &quot;url&quot;: &quot;/home/inbox/i1.ics&quot;,
+      &quot;scheduleTag&quot;: null,
+      &quot;etag&quot;: &quot;123.123&quot;,
+      &quot;uid&quot;: &quot;00a79cad-857b-418e-a54a-340b5686d747&quot;
+    }
+  ]
+}&quot;&quot;&quot;)))
+
+        event1Path = os.path.join(clientPath, &quot;calendar&quot;, &quot;1.ics&quot;)
+        self.assertTrue(os.path.exists(event1Path))
+        self.assertEqual(open(event1Path).read(), cal1)
+
+        event2Path = os.path.join(clientPath, &quot;inbox&quot;, &quot;i1.ics&quot;)
+        self.assertTrue(os.path.exists(event2Path))
+        self.assertEqual(open(event2Path).read(), cal2)
+
+
+    def test_deserialization(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6.deserailize} properly parses a JSON document.
+        &quot;&quot;&quot;
+
+        cal1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:004f8e41-b071-4b30-bb3b-6aada4adcc10
+DTSTART:20120817T113000
+DTEND:20120817T114500
+DTSTAMP:20120815T154420Z
+SEQUENCE:2
+SUMMARY:Simple event
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        cal2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:00a79cad-857b-418e-a54a-340b5686d747
+DTSTART:20120817T113000
+DTEND:20120817T114500
+DTSTAMP:20120815T154420Z
+SEQUENCE:2
+SUMMARY:Simple event
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        clientPath = os.path.join(self.client.serializePath, &quot;user91-OS_X_10.6&quot;)
+        os.mkdir(clientPath)
+        indexPath = os.path.join(clientPath, &quot;index.json&quot;)
+        open(indexPath, &quot;w&quot;).write(&quot;&quot;&quot;{
+  &quot;calendars&quot;: [
+    {
+      &quot;changeToken&quot;: &quot;321&quot;,
+      &quot;name&quot;: &quot;calendar&quot;,
+      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
+      &quot;componentTypes&quot;: [
+        &quot;VEVENT&quot;
+      ],
+      &quot;url&quot;: &quot;/home/calendar/&quot;,
+      &quot;events&quot;: [
+        &quot;2.ics&quot;
+      ]
+    },
+    {
+      &quot;changeToken&quot;: &quot;987&quot;,
+      &quot;name&quot;: &quot;calendar&quot;,
+      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}schedule-inbox&quot;,
+      &quot;componentTypes&quot;: [
+        &quot;VEVENT&quot;,
+        &quot;VTODO&quot;
+      ],
+      &quot;url&quot;: &quot;/home/inbox/&quot;,
+      &quot;events&quot;: [
+        &quot;i2.ics&quot;
+      ]
+    },
+    {
+      &quot;changeToken&quot;: &quot;654&quot;,
+      &quot;name&quot;: &quot;tasks&quot;,
+      &quot;resourceType&quot;: &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;,
+      &quot;componentTypes&quot;: [
+        &quot;VTODO&quot;
+      ],
+      &quot;url&quot;: &quot;/home/tasks/&quot;,
+      &quot;events&quot;: []
+    }
+  ],
+  &quot;principalURL&quot;: null,
+  &quot;events&quot;: [
+    {
+      &quot;url&quot;: &quot;/home/calendar/2.ics&quot;,
+      &quot;scheduleTag&quot;: null,
+      &quot;etag&quot;: &quot;321.321&quot;,
+      &quot;uid&quot;: &quot;004f8e41-b071-4b30-bb3b-6aada4adcc10&quot;
+    },
+    {
+      &quot;url&quot;: &quot;/home/inbox/i2.ics&quot;,
+      &quot;scheduleTag&quot;: null,
+      &quot;etag&quot;: &quot;987.987&quot;,
+      &quot;uid&quot;: &quot;00a79cad-857b-418e-a54a-340b5686d747&quot;
+    }
+  ]
+}&quot;&quot;&quot;)
+
+        os.mkdir(os.path.join(clientPath, &quot;calendar&quot;))
+        event1Path = os.path.join(clientPath, &quot;calendar&quot;, &quot;2.ics&quot;)
+        open(event1Path, &quot;w&quot;).write(cal1)
+        os.mkdir(os.path.join(clientPath, &quot;inbox&quot;))
+        event1Path = os.path.join(clientPath, &quot;inbox&quot;, &quot;i2.ics&quot;)
+        open(event1Path, &quot;w&quot;).write(cal2)
+
+        self.client.deserialize()
+
+        self.assertEqual(len(self.client._calendars), 3)
+        self.assertTrue(&quot;/home/calendar/&quot; in self.client._calendars)
+        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].changeToken, &quot;321&quot;)
+        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].name, &quot;calendar&quot;)
+        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].resourceType, &quot;{urn:ietf:params:xml:ns:caldav}calendar&quot;)
+        self.assertEqual(self.client._calendars[&quot;/home/calendar/&quot;].componentTypes, set((&quot;VEVENT&quot;,)))
+        self.assertTrue(&quot;/home/tasks/&quot; in self.client._calendars)
+        self.assertTrue(&quot;/home/inbox/&quot; in self.client._calendars)
+        self.assertEqual(self.client._calendars[&quot;/home/inbox/&quot;].componentTypes, set((&quot;VEVENT&quot;, &quot;VTODO&quot;,)))
+        self.assertEqual(len(self.client._events), 2)
+        self.assertTrue(&quot;/home/calendar/2.ics&quot; in self.client._events)
+        self.assertEqual(self.client._events[&quot;/home/calendar/2.ics&quot;].scheduleTag, None)
+        self.assertEqual(self.client._events[&quot;/home/calendar/2.ics&quot;].etag, &quot;321.321&quot;)
+        self.assertEqual(self.client._events[&quot;/home/calendar/2.ics&quot;].getUID(), &quot;004f8e41-b071-4b30-bb3b-6aada4adcc10&quot;)
+        self.assertEqual(str(self.client._events[&quot;/home/calendar/2.ics&quot;].component), cal1)
+        self.assertTrue(&quot;/home/inbox/i2.ics&quot; in self.client._events)
+        self.assertEqual(self.client._events[&quot;/home/inbox/i2.ics&quot;].scheduleTag, None)
+        self.assertEqual(self.client._events[&quot;/home/inbox/i2.ics&quot;].etag, &quot;987.987&quot;)
+        self.assertEqual(self.client._events[&quot;/home/inbox/i2.ics&quot;].getUID(), &quot;00a79cad-857b-418e-a54a-340b5686d747&quot;)
+        self.assertEqual(str(self.client._events[&quot;/home/inbox/i2.ics&quot;].component), cal2)
+
+
+
+class UpdateCalendarTests(OS_X_10_6Mixin, TestCase):
+    &quot;&quot;&quot;
+    Tests for L{OS_X_10_6._updateCalendar}.
+    &quot;&quot;&quot;
+
+    _CALENDAR_PROPFIND_RESPONSE_BODY = &quot;&quot;&quot;\
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;multistatus xmlns='DAV:'&gt;
+  &lt;response&gt;
+    &lt;href&gt;/something/anotherthing.ics&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+        &lt;/resourcetype&gt;
+        &lt;getetag&gt;&quot;None&quot;&lt;/getetag&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/something/else.ics&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;resourcetype&gt;
+          &lt;collection/&gt;
+        &lt;/resourcetype&gt;
+        &lt;getetag&gt;&quot;None&quot;&lt;/getetag&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+   &lt;/response&gt;
+&lt;/multistatus&gt;
+&quot;&quot;&quot;
+    _CALENDAR_REPORT_RESPONSE_BODY = &quot;&quot;&quot;\
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;multistatus xmlns='DAV:'&gt;
+  &lt;response&gt;
+    &lt;href&gt;/something/anotherthing.ics&lt;/href&gt;
+    &lt;status&gt;HTTP/1.1 404 Not Found&lt;/status&gt;
+  &lt;/response&gt;
+  &lt;response&gt;
+    &lt;href&gt;/something/else.ics&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;getetag&gt;&quot;ef70beb4cb7da4b2e2950350b09e9a01&quot;&lt;/getetag&gt;
+        &lt;calendar-data xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:CD54161A13AA8A4649D3781E@caldav.corp.apple.com
+DTSTART:20110715T140000Z
+DURATION:PT1H
+DTSTAMP:20110715T144217Z
+SUMMARY:Test2
+END:VEVENT
+END:VCALENDAR
+]]&gt;&lt;/calendar-data&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+&lt;/multistatus&gt;
+&quot;&quot;&quot;
+
+    _CALENDAR_REPORT_RESPONSE_BODY_1 = &quot;&quot;&quot;\
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;multistatus xmlns='DAV:'&gt;
+  &lt;response&gt;
+    &lt;href&gt;/something/anotherthing.ics&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;getetag&gt;&quot;ef70beb4cb7da4b2e2950350b09e9a01&quot;&lt;/getetag&gt;
+        &lt;calendar-data xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:anotherthing@caldav.corp.apple.com
+DTSTART:20110715T140000Z
+DURATION:PT1H
+DTSTAMP:20110715T144217Z
+SUMMARY:Test1
+END:VEVENT
+END:VCALENDAR
+]]&gt;&lt;/calendar-data&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+&lt;/multistatus&gt;
+&quot;&quot;&quot;
+
+    _CALENDAR_REPORT_RESPONSE_BODY_2 = &quot;&quot;&quot;\
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;multistatus xmlns='DAV:'&gt;
+  &lt;response&gt;
+    &lt;href&gt;/something/else.ics&lt;/href&gt;
+    &lt;propstat&gt;
+      &lt;prop&gt;
+        &lt;getetag&gt;&quot;ef70beb4cb7da4b2e2950350b09e9a01&quot;&lt;/getetag&gt;
+        &lt;calendar-data xmlns='urn:ietf:params:xml:ns:caldav'&gt;&lt;![CDATA[BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VEVENT
+UID:else@caldav.corp.apple.com
+DTSTART:20110715T140000Z
+DURATION:PT1H
+DTSTAMP:20110715T144217Z
+SUMMARY:Test2
+END:VEVENT
+END:VCALENDAR
+]]&gt;&lt;/calendar-data&gt;
+      &lt;/prop&gt;
+      &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
+    &lt;/propstat&gt;
+  &lt;/response&gt;
+&lt;/multistatus&gt;
+&quot;&quot;&quot;
+
+    def test_eventMissing(self):
+        &quot;&quot;&quot;
+        If an event included in the calendar PROPFIND response no longer exists
+        by the time a REPORT is issued for that event, the 404 is handled and
+        the rest of the normal update logic for that event is skipped.
+        &quot;&quot;&quot;
+        requests = self.interceptRequests()
+
+        calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
+        self.client._calendars[calendar.url] = calendar
+        self.client._updateCalendar(calendar, &quot;1234&quot;)
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
+        self.assertEqual('PROPFIND', method)
+        self.assertEqual('http://127.0.0.1/something/', url)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+
+        result.callback(
+            MemoryResponse(
+                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
+
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
+        self.assertEqual('REPORT', method)
+        self.assertEqual('http://127.0.0.1/something/', url)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+
+        # Someone else comes along and gets rid of the event
+        del self.client._events[&quot;/something/anotherthing.ics&quot;]
+
+        result.callback(
+            MemoryResponse(
+                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)))
+
+        # Verify that processing proceeded to the response after the one with a
+        # 404 status.
+        self.assertIn('/something/else.ics', self.client._events)
+
+
+    def test_multigetBatch(self):
+        &quot;&quot;&quot;
+        If an event included in the calendar PROPFIND response no longer exists
+        by the time a REPORT is issued for that event, the 404 is handled and
+        the rest of the normal update logic for that event is skipped.
+        &quot;&quot;&quot;
+        requests = self.interceptRequests()
+
+        self.patch(self.client, &quot;MULTIGET_BATCH_SIZE&quot;, 1)
+
+        calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
+        self.client._calendars[calendar.url] = calendar
+        self.client._updateCalendar(calendar, &quot;1234&quot;)
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
+        self.assertEqual('PROPFIND', method)
+        self.assertEqual('http://127.0.0.1/something/', url)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+
+        result.callback(
+            MemoryResponse(
+                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
+
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
+        self.assertEqual('REPORT', method)
+        self.assertEqual('http://127.0.0.1/something/', url)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+
+        result.callback(
+            MemoryResponse(
+                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_1)))
+
+        self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
+        self.assertTrue(self.client._events['/something/else.ics'].etag is None)
+
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
+        self.assertEqual('REPORT', method)
+        self.assertEqual('http://127.0.0.1/something/', url)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
+
+        result.callback(
+            MemoryResponse(
+                ('HTTP', '1', '1'), MULTI_STATUS, &quot;Multi-status&quot;, None,
+                StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY_2)))
+
+        self.assertTrue(self.client._events['/something/anotherthing.ics'].etag is not None)
+        self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
+
+
+
+class VFreeBusyTests(OS_X_10_6Mixin, TestCase):
+    &quot;&quot;&quot;
+    Tests for L{OS_X_10_6.requestAvailability}.
+    &quot;&quot;&quot;
+    def test_requestAvailability(self):
+        &quot;&quot;&quot;
+        L{OS_X_10_6.requestAvailability} accepts a date range and a set of
+        account uuids and issues a VFREEBUSY request.  It returns a Deferred
+        which fires with a dict mapping account uuids to availability range
+        information.
+        &quot;&quot;&quot;
+        self.client.uuid = u'urn:uuid:user01'
+        self.client.email = u'mailto:user01@example.com'
+        self.client.outbox = &quot;/calendars/__uids__/%s/outbox/&quot; % (self.record.uid,)
+        requests = self.interceptRequests()
+
+        start = DateTime(2011, 6, 10, 10, 45, 0, tzid=Timezone.UTCTimezone)
+        end = DateTime(2011, 6, 10, 11, 15, 0, tzid=Timezone.UTCTimezone)
+        d = self.client.requestAvailability(
+            start, end, [u&quot;urn:uuid:user05&quot;, u&quot;urn:uuid:user10&quot;])
+
+        result, req = requests.pop(0)
+        expectedResponseCode, method, url, headers, body = req
+
+        self.assertEqual(OK, expectedResponseCode)
+        self.assertEqual('POST', method)
+        self.assertEqual(
+            'http://127.0.0.1/calendars/__uids__/%s/outbox/' % (self.record.uid,),
+            url)
+
+        self.assertEqual(headers.getRawHeaders('originator'), ['mailto:user01@example.com'])
+        self.assertEqual(headers.getRawHeaders('recipient'), ['urn:uuid:user05, urn:uuid:user10'])
+        self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+        consumer = MemoryConsumer()
+        finished = body.startProducing(consumer)
+        def cbFinished(ignored):
+            vevent = Component.fromString(consumer.value())
+            uid = vevent.resourceUID()
+            dtstamp = vevent.mainComponent().propertyValue(&quot;DTSTAMP&quot;)
+            dtstamp = dtstamp.getText()
+            self.assertEqual(&quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(uid)s
+DTEND:20110611T000000Z
+ATTENDEE:urn:uuid:user05
+ATTENDEE:urn:uuid:user10
+DTSTART:20110610T000000Z
+DTSTAMP:%(dtstamp)s
+ORGANIZER:mailto:user01@example.com
+SUMMARY:Availability for urn:uuid:user05, urn:uuid:user10
+END:VFREEBUSY
+END:VCALENDAR
+&quot;&quot;&quot;.replace('\n', '\r\n') % {'uid': uid, 'dtstamp': dtstamp}, consumer.value())
+
+        finished.addCallback(cbFinished)
+
+        def requested(ignored):
+            response = MemoryResponse(
+                ('HTTP', '1', '1'), OK, &quot;Ok&quot;, Headers({}),
+                StringProducer(&quot;&quot;))
+            result.callback(response)
+        finished.addCallback(requested)
+
+        return d
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_loggerpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_logger.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_logger.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_logger.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,447 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 StringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.logger import ReportStatistics, OperationLogger
+
+class ReportStatisticsTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{loadtest.population.ReportStatistics}.
+    &quot;&quot;&quot;
+    def test_countUsers(self):
+        &quot;&quot;&quot;
+        L{ReportStatistics.countUsers} returns the number of users observed to
+        have acted in the simulation.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        users = ['user01', 'user02', 'user03']
+        for user in users:
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=1.23, user=user, client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+        self.assertEqual(len(users), logger.countUsers())
+
+
+    def test_countClients(self):
+        &quot;&quot;&quot;
+        L{ReportStatistics.countClients} returns the number of clients observed to
+        have acted in the simulation.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=1.23, user=&quot;user01&quot;, client_type=&quot;test&quot;, client_id=client
+            ))
+        self.assertEqual(len(clients), logger.countClients())
+
+
+    def test_clientFailures(self):
+        &quot;&quot;&quot;
+        L{ReportStatistics.countClientFailures} returns the number of clients observed to
+        have failed in the simulation.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                type='client-failure', reason=&quot;testing %s&quot; % (client,)
+            ))
+        self.assertEqual(len(clients), logger.countClientFailures())
+
+
+    def test_simFailures(self):
+        &quot;&quot;&quot;
+        L{ReportStatistics.countSimFailures} returns the number of clients observed to
+        have caused an error in the simulation.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                type='sim-failure', reason=&quot;testing %s&quot; % (client,)
+            ))
+        self.assertEqual(len(clients), logger.countSimFailures())
+
+
+    def test_noFailures(self):
+        &quot;&quot;&quot;
+        If fewer than 1% of requests fail, fewer than 1% of requests take 5
+        seconds or more, and fewer than 5% of requests take 3 seconds or more,
+        L{ReportStatistics.failures} returns an empty list.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        logger.observe(dict(
+            type='response', method='GET', success=True,
+            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual([], logger.failures())
+
+
+    def test_requestFailures(self):
+        &quot;&quot;&quot;
+        If more than 1% of requests fail, L{ReportStatistics.failures} returns a
+        list containing a string describing this.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        for _ignore in range(98):
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+        logger.observe(dict(
+            type='response', method='GET', success=False,
+            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [&quot;Greater than 1% GET failed&quot;],
+            logger.failures())
+
+
+    def test_threeSecondFailure(self):
+        &quot;&quot;&quot;
+        If more than 5% of requests take longer than 3 seconds,
+        L{ReportStatistics.failures} returns a list containing a string
+        describing that.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        for _ignore in range(94):
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+        for _ignore in range(5):
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=3.5, user='user02', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+        self.assertEqual(
+            [&quot;Greater than 5% GET exceeded 3 second response time&quot;],
+            logger.failures())
+
+
+    def test_fiveSecondFailure(self):
+        &quot;&quot;&quot;
+        If more than 1% of requests take longer than 5 seconds,
+        L{ReportStatistics.failures} returns a list containing a string
+        describing that.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        for _ignore in range(98):
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+        logger.observe(dict(
+            type='response', method='GET', success=True,
+            duration=5.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [&quot;Greater than 1% GET exceeded 5 second response time&quot;],
+            logger.failures())
+
+
+    def test_methodsCountedSeparately(self):
+        &quot;&quot;&quot;
+        The counts for one method do not affect the results of another method.
+        &quot;&quot;&quot;
+        logger = ReportStatistics()
+        for _ignore in range(99):
+            logger.observe(dict(
+                type='response', method='GET', success=True,
+                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+            logger.observe(dict(
+                type='response', method='POST', success=True,
+                duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+            ))
+
+        logger.observe(dict(
+            type='response', method='GET', success=False,
+            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='POST', success=False,
+            duration=2.5, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+
+        self.assertEqual([], logger.failures())
+
+
+    def test_bucketRequest(self):
+        &quot;&quot;&quot;
+        PUT(xxx-huge/large/medium/small} have different thresholds. Test that requests straddling
+        each of those are correctly determined to be failures or not.
+        &quot;&quot;&quot;
+
+        _thresholds = {
+            &quot;requests&quot;: {
+                &quot;limits&quot;: [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
+                &quot;thresholds&quot;: {
+                    &quot;default&quot;: [100.0, 100.0, 100.0, 5.0, 1.0, 0.5, 0.0],
+                    &quot;PUT{organizer-small}&quot;: [100.0, 50.0, 25.0, 5.0, 1.0, 0.5, 0.0],
+                    &quot;PUT{organizer-medium}&quot;: [100.0, 100.0, 50.0, 25.0, 5.0, 1.0, 0.5],
+                    &quot;PUT{organizer-large}&quot;: [100.0, 100.0, 100.0, 50.0, 25.0, 5.0, 1.0],
+                    &quot;PUT{organizer-huge}&quot;: [100.0, 100.0, 100.0, 100.0, 100.0, 50.0, 25.0],
+                }
+            }
+        }
+
+        # -small below threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual([], logger.failures())
+
+        # -small above 0.5 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-small}', success=True,
+            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [&quot;Greater than 50% PUT{organizer-small} exceeded 0.5 second response time&quot;],
+            logger.failures()
+        )
+
+        # -medium below 0.5 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=0.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [],
+            logger.failures()
+        )
+
+        # -medium above 1.0 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-medium}', success=True,
+            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [&quot;Greater than 50% PUT{organizer-medium} exceeded 1 second response time&quot;],
+            logger.failures()
+        )
+
+        # -large below 1.0 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=1.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [],
+            logger.failures()
+        )
+
+        # -large above 3.0 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=0.2, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=3.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=3.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-large}', success=True,
+            duration=3.6, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [&quot;Greater than 50% PUT{organizer-large} exceeded 3 second response time&quot;],
+            logger.failures()
+        )
+
+        # -huge below 10.0 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=12.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=8, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=11.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=9.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [],
+            logger.failures()
+        )
+
+        # -huge above 10.0 threshold
+        logger = ReportStatistics(thresholds=_thresholds)
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=12.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=9.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=12.0, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        logger.observe(dict(
+            type='response', method='PUT{organizer-huge}', success=True,
+            duration=42.42, user='user01', client_type=&quot;test&quot;, client_id=&quot;1234&quot;
+        ))
+        self.assertEqual(
+            [&quot;Greater than 50% PUT{organizer-huge} exceeded 10 second response time&quot;],
+            logger.failures()
+        )
+
+
+
+class OperationLoggerTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{OperationLogger}.
+    &quot;&quot;&quot;
+    def test_noFailures(self):
+        &quot;&quot;&quot;
+        If the median lag is below 1 second and the failure rate is below 1%,
+        L{OperationLogger.failures} returns an empty list.
+        &quot;&quot;&quot;
+        logger = OperationLogger(outfile=StringIO())
+        logger.observe(dict(
+            type='operation', phase='start', user='user01',
+            label='testing', lag=0.5)
+        )
+        logger.observe(dict(
+            type='operation', phase='end', user='user01',
+            duration=0.35, label='testing', success=True)
+        )
+        self.assertEqual([], logger.failures())
+
+
+    def test_lagLimitExceeded(self):
+        &quot;&quot;&quot;
+        If the median scheduling lag for any operation in the simulation
+        exceeds 1 second, L{OperationLogger.failures} returns a list containing
+        a string describing that issue.
+        &quot;&quot;&quot;
+        logger = OperationLogger(outfile=StringIO())
+        for lag in [100.0, 1100.0, 1200.0]:
+            logger.observe(dict(
+                type='operation', phase='start', user='user01',
+                label='testing', lag=lag)
+            )
+        self.assertEqual(
+            [&quot;Median TESTING scheduling lag greater than 1000.0ms&quot;],
+            logger.failures())
+
+
+    def test_failureLimitExceeded(self):
+        &quot;&quot;&quot;
+        If the failure rate for any operation exceeds 1%,
+        L{OperationLogger.failures} returns a list containing a string
+        describing that issue.
+        &quot;&quot;&quot;
+        logger = OperationLogger(outfile=StringIO())
+        for _ignore in range(98):
+            logger.observe(dict(
+                type='operation', phase='end', user='user01',
+                duration=0.25, label='testing', success=True)
+            )
+        logger.observe(dict(
+            type='operation', phase='end', user='user01',
+            duration=0.25, label='testing', success=False)
+        )
+        self.assertEqual(
+            [&quot;Greater than 1% TESTING failed&quot;],
+            logger.failures())
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_populationpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_population.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_population.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_population.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,21 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+#
+##
+
+&quot;&quot;&quot;
+Tests for some things in L{loadtest.population}.
+&quot;&quot;&quot;
+
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_profilespy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_profiles.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_profiles.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_profiles.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,1029 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+#
+##
+
+&quot;&quot;&quot;
+Tests for loadtest.profiles.
+&quot;&quot;&quot;
+
+from caldavclientlibrary.protocol.caldav.definitions import caldavxml, csxml
+
+from twisted.trial.unittest import TestCase
+from twisted.internet.task import Clock
+from twisted.internet.defer import succeed, fail
+from twisted.web.http import NO_CONTENT, PRECONDITION_FAILED
+from twisted.web.client import Response
+
+from twistedcaldav.ical import Component, Property
+
+from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
+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
+
+import os
+
+SIMPLE_EVENT = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+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
+&quot;&quot;&quot;
+
+INVITED_EVENT = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:882C3D50-0DAE-45CB-A2E7-DA75DA9BE452
+DTSTART;TZID=America/New_York:20110131T130000
+DTEND;TZID=America/New_York:20110131T140000
+ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
+ CEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02@example.com;PARTSTAT=NE
+ EDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:urn:uuid:user02
+CREATED:20110124T170357Z
+DTSTAMP:20110124T170425Z
+ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01
+SEQUENCE:3
+SUMMARY:Some Event For You
+TRANSP:TRANSPARENT
+X-APPLE-NEEDS-REPLY:TRUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+ACCEPTED_EVENT = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:882C3D50-0DAE-45CB-A2E7-DA75DA9BE452
+DTSTART;TZID=America/New_York:20110131T130000
+DTEND;TZID=America/New_York:20110131T140000
+ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
+ CEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;CUTYPE=INDIVIDUAL;EMAIL=user02@example.com;PARTSTAT=AC
+ CEPTED:urn:uuid:user02
+CREATED:20110124T170357Z
+DTSTAMP:20110124T170425Z
+ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:uuid:user01
+SEQUENCE:3
+SUMMARY:Some Event For You
+TRANSP:TRANSPARENT
+X-APPLE-NEEDS-REPLY:TRUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+INBOX_REPLY = &quot;&quot;&quot;\
+BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+
+
+class AnyUser(object):
+    def __getitem__(self, index):
+        return _AnyRecord(index)
+
+
+
+class _AnyRecord(object):
+    def __init__(self, index):
+        self.uid = u&quot;user%02d&quot; % (index,)
+        self.password = u&quot;user%02d&quot; % (index,)
+        self.commonName = u&quot;User %02d&quot; % (index,)
+        self.email = u&quot;user%02d@example.com&quot; % (index,)
+        self.guid = u&quot;user%02d&quot; % (index,)
+
+
+
+class Deterministic(object):
+    def __init__(self, value=None):
+        self.value = value
+
+
+    def gauss(self, mean, stddev):
+        &quot;&quot;&quot;
+        Pretend to return a value from a gaussian distribution with mu
+        parameter C{mean} and sigma parameter C{stddev}.  But actually
+        always return C{mean + 1}.
+        &quot;&quot;&quot;
+        return mean + 1
+
+
+    def choice(self, sequence):
+        return sequence[0]
+
+
+    def sample(self):
+        return self.value
+
+
+
+class StubClient(BaseClient):
+    &quot;&quot;&quot;
+    Stand in for an iCalendar client.
+
+    @ivar rescheduled: A set of event URLs which will not allow
+        attendee changes due to a changed schedule tag.
+    @ivar _pendingFailures: dict mapping URLs to failure objects
+    &quot;&quot;&quot;
+    def __init__(self, number, serializePath):
+        self.serializePath = serializePath
+        os.mkdir(self.serializePath)
+        self.title = &quot;StubClient&quot;
+        self._events = {}
+        self._calendars = {}
+        self._pendingFailures = {}
+        self.record = _DirectoryRecord(
+            &quot;user%02d&quot; % (number,), &quot;user%02d&quot; % (number,),
+            &quot;User %02d&quot; % (number,), &quot;user%02d@example.org&quot; % (number,),
+            &quot;user%02d&quot; % (number,))
+        self.email = &quot;mailto:user%02d@example.com&quot; % (number,)
+        self.uuid = &quot;urn:uuid:user%02d&quot; % (number,)
+        self.rescheduled = set()
+        self.started = True
+
+
+    def _failDeleteWithObject(self, href, failureObject):
+        &quot;&quot;&quot;
+        Accessor for inserting intentional failures for deletes.
+        &quot;&quot;&quot;
+        self._pendingFailures[href] = failureObject
+
+
+    def serializeLocation(self):
+        &quot;&quot;&quot;
+        Return the path to the directory where data for this user is serialized.
+        &quot;&quot;&quot;
+        if self.serializePath is None or not os.path.isdir(self.serializePath):
+            return None
+
+        key = &quot;%s-%s&quot; % (self.record.uid, &quot;StubClient&quot;)
+        path = os.path.join(self.serializePath, key)
+        if not os.path.exists(path):
+            os.mkdir(path)
+        elif not os.path.isdir(path):
+            return None
+
+        return path
+
+
+    def addEvent(self, href, vevent):
+        self._events[href] = Event(self.serializePath, href, None, vevent)
+        return succeed(None)
+
+
+    def addInvite(self, href, vevent):
+        return self.addEvent(href, vevent)
+
+
+    def deleteEvent(self, href,):
+        del self._events[href]
+        calendar, uid = href.rsplit('/', 1)
+        del self._calendars[calendar + '/'].events[uid]
+        if href in self._pendingFailures:
+            failureObject = self._pendingFailures.pop(href)
+            return fail(failureObject)
+        else:
+            return succeed(None)
+
+
+    def updateEvent(self, href):
+        self.rescheduled.remove(href)
+        return succeed(None)
+
+
+    def addEventAttendee(self, href, attendee):
+        vevent = self._events[href].component
+        vevent.mainComponent().addProperty(attendee)
+        self._events[href].component = vevent
+
+
+    def changeEventAttendee(self, href, old, new):
+        if href in self.rescheduled:
+            return fail(IncorrectResponseCode(
+                NO_CONTENT,
+                Response(
+                    ('HTTP', 1, 1), PRECONDITION_FAILED,
+                    'Precondition Failed', None, None))
+            )
+
+        vevent = self._events[href].component
+        vevent.mainComponent().removeProperty(old)
+        vevent.mainComponent().addProperty(new)
+        self._events[href].component = vevent
+        return succeed(None)
+
+
+    def _makeSelfAttendee(self):
+        attendee = Property(
+            name=u'ATTENDEE',
+            value=self.email,
+            params={
+                'CN': self.record.commonName,
+                'CUTYPE': 'INDIVIDUAL',
+                'PARTSTAT': 'ACCEPTED',
+            },
+        )
+        return attendee
+
+
+    def _makeSelfOrganizer(self):
+        organizer = Property(
+            name=u'ORGANIZER',
+            value=self.email,
+            params={
+                'CN': self.record.commonName,
+            },
+        )
+        return organizer
+
+
+
+class SequentialDistribution(object):
+    def __init__(self, values):
+        self.values = values
+
+
+    def sample(self):
+        return self.values.pop(0)
+
+
+
+class InviterTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for loadtest.profiles.Inviter.
+    &quot;&quot;&quot;
+    def setUp(self):
+        self.sim = CalendarClientSimulator(
+            AnyUser(), Populator(None), None, None, None, None, None)
+
+
+    def _simpleAccount(self, userNumber, eventText):
+        client = StubClient(userNumber, self.mktemp())
+
+        vevent = Component.fromString(eventText)
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None)
+        client._calendars.update({calendar.url: calendar})
+
+        event = Event(client.serializeLocation(), calendar.url + u'1234.ics', None, vevent)
+
+        client._events.update({event.url: event})
+        calendar.events = {u'1234.ics': event}
+
+        return vevent, event, calendar, client
+
+
+    def test_enabled(self):
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+
+        inviter = Inviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
+        self.assertEqual(inviter.enabled, False)
+
+        inviter = Inviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: True})
+        self.assertEqual(inviter.enabled, True)
+
+
+    def test_doNotAddAttendeeToInbox(self):
+        &quot;&quot;&quot;
+        When the only calendar with any events is a schedule inbox, no
+        attempt is made to add attendees to an event on that calendar.
+        &quot;&quot;&quot;
+        userNumber = 10
+        vevent, _ignore_event, calendar, client = self._simpleAccount(
+            userNumber, SIMPLE_EVENT)
+        calendar.resourceType = caldavxml.schedule_inbox
+        inviter = Inviter(None, self.sim, client, userNumber)
+        inviter._invite()
+        self.assertFalse(vevent.mainComponent().hasProperty('ATTENDEE'))
+
+
+    def test_doNotAddAttendeeToNoCalendars(self):
+        &quot;&quot;&quot;
+        When there are no calendars and no events at all, the inviter
+        does nothing.
+        &quot;&quot;&quot;
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+        inviter = Inviter(None, self.sim, client, userNumber)
+        inviter._invite()
+        self.assertEquals(client._events, {})
+        self.assertEquals(client._calendars, {})
+
+
+    def test_doNotAddAttendeeToUninitializedEvent(self):
+        &quot;&quot;&quot;
+        When there is an L{Event} on a calendar but the details of the
+        event have not yet been retrieved, no attempt is made to add
+        invitees to that event.
+        &quot;&quot;&quot;
+        userNumber = 19
+        _ignore_vevent, event, calendar, client = self._simpleAccount(
+            userNumber, SIMPLE_EVENT)
+        event.component = event.etag = event.scheduleTag = None
+        inviter = Inviter(None, self.sim, client, userNumber)
+        inviter._invite()
+        self.assertEquals(client._events, {event.url: event})
+        self.assertEquals(client._calendars, {calendar.url: calendar})
+
+
+    def test_addAttendeeToEvent(self):
+        &quot;&quot;&quot;
+        When there is a normal calendar with an event, inviter adds an
+        attendee to it.
+        &quot;&quot;&quot;
+        userNumber = 16
+        _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount(
+            userNumber, SIMPLE_EVENT)
+        inviter = Inviter(Clock(), self.sim, client, userNumber)
+        inviter.setParameters(inviteeDistribution=Deterministic(1))
+        inviter._invite()
+        attendees = tuple(event.component.mainComponent().properties('ATTENDEE'))
+        self.assertEquals(len(attendees), 1)
+        for paramname, paramvalue in {
+            'CN': 'User %d' % (userNumber + 1,),
+            'CUTYPE': 'INDIVIDUAL',
+            'PARTSTAT': 'NEEDS-ACTION',
+            'ROLE': 'REQ-PARTICIPANT',
+            'RSVP': 'TRUE'
+        }.items():
+            self.assertTrue(attendees[0].hasParameter(paramname))
+            self.assertEqual(attendees[0].parameterValue(paramname), paramvalue)
+
+
+    def test_doNotAddSelfToEvent(self):
+        &quot;&quot;&quot;
+        If the inviter randomly selects its own user to be added to
+        the attendee list, a different user is added instead.
+        &quot;&quot;&quot;
+        selfNumber = 12
+        _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount(
+            selfNumber, SIMPLE_EVENT)
+
+        otherNumber = 20
+        values = [selfNumber - selfNumber, otherNumber - selfNumber]
+
+        inviter = Inviter(Clock(), self.sim, client, selfNumber)
+        inviter.setParameters(inviteeDistribution=SequentialDistribution(values))
+        inviter._invite()
+        attendees = tuple(event.component.mainComponent().properties('ATTENDEE'))
+        self.assertEquals(len(attendees), 1)
+        for paramname, paramvalue in {
+            'CN': 'User %d' % (otherNumber,),
+            'CUTYPE': 'INDIVIDUAL',
+            'PARTSTAT': 'NEEDS-ACTION',
+            'ROLE': 'REQ-PARTICIPANT',
+            'RSVP': 'TRUE'
+        }.items():
+            self.assertTrue(attendees[0].hasParameter(paramname))
+            self.assertEqual(attendees[0].parameterValue(paramname), paramvalue)
+
+
+    def test_doNotAddExistingToEvent(self):
+        &quot;&quot;&quot;
+        If the inviter randomly selects a user which is already an
+        invitee on the event, a different user is added instead.
+        &quot;&quot;&quot;
+        selfNumber = 1
+        _ignore_vevent, event, _ignore_calendar, client = self._simpleAccount(
+            selfNumber, INVITED_EVENT)
+
+        invitee = tuple(event.component.mainComponent().properties('ATTENDEE'))[0]
+        inviteeNumber = int(invitee.parameterValue('CN').split()[1])
+        anotherNumber = inviteeNumber + 5
+        values = [inviteeNumber - selfNumber, anotherNumber - selfNumber]
+
+        inviter = Inviter(Clock(), self.sim, client, selfNumber)
+        inviter.setParameters(inviteeDistribution=SequentialDistribution(values))
+        inviter._invite()
+        attendees = tuple(event.component.mainComponent().properties('ATTENDEE'))
+        self.assertEquals(len(attendees), 3)
+        for paramname, paramvalue in {
+            'CN': 'User %02d' % (anotherNumber,),
+            'CUTYPE': 'INDIVIDUAL',
+            'PARTSTAT': 'NEEDS-ACTION',
+            'ROLE': 'REQ-PARTICIPANT',
+            'RSVP': 'TRUE'
+        }.items():
+            self.assertTrue(attendees[2].hasParameter(paramname))
+            self.assertEqual(attendees[2].parameterValue(paramname), paramvalue)
+
+
+    def test_everybodyInvitedAlready(self):
+        &quot;&quot;&quot;
+        If the first so-many randomly selected users we come across
+        are already attendees on the event, the invitation attempt is
+        abandoned.
+        &quot;&quot;&quot;
+        selfNumber = 1
+        vevent, _ignore_event, _ignore_calendar, client = self._simpleAccount(
+            selfNumber, INVITED_EVENT)
+        inviter = Inviter(Clock(), self.sim, client, selfNumber)
+        # Always return a user number which has already been invited.
+        inviter.setParameters(inviteeDistribution=Deterministic(2 - selfNumber))
+        inviter._invite()
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        self.assertEquals(len(attendees), 2)
+
+
+    def test_doNotInviteToSomeoneElsesEvent(self):
+        &quot;&quot;&quot;
+        If there are events on our calendar which are being organized
+        by someone else, the inviter does not attempt to invite new
+        users to them.
+        &quot;&quot;&quot;
+        selfNumber = 2
+        vevent, _ignore_event, _ignore_calendar, client = self._simpleAccount(
+            selfNumber, INVITED_EVENT)
+        inviter = Inviter(None, self.sim, client, selfNumber)
+        # Try to send an invitation, but with only one event on the
+        # calendar, of which we are not the organizer.  It should be
+        # unchanged afterwards.
+        inviter._invite()
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        self.assertEqual(len(attendees), 2)
+        self.assertEqual(attendees[0].parameterValue('CN'), 'User 01')
+        self.assertEqual(attendees[1].parameterValue('CN'), 'User 02')
+
+
+
+class RealisticInviterTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for loadtest.profiles.RealisticInviter.
+    &quot;&quot;&quot;
+    def setUp(self):
+        self.sim = CalendarClientSimulator(
+            AnyUser(), Populator(None), None, None, None, None, None)
+
+
+    def _simpleAccount(self, userNumber, eventText):
+        client = StubClient(userNumber, self.mktemp())
+        vevent = Component.fromString(eventText)
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', u'/cal/', None)
+        event = Event(client.serializeLocation(), calendar.url + u'1234.ics', None, vevent)
+        calendar.events = {u'1234.ics': event}
+        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, self.mktemp())
+
+        inviter = RealisticInviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
+        self.assertEqual(inviter.enabled, False)
+
+        inviter = RealisticInviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: True})
+        self.assertEqual(inviter.enabled, True)
+
+
+    def test_doNotAddInviteToInbox(self):
+        &quot;&quot;&quot;
+        When the only calendar with any events is a schedule inbox, no
+        attempt is made to add attendees to that calendar.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None)
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars.update({calendar.url: calendar})
+
+        inviter = RealisticInviter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
+        inviter._invite()
+
+        self.assertEquals(client._events, {})
+
+
+    def test_doNotAddInviteToNoCalendars(self):
+        &quot;&quot;&quot;
+        When there are no calendars and no events at all, the inviter
+        does nothing.
+        &quot;&quot;&quot;
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+        inviter = RealisticInviter(None, self.sim, client, userNumber)
+        inviter._invite()
+        self.assertEquals(client._events, {})
+        self.assertEquals(client._calendars, {})
+
+
+    def test_addInvite(self):
+        &quot;&quot;&quot;
+        When there is a normal calendar, inviter adds an invite to it.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        userNumber = 16
+        serializePath = self.mktemp()
+        os.mkdir(serializePath)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars.update({calendar.url: calendar})
+        inviter = RealisticInviter(Clock(), self.sim, client, userNumber)
+        inviter.setParameters(
+            inviteeDistribution=Deterministic(1),
+            inviteeCountDistribution=Deterministic(1)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 1)
+        attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE'))
+        expected = set((&quot;mailto:user%02d@example.com&quot; % (userNumber,), &quot;mailto:user%02d@example.com&quot; % (userNumber + 1,),))
+        for attendee in attendees:
+            expected.remove(attendee.value())
+        self.assertEqual(len(expected), 0)
+
+
+    def test_doNotAddSelfToEvent(self):
+        &quot;&quot;&quot;
+        If the inviter randomly selects its own user to be added to
+        the attendee list, a different user is added instead.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        selfNumber = 12
+        client = StubClient(selfNumber, self.mktemp())
+        client._calendars.update({calendar.url: calendar})
+
+        otherNumber = 20
+        values = [selfNumber - selfNumber, otherNumber - selfNumber]
+
+        inviter = RealisticInviter(Clock(), self.sim, client, selfNumber)
+        inviter.setParameters(
+            inviteeDistribution=SequentialDistribution(values),
+            inviteeCountDistribution=Deterministic(1)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 1)
+        attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE'))
+        expected = set((&quot;mailto:user%02d@example.com&quot; % (selfNumber,), &quot;mailto:user%02d@example.com&quot; % (otherNumber,),))
+        for attendee in attendees:
+            expected.remove(attendee.value())
+        self.assertEqual(len(expected), 0)
+
+
+    def test_doNotAddExistingToEvent(self):
+        &quot;&quot;&quot;
+        If the inviter randomly selects a user which is already an
+        invitee on the event, a different user is added instead.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        selfNumber = 1
+        client = StubClient(selfNumber, self.mktemp())
+        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(
+            inviteeDistribution=SequentialDistribution(values),
+            inviteeCountDistribution=Deterministic(2)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 1)
+        attendees = tuple(client._events.values()[0].component.mainComponent().properties('ATTENDEE'))
+        expected = set((
+            &quot;mailto:user%02d@example.com&quot; % (selfNumber,),
+            &quot;mailto:user%02d@example.com&quot; % (inviteeNumber,),
+            &quot;mailto:user%02d@example.com&quot; % (anotherNumber,),
+        ))
+        for attendee in attendees:
+            expected.remove(attendee.value())
+        self.assertEqual(len(expected), 0)
+
+
+    def test_everybodyInvitedAlready(self):
+        &quot;&quot;&quot;
+        If the first so-many randomly selected users we come across
+        are already attendees on the event, the invitation attempt is
+        abandoned.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        userNumber = 1
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars.update({calendar.url: calendar})
+        inviter = RealisticInviter(Clock(), self.sim, client, userNumber)
+        inviter.setParameters(
+            inviteeDistribution=Deterministic(1),
+            inviteeCountDistribution=Deterministic(2)
+        )
+        inviter._invite()
+        self.assertEquals(len(client._events), 0)
+
+
+
+class AccepterTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for loadtest.profiles.Accepter.
+    &quot;&quot;&quot;
+    def setUp(self):
+        self.sim = CalendarClientSimulator(
+            AnyUser(), Populator(None), None, None, None, None, None)
+
+
+    def test_enabled(self):
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+
+        accepter = Accepter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: False})
+        self.assertEqual(accepter.enabled, False)
+
+        accepter = Accepter(None, self.sim, client, userNumber, **{&quot;enabled&quot;: True})
+        self.assertEqual(accepter.enabled, True)
+
+
+    def test_ignoreEventOnUnknownCalendar(self):
+        &quot;&quot;&quot;
+        If an event on an unknown calendar changes, it is ignored.
+        &quot;&quot;&quot;
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+        accepter = Accepter(None, self.sim, client, userNumber)
+        accepter.eventChanged('/some/calendar/1234.ics')
+
+
+    def test_ignoreNonCalendar(self):
+        &quot;&quot;&quot;
+        If an event is on a calendar which is not of type
+        {CALDAV:}calendar, it is ignored.
+        &quot;&quot;&quot;
+        userNumber = 14
+        calendarURL = '/some/calendar/'
+        calendar = Calendar(
+            csxml.dropbox_home, set(), u'notification', calendarURL, None)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars[calendarURL] = calendar
+        accepter = Accepter(None, self.sim, client, userNumber)
+        accepter.eventChanged(calendarURL + '1234.ics')
+
+
+    def test_ignoreAccepted(self):
+        &quot;&quot;&quot;
+        If the client is an attendee on an event but the PARTSTAT is
+        not NEEDS-ACTION, the event is ignored.
+        &quot;&quot;&quot;
+        vevent = Component.fromString(ACCEPTED_EVENT)
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
+        calendarURL = '/some/calendar/'
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars[calendarURL] = calendar
+        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
+        client._events[event.url] = event
+        accepter = Accepter(None, self.sim, client, userNumber)
+        accepter.eventChanged(event.url)
+
+
+    def test_ignoreAlreadyAccepting(self):
+        &quot;&quot;&quot;
+        If the client sees an event change a second time before
+        responding to an invitation found on it during the first
+        change notification, the second change notification does not
+        generate another accept attempt.
+        &quot;&quot;&quot;
+        clock = Clock()
+        randomDelay = 7
+        vevent = Component.fromString(INVITED_EVENT)
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
+        calendarURL = '/some/calendar/'
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars[calendarURL] = calendar
+        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
+        client._events[event.url] = event
+        accepter = Accepter(clock, self.sim, client, userNumber)
+        accepter.random = Deterministic()
+
+        def _gauss(mu, sigma):
+            return randomDelay
+        accepter.random.gauss = _gauss
+        accepter.eventChanged(event.url)
+        accepter.eventChanged(event.url)
+        clock.advance(randomDelay)
+
+
+    def test_inboxReply(self):
+        &quot;&quot;&quot;
+        When an inbox item that contains a reply is seen by the client, it
+        deletes it immediately.
+        &quot;&quot;&quot;
+        userNumber = 1
+        clock = Clock()
+        inboxURL = '/some/inbox/'
+        vevent = Component.fromString(INBOX_REPLY)
+        inbox = Calendar(
+            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars[inboxURL] = inbox
+
+        inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent)
+        client._setEvent(inboxEvent.url, inboxEvent)
+        accepter = Accepter(clock, self.sim, client, userNumber)
+        accepter.eventChanged(inboxEvent.url)
+        clock.advance(3)
+        self.assertNotIn(inboxEvent.url, client._events)
+        self.assertNotIn('4321.ics', inbox.events)
+
+
+    def test_inboxReplyFailedDelete(self):
+        &quot;&quot;&quot;
+        When an inbox item that contains a reply is seen by the client, it
+        deletes it immediately.  If the delete fails, the appropriate response
+        code is returned.
+        &quot;&quot;&quot;
+        userNumber = 1
+        clock = Clock()
+        inboxURL = '/some/inbox/'
+        vevent = Component.fromString(INBOX_REPLY)
+        inbox = Calendar(
+            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars[inboxURL] = inbox
+
+        inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent)
+        client._setEvent(inboxEvent.url, inboxEvent)
+        client._failDeleteWithObject(inboxEvent.url, IncorrectResponseCode(
+            NO_CONTENT,
+            Response(
+                ('HTTP', 1, 1), PRECONDITION_FAILED,
+                'Precondition Failed', None, None))
+        )
+        accepter = Accepter(clock, self.sim, client, userNumber)
+        accepter.eventChanged(inboxEvent.url)
+        clock.advance(3)
+        self.assertNotIn(inboxEvent.url, client._events)
+        self.assertNotIn('4321.ics', inbox.events)
+
+
+    def test_acceptInvitation(self):
+        &quot;&quot;&quot;
+        If the client is an attendee on an event and the PARTSTAT is
+        NEEDS-ACTION, a response is generated which accepts the
+        invitation and the corresponding event in the
+        I{schedule-inbox} is deleted.
+        &quot;&quot;&quot;
+        clock = Clock()
+        randomDelay = 7
+        vevent = Component.fromString(INVITED_EVENT)
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
+        client = StubClient(userNumber, self.mktemp())
+
+        calendarURL = '/some/calendar/'
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
+        client._calendars[calendarURL] = calendar
+
+        inboxURL = '/some/inbox/'
+        inbox = Calendar(
+            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
+        client._calendars[inboxURL] = inbox
+
+        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
+        client._setEvent(event.url, event)
+
+        inboxEvent = Event(client.serializeLocation(), inboxURL + u'4321.ics', None, vevent)
+        client._setEvent(inboxEvent.url, inboxEvent)
+
+        accepter = Accepter(clock, self.sim, client, userNumber)
+        accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay))
+        accepter.eventChanged(event.url)
+        clock.advance(randomDelay)
+
+        vevent = client._events[event.url].component
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        self.assertEquals(len(attendees), 2)
+        self.assertEquals(
+            attendees[1].parameterValue('CN'), 'User %02d' % (userNumber,))
+        self.assertEquals(
+            attendees[1].parameterValue('PARTSTAT'), 'ACCEPTED')
+        self.assertFalse(attendees[1].hasParameter('RSVP'))
+
+        self.assertNotIn(inboxEvent.url, client._events)
+        self.assertNotIn('4321.ics', inbox.events)
+
+
+    def test_reacceptInvitation(self):
+        &quot;&quot;&quot;
+        If a client accepts an invitation on an event and then is
+        later re-invited to the same event, the invitation is again
+        accepted.
+        &quot;&quot;&quot;
+        clock = Clock()
+        randomDelay = 7
+        vevent = Component.fromString(INVITED_EVENT)
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        userNumber = int(attendees[1].parameterValue('CN').split(None, 1)[1])
+        calendarURL = '/some/calendar/'
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
+        client = StubClient(userNumber, self.mktemp())
+        client._calendars[calendarURL] = calendar
+        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
+        client._events[event.url] = event
+        accepter = Accepter(clock, self.sim, client, userNumber)
+        accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay))
+        accepter.eventChanged(event.url)
+        clock.advance(randomDelay)
+
+        # Now re-set the event so it has to be accepted again
+        event.component = Component.fromString(INVITED_EVENT)
+
+        # And now re-deliver it
+        accepter.eventChanged(event.url)
+        clock.advance(randomDelay)
+
+        # And ensure that it was accepted again
+        vevent = client._events[event.url].component
+        attendees = tuple(vevent.mainComponent().properties('ATTENDEE'))
+        self.assertEquals(len(attendees), 2)
+        self.assertEquals(
+            attendees[1].parameterValue('CN'), 'User %02d' % (userNumber,))
+        self.assertEquals(
+            attendees[1].parameterValue('PARTSTAT'), 'ACCEPTED')
+        self.assertFalse(attendees[1].hasParameter('RSVP'))
+
+
+    def test_changeEventAttendeePreconditionFailed(self):
+        &quot;&quot;&quot;
+        If the attempt to accept an invitation fails because of an
+        unmet precondition (412), the event is re-retrieved and the
+        PUT is re-issued with the new data.
+        &quot;&quot;&quot;
+        clock = Clock()
+        userNumber = 2
+        client = StubClient(userNumber, self.mktemp())
+        randomDelay = 3
+
+        calendarURL = '/some/calendar/'
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'calendar', calendarURL, None)
+        client._calendars[calendarURL] = calendar
+
+        vevent = Component.fromString(INVITED_EVENT)
+        event = Event(client.serializeLocation(), calendarURL + u'1234.ics', None, vevent)
+        client._setEvent(event.url, event)
+
+        accepter = Accepter(clock, self.sim, client, userNumber)
+        accepter.setParameters(acceptDelayDistribution=Deterministic(randomDelay))
+
+        client.rescheduled.add(event.url)
+
+        accepter.eventChanged(event.url)
+        clock.advance(randomDelay)
+
+
+
+class EventerTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for loadtest.profiles.Eventer, a profile which adds new
+    events on calendars.
+    &quot;&quot;&quot;
+    def setUp(self):
+        self.sim = CalendarClientSimulator(
+            AnyUser(), Populator(None), None, None, None, None, None)
+
+
+    def test_enabled(self):
+        userNumber = 13
+        client = StubClient(userNumber, self.mktemp())
+
+        eventer = Eventer(None, self.sim, client, None, **{&quot;enabled&quot;: False})
+        self.assertEqual(eventer.enabled, False)
+
+        eventer = Eventer(None, self.sim, client, None, **{&quot;enabled&quot;: True})
+        self.assertEqual(eventer.enabled, True)
+
+
+    def test_doNotAddEventOnInbox(self):
+        &quot;&quot;&quot;
+        When the only calendar is a schedule inbox, no attempt is made
+        to add events on it.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.schedule_inbox, set(), u'inbox', u'/sched/inbox', None)
+        client = StubClient(21, self.mktemp())
+        client._calendars.update({calendar.url: calendar})
+
+        eventer = Eventer(None, self.sim, client, None)
+        eventer._addEvent()
+
+        self.assertEquals(client._events, {})
+
+
+    def test_addEvent(self):
+        &quot;&quot;&quot;
+        When there is a normal calendar to add events to,
+        L{Eventer._addEvent} adds an event to it.
+        &quot;&quot;&quot;
+        calendar = Calendar(
+            caldavxml.calendar, set(('VEVENT',)), u'personal stuff', u'/cals/personal', None)
+        client = StubClient(31, self.mktemp())
+        client._calendars.update({calendar.url: calendar})
+
+        eventer = Eventer(Clock(), self.sim, client, None)
+        eventer._addEvent()
+
+        self.assertEquals(len(client._events), 1)
+
+        # XXX Vary the event period/interval and the uid
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_pubsubpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_pubsub.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_pubsub.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_pubsub.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,86 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+#
+##
+&quot;&quot;&quot;
+Tests for loadtest.pubsub
+&quot;&quot;&quot;
+from collections import defaultdict
+
+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.pubsub import Publisher
+
+
+class PubSubTests(TestCase):
+    &quot;&quot;&quot;Tests for Publisher&quot;&quot;&quot;
+    def setUp(self):
+        self.publisher = Publisher()
+        # Maps IDs of subscribers to values issued
+        self.values = defaultdict(list)
+
+
+    def recordValueWithID(self, id):
+        &quot;&quot;&quot;
+        Return a one-argument callable for use as a subscriber
+        that appends its argument to the list of values associated with id
+        &quot;&quot;&quot;
+        def recordValue(value):
+            self.values[id].append(value)
+        return recordValue
+
+
+    def test_noSubscribers(self):
+        &quot;&quot;&quot;When no subscriptions are present, no value is published&quot;&quot;&quot;
+        value = 'foobar'
+        self.publisher.issue(value)
+        self.assertEqual(self.values['sub1'], [])
+
+
+    def test_subscribe(self):
+        &quot;&quot;&quot;Published values propagate to their subscribers&quot;&quot;&quot;
+        value = 'foobar'
+        self.publisher.subscribe(self.recordValueWithID('sub1'))
+        self.publisher.issue(value)
+        self.assertEqual(self.values['sub1'], [value])
+
+
+    def test_cancel(self):
+        &quot;&quot;&quot;Cancelled subscriptions do not receive new publications&quot;&quot;&quot;
+        value = 'foobar'
+        subscr = self.publisher.subscribe(self.recordValueWithID('sub1'))
+        subscr.cancel()
+        self.publisher.issue(value)
+        self.assertEqual(self.values['sub1'], [])
+
+
+    def test_multiple_subscriptions(self):
+        &quot;&quot;&quot;Publisher supports adding and removing multiple subscriptions&quot;&quot;&quot;
+        value = 'foobar'
+        subscr1 = self.publisher.subscribe(self.recordValueWithID('sub1'))
+        subscr2 = self.publisher.subscribe(self.recordValueWithID('sub2'))
+        self.publisher.issue(value)
+        self.assertEqual(self.values['sub1'], [value])
+        self.assertEqual(self.values['sub2'], [value])
+
+        subscr2.cancel()
+        self.publisher.issue(value)
+        self.assertEqual(self.values['sub1'], [value, value])
+        self.assertEqual(self.values['sub2'], [value])
+
+        subscr1.cancel()
+        self.publisher.issue(value)
+        self.assertEqual(self.values['sub1'], [value, value])
+        self.assertEqual(self.values['sub2'], [value])
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_pushpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_push.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_push.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_push.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,114 @@
</span><ins>+from twisted.trial.unittest import TestCase
+from twisted.internet.defer import inlineCallbacks
+
+from contrib.performance.loadtest.push import PushMonitor
+
+class PushMonitorTests(TestCase):
+    def sendFakePush(self, pushkey):
+        self.monitor._receivedAMPPush(inboundID=pushkey, dataChangedTimestamp=None, priority=None)
+
+    def receivedPush(self, calendar_href):
+        self.history.append(calendar_href)
+
+    def setUp(self):
+        &quot;&quot;&quot;
+        Creates and begins a PushMonitor with a history-tracking callback
+        &quot;&quot;&quot;
+
+        self.monitor = PushMonitor(
+            None,
+            'localhost',
+            62311,
+            self.receivedPush
+        )
+        self.history = []
+        return self.monitor.begin()
+
+    def test_noPushkey(self):
+        &quot;&quot;&quot;
+        Monitor will not react to a push if there are no registered pushkeys
+        &quot;&quot;&quot;
+        pushkey = '/CalDAV/abc/def/'
+        calendar_home = '/foo/bar/'
+        self.assertFalse(self.monitor.isSubscribedTo(calendar_home))
+        self.sendFakePush(pushkey)
+        self.assertEqual(self.history, [])
+
+    @inlineCallbacks
+    def test_addPushkey(self):
+        &quot;&quot;&quot;
+        Adding a pushkey triggers a notification for the corresponding calendar home
+        &quot;&quot;&quot;
+        pushkey = '/CalDAV/abc/def/'
+        calendar_home = '/foo/bar/'
+        yield self.monitor.addPushkey(pushkey, calendar_home)
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home))
+        self.sendFakePush(pushkey)
+        self.assertEqual(self.history, [calendar_home])
+
+    @inlineCallbacks
+    def test_removePushkey(self):
+        &quot;&quot;&quot;
+        Pushkeys can be unregistered
+        &quot;&quot;&quot;
+        pushkey = '/CalDAV/abc/def/'
+        calendar_home = '/foo/bar/'
+        yield self.monitor.addPushkey(pushkey, calendar_home)
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home))
+        yield self.monitor.removePushkey(pushkey)
+        self.assertFalse(self.monitor.isSubscribedTo(calendar_home))
+
+        self.sendFakePush(pushkey)
+        self.assertEqual(self.history, [])
+
+    @inlineCallbacks
+    def test_addDuplicatePushkeys(self):
+        &quot;&quot;&quot;
+        Adding the same pushkey twice only registers it once
+        &quot;&quot;&quot;
+        pushkey = '/CalDAV/abc/def/'
+        calendar_home = '/foo/bar/'
+        yield self.monitor.addPushkey(pushkey, calendar_home)
+        yield self.monitor.addPushkey(pushkey, calendar_home)
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home))
+        self.sendFakePush(pushkey)
+        self.assertEqual(self.history, [calendar_home])
+
+    @inlineCallbacks
+    def test_addOverridePushkeys(self):
+        &quot;&quot;&quot;
+        Adding the same pushkey with a different calendar home
+        unregisters the original
+        &quot;&quot;&quot;
+        pushkey = '/CalDAV/abc/def/'
+        calendar_home = '/foo/bar/'
+        calendar_home_2 = '/foo/baz/'
+        yield self.monitor.addPushkey(pushkey, calendar_home)
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home))
+        yield self.monitor.addPushkey(pushkey, calendar_home_2)
+        self.assertFalse(self.monitor.isSubscribedTo(calendar_home))
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home_2))
+
+        self.sendFakePush(pushkey)
+        self.assertEqual(self.history, [calendar_home_2])
+
+    @inlineCallbacks
+    def test_multiplePushkeys(self):
+        &quot;&quot;&quot;
+        Monitor supports registering multiple pushkeys
+        &quot;&quot;&quot;
+        pushkey = '/CalDAV/abc/def/'
+        pushkey_2 = '/CalDAV/abc/xyz/'
+        calendar_home = '/foo/bar/'
+        calendar_home_2 = '/foo/baz/'
+        yield self.monitor.addPushkey(pushkey, calendar_home)
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home))
+        yield self.monitor.addPushkey(pushkey_2, calendar_home_2)
+        self.assertTrue(self.monitor.isSubscribedTo(calendar_home_2))
+        self.sendFakePush(pushkey_2)
+        self.assertEqual(self.history, [calendar_home_2])
+        self.sendFakePush(pushkey)
+        self.assertEqual(self.history, [calendar_home_2, calendar_home])
+
+    def tearDown(self):
+        return self.monitor.end()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_recordspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_records.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_records.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_records.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,133 @@
</span><ins>+##
+# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+##
+&quot;&quot;&quot;
+Tests for loadtest.records
+&quot;&quot;&quot;
+
+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.records import DirectoryRecord, recordsFromCSVFile
+
+class RecordTests(TestCase):
+    def test_loadAccountsFromFile(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.fromCommandLine} takes an account loader from the
+        config file and uses it to create user records for use in the
+        simulation.
+        &quot;&quot;&quot;
+        accounts = FilePath(self.mktemp())
+        accounts.setContent(&quot;foo,bar,baz,quux,goo\nfoo2,bar2,baz2,quux2,goo2\n&quot;)
+        config = VALID_CONFIG.copy()
+        config[&quot;accounts&quot;] = {
+            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.recordsFromCSVFile&quot;,
+            &quot;params&quot;: {
+                &quot;path&quot;: accounts.path
+            },
+        }
+        configpath = FilePath(self.mktemp())
+        configpath.setContent(writePlistToString(config))
+        io = StringIO()
+        sim = LoadSimulator.fromCommandLine(['--config', configpath.path], io)
+        self.assertEquals(io.getvalue(), &quot;Loaded 2 accounts.\n&quot;)
+        self.assertEqual(2, len(sim.records))
+        self.assertEqual(sim.records[0].uid, 'foo')
+        self.assertEqual(sim.records[0].password, 'bar')
+        self.assertEqual(sim.records[0].commonName, 'baz')
+        self.assertEqual(sim.records[0].email, 'quux')
+        self.assertEqual(sim.records[1].uid, 'foo2')
+        self.assertEqual(sim.records[1].password, 'bar2')
+        self.assertEqual(sim.records[1].commonName, 'baz2')
+        self.assertEqual(sim.records[1].email, 'quux2')
+
+
+    def test_loadDefaultAccountsFromFile(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.fromCommandLine} takes an account loader (with
+        empty path)from the config file and uses it to create user
+        records for use in the simulation.
+        &quot;&quot;&quot;
+        config = VALID_CONFIG.copy()
+        config[&quot;accounts&quot;] = {
+            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.recordsFromCSVFile&quot;,
+            &quot;params&quot;: {
+                &quot;path&quot;: &quot;&quot;
+            },
+        }
+        configpath = FilePath(self.mktemp())
+        configpath.setContent(writePlistToString(config))
+        sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
+                                            StringIO())
+        self.assertEqual(99, len(sim.records))
+        self.assertEqual(sim.records[0].uid, 'user01')
+        self.assertEqual(sim.records[0].password, 'user01')
+        self.assertEqual(sim.records[0].commonName, 'User 01')
+        self.assertEqual(sim.records[0].email, 'user01@example.com')
+        self.assertEqual(sim.records[98].uid, 'user99')
+        self.assertEqual(sim.records[98].password, 'user99')
+        self.assertEqual(sim.records[98].commonName, 'User 99')
+        self.assertEqual(sim.records[98].email, 'user99@example.com')
+
+
+ormance.loadtest.sim.generateRecords&quot;,
+            &quot;params&quot;: {
+                &quot;count&quot;: 2
+            },
+        }
+        configpath = FilePath(self.mktemp())
+        configpath.setContent(writePlistToString(config))
+        sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
+                                            StringIO())
+        self.assertEqual(2, len(sim.records))
+        self.assertEqual(sim.records[0].uid, 'user1')
+        self.assertEqual(sim.records[0].password, 'user1')
+        self.assertEqual(sim.records[0].commonName, 'User 1')
+        self.assertEqual(sim.records[0].email, 'user1@example.com')
+        self.assertEqual(sim.records[1].uid, 'user2')
+        self.assertEqual(sim.records[1].password, 'user2')
+        self.assertEqual(sim.records[1].commonName, 'User 2')
+        self.assertEqual(sim.records[1].email, 'user2@example.com')
+
+
+    def test_generateRecordsNonDefaultPatterns(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.fromCommandLine} takes an account loader from the
+        config file and uses it to generate user records for use in the
+        simulation.
+        &quot;&quot;&quot;
+        config = VALID_CONFIG.copy()
+        config[&quot;accounts&quot;] = {
+            &quot;loader&quot;: &quot;contrib.performance.loadtest.sim.generateRecords&quot;,
+            &quot;params&quot;: {
+                &quot;count&quot;: 3,
+                &quot;uidPattern&quot;: &quot;USER%03d&quot;,
+                &quot;passwordPattern&quot;: &quot;PASSWORD%03d&quot;,
+                &quot;namePattern&quot;: &quot;Test User %03d&quot;,
+                &quot;emailPattern&quot;: &quot;USER%03d@example2.com&quot;,
+            },
+        }
+        configpath = FilePath(self.mktemp())
+        configpath.setContent(writePlistToString(config))
+        sim = LoadSimulator.fromCommandLine(['--config', configpath.path],
+                                            StringIO())
+        self.assertEqual(3, len(sim.records))
+        self.assertEqual(sim.records[0].uid, 'USER001')
+        self.assertEqual(sim.records[0].password, 'PASSWORD001')
+        self.assertEqual(sim.records[0].commonName, 'Test User 001')
+        self.assertEqual(sim.records[0].email, 'USER001@example2.com')
+        self.assertEqual(sim.records[2].uid, 'USER003')
+        self.assertEqual(sim.records[2].password, 'PASSWORD003')
+        self.assertEqual(sim.records[2].commonName, 'Test User 003')
+        self.assertEqual(sim.records[2].email, 'USER003@example2.com')
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_requesterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_requester.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_requester.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_requester.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,48 @@
</span><ins>+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.requester import (
+    Requester, IncorrectResponseCode, WebClientContextFactory
+)
+
+class _FakeAgent(object):
+    def __init__(self):
+        self.requests = []
+        self.next = None
+
+    def request(self, method, uri, headers=None, bodyProducer=None):
+        self.requests.append((method, uri, headers, bodyProducer))
+
+    def setNextResponse(response):
+
+
+    # {'reactor': &lt;contrib.performance.loadtest.trafficlogger.(Logged Reactor) object at 0x10d1860d0&gt;, 'title': '10.11 Intern', 'self': &lt;contrib.performance.loadtest.requester.Requester object at 0x10d184a10&gt;, 'auth': {'digest': &lt;urllib2.HTTPDigestAuthHandler instance at 0x10d192128&gt;, 'basic': &lt;urllib2.HTTPBasicAuthHandler instance at 0x10d180fc8&gt;}, 'headers': {'Connection': ['keep-alive'], 'Accept-Language': ['en-us'], 'Accept-Encoding': ['gzip,deflate'], 'Accept': ['*/*'], 'User-Agent': ['Mac+OS+X/10.11 (15A216g) CalendarAgent/353']}, 'client_id': 'a11bce96-f787-4096-91a3-44f7dbf38a06', 'root': 'https://127.0.0.1:8443', 'uid': u'user01'}
+    
+
+class RequesterTests(TestCase):
+    def setUp(self):
+        self.root = '/foo/bar/'
+        self.headers = Headers({
+            'Connection': ['keep-alive'],
+            'Accept-Language': ['en-us'],
+            'Accept-Encoding': ['gzip,deflate'],
+            'Accept': ['*/*'],
+            'User-Agent': ['Mac+OS+X/10.11 (15A216g) CalendarAgent/353'
+        }
+        self.title = 'Requester '
+
+    self,
+        root,
+        headers,
+        title,
+        uid,
+        client_id,
+        auth,
+        reactor
+
+    
+
+class IncorrectResponseCodeTests(TestCase):
+    pass
+
+class WebClientContextFactoryTests(TestCase):
+    pass
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_resourcespy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_resources.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_resources.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_resources.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,39 @@
</span><ins>+##
+# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.resources import Event, Calendar
+class EventTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{Event}.
+    &quot;&quot;&quot;
+    def test_uid(self):
+        &quot;&quot;&quot;
+        When the C{vevent} attribute of an L{Event} instance is set,
+        L{Event.getUID} returns the UID value from it.
+        &quot;&quot;&quot;
+        event = Event(None, u'/foo/bar', u'etag', Component.fromString(EVENT))
+        self.assertEquals(event.getUID(), EVENT_UID)
+
+
+    def test_withoutUID(self):
+        &quot;&quot;&quot;
+        When an L{Event} has a C{vevent} attribute set to C{None},
+        L{Event.getUID} returns C{None}.
+        &quot;&quot;&quot;
+        event = Event(None, u'/bar/baz', u'etag')
+        self.assertIdentical(event.getUID(), None)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_simpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_sim.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_sim.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_sim.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,474 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 cStringIO import StringIO
+
+from twisted.python.log import msg
+from twisted.python.usage import UsageError
+from twisted.python.filepath import FilePath
+from twisted.internet.defer import Deferred, succeed
+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.distributions import NormalDistribution
+from contrib.performance.loadtest.ical import OS_X_10_6
+from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
+from contrib.performance.loadtest.population import (
+    SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
+    ProfileType
+)
+from contrib.performance.loadtest.logger import SimpleStatistics
+from contrib.performance.loadtest.sim import (
+    Arrival, SimOptions, LoadSimulator, LagTrackingReactor,
+    _DirectoryRecord
+)
+
+
+VALID_CONFIG = {
+    'server': 'tcp:127.0.0.1:8008',
+    'webadmin': {
+        'enabled': True,
+        'HTTPPort': 8080,
+    },
+    'arrival': {
+        'factory': 'contrib.performance.loadtest.population.SmoothRampUp',
+        'params': {
+            'groups': 10,
+            'groupSize': 1,
+            'interval': 3,
+        },
+    },
+}
+
+VALID_CONFIG_PLIST = writePlistToString(VALID_CONFIG)
+
+
+
+class SimOptionsTests(TestCase):
+    def test_defaultConfig(self):
+        &quot;&quot;&quot;
+        If the I{config} option is not specified, the default config.plist in
+        the source tree is used.
+        &quot;&quot;&quot;
+        options = SimOptions()
+        self.assertEqual(options['config'], FilePath(__file__).sibling('config.plist'))
+
+
+    def test_configFileNotFound(self):
+        &quot;&quot;&quot;
+        If the filename given to the I{config} option is not found,
+        L{SimOptions.parseOptions} raises a L{UsageError} indicating
+        this.
+        &quot;&quot;&quot;
+        name = FilePath(self.mktemp())
+        options = SimOptions()
+        exc = self.assertRaises(
+            UsageError, options.parseOptions, ['--config', name.path])
+        self.assertEquals(
+            str(exc), &quot;--config %s: No such file or directory&quot; % (name.path,))
+
+
+    def test_configFileNotParseable(self):
+        &quot;&quot;&quot;
+        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.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(&quot;some random junk&quot;)
+        options = SimOptions()
+        exc = self.assertRaises(
+            UsageError, options.parseOptions, ['--config', config.path])
+        self.assertEquals(
+            str(exc),
+            &quot;--config %s: syntax error: line 1, column 0&quot; % (config.path,))
+
+
+
+class CalendarClientSimulatorTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{CalendarClientSimulator} which adds running clients to
+    a simulation.
+    &quot;&quot;&quot;
+    realmName = 'stub'
+
+    def _user(self, name):
+        password = 'password-' + name
+        email = name + &quot;@example.com&quot;
+        record = _DirectoryRecord(name, password, name, email, name)
+        return record
+
+
+    def test_createUser(self):
+        &quot;&quot;&quot;
+        Subsequent calls to L{CalendarClientSimulator._createUser}
+        with different user numbers return user details from different
+        directory records.
+        &quot;&quot;&quot;
+        calsim = CalendarClientSimulator(
+            [self._user('alice'), self._user('bob'), self._user('carol')],
+            Populator(None), None, None, 'http://example.org:1234/', None, None)
+        users = sorted([
+            calsim._createUser(0)[0],
+            calsim._createUser(1)[0],
+            calsim._createUser(2)[0],
+        ])
+        self.assertEqual(['alice', 'bob', 'carol'], users)
+
+
+    def test_createUserAuthInfo(self):
+        &quot;&quot;&quot;
+        The auth handler returned by L{CalendarClientSimulator._createUser}
+        includes the password taken from user's directory record.
+        &quot;&quot;&quot;
+        calsim = CalendarClientSimulator(
+            [self._user('alice')],
+            Populator(None), None, None, 'http://example.org:1234/', None, None)
+        user, auth = calsim._createUser(0)
+        self.assertEqual(
+            auth['basic'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
+            'password-' + user)
+        self.assertEqual(
+            auth['digest'].passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
+            'password-' + user)
+
+
+    def test_stop(self):
+        &quot;&quot;&quot;
+        After L{CalendarClientSimulator.stop} is called, failed clients and
+        profiles are not logged.
+        &quot;&quot;&quot;
+        class BrokenClient(object):
+            def __init__(self, reactor, serverAddress, serializationPath, userInfo, auth, runResult):
+                self._runResult = runResult
+
+            def run(self):
+                return self._runResult
+
+            def stop(self):
+                return succeed(None)
+
+        class BrokenProfile(object):
+            def __init__(self, reactor, simulator, client, userNumber, runResult):
+                self._runResult = runResult
+                self.enabled = True
+
+            def initialize(self):
+                return succeed(None)
+
+            def run(self):
+                return self._runResult
+
+        clientRunResult = Deferred()
+        profileRunResult = Deferred()
+
+        params = PopulationParameters()
+        params.addClient(1, ClientType(
+            BrokenClient, {'runResult': clientRunResult},
+            [ProfileType(BrokenProfile, {'runResult': profileRunResult})])
+        )
+        sim = CalendarClientSimulator(
+            [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/', None, None)
+        sim.add(1, 1)
+        sim.stop()
+        clientRunResult.errback(RuntimeError(&quot;Some fictional client problem&quot;))
+        profileRunResult.errback(RuntimeError(&quot;Some fictional profile problem&quot;))
+
+        self.assertEqual([], self.flushLoggedErrors())
+
+
+
+class Reactor(object):
+    message = &quot;some event to be observed&quot;
+
+    def __init__(self):
+        self._triggers = []
+        self._whenRunning = []
+
+
+    def run(self):
+        for thunk in self._whenRunning:
+            thunk()
+        msg(thingo=self.message)
+        for _ignore_phase, event, thunk in self._triggers:
+            if event == 'shutdown':
+                thunk()
+
+
+    def callWhenRunning(self, thunk):
+        self._whenRunning.append(thunk)
+
+
+    def addSystemEventTrigger(self, phase, event, thunk):
+        self._triggers.append((phase, event, thunk))
+
+
+
+class Observer(object):
+    def __init__(self):
+        self.reported = False
+        self.events = []
+
+
+    def observe(self, event):
+        self.events.append(event)
+
+
+    def report(self, output):
+        self.reported = True
+
+
+    def failures(self):
+        return []
+
+
+
+class NullArrival(object):
+    def run(self, sim):
+        pass
+
+
+
+class StubSimulator(LoadSimulator):
+    def run(self):
+        return 3
+
+
+
+class LoadSimulatorTests(TestCase):
+    def test_main(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.main} raises L{SystemExit} with the result of
+        L{LoadSimulator.run}.
+        &quot;&quot;&quot;
+        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, None, None, None, None, None).run(),))
+
+
+    def test_createSimulator(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.createSimulator} creates a L{CalendarClientSimulator}
+        with its own reactor and host and port information from the
+        configuration file.
+        &quot;&quot;&quot;
+        server = 'http://127.0.0.7:1243/'
+        reactor = object()
+        sim = LoadSimulator(server, None, None, None, None, None, None, reactor=reactor)
+        calsim = sim.createSimulator()
+        self.assertIsInstance(calsim, CalendarClientSimulator)
+        self.assertIsInstance(calsim.reactor, LagTrackingReactor)
+        self.assertIdentical(calsim.reactor._reactor, reactor)
+        self.assertEquals(calsim.server, server)
+
+
+    def test_specifyRuntime(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.fromCommandLine} recognizes the I{--runtime} option to
+        specify a limit on how long the simulation will run.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(VALID_CONFIG_PLIST)
+        sim = LoadSimulator.fromCommandLine(['--config', config.path, '--runtime', '123'])
+        self.assertEqual(123, sim.runtime)
+
+
+    def test_loadServerConfig(self):
+        &quot;&quot;&quot;
+        The Calendar Server host and port are loaded from the [server]
+        section of the configuration file specified.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(
+            writePlistToString({&quot;server&quot;: &quot;https://127.0.0.3:8432/&quot;})
+        )
+        sim = LoadSimulator.fromCommandLine(['--config', config.path])
+        self.assertEquals(sim.server, &quot;https://127.0.0.3:8432/&quot;)
+
+
+    def test_loadArrivalConfig(self):
+        &quot;&quot;&quot;
+        The arrival policy type and arguments are loaded from the
+        [arrival] section of the configuration file specified.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(
+            writePlistToString({
+                &quot;arrival&quot;: {
+                    &quot;factory&quot;: &quot;contrib.performance.loadtest.population.SmoothRampUp&quot;,
+                    &quot;params&quot;: {
+                        &quot;groups&quot;: 10,
+                        &quot;groupSize&quot;: 1,
+                        &quot;interval&quot;: 3,
+                    },
+                },
+            })
+        )
+        sim = LoadSimulator.fromCommandLine(['--config', config.path])
+        self.assertEquals(
+            sim.arrival,
+            Arrival(SmoothRampUp, dict(groups=10, groupSize=1, interval=3)))
+
+
+    def test_createArrivalPolicy(self):
+        &quot;&quot;&quot;
+        L{LoadSimulator.createArrivalPolicy} creates an arrival
+        policy based on the L{Arrival} passed to its initializer.
+        &quot;&quot;&quot;
+        class FakeArrival(object):
+            def __init__(self, reactor, x, y):
+                self.reactor = reactor
+                self.x = x
+                self.y = y
+
+        reactor = object()
+        sim = LoadSimulator(
+            None, None, None, None, None, Arrival(FakeArrival, {'x': 3, 'y': 2}), None, reactor=reactor)
+        arrival = sim.createArrivalPolicy()
+        self.assertIsInstance(arrival, FakeArrival)
+        self.assertIdentical(arrival.reactor, sim.reactor)
+        self.assertEquals(arrival.x, 3)
+        self.assertEquals(arrival.y, 2)
+
+
+    def test_loadPopulationParameters(self):
+        &quot;&quot;&quot;
+        Client weights and profiles are loaded from the [clients]
+        section of the configuration file specified.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(
+            writePlistToString(
+                {
+                    &quot;clients&quot;: [
+                        {
+                            &quot;software&quot;: &quot;contrib.performance.loadtest.ical.OS_X_10_6&quot;,
+                            &quot;params&quot;: {
+                                &quot;foo&quot;: &quot;bar&quot;
+                            },
+                            &quot;profiles&quot;: [
+                                {
+                                    &quot;params&quot;: {
+                                        &quot;interval&quot;: 25,
+                                        &quot;eventStartDistribution&quot;: {
+                                            &quot;type&quot;: &quot;contrib.performance.stats.NormalDistribution&quot;,
+                                            &quot;params&quot;: {
+                                                &quot;mu&quot;: 123,
+                                                &quot;sigma&quot;: 456,
+                                            }
+                                        }
+                                    },
+                                    &quot;class&quot;: &quot;contrib.performance.loadtest.profiles.Eventer&quot;
+                                }
+                            ],
+                            &quot;weight&quot;: 3,
+                        }
+                    ]
+                }
+            )
+        )
+
+        sim = LoadSimulator.fromCommandLine(
+            ['--config', config.path, '--clients', config.path]
+        )
+        expectedParameters = PopulationParameters()
+        expectedParameters.addClient(
+            3,
+            ClientType(
+                OS_X_10_6,
+                {&quot;foo&quot;: &quot;bar&quot;},
+                [
+                    ProfileType(
+                        Eventer, {
+                            &quot;interval&quot;: 25,
+                            &quot;eventStartDistribution&quot;: NormalDistribution(123, 456)
+                        }
+                    )
+                ]
+            )
+        )
+        self.assertEquals(sim.parameters, expectedParameters)
+
+
+    def test_requireClient(self):
+        &quot;&quot;&quot;
+        At least one client is required, so if a configuration with an
+        empty clients array is specified, a single default client type
+        is used.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(writePlistToString({&quot;clients&quot;: []}))
+        sim = LoadSimulator.fromCommandLine(
+            ['--config', config.path, '--clients', config.path]
+        )
+        expectedParameters = PopulationParameters()
+        expectedParameters.addClient(
+            1, ClientType(OS_X_10_6, {}, [Eventer, Inviter, Accepter]))
+        self.assertEquals(sim.parameters, expectedParameters)
+
+
+    def test_loadLogObservers(self):
+        &quot;&quot;&quot;
+        Log observers specified in the [observers] section of the
+        configuration file are added to the logging system.
+        &quot;&quot;&quot;
+        config = FilePath(self.mktemp())
+        config.setContent(
+            writePlistToString(
+                {
+                    &quot;observers&quot;: [
+                        {
+                            &quot;type&quot;: &quot;contrib.performance.loadtest.logger.SimpleStatistics&quot;,
+                            &quot;params&quot;: {},
+                        },
+                    ]
+                }
+            )
+        )
+        sim = LoadSimulator.fromCommandLine(['--config', config.path])
+        self.assertEquals(len(sim.observers), 1)
+        self.assertIsInstance(sim.observers[0], SimpleStatistics)
+
+
+    def test_observeRunReport(self):
+        &quot;&quot;&quot;
+        Each log observer is added to the log publisher before the
+        simulation run is started and has its C{report} method called
+        after the simulation run completes.
+        &quot;&quot;&quot;
+        observers = [Observer()]
+        sim = LoadSimulator(
+            &quot;http://example.com:123/&quot;,
+            &quot;/principals/users/%s/&quot;,
+            None,
+            None,
+            None,
+            Arrival(lambda reactor: NullArrival(), {}),
+            None, observers, reactor=Reactor())
+        io = StringIO()
+        sim.run(io)
+        self.assertEquals(io.getvalue(), &quot;\n*** PASS\n&quot;)
+        self.assertTrue(observers[0].reported)
+        self.assertEquals(
+            [e for e in observers[0].events if &quot;thingo&quot; in e][0][&quot;thingo&quot;],
+            Reactor.message
+        )
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_templatespy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_templates.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_templates.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_templates.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,47 @@
</span><ins>+# -*- test-case-name: contrib.performance.loadtest.test_templates -*-
+##
+# Copyright (c) 2010-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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.
+#
+##
+&quot;&quot;&quot;
+Tests for loadtest.templates
+&quot;&quot;&quot;
+
+from twisted.trial.unittest import TestCase
+
+from contrib.performance.loadtest.templates import eventTemplate, alarmTemplate, taskTemplate
+
+
+class TemplateTests(TestCase):
+    def assertVCalendar(self, vcal):
+        self.assertEqual(vcal.name(), 'VCALENDAR')
+        self.assertEqual(vcal.propertyValue('VERSION'), '2.0')
+
+    def test_eventTemplate(self):
+        self.assertVCalendar(eventTemplate)
+        vevent = eventTemplate.mainComponent()
+        self.assertEqual(vevent.name(), 'VEVENT')
+
+    def test_taskTemplate(self):
+        self.assertVCalendar(taskTemplate)
+        vtodo = taskTemplate.mainComponent()
+        self.assertEqual(vtodo.name(), 'VTODO')
+
+    def test_alarmTemplate(self):
+        self.assertVCalendar(alarmTemplate)
+        valarm = alarmTemplate.mainComponent()
+        self.assertEqual(valarm.name(), 'VALARM')
+        self.assertTrue(valarm.hasProperty('ACTION'))
+        self.assertTrue(valarm.hasProperty('TRIGGER'))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_trafficloggerpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_trafficlogger.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_trafficlogger.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_trafficlogger.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,210 @@
</span><ins>+##
+# Copyright (c) 2011-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 zope.interface import Interface, implements
+
+from twisted.internet.protocol import ClientFactory, Protocol
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransport, MemoryReactor
+from twisted.protocols.wire import Discard
+
+from contrib.performance.loadtest.trafficlogger import _TrafficLoggingFactory, loggedReactor
+
+
+class IProbe(Interface):
+    &quot;&quot;&quot;
+    An interface which can be used to verify some interface-related behavior of
+    L{loggedReactor}.
+    &quot;&quot;&quot;
+    def probe(): #@NoSelf
+        pass
+
+
+
+class Probe(object):
+    implements(IProbe)
+
+    _probed = False
+
+    def __init__(self, result=None):
+        self._result = result
+
+
+    def probe(self):
+        self._probed = True
+        return self._result
+
+
+
+class TrafficLoggingReactorTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{loggedReactor}.
+    &quot;&quot;&quot;
+    def test_nothing(self):
+        &quot;&quot;&quot;
+        L{loggedReactor} returns the object passed to it, if the object passed
+        to it doesn't provide any interfaces.  This is mostly for testing
+        convenience rather than a particularly useful feature.
+        &quot;&quot;&quot;
+        probe = object()
+        self.assertIdentical(probe, loggedReactor(probe))
+
+
+    def test_interfaces(self):
+        &quot;&quot;&quot;
+        The object returned by L{loggedReactor} provides all of the interfaces
+        provided by the object passed to it.
+        &quot;&quot;&quot;
+        probe = Probe()
+        reactor = loggedReactor(probe)
+        self.assertTrue(IProbe.providedBy(reactor))
+
+
+    def test_passthrough(self):
+        &quot;&quot;&quot;
+        Methods on interfaces on the object passed to L{loggedReactor} can be
+        called by calling them on the object returned by L{loggedReactor}.
+        &quot;&quot;&quot;
+        expected = object()
+        probe = Probe(expected)
+        reactor = loggedReactor(probe)
+        result = reactor.probe()
+        self.assertTrue(probe._probed)
+        self.assertIdentical(expected, result)
+
+
+    def test_connectTCP(self):
+        &quot;&quot;&quot;
+        Called on the object returned by L{loggedReactor}, C{connectTCP} calls
+        the wrapped reactor's C{connectTCP} method with the original factory
+        wrapped in a L{_TrafficLoggingFactory}.
+        &quot;&quot;&quot;
+        class RecordDataProtocol(Protocol):
+            def dataReceived(self, data):
+                self.data = data
+        proto = RecordDataProtocol()
+        factory = ClientFactory()
+        factory.protocol = lambda: proto
+        reactor = MemoryReactor()
+        logged = loggedReactor(reactor)
+        logged.connectTCP('192.168.1.2', 1234, factory, 21, '127.0.0.2')
+        [(host, port, factory, timeout, bindAddress)] = reactor.tcpClients
+        self.assertEqual('192.168.1.2', host)
+        self.assertEqual(1234, port)
+        self.assertIsInstance(factory, _TrafficLoggingFactory)
+        self.assertEqual(21, timeout)
+        self.assertEqual('127.0.0.2', bindAddress)
+
+        # Verify that the factory and protocol specified are really being used
+        protocol = factory.buildProtocol(None)
+        protocol.makeConnection(None)
+        protocol.dataReceived(&quot;foo&quot;)
+        self.assertEqual(proto.data, &quot;foo&quot;)
+
+
+    def test_getLogFiles(self):
+        &quot;&quot;&quot;
+        The reactor returned by L{loggedReactor} has a C{getLogFiles} method
+        which returns a L{logstate} instance containing the active and
+        completed log files tracked by the logging wrapper.
+        &quot;&quot;&quot;
+        wrapped = ClientFactory()
+        wrapped.protocol = Discard
+        reactor = MemoryReactor()
+        logged = loggedReactor(reactor)
+        logged.connectTCP('127.0.0.1', 1234, wrapped)
+        factory = reactor.tcpClients[0][2]
+
+        finished = factory.buildProtocol(None)
+        finished.makeConnection(StringTransport())
+        finished.dataReceived('finished')
+        finished.connectionLost(None)
+
+        active = factory.buildProtocol(None)
+        active.makeConnection(StringTransport())
+        active.dataReceived('active')
+
+        logs = logged.getLogFiles()
+        self.assertEqual(1, len(logs.finished))
+        self.assertIn('finished', logs.finished[0].getvalue())
+        self.assertEqual(1, len(logs.active))
+        self.assertIn('active', logs.active[0].getvalue())
+
+
+
+class TrafficLoggingFactoryTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{_TrafficLoggingFactory}.
+    &quot;&quot;&quot;
+    def setUp(self):
+        self.wrapped = ClientFactory()
+        self.wrapped.protocol = Discard
+        self.factory = _TrafficLoggingFactory(self.wrapped)
+
+
+    def test_receivedBytesLogged(self):
+        &quot;&quot;&quot;
+        When bytes are delivered through a protocol created by
+        L{_TrafficLoggingFactory}, they are added to a log kept on that
+        factory.
+        &quot;&quot;&quot;
+        protocol = self.factory.buildProtocol(None)
+
+        # The factory should now have a new StringIO log file
+        self.assertEqual(1, len(self.factory.logs))
+
+        transport = StringTransport()
+        protocol.makeConnection(transport)
+
+        protocol.dataReceived(&quot;hello, world&quot;)
+        self.assertEqual(
+            &quot;*\nC 0: 'hello, world'\n&quot;, self.factory.logs[0].getvalue())
+
+
+    def test_finishedLogs(self):
+        &quot;&quot;&quot;
+        When connections are lost, the corresponding log files are moved into
+        C{_TrafficLoggingFactory.finishedLogs}.
+        &quot;&quot;&quot;
+        protocol = self.factory.buildProtocol(None)
+        transport = StringTransport()
+        protocol.makeConnection(transport)
+        logfile = self.factory.logs[0]
+        protocol.connectionLost(None)
+        self.assertEqual(0, len(self.factory.logs))
+        self.assertEqual([logfile], self.factory.finishedLogs)
+
+
+    def test_finishedLogsLimit(self):
+        &quot;&quot;&quot;
+        Only the most recent C{_TrafficLoggingFactory.LOGFILE_LIMIT} logfiles
+        are kept in C{_TrafficLoggingFactory.finishedLogs}.
+        &quot;&quot;&quot;
+        self.factory.LOGFILE_LIMIT = 2
+        first = self.factory.buildProtocol(None)
+        first.makeConnection(StringTransport())
+        second = self.factory.buildProtocol(None)
+        second.makeConnection(StringTransport())
+        third = self.factory.buildProtocol(None)
+        third.makeConnection(StringTransport())
+
+        second.connectionLost(None)
+        first.connectionLost(None)
+        third.connectionLost(None)
+
+        self.assertEqual(
+            [first.logfile, third.logfile], self.factory.finishedLogs)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestteststest_webadminpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_webadmin.py (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_webadmin.py                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/tests/test_webadmin.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,144 @@
</span><ins>+##
+# Copyright (c) 2012-2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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 twisted.trial.unittest import TestCase
+from contrib.performance.loadtest.webadmin import LoadSimAdminResource
+
+class WebAdminTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for L{LoadSimAdminResource}.
+    &quot;&quot;&quot;
+
+    class FakeReporter(object):
+
+        def generateReport(self, output):
+            output.write(&quot;FakeReporter&quot;)
+
+
+    class FakeReactor(object):
+
+        def __init__(self):
+            self.running = True
+
+        def stop(self):
+            self.running = False
+
+
+    class FakeLoadSim(object):
+
+        def __init__(self):
+            self.reactor = WebAdminTests.FakeReactor()
+            self.reporter = WebAdminTests.FakeReporter()
+            self.running = True
+
+        def stop(self):
+            self.running = False
+
+
+    class FakeRequest(object):
+
+        def __init__(self, **kwargs):
+            self.args = kwargs
+
+
+    def test_resourceGET(self):
+        &quot;&quot;&quot;
+        Test render_GET
+        &quot;&quot;&quot;
+
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+
+        response = resource.render_GET(WebAdminTests.FakeRequest())
+        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
+        self.assertTrue(response.find(resource.token) != -1)
+
+
+    def test_resourcePOST_Stop(self):
+        &quot;&quot;&quot;
+        Test render_POST when Stop button is clicked
+        &quot;&quot;&quot;
+
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(resource.token,),
+            stop=None,
+        ))
+        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
+        self.assertTrue(response.find(resource.token) == -1)
+        self.assertTrue(response.find(&quot;FakeReporter&quot;) != -1)
+        self.assertFalse(loadsim.running)
+
+
+    def test_resourcePOST_Stop_BadToken(self):
+        &quot;&quot;&quot;
+        Test render_POST when Stop button is clicked but token is wrong
+        &quot;&quot;&quot;
+
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(&quot;xyz&quot;,),
+            stop=None,
+        ))
+        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find(&quot;FakeReporter&quot;) == -1)
+        self.assertTrue(loadsim.running)
+
+
+    def test_resourcePOST_Results(self):
+        &quot;&quot;&quot;
+        Test render_POST when Results button is clicked
+        &quot;&quot;&quot;
+
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(resource.token,),
+            results=None,
+        ))
+        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find(&quot;FakeReporter&quot;) != -1)
+        self.assertTrue(loadsim.running)
+
+
+    def test_resourcePOST_Results_BadToken(self):
+        &quot;&quot;&quot;
+        Test render_POST when Results button is clicked and token is wrong
+        &quot;&quot;&quot;
+
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(&quot;xyz&quot;,),
+            results=None,
+        ))
+        self.assertTrue(response.startswith(&quot;&lt;html&gt;&quot;))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find(&quot;FakeReporter&quot;) == -1)
+        self.assertTrue(loadsim.running)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestwebadminpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/webadmin.py (15119 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/webadmin.py        2015-09-10 20:09:06 UTC (rev 15119)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/webadmin.py        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -39,6 +39,7 @@
</span><span class="cx">     HEAD = &quot;&quot;&quot;\
</span><span class="cx"> &lt;html&gt;
</span><span class="cx"> &lt;head&gt;
</span><ins>+&lt;meta http-equiv=&quot;refresh&quot; content=&quot;1&quot; &gt;
</ins><span class="cx"> &lt;style type=&quot;text/css&quot;&gt;
</span><span class="cx"> body {color:#000000;}
</span><span class="cx"> h1 h2 h3 {color:#333333;}
</span><span class="lines">@@ -80,9 +81,11 @@
</span><span class="cx">     def __init__(self, loadsim):
</span><span class="cx">         self.loadsim = loadsim
</span><span class="cx">         self.token = str(uuid.uuid4())
</span><ins>+        self.count = 0
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def render_GET(self, request):
</span><ins>+        self.count += 1
</ins><span class="cx">         return self._renderReport()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -109,5 +112,5 @@
</span><span class="cx">             html = self.HEAD + self.BODY_RESULTS_STOPPED
</span><span class="cx">             return html % (None, report.getvalue(), ms)
</span><span class="cx">         else:
</span><del>-            html = self.HEAD + self.BODY_RESULTS
</del><ins>+            html = self.HEAD + &quot;&lt;h3&gt;Request #&quot; + str(self.count) + &quot;&lt;/h3&gt;&quot; + self.BODY_RESULTS
</ins><span class="cx">             return html % (self.token, report.getvalue(), ms)
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagenclientsimcontribperformanceloadtestwwwlayouthtml"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/www/layout.html (0 => 15120)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/www/layout.html                                (rev 0)
+++ CalendarServer/branches/users/sagen/clientsim/contrib/performance/loadtest/www/layout.html        2015-09-10 20:25:07 UTC (rev 15120)
</span><span class="lines">@@ -0,0 +1,28 @@
</span><ins>+&lt;html&gt;
+&lt;head&gt;
+    &lt;style type=&quot;text/css&quot;&gt;
+        body {color:#000000;}
+        h1 h2 h3 {color:#333333;}
+        td {text-align: center; padding: 5px;}
+        pre.light {color:#CCCCCC; font-size:12px;}
+        table.rounded-corners {
+            border: 1px solid #000000; background-color:#cccccc;
+            -moz-border-radius: 5px;
+            -webkit-border-radius: 5px;
+            -khtml-border-radius: 5px;
+            border-radius: 5px;
+        }
+    &lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+    &lt;h1&gt;Load Simulator Web Admin&lt;/h1&gt;
+    &lt;form method=&quot;POST&quot;&gt;
+        &lt;input name=&quot;token&quot; type=&quot;hidden&quot; value=&quot;%s&quot; /&gt;
+        &lt;table class=&quot;rounded-corners&quot;&gt;
+            &lt;tr&gt;&lt;td&gt;&lt;input name=&quot;results&quot; type=&quot;submit&quot; value=&quot;Refresh&quot; /&gt;&lt;/td&gt;&lt;/tr&gt;
+            &lt;tr&gt;&lt;td&gt;&lt;input name=&quot;stop&quot; type=&quot;submit&quot; value=&quot;Stop Sim&quot; /&gt;&lt;/td&gt;&lt;/tr&gt;
+        &lt;/table&gt;
+    &lt;/form&gt;
+    {% block results %}
+&lt;/body&gt;
+&lt;/html&gt;
</ins><span class="cx">\ No newline at end of file
</span></span></pre>
</div>
</div>

</body>
</html>