[CalendarServer-changes] [11831] CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/ performance/loadtest

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:25:18 PDT 2014


Revision: 11831
          http://trac.calendarserver.org//changeset/11831
Author:   cdaboo at apple.com
Date:     2013-10-21 19:21:03 -0700 (Mon, 21 Oct 2013)
Log Message:
-----------
Allow client config to be set via a separate plist file (which can also override the arrival interval). Print the
stats socket data for the last 5 minutes averaged data from the server in the final report.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.dist.plist
    CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.plist
    CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/population.py
    CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/sim.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/clients.plist

Added: CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/clients.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/clients.plist	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/clients.plist	2013-10-22 02:21:03 UTC (rev 11831)
@@ -0,0 +1,445 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+	<dict>
+		<!-- Define the kinds of software and user behavior the load simulation
+			will simulate. -->
+		<key>clients</key>
+
+		<!-- Have as many different kinds of software and user behavior configurations
+			as you want. Each is a dict -->
+		<array>
+
+			<dict>
+
+				<!-- Here is a OS X client simulator. -->
+				<key>software</key>
+				<string>contrib.performance.loadtest.ical.OS_X_10_7</string>
+
+				<!-- Arguments to use to initialize the OS_X_10_7 instance. -->
+				<key>params</key>
+				<dict>
+					<!-- Name that appears in logs. -->
+					<key>title</key>
+					<string>10.7</string>
+	
+					<!-- OS_X_10_7 can poll the calendar home at some interval. This is
+						in seconds. -->
+					<key>calendarHomePollInterval</key>
+					<integer>30</integer>
+
+					<!-- 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. -->
+					<key>supportPush</key>
+					<false />
+
+					<key>supportAmpPush</key>
+					<true/>
+					<key>ampPushHost</key>
+					<string>localhost</string>
+					<key>ampPushPort</key>
+					<integer>62311</integer>
+				</dict>
+
+				<!-- The profiles define certain types of user behavior on top of the
+					client software being simulated. -->
+				<key>profiles</key>
+				<array>
+
+					<!-- First an event-creating profile, which will periodically create
+						new events at a random time on a random calendar. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.Eventer</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the interval (in seconds) at which this profile will use
+								its client to create a new event. -->
+							<key>interval</key>
+							<integer>60</integer>
+
+							<!-- Define how start times (DTSTART) for the randomly generated events
+								will be selected. This is an example of a "Distribution" parameter. The value
+								for most "Distribution" parameters are interchangeable and extensible. -->
+							<key>eventStartDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized. It produces timestamps
+									in the near future, limited to certain days of the week and certain hours
+									of the day. -->
+								<key>type</key>
+								<string>contrib.performance.stats.WorkDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- These are the days of the week the distribution will use. -->
+									<key>daysOfWeek</key>
+									<array>
+										<string>mon</string>
+										<string>tue</string>
+										<string>wed</string>
+										<string>thu</string>
+										<string>fri</string>
+									</array>
+
+									<!-- The earliest hour of a day at which an event might be scheduled. -->
+									<key>beginHour</key>
+									<integer>8</integer>
+
+									<!-- And the latest hour of a day (at which an event will be scheduled
+										to begin!). -->
+									<key>endHour</key>
+									<integer>16</integer>
+
+									<!-- The timezone in which the event is scheduled. (XXX Does this
+										really work right?) -->
+									<key>tzname</key>
+									<string>America/Los_Angeles</string>
+								</dict>
+							</dict>
+
+							<!-- Define how recurrences are created. -->
+							<key>recurrenceDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized.  We have a fixed set of
+								     RRULEs defined for this distribution and pick each based on a
+								     weight. -->
+								<key>type</key>
+								<string>contrib.performance.stats.RecurrenceDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- False to disable RRULEs -->
+									<key>allowRecurrence</key>
+									<true/>
+
+									<!-- These are the weights for the specific set of RRULEs. -->
+									<key>weights</key>
+									<dict>
+										<!-- Half of all events will be non-recurring -->
+										<key>none</key>
+										<integer>50</integer>
+										
+										<!-- Daily and weekly are pretty common -->
+										<key>daily</key>
+										<integer>10</integer>
+										<key>weekly</key>
+										<integer>20</integer>
+										
+										<!-- Monthly, yearly, daily & weekly limit not so common -->
+										<key>monthly</key>
+										<integer>2</integer>
+										<key>yearly</key>
+										<integer>1</integer>
+										<key>dailylimit</key>
+										<integer>2</integer>
+										<key>weeklylimit</key>
+										<integer>5</integer>
+										
+										<!-- Work days pretty common -->
+										<key>workdays</key>
+										<integer>10</integer>
+									</dict>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
+					<!-- This profile invites some number of new attendees to new events. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.RealisticInviter</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the frequency at which new invitations will be sent out. -->
+							<key>sendInvitationDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.NormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mu gives the mean of the normal distribution (in seconds). -->
+									<key>mu</key>
+									<integer>60</integer>
+
+									<!-- and sigma gives its standard deviation. -->
+									<key>sigma</key>
+									<integer>5</integer>
+								</dict>
+							</dict>
+
+							<!-- Define the distribution of who will be invited to an event.
+							
+								When inviteeClumping is turned on each invitee is based on a sample of
+								users "close to" the organizer based on account index. If the clumping
+								is too "tight" 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.
+							-->
+							<key>inviteeDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.UniformIntegerDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- The minimum value (inclusive) of the uniform distribution. -->
+									<key>min</key>
+									<integer>0</integer>
+									<!-- The maximum value (exclusive) of the uniform distribution. -->
+									<key>max</key>
+									<integer>99</integer>
+								</dict>
+							</dict>
+
+							<key>inviteeClumping</key>
+							<true/>
+
+							<!-- Define the distribution of how many attendees will be invited to an event.
+							
+								LogNormal is the best fit to observed data.
+
+
+								For LogNormal "mode" is the peak, "mean" is the mean value.	For invites,
+								mode should typically be 1, and mean whatever matches the user behavior.
+								Our typical mean is 6. 							
+							     -->
+							<key>inviteeCountDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.LogNormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mode - peak-->
+									<key>mode</key>
+									<integer>1</integer>
+									<!-- mean - average-->
+									<key>median</key>
+									<integer>6</integer>
+									<!-- maximum -->
+									<key>maximum</key>
+									<real>60</real>
+								</dict>
+							</dict>
+
+							<!-- Define how start times (DTSTART) for the randomly generated events
+								will be selected. This is an example of a "Distribution" parameter. The value
+								for most "Distribution" parameters are interchangeable and extensible. -->
+							<key>eventStartDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized. It produces timestamps
+									in the near future, limited to certain days of the week and certain hours
+									of the day. -->
+								<key>type</key>
+								<string>contrib.performance.stats.WorkDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- These are the days of the week the distribution will use. -->
+									<key>daysOfWeek</key>
+									<array>
+										<string>mon</string>
+										<string>tue</string>
+										<string>wed</string>
+										<string>thu</string>
+										<string>fri</string>
+									</array>
+
+									<!-- The earliest hour of a day at which an event might be scheduled. -->
+									<key>beginHour</key>
+									<integer>8</integer>
+
+									<!-- And the latest hour of a day (at which an event will be scheduled
+										to begin!). -->
+									<key>endHour</key>
+									<integer>16</integer>
+
+									<!-- The timezone in which the event is scheduled. (XXX Does this
+										really work right?) -->
+									<key>tzname</key>
+									<string>America/Los_Angeles</string>
+								</dict>
+							</dict>
+
+							<!-- Define how recurrences are created. -->
+							<key>recurrenceDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized.  We have a fixed set of
+								     RRULEs defined for this distribution and pick each based on a
+								     weight. -->
+								<key>type</key>
+								<string>contrib.performance.stats.RecurrenceDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- False to disable RRULEs -->
+									<key>allowRecurrence</key>
+									<true/>
+
+									<!-- These are the weights for the specific set of RRULEs. -->
+									<key>weights</key>
+									<dict>
+										<!-- Half of all events will be non-recurring -->
+										<key>none</key>
+										<integer>50</integer>
+										
+										<!-- Daily and weekly are pretty common -->
+										<key>daily</key>
+										<integer>10</integer>
+										<key>weekly</key>
+										<integer>20</integer>
+										
+										<!-- Monthly, yearly, daily & weekly limit not so common -->
+										<key>monthly</key>
+										<integer>2</integer>
+										<key>yearly</key>
+										<integer>1</integer>
+										<key>dailylimit</key>
+										<integer>2</integer>
+										<key>weeklylimit</key>
+										<integer>5</integer>
+										
+										<!-- Work days pretty common -->
+										<key>workdays</key>
+										<integer>10</integer>
+									</dict>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
+					<!-- This profile accepts invitations to events, handles cancels, and
+					     handles replies received. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.Accepter</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define how long to wait after seeing a new invitation before
+								accepting it.
+
+								For LogNormal "mode" is the peak, "median" is the 50% cummulative value
+								(i.e., half of the user have accepted by that time).								
+							-->
+							<key>acceptDelayDistribution</key>
+							<dict>
+								<key>type</key>
+								<string>contrib.performance.stats.LogNormalDistribution</string>
+								<key>params</key>
+								<dict>
+									<!-- mode - peak-->
+									<key>mode</key>
+									<integer>300</integer>
+									<!-- median - 50% done-->
+									<key>median</key>
+									<integer>1800</integer>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
+					<!-- A task-creating profile, which will periodically create
+						new tasks at a random time on a random calendar. -->
+					<dict>
+						<key>class</key>
+						<string>contrib.performance.loadtest.profiles.Tasker</string>
+
+						<key>params</key>
+						<dict>
+							<key>enabled</key>
+							<true/>
+
+							<!-- Define the interval (in seconds) at which this profile will use
+								its client to create a new task. -->
+							<key>interval</key>
+							<integer>300</integer>
+
+							<!-- Define how due times (DUE) for the randomly generated tasks
+								will be selected. This is an example of a "Distribution" parameter. The value
+								for most "Distribution" parameters are interchangeable and extensible. -->
+							<key>taskDueDistribution</key>
+							<dict>
+
+								<!-- This distribution is pretty specialized. It produces timestamps
+									in the near future, limited to certain days of the week and certain hours
+									of the day. -->
+								<key>type</key>
+								<string>contrib.performance.stats.WorkDistribution</string>
+
+								<key>params</key>
+								<dict>
+									<!-- These are the days of the week the distribution will use. -->
+									<key>daysOfWeek</key>
+									<array>
+										<string>mon</string>
+										<string>tue</string>
+										<string>wed</string>
+										<string>thu</string>
+										<string>fri</string>
+									</array>
+
+									<!-- The earliest hour of a day at which an event might be scheduled. -->
+									<key>beginHour</key>
+									<integer>8</integer>
+
+									<!-- And the latest hour of a day (at which an event will be scheduled
+										to begin!). -->
+									<key>endHour</key>
+									<integer>16</integer>
+
+									<!-- The timezone in which the event is scheduled. (XXX Does this
+										really work right?) -->
+									<key>tzname</key>
+									<string>America/Los_Angeles</string>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+
+				</array>
+
+				<!-- Determine the frequency at which this client configuration will
+					appear in the clients which are created by the load tester. -->
+				<key>weight</key>
+				<integer>1</integer>
+			</dict>
+		</array>
+	</dict>
+</plist>

Modified: CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.dist.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.dist.plist	2013-10-21 22:00:05 UTC (rev 11830)
+++ CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.dist.plist	2013-10-22 02:21:03 UTC (rev 11831)
@@ -50,6 +50,15 @@
 			<integer>8080</integer>
 		</dict>
 
+		<!--  Define whether server supports stats socket. -->
+		<key>serverStats</key>
+		<dict>
+			<key>Enabled</key>
+			<true/>
+			<key>Port</key>
+			<integer>8100</integer>
+		</dict>
+
 		<!--  Define whether client data should be saved and re-used. -->
 		<key>clientDataSerialization</key>
 		<dict>
@@ -119,471 +128,6 @@
 
 		</dict>
 
-		<!-- Define the kinds of software and user behavior the load simulation 
-			will simulate. -->
-		<key>clients</key>
-
-		<!-- Have as many different kinds of software and user behavior configurations 
-			as you want. Each is a dict -->
-		<array>
-
-			<dict>
-
-				<!-- Here is a OS X client simulator. -->
-				<key>software</key>
-				<string>contrib.performance.loadtest.ical.OS_X_10_7</string>
-
-				<!-- Arguments to use to initialize the OS_X_10_7 instance. -->
-				<key>params</key>
-				<dict>
-					<!-- Name that appears in logs. -->
-					<key>title</key>
-					<string>10.7</string>
-
-					<!-- OS_X_10_7 can poll the calendar home at some interval. This is 
-						in seconds. -->
-					<key>calendarHomePollInterval</key>
-					<integer>30</integer>
-
-					<!-- 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. -->
-					<key>supportPush</key>
-					<false />
-				</dict>
-
-				<!-- The profiles define certain types of user behavior on top of the 
-					client software being simulated. -->
-				<key>profiles</key>
-				<array>
-
-					<!-- First an event-creating profile, which will periodically create 
-						new events at a random time on a random calendar. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Eventer</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define the interval (in seconds) at which this profile will use 
-								its client to create a new event. -->
-							<key>interval</key>
-							<integer>60</integer>
-
-							<!-- Define how start times (DTSTART) for the randomly generated events 
-								will be selected. This is an example of a "Distribution" parameter. The value 
-								for most "Distribution" parameters are interchangeable and extensible. -->
-							<key>eventStartDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized. It produces timestamps 
-									in the near future, limited to certain days of the week and certain hours 
-									of the day. -->
-								<key>type</key>
-								<string>contrib.performance.stats.WorkDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- These are the days of the week the distribution will use. -->
-									<key>daysOfWeek</key>
-									<array>
-										<string>mon</string>
-										<string>tue</string>
-										<string>wed</string>
-										<string>thu</string>
-										<string>fri</string>
-									</array>
-
-									<!-- The earliest hour of a day at which an event might be scheduled. -->
-									<key>beginHour</key>
-									<integer>8</integer>
-
-									<!-- And the latest hour of a day (at which an event will be scheduled 
-										to begin!). -->
-									<key>endHour</key>
-									<integer>16</integer>
-
-									<!-- The timezone in which the event is scheduled. (XXX Does this 
-										really work right?) -->
-									<key>tzname</key>
-									<string>America/Los_Angeles</string>
-								</dict>
-							</dict>
-
-							<!-- Define how recurrences are created. -->
-							<key>recurrenceDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized.  We have a fixed set of
-								     RRULEs defined for this distribution and pick each based on a
-								     weight. -->
-								<key>type</key>
-								<string>contrib.performance.stats.RecurrenceDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- False to disable RRULEs -->
-									<key>allowRecurrence</key>
-									<true/>
-
-									<!-- These are the weights for the specific set of RRULEs. -->
-									<key>weights</key>
-									<dict>
-										<!-- Half of all events will be non-recurring -->
-										<key>none</key>
-										<integer>50</integer>
-										
-										<!-- Daily and weekly are pretty common -->
-										<key>daily</key>
-										<integer>10</integer>
-										<key>weekly</key>
-										<integer>20</integer>
-										
-										<!-- Monthly, yearly, daily & weekly limit not so common -->
-										<key>monthly</key>
-										<integer>2</integer>
-										<key>yearly</key>
-										<integer>1</integer>
-										<key>dailylimit</key>
-										<integer>2</integer>
-										<key>weeklylimit</key>
-										<integer>5</integer>
-										
-										<!-- Work days pretty common -->
-										<key>workdays</key>
-										<integer>10</integer>
-									</dict>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- This profile invites new attendees to existing events. 
-					     This profile should no longer be used - use RealisticInviter instead. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Inviter</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<false/>
-
-							<!-- Define the frequency at which new invitations will be sent out. -->
-							<key>sendInvitationDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.NormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mu gives the mean of the normal distribution (in seconds). -->
-									<key>mu</key>
-									<integer>60</integer>
-
-									<!-- and sigma gives its standard deviation. -->
-									<key>sigma</key>
-									<integer>5</integer>
-								</dict>
-							</dict>
-
-							<!-- Define the distribution of who will be invited to an event. Each 
-								set of credentials loaded by the load tester has an index; samples from this 
-								distribution will be added to that index to arrive at the index of some other 
-								credentials, which will be the target of the invitation. -->
-							<key>inviteeDistanceDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.UniformIntegerDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- The minimum value (inclusive) of the uniform distribution. -->
-									<key>min</key>
-									<integer>-100</integer>
-									<!-- The maximum value (exclusive) of the uniform distribution. -->
-									<key>max</key>
-									<integer>101</integer>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- This profile invites some number of new attendees to new events. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.RealisticInviter</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define the frequency at which new invitations will be sent out. -->
-							<key>sendInvitationDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.NormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mu gives the mean of the normal distribution (in seconds). -->
-									<key>mu</key>
-									<integer>60</integer>
-
-									<!-- and sigma gives its standard deviation. -->
-									<key>sigma</key>
-									<integer>5</integer>
-								</dict>
-							</dict>
-
-							<!-- Define the distribution of who will be invited to an event.
-							
-								When inviteeClumping is turned on each invitee is based on a sample of
-								users "close to" the organizer based on account index. If the clumping
-								is too "tight" 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.
-							-->
-							<key>inviteeDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.UniformIntegerDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- The minimum value (inclusive) of the uniform distribution. -->
-									<key>min</key>
-									<integer>-100</integer>
-									<!-- The maximum value (exclusive) of the uniform distribution. -->
-									<key>max</key>
-									<integer>101</integer>
-								</dict>
-							</dict>
-
-							<key>inviteeClumping</key>
-							<true/>
-
-							<!-- Define the distribution of how many attendees will be invited to an event.
-							
-								LogNormal is the best fit to observed data.
-
-
-								For LogNormal "mode" is the peak, "mean" is the mean value.	For invites,
-								mode should typically be 1, and mean whatever matches the user behavior.
-								Our typical mean is 6. 							
-							     -->
-							<key>inviteeCountDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.LogNormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mode - peak-->
-									<key>mode</key>
-									<integer>1</integer>
-									<!-- mean - average-->
-									<key>median</key>
-									<integer>6</integer>
-									<!-- maximum -->
-									<key>maximum</key>
-									<real>100</real>
-								</dict>
-							</dict>
-
-							<!-- Define how start times (DTSTART) for the randomly generated events 
-								will be selected. This is an example of a "Distribution" parameter. The value 
-								for most "Distribution" parameters are interchangeable and extensible. -->
-							<key>eventStartDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized. It produces timestamps 
-									in the near future, limited to certain days of the week and certain hours 
-									of the day. -->
-								<key>type</key>
-								<string>contrib.performance.stats.WorkDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- These are the days of the week the distribution will use. -->
-									<key>daysOfWeek</key>
-									<array>
-										<string>mon</string>
-										<string>tue</string>
-										<string>wed</string>
-										<string>thu</string>
-										<string>fri</string>
-									</array>
-
-									<!-- The earliest hour of a day at which an event might be scheduled. -->
-									<key>beginHour</key>
-									<integer>8</integer>
-
-									<!-- And the latest hour of a day (at which an event will be scheduled 
-										to begin!). -->
-									<key>endHour</key>
-									<integer>16</integer>
-
-									<!-- The timezone in which the event is scheduled. (XXX Does this 
-										really work right?) -->
-									<key>tzname</key>
-									<string>America/Los_Angeles</string>
-								</dict>
-							</dict>
-
-							<!-- Define how recurrences are created. -->
-							<key>recurrenceDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized.  We have a fixed set of
-								     RRULEs defined for this distribution and pick each based on a
-								     weight. -->
-								<key>type</key>
-								<string>contrib.performance.stats.RecurrenceDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- False to disable RRULEs -->
-									<key>allowRecurrence</key>
-									<true/>
-
-									<!-- These are the weights for the specific set of RRULEs. -->
-									<key>weights</key>
-									<dict>
-										<!-- Half of all events will be non-recurring -->
-										<key>none</key>
-										<integer>50</integer>
-										
-										<!-- Daily and weekly are pretty common -->
-										<key>daily</key>
-										<integer>10</integer>
-										<key>weekly</key>
-										<integer>20</integer>
-										
-										<!-- Monthly, yearly, daily & weekly limit not so common -->
-										<key>monthly</key>
-										<integer>2</integer>
-										<key>yearly</key>
-										<integer>1</integer>
-										<key>dailylimit</key>
-										<integer>2</integer>
-										<key>weeklylimit</key>
-										<integer>5</integer>
-										
-										<!-- Work days pretty common -->
-										<key>workdays</key>
-										<integer>10</integer>
-									</dict>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- This profile accepts invitations to events, handles cancels, and
-					     handles replies received. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Accepter</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define how long to wait after seeing a new invitation before
-								accepting it.
-
-								For LogNormal "mode" is the peak, "median" is the 50% cummulative value
-								(i.e., half of the user have accepted by that time).								
-							-->
-							<key>acceptDelayDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.LogNormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mode - peak-->
-									<key>mode</key>
-									<integer>300</integer>
-									<!-- median - 50% done-->
-									<key>median</key>
-									<integer>1800</integer>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- A task-creating profile, which will periodically create 
-						new tasks at a random time on a random calendar. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Tasker</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define the interval (in seconds) at which this profile will use 
-								its client to create a new task. -->
-							<key>interval</key>
-							<integer>300</integer>
-
-							<!-- Define how due times (DUE) for the randomly generated tasks 
-								will be selected. This is an example of a "Distribution" parameter. The value 
-								for most "Distribution" parameters are interchangeable and extensible. -->
-							<key>taskDueDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized. It produces timestamps 
-									in the near future, limited to certain days of the week and certain hours 
-									of the day. -->
-								<key>type</key>
-								<string>contrib.performance.stats.WorkDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- These are the days of the week the distribution will use. -->
-									<key>daysOfWeek</key>
-									<array>
-										<string>mon</string>
-										<string>tue</string>
-										<string>wed</string>
-										<string>thu</string>
-										<string>fri</string>
-									</array>
-
-									<!-- The earliest hour of a day at which an event might be scheduled. -->
-									<key>beginHour</key>
-									<integer>8</integer>
-
-									<!-- And the latest hour of a day (at which an event will be scheduled 
-										to begin!). -->
-									<key>endHour</key>
-									<integer>16</integer>
-
-									<!-- The timezone in which the event is scheduled. (XXX Does this 
-										really work right?) -->
-									<key>tzname</key>
-									<string>America/Los_Angeles</string>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-				</array>
-
-				<!-- Determine the frequency at which this client configuration will 
-					appear in the clients which are created by the load tester. -->
-				<key>weight</key>
-				<integer>1</integer>
-			</dict>
-		</array>
-
 		<!-- Define some log observers to report on the load test. -->
 		<key>observers</key>
 		<array>

Modified: CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.plist	2013-10-21 22:00:05 UTC (rev 11830)
+++ CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/config.plist	2013-10-22 02:21:03 UTC (rev 11831)
@@ -37,6 +37,15 @@
 			<integer>8080</integer>
 		</dict>
 
+		<!--  Define whether server supports stats socket. -->
+		<key>serverStats</key>
+		<dict>
+			<key>Enabled</key>
+			<true/>
+			<key>Port</key>
+			<integer>8100</integer>
+		</dict>
+
 		<!--  Define whether client data should be saved and re-used. -->
 		<key>clientDataSerialization</key>
 		<dict>
@@ -106,429 +115,6 @@
 
 		</dict>
 
-		<!-- Define the kinds of software and user behavior the load simulation
-			will simulate. -->
-		<key>clients</key>
-
-		<!-- Have as many different kinds of software and user behavior configurations
-			as you want. Each is a dict -->
-		<array>
-
-			<dict>
-
-				<!-- Here is a OS X client simulator. -->
-				<key>software</key>
-				<string>contrib.performance.loadtest.ical.OS_X_10_7</string>
-
-				<!-- Arguments to use to initialize the OS_X_10_7 instance. -->
-				<key>params</key>
-				<dict>
-					<!-- Name that appears in logs. -->
-					<key>title</key>
-					<string>10.7</string>
-	
-					<!-- OS_X_10_7 can poll the calendar home at some interval. This is
-						in seconds. -->
-					<key>calendarHomePollInterval</key>
-					<integer>30</integer>
-
-					<!-- 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. -->
-					<key>supportPush</key>
-					<false />
-
-					<key>supportAmpPush</key>
-					<true/>
-					<key>ampPushHost</key>
-					<string>localhost</string>
-					<key>ampPushPort</key>
-					<integer>62311</integer>
-				</dict>
-
-				<!-- The profiles define certain types of user behavior on top of the
-					client software being simulated. -->
-				<key>profiles</key>
-				<array>
-
-					<!-- First an event-creating profile, which will periodically create
-						new events at a random time on a random calendar. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Eventer</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define the interval (in seconds) at which this profile will use
-								its client to create a new event. -->
-							<key>interval</key>
-							<integer>60</integer>
-
-							<!-- Define how start times (DTSTART) for the randomly generated events
-								will be selected. This is an example of a "Distribution" parameter. The value
-								for most "Distribution" parameters are interchangeable and extensible. -->
-							<key>eventStartDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized. It produces timestamps
-									in the near future, limited to certain days of the week and certain hours
-									of the day. -->
-								<key>type</key>
-								<string>contrib.performance.stats.WorkDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- These are the days of the week the distribution will use. -->
-									<key>daysOfWeek</key>
-									<array>
-										<string>mon</string>
-										<string>tue</string>
-										<string>wed</string>
-										<string>thu</string>
-										<string>fri</string>
-									</array>
-
-									<!-- The earliest hour of a day at which an event might be scheduled. -->
-									<key>beginHour</key>
-									<integer>8</integer>
-
-									<!-- And the latest hour of a day (at which an event will be scheduled
-										to begin!). -->
-									<key>endHour</key>
-									<integer>16</integer>
-
-									<!-- The timezone in which the event is scheduled. (XXX Does this
-										really work right?) -->
-									<key>tzname</key>
-									<string>America/Los_Angeles</string>
-								</dict>
-							</dict>
-
-							<!-- Define how recurrences are created. -->
-							<key>recurrenceDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized.  We have a fixed set of
-								     RRULEs defined for this distribution and pick each based on a
-								     weight. -->
-								<key>type</key>
-								<string>contrib.performance.stats.RecurrenceDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- False to disable RRULEs -->
-									<key>allowRecurrence</key>
-									<true/>
-
-									<!-- These are the weights for the specific set of RRULEs. -->
-									<key>weights</key>
-									<dict>
-										<!-- Half of all events will be non-recurring -->
-										<key>none</key>
-										<integer>50</integer>
-										
-										<!-- Daily and weekly are pretty common -->
-										<key>daily</key>
-										<integer>10</integer>
-										<key>weekly</key>
-										<integer>20</integer>
-										
-										<!-- Monthly, yearly, daily & weekly limit not so common -->
-										<key>monthly</key>
-										<integer>2</integer>
-										<key>yearly</key>
-										<integer>1</integer>
-										<key>dailylimit</key>
-										<integer>2</integer>
-										<key>weeklylimit</key>
-										<integer>5</integer>
-										
-										<!-- Work days pretty common -->
-										<key>workdays</key>
-										<integer>10</integer>
-									</dict>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- This profile invites some number of new attendees to new events. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.RealisticInviter</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define the frequency at which new invitations will be sent out. -->
-							<key>sendInvitationDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.NormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mu gives the mean of the normal distribution (in seconds). -->
-									<key>mu</key>
-									<integer>60</integer>
-
-									<!-- and sigma gives its standard deviation. -->
-									<key>sigma</key>
-									<integer>5</integer>
-								</dict>
-							</dict>
-
-							<!-- Define the distribution of who will be invited to an event.
-							
-								When inviteeClumping is turned on each invitee is based on a sample of
-								users "close to" the organizer based on account index. If the clumping
-								is too "tight" 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.
-							-->
-							<key>inviteeDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.UniformIntegerDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- The minimum value (inclusive) of the uniform distribution. -->
-									<key>min</key>
-									<integer>0</integer>
-									<!-- The maximum value (exclusive) of the uniform distribution. -->
-									<key>max</key>
-									<integer>99</integer>
-								</dict>
-							</dict>
-
-							<key>inviteeClumping</key>
-							<true/>
-
-							<!-- Define the distribution of how many attendees will be invited to an event.
-							
-								LogNormal is the best fit to observed data.
-
-
-								For LogNormal "mode" is the peak, "mean" is the mean value.	For invites,
-								mode should typically be 1, and mean whatever matches the user behavior.
-								Our typical mean is 6. 							
-							     -->
-							<key>inviteeCountDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.LogNormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mode - peak-->
-									<key>mode</key>
-									<integer>1</integer>
-									<!-- mean - average-->
-									<key>median</key>
-									<integer>6</integer>
-									<!-- maximum -->
-									<key>maximum</key>
-									<real>60</real>
-								</dict>
-							</dict>
-
-							<!-- Define how start times (DTSTART) for the randomly generated events
-								will be selected. This is an example of a "Distribution" parameter. The value
-								for most "Distribution" parameters are interchangeable and extensible. -->
-							<key>eventStartDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized. It produces timestamps
-									in the near future, limited to certain days of the week and certain hours
-									of the day. -->
-								<key>type</key>
-								<string>contrib.performance.stats.WorkDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- These are the days of the week the distribution will use. -->
-									<key>daysOfWeek</key>
-									<array>
-										<string>mon</string>
-										<string>tue</string>
-										<string>wed</string>
-										<string>thu</string>
-										<string>fri</string>
-									</array>
-
-									<!-- The earliest hour of a day at which an event might be scheduled. -->
-									<key>beginHour</key>
-									<integer>8</integer>
-
-									<!-- And the latest hour of a day (at which an event will be scheduled
-										to begin!). -->
-									<key>endHour</key>
-									<integer>16</integer>
-
-									<!-- The timezone in which the event is scheduled. (XXX Does this
-										really work right?) -->
-									<key>tzname</key>
-									<string>America/Los_Angeles</string>
-								</dict>
-							</dict>
-
-							<!-- Define how recurrences are created. -->
-							<key>recurrenceDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized.  We have a fixed set of
-								     RRULEs defined for this distribution and pick each based on a
-								     weight. -->
-								<key>type</key>
-								<string>contrib.performance.stats.RecurrenceDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- False to disable RRULEs -->
-									<key>allowRecurrence</key>
-									<true/>
-
-									<!-- These are the weights for the specific set of RRULEs. -->
-									<key>weights</key>
-									<dict>
-										<!-- Half of all events will be non-recurring -->
-										<key>none</key>
-										<integer>50</integer>
-										
-										<!-- Daily and weekly are pretty common -->
-										<key>daily</key>
-										<integer>10</integer>
-										<key>weekly</key>
-										<integer>20</integer>
-										
-										<!-- Monthly, yearly, daily & weekly limit not so common -->
-										<key>monthly</key>
-										<integer>2</integer>
-										<key>yearly</key>
-										<integer>1</integer>
-										<key>dailylimit</key>
-										<integer>2</integer>
-										<key>weeklylimit</key>
-										<integer>5</integer>
-										
-										<!-- Work days pretty common -->
-										<key>workdays</key>
-										<integer>10</integer>
-									</dict>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- This profile accepts invitations to events, handles cancels, and
-					     handles replies received. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Accepter</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define how long to wait after seeing a new invitation before
-								accepting it.
-
-								For LogNormal "mode" is the peak, "median" is the 50% cummulative value
-								(i.e., half of the user have accepted by that time).								
-							-->
-							<key>acceptDelayDistribution</key>
-							<dict>
-								<key>type</key>
-								<string>contrib.performance.stats.LogNormalDistribution</string>
-								<key>params</key>
-								<dict>
-									<!-- mode - peak-->
-									<key>mode</key>
-									<integer>300</integer>
-									<!-- median - 50% done-->
-									<key>median</key>
-									<integer>1800</integer>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-					<!-- A task-creating profile, which will periodically create
-						new tasks at a random time on a random calendar. -->
-					<dict>
-						<key>class</key>
-						<string>contrib.performance.loadtest.profiles.Tasker</string>
-
-						<key>params</key>
-						<dict>
-							<key>enabled</key>
-							<true/>
-
-							<!-- Define the interval (in seconds) at which this profile will use
-								its client to create a new task. -->
-							<key>interval</key>
-							<integer>300</integer>
-
-							<!-- Define how due times (DUE) for the randomly generated tasks
-								will be selected. This is an example of a "Distribution" parameter. The value
-								for most "Distribution" parameters are interchangeable and extensible. -->
-							<key>taskDueDistribution</key>
-							<dict>
-
-								<!-- This distribution is pretty specialized. It produces timestamps
-									in the near future, limited to certain days of the week and certain hours
-									of the day. -->
-								<key>type</key>
-								<string>contrib.performance.stats.WorkDistribution</string>
-
-								<key>params</key>
-								<dict>
-									<!-- These are the days of the week the distribution will use. -->
-									<key>daysOfWeek</key>
-									<array>
-										<string>mon</string>
-										<string>tue</string>
-										<string>wed</string>
-										<string>thu</string>
-										<string>fri</string>
-									</array>
-
-									<!-- The earliest hour of a day at which an event might be scheduled. -->
-									<key>beginHour</key>
-									<integer>8</integer>
-
-									<!-- And the latest hour of a day (at which an event will be scheduled
-										to begin!). -->
-									<key>endHour</key>
-									<integer>16</integer>
-
-									<!-- The timezone in which the event is scheduled. (XXX Does this
-										really work right?) -->
-									<key>tzname</key>
-									<string>America/Los_Angeles</string>
-								</dict>
-							</dict>
-						</dict>
-					</dict>
-
-				</array>
-
-				<!-- Determine the frequency at which this client configuration will
-					appear in the clients which are created by the load tester. -->
-				<key>weight</key>
-				<integer>1</integer>
-			</dict>
-		</array>
-
 		<!-- Define some log observers to report on the load test. -->
 		<key>observers</key>
 		<array>

Modified: CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/population.py	2013-10-21 22:00:05 UTC (rev 11830)
+++ CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/population.py	2013-10-22 02:21:03 UTC (rev 11831)
@@ -396,6 +396,7 @@
         self._failed_clients = []
         self._failed_sim = collections.defaultdict(int)
         self._startTime = datetime.now()
+        self._expired_data = None
 
         # Load parameters from config
         if "thresholdsPath" in params:
@@ -423,6 +424,13 @@
             self._fail_cut_off = params["failCutoff"]
 
 
+    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)
 
@@ -454,6 +462,10 @@
         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 = "%" + maxColumnWidth + "s : %-s\n"
@@ -527,8 +539,22 @@
         if self.countSimFailures() > 0:
             for reason, count in self._failed_sim.items():
                 items['Failed operation'] = "%s : %d times" % (reason, count,)
+        output.write("* Client\n")
         self.printMiscellaneous(output, items)
         output.write("\n")
+
+        if self._expired_data is not None:
+            items = {
+                "Req/sec" : "%.1f" % (self._expired_data[0],),
+                "Response": "%.1f (ms)" % (self._expired_data[1],),
+                "Slots": "%.2f" % (self._expired_data[2],),
+                "CPU": "%.1f%%" % (self._expired_data[3],),
+            }
+            output.write("* Server (Last 5 minutes)\n")
+            self.printMiscellaneous(output, items)
+            output.write("\n")
+        output.write("* Details\n")
+
         self.printHeader(output, [
                 (label, width)
                 for (label, width, _ignore_fmt)

Modified: CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/sim.py	2013-10-21 22:00:05 UTC (rev 11830)
+++ CalendarServer/branches/users/cdaboo/performance-tweaks/contrib/performance/loadtest/sim.py	2013-10-22 02:21:03 UTC (rev 11831)
@@ -23,11 +23,14 @@
 from plistlib import readPlist
 from random import Random
 from sys import argv, stdout
+from urlparse import urlsplit
 from xml.parsers.expat import ExpatError
+import json
+import socket
 
 from twisted.python import context
 from twisted.python.filepath import FilePath
-from twisted.python.log import startLogging, addObserver, removeObserver
+from twisted.python.log import startLogging, addObserver, removeObserver, msg
 from twisted.python.usage import UsageError, Options
 from twisted.python.reflect import namedAny
 
@@ -56,6 +59,11 @@
 
 
 
+def safeDivision(value, total, factor=1):
+    return value * factor / total if total else 0
+
+
+
 def generateRecords(count, uidPattern="user%d", passwordPattern="user%d",
     namePattern="User %d", emailPattern="user%d at example.com"):
     for i in xrange(count):
@@ -121,6 +129,7 @@
     """
     config = None
     _defaultConfig = FilePath(__file__).sibling("config.plist")
+    _defaultClients = FilePath(__file__).sibling("clients.plist")
 
     optParameters = [
         ("runtime", "t", None,
@@ -129,6 +138,9 @@
         ("config", None, _defaultConfig,
          "Configuration plist file name from which to read simulation parameters.",
          FilePath),
+        ("clients", None, _defaultClients,
+         "Configuration plist file name from which to read client parameters.",
+         FilePath),
         ]
 
 
@@ -181,7 +193,23 @@
         finally:
             configFile.close()
 
+        try:
+            clientFile = self['clients'].open()
+        except IOError, e:
+            raise UsageError("--clients %s: %s" % (
+                    self['clients'].path, e.strerror))
+        try:
+            try:
+                client_config = readPlist(clientFile)
+                self.config["clients"] = client_config["clients"]
+                if "arrivalInterval" in client_config:
+                    self.config["arrival"]["params"]["interval"] = client_config["arrivalInterval"]
+            except ExpatError, e:
+                raise UsageError("--clients %s: %s" % (self['clients'].path, e))
+        finally:
+            clientFile.close()
 
+
 Arrival = namedtuple('Arrival', 'factory parameters')
 
 
@@ -200,7 +228,7 @@
         user information about the accounts on the server being put
         under load.
     """
-    def __init__(self, server, principalPathTemplate, webadminPort, serializationPath, arrival, parameters, observers=None,
+    def __init__(self, server, principalPathTemplate, webadminPort, serverStats, serializationPath, arrival, parameters, observers=None,
                  records=None, reactor=None, runtime=None, workers=None,
                  configTemplate=None, workerID=None, workerCount=1):
         if reactor is None:
@@ -208,6 +236,7 @@
         self.server = server
         self.principalPathTemplate = principalPathTemplate
         self.webadminPort = webadminPort
+        self.serverStats = serverStats
         self.serializationPath = serializationPath
         self.arrival = arrival
         self.parameters = parameters
@@ -307,9 +336,14 @@
 
         webadminPort = None
         if 'webadmin' in config:
-            if config['webadmin']['enabled']:
+            if config['webadmin']['Enabled']:
                 webadminPort = config['webadmin']['HTTPPort']
 
+        serverStats = None
+        if 'serverStats' in config:
+            if config['serverStats']['Enabled']:
+                serverStats = config['serverStats']
+
         observers = []
         if 'observers' in config:
             for observer in config['observers']:
@@ -324,11 +358,23 @@
             records.extend(namedAny(loader)(**params))
             output.write("Loaded {0} accounts.\n".format(len(records)))
 
-        return cls(server, principalPathTemplate, webadminPort, serializationPath,
-                   arrival, parameters, observers=observers,
-                   records=records, runtime=runtime, reactor=reactor,
-                   workers=workers, configTemplate=configTemplate,
-                   workerID=workerID, workerCount=workerCount)
+        return cls(
+            server,
+            principalPathTemplate,
+            webadminPort,
+            serverStats,
+            serializationPath,
+            arrival,
+            parameters,
+            observers=observers,
+            records=records,
+            runtime=runtime,
+            reactor=reactor,
+            workers=workers,
+            configTemplate=configTemplate,
+            workerID=workerID,
+            workerCount=workerCount,
+        )
 
 
     @classmethod
@@ -409,7 +455,7 @@
     def run(self, output=stdout):
         self.attachServices(output)
         if self.runtime is not None:
-            self.reactor.callLater(self.runtime, self.reactor.stop)
+            self.reactor.callLater(self.runtime, self.stopAndReport)
         if self.webadminPort:
             self.reactor.listenTCP(self.webadminPort, server.Site(LoadSimAdminResource(self)))
         self.reactor.run()
@@ -417,16 +463,65 @@
 
     def stop(self):
         if self.ms.running:
+            self.updateStats()
             self.ms.stopService()
-            self.reactor.callLater(5, self.reactor.stop)
+            self.reactor.callLater(5, self.stopAndReport)
 
 
     def shutdown(self):
         if self.ms.running:
+            self.updateStats()
             return self.ms.stopService()
 
 
+    def updateStats(self):
+        """
+        Capture server stats and stop.
+        """
 
+        if self.serverStats is not None:
+            _ignore_scheme, hostname, _ignore_path, _ignore_query, _ignore_fragment = urlsplit(self.server)
+            data = self.readStatsSock((hostname.split(":")[0], self.serverStats["Port"],), True)
+            if "Failed" not in data:
+                data = data["5 Minutes"]
+                result = (
+                    safeDivision(float(data["requests"]), 5 * 60),
+                    safeDivision(data["t"], data["requests"]),
+                    safeDivision(float(data["slots"]), data["requests"]),
+                    safeDivision(data["cpu"], data["requests"]),
+                )
+                msg(type="sim-expired", reason=result)
+
+
+    def stopAndReport(self):
+        """
+        Runtime has expired - capture server stats and stop.
+        """
+
+        self.updateStats()
+        self.reactor.stop()
+
+
+    def readStatsSock(self, sockname, useTCP):
+        try:
+            s = socket.socket(socket.AF_INET if useTCP else socket.AF_UNIX, socket.SOCK_STREAM)
+            s.connect(sockname)
+            data = ""
+            while True:
+                d = s.recv(1024)
+                if d:
+                    data += d
+                else:
+                    break
+            s.close()
+            data = json.loads(data)
+        except socket.error:
+            data = {"Failed": "Unable to read statistics from server: %s" % (sockname,)}
+        data["Server"] = sockname
+        return data
+
+
+
 def attachService(reactor, loadsim, service):
     """
     Attach a given L{IService} provider to the given L{IReactorCore}; cause it
@@ -557,7 +652,6 @@
 
 
     def errReceived(self, error):
-        from twisted.python.log import msg
         msg("stderr received from " + str(self.transport.pid))
         msg("    " + repr(error))
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/3a18396e/attachment.html>


More information about the calendarserver-changes mailing list