[CalendarServer-changes] [15081] CalendarServer/branches/users/sredmond/clientsim/contrib/ performance/loadtest
source_changes at macosforge.org
source_changes at macosforge.org
Mon Aug 31 13:37:45 PDT 2015
Revision: 15081
http://trac.calendarserver.org//changeset/15081
Author: sredmond at apple.com
Date: 2015-08-31 13:37:45 -0700 (Mon, 31 Aug 2015)
Log Message:
-----------
Adds Python configuration and a lot more
Modified Paths:
--------------
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ampsim.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ical.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/logger.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/population.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/profiles.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/push.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/records.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/requester.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/resources.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/clients.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/config.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/sim.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_push.py
CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_resources.py
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ampsim.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ampsim.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ampsim.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -46,7 +46,6 @@
exit(0)
runmain()
-
from copy import deepcopy
from plistlib import writePlistToString, readPlistFromString
@@ -58,14 +57,16 @@
from contrib.performance.loadtest.sim import LoadSimulator
from contrib.performance.loadtest.records import DirectoryRecord
+from contrib.performance.loadtest.config import Config
class Configure(Command):
"""
Configure this worker process with the text of an XML property list.
"""
- arguments = [("plist", String())]
+ arguments = [("cfg", Pickle())]
# Pass OSError exceptions through, presenting the exception message to the user.
- errors = {OSError: 'OSError'}
+ # errors = {OSError: 'OSError'}
+ errors = {Exception: 'Exception'}
@@ -112,11 +113,16 @@
@Configure.responder
- def config(self, plist):
+ def config(self, cfg):
from sys import stderr
- cfg = readPlistFromString(plist)
+ # cfg = readPlistFromString(plist)
+ config = Config.deserializeFromWorker(cfg)
+ with open('logs.txt', 'a') as f:
+ f.write('here')
+ f.write(str(config.__dict__))
+
addObserver(self.emit)
- sim = LoadSimulator.fromConfig(cfg)
+ sim = LoadSimulator.fromConfigObject(config)
sim.records = self.records
sim.attachServices(stderr)
return {}
@@ -161,26 +167,29 @@
email=record.email,
guid=record.guid)
- 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["workers"]
- # 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["accounts"]
+ 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["workers"]
+ # # 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["accounts"]
- workerConfig["workerID"] = self.whichWorker
- workerConfig["workerCount"] = self.numWorkers
- workerConfig["observers"] = []
- workerConfig.pop("accounts", None)
+ # workerConfig["workerID"] = self.whichWorker
+ # workerConfig["workerCount"] = self.numWorkers
+ # workerConfig["observers"] = []
+ # workerConfig.pop("accounts", None)
- plist = writePlistToString(workerConfig)
+ # plist = writePlistToString(workerConfig)
self.output.write("Initiating worker configuration\n")
def completed(x):
self.output.write("Worker configuration complete.\n")
- self.callRemote(Configure, plist=plist).addCallback(completed)
+ self.callRemote(Configure, cfg=workerConfig).addCallback(completed)
@LogMessage.responder
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/config.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -1,69 +1,127 @@
from importlib import import_module
+from twisted.python.log import msg
from contrib.performance.loadtest.logger import ReportStatistics, RequestLogger, OperationLogger
-from contrib.performance.loadtest.sim import recordsFromCSVFile
+from contrib.performance.loadtest.records import recordsFromCSVFile
+from contrib.performance.loadtest.population import ClientFactory, PopulationParameters
-DEFAULTS = {
- server = "https://127.0.0.1:8443"
+class Config(object):
+ def __init__(self):
+ pass
- accounts = recordsFromCSVFile("contrib/performance/loadtest/accounts.csv")
+ 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
- _requestLogger = RequestLogger()
- _operationLogger = OperationLogger(
- thresholdsPath="contrib/performance/loadtest/thresholds.json",
- lagCutoff=1.0,
- failCutoff=1.0
- )
- _statisticsReporter = ReportStatistics(
- thresholdsPath="contrib/performance/loadtest/thresholds.json",
- benchmarksPath="contrib/performance/loadtest/benchmarks.json",
- failCutoff=1.0
- )
+ 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("workerID", 0)
+ workerCount = config.get("workerCount", 1)
+ configTemplate = None
+ server = config.get('server', 'http://127.0.0.1:8008')
+ serializationPath = None
- arrival = SmoothRampUp(
- groups=2,
- groupSize=1,
- interval=3,
- clientsPerUser=1
- )
-}
+ serializationPath = config['serializationPath']
-class Config(object):
+ 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["weight"],
+ ClientType(
+ clientConfig["software"],
+ clientConfig["params"],
+ clientConfig["profiles"]
+ )
+ )
+ # ClientType(
+ # namedAny(clientConfig["software"]),
+ # cls._convertParams(clientConfig["params"]),
+ # [
+ # ProfileType(
+ # namedAny(profile["class"]),
+ # cls._convertParams(profile["params"])
+ # ) for profile in clientConfig["profiles"]
+ # ]))
+ 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
- def __init__(self, serverConfigFile, clientConfigFile):
- # These are modules
- serverConfigModule = import_module(serverConfigFile)
- clientConfigModule = import_module(clientConfigFile)
+ # webadminPort =
+ webadminPort = None
+ if 'webadmin' in config:
+ if config['webadmin']['enabled']:
+ webadminPort = config['webadmin']['HTTPPort']
- self.clients = clientConfigModule.clientConfiguration
- self.workers = workers
- self.configTemplate = configTemplate
- self.workerID = workerID
- self.workerCount = workerCount
+ serverStats = None
+ if 'serverStats' in config:
+ if config['serverStats']['enabled']:
+ serverStats = config['serverStats']
+ serverStats['server'] = config['server'] if 'server' in config else ''
- self.server = serverConfig.get('server')
- self.webadminPort = serverConfig.get('webadminPort')
- self.serverStats = serverConfig.get('serverStatsPort')
- self.serializationPath = serverConfig.get('serializationPath')
- self.arrival = serverConfig.get('arrival')
- self.observers = serverConfig.get('observers')
- self.records = serverConfig.get('records')
- self.workers = serverConfig.get('workers')
+ observers = []
+ if 'observers' in config:
+ for observer in config['observers']:
+ observerName = observer["type"]
+ observerParams = observer["params"]
+ observers.append(namedAny(observerName)(**observerParams))
- self.buildParameters()
+ records = []
+ if 'accounts' in config:
+ loader = config['accounts']['loader']
+ params = config['accounts']['params']
+ records.extend(namedAny(loader)(**params))
+ output.write("Loaded {0} accounts.\n".format(len(records)))
- def buildParameters(self):
- self.parameters = PopulationParameters()
- for client in self.clients:
- self.parameters.addClient(
+ 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["weight"],
- ClientType(
+ ClientFactory(
client["software"],
client["params"],
client["profiles"]
)
)
+ return parameters
def buildSerializationPath(self):
if self.serializationPath:
@@ -75,7 +133,54 @@
print("Please consult the clientDataSerialization stanza of contrib/performance/loadtest/config.plist")
raise
- def get(self, attr):
- if hasattr(self, attr):
- return getattr(self, attr)
- return DEFAULTS.get(attr, None)
+ def serializeForWorker(self, workerID, workerCount):
+ if not self.workers: # If we are workers, don't try to be a manager
+ return {}
+ # print "Trying to serialize for worker #" + str(workerID)
+ # print "My info, btw is " + 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):
+ """
+ Find parameter values which should be more structured than plistlib is
+ capable of constructing and replace them with the more structured form.
+
+ Specifically, find keys that end with C{"Distribution"} and convert
+ them into some kind of distribution object using the associated
+ dictionary of keyword arguments.
+ """
+ for k, v in params.iteritems():
+ if k.endswith('Distribution'): # Goodness how fragile
+ params[k] = cls._convertDistribution(v)
+ return params
+
+
+ @classmethod
+ def _convertDistribution(cls, value):
+ """
+ Construct and return a new distribution object using the type and
+ params specified by C{value}.
+ """
+ return namedAny(value['type'])(**value['params'])
\ No newline at end of file
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/distributions.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -26,14 +26,36 @@
Sampling from this distribution must *not* change the underlying behavior of a distribution
Distributions (all of which implement IDistribution):
- UniformDiscreteDistribution
- LogNormalDistribution
- FixedDistribution
- NearFutureDistribution
- NormalDistribution
- UniformIntegerDistribution
- WorkDistribution
- RecurrenceDistribution
+ # Discrete Distributions / Finite Support
+ Bernoulli
+ Binomial
+ Rademacher
+ Fixed
+ UniformDiscrete
+ UniformInteger
+
+ # Discrete Distributions / Infinite Support (> 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
"""
from math import log, sqrt
from time import mktime
@@ -54,9 +76,7 @@
def sample(): #@NoSelf
pass
-# class Bounded
-
class UniformDiscreteDistribution(object, FancyEqMixin):
"""
@@ -65,22 +85,11 @@
compareAttributes = ['_values']
- def __init__(self, values, randomize=True):
+ def __init__(self, values):
self._values = values
- self._randomize = randomize
- self._refill()
-
- def _refill(self):
- self._remaining = self._values[:]
- if self._randomize:
- random.shuffle(self._remaining)
-
-
def sample(self):
- if not self._remaining:
- self._refill()
- return self._remaining.pop()
+ return random.choice(self._values)
@@ -218,7 +227,20 @@
return 1 if random.random() <= self._p else 0
+class RademacherDistribution(object, FancyEqMixin):
+ """
+ Takes value 1 with probability 1/2 and value -1 with probability 1/2
+ """
+ def __init__(self):
+ """
+ """
+ self._d = BernoulliDistribution(proportion=0.5)
+ def sample(self):
+ return [-1, 1][self._d.sample()]
+
+
+
class BinomialDistribution(object, FancyEqMixin):
compareAttributes = ["_successProbability", "_numTrials"]
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ical.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/ical.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -249,7 +249,6 @@
ampPushPort=62311,
):
self._client_id = str(uuid4())
-
self.reactor = reactor
self.requester = Requester(
@@ -432,6 +431,7 @@
self.checkCalendarsForEvents, calendarHome)
return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
+ ### TODO this doesn't seem to always work
@inlineCallbacks
def updateCalendarHomeFromPush(self, calendarHomeSet):
"""
@@ -538,8 +538,8 @@
except KeyError:
pass
else:
- if pushkey:
- self.monitor.addPushkey(href, pushkey)
+ if pushkey and self.monitor:
+ self.monitor.addPushkey(pushkey, href)
nodes = results[href].getNodeProperties()
for nodeType in nodes[davxml.resourcetype]:
@@ -888,7 +888,7 @@
# Start monitoring AMP push notifications, if possible
if self.monitor and self.monitor.isSubscribedTo(calendarHome):
- self.monitor.begin()
+ yield self.monitor.begin()
# Run indefinitely.
yield Deferred()
else:
@@ -902,6 +902,8 @@
Called before connections are closed, giving a chance to clean up
"""
self.serialize()
+ if not self.monitor:
+ return succeed(None)
return self.monitor.end()
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/logger.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/logger.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/logger.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -107,7 +107,23 @@
self._printRow(output, formats, self._summarizeData(method, data))
+class MessageLogger(object):
+ def observe(self, event):
+ if event.get("type") == "log":
+ import random
+ identifier = random.random()
+ print(TerminalColors.WARNING + str(identifier) + '/' + event.get('val') + ':' + event.get('text') + TerminalColors.ENDC)
+
+class EverythingLogger(object):
+ def observe(self, event):
+ # if event.get("type") == "response":
+ # from pprint import pprint
+ # pprint(event)
+ pass
+
+
+
class RequestLogger(object):
format = u"%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s"
success = u"\N{CHECK MARK}"
@@ -129,7 +145,7 @@
else:
formatArgs['success'] = self.failure
start = TerminalColors.FAIL
- print(start + (self.format % formatArgs).encode('utf-8') + TerminalColors.ENDC)
+ print(start + (self.format % formatArgs).encode('utf-8') + "from Logger w/ id: " + str(id(self)) + TerminalColors.ENDC)
def report(self, output):
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/population.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/population.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -43,51 +43,51 @@
from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
-class ProfileType(object, FancyEqMixin):
- """
- @ivar profileType: A L{ProfileBase} subclass, or an L{ICalendarUserProfile}
- implementation.
+# class ProfileType(object, FancyEqMixin):
+# """
+# @ivar profileType: A L{ProfileBase} subclass
+# @type profileType: C{type}
- @ivar params: A C{dict} which will be passed to C{profileType} as keyword
- arguments to create a new profile instance.
- """
- compareAttributes = ("profileType", "params")
+# @ivar params: A C{dict} which will be passed to C{profileType} as keyword
+# arguments to create a new profile instance.
+# """
+# compareAttributes = ("profileType", "params")
- def __init__(self, profileType, params):
- self.profileType = profileType
- self.params = params
+# def __init__(self, profileType, params):
+# self.profileType = profileType
+# self.params = params
- def __call__(self, reactor, simulator, client, number):
- base = self.profileType(**self.params)
- base.setUp(reactor, simulator, client, number)
- return base
+# def __call__(self, reactor, simulator, client, number):
+# base = self.profileType(**self.params)
+# base.setUp(reactor, simulator, client, number)
+# return base
- def __repr__(self):
- return "ProfileType(%s, params=%s)" % (self.profileType.__name__, self.params)
+# def __repr__(self):
+# return "ProfileType(%s, params=%s)" % (self.profileType.__name__, self.params)
-class ClientType(object, FancyEqMixin):
+class ClientFactory(object, FancyEqMixin):
"""
- @ivar clientType: An L{ICalendarClient} implementation
+ @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
@ivar profileTypes: A list of L{ProfileType} instances
"""
compareAttributes = ("clientType", "profileTypes")
- def __init__(self, clientType, clientParams, profileTypes):
+ def __init__(self, clientType, clientParams, profiles):
self.clientType = clientType
self.clientParams = clientParams
- self.profileTypes = profileTypes
+ self.profiles = profiles
def new(self, reactor, serverAddress, serializationPath, userRecord, authInfo):
"""
Create a new instance of this client type.
"""
- # print(self.clientType)
- # print(self.clientParams)
return self.clientType(
reactor, serverAddress, serializationPath,
userRecord, authInfo, **self.clientParams
@@ -120,60 +120,67 @@
self.clients.append((weight, clientType))
- def clientTypes(self):
- """
- Return a list of two-tuples giving the weights and types of
- clients in the population.
- """
- return self.clients
+ def clientGenerator(self):
+ while True:
+ for (weight, clientFactory) in self.clients:
+ for _ignore_i in xrange(weight):
+ yield clientFactory
+ # def clientTypes(self):
+ # """
+ # Return a list of two-tuples giving the weights and types of
+ # clients in the population.
+ # """
+ # return self.clients
-class Populator(object):
- """
- @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 "used".
- @ivar passwordPattern: Similar to C{userPattern}, but for
- passwords.
- """
- def __init__(self, random):
- self._random = random
- def _cycle(self, elements):
- while True:
- for (weight, value) in elements:
- for _ignore_i in range(weight):
- yield value
+# class Populator(object):
+# """
+# """
+# def __init__(self):
+# self._random = random
- def populate(self, parameters):
- """
- Generate individuals such as might be randomly selected from a
- population with the given parameters.
- @type parameters: L{PopulationParameters}
- @rtype: generator of L{ClientType} instances
- """
- for (clientType,) in izip(self._cycle(parameters.clientTypes())):
- yield clientType
+# def _cycle(self, elements):
+# while True:
+# for (weight, value) in elements:
+# for _ignore_i in range(weight):
+# yield value
+# def populate(self, parameters):
+# """
+# Generate individuals such as might be randomly selected from a
+# population with the given parameters.
+# @type parameters: L{PopulationParameters}
+# @rtype: generator of L{ClientType} instances
+# """
+# for (clientType,) in izip(self._cycle(parameters.clientTypes())):
+# yield clientType
+
+
+
class CalendarClientSimulator(object):
- def __init__(self, records, populator, parameters, reactor, server,
+ 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="log", text="In create client sim", val=str(val))
from pprint import pprint
pprint(records)
self._records = records
- self.populator = populator
self.reactor = reactor
self.server = server
self.serializationPath = serializationPath
- self._pop = self.populator.populate(parameters)
+ self._populator = parameters.clientGenerator()
self._user = 0
self._stopped = False
self.workerIndex = workerIndex
@@ -228,63 +235,74 @@
def add(self, numClients, clientsPerUser):
- for _ignore_n in range(numClients):
+ # for _ignore_n in range(numClients):
+ # number = self._nextUserNumber()
+
+ # 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()
-
- for _ignore_peruser in range(clientsPerUser):
- clientType = self._pop.next()
+ # What user are we representing?
+ for j in range(clientsPerUser):
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
+ clientFactory = self._populator.next()
_ignore_user, auth = self._createUser(number)
+ reactor = loggedReactor(self.reactor)
- reactor = loggedReactor(self.reactor)
- client = clientType.new(
- reactor,
+ client = clientFactory.new(
+ self.reactor,
self.server,
self.serializationPath,
self.getUserRecord(number),
- auth,
+ auth
)
+ import random
self.clients.append(client)
- d = client.run()
- d.addErrback(self._clientFailure, reactor)
+ client.run().addErrback(self._clientFailure, reactor)
+ for profileTemplate in clientFactory.profiles:
+ print("Templated interval is:" + str(profileTemplate._interval))
+ profile = profileTemplate.duplicate()
+ print("I see the interval as:" + str(profile._interval))
+ profile.setUp(self.reactor, self, client, number)
+ profile.run().addErrback(self._profileFailure, 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)
- # XXX this status message is prone to be slightly inaccurate, but isn't
- # really used by much anyway.
- msg(type="status", clientCount=self._user - 1)
-
- # for i in range(numClients):
- # for j in range(clientsPerUser):
- # client = self._pop.next()
- # # Reactor magic goes here
- # client.setUp(
- # self.reactor,
- # self.server,
- # self.serializationPath,
- # self.getUserRecord(number),
- # auth
- # )
- # for profile in client.profiles:
- # profile.setUp(self.reactor, self, client, number)
-
-
-
-
def _dumpLogs(self, loggingReactor, reason):
path = FilePath(mkdtemp())
logstate = loggingReactor.getLogFiles()
@@ -297,7 +315,7 @@
return path
- def _profileFailure(self, reason, profileType, reactor):
+ def _profileFailure(self, reason, reactor):
if not self._stopped:
where = self._dumpLogs(reactor, reason)
err(reason, "Profile stopped with error; recent traffic in %r" % (
@@ -321,17 +339,16 @@
class SmoothRampUp(object):
- def __init__(self, reactor, groups, groupSize, interval, clientsPerUser):
- self.reactor = reactor
+ def __init__(self, groups, groupSize, interval, clientsPerUser):
self.groups = groups
self.groupSize = groupSize
self.interval = interval
self.clientsPerUser = clientsPerUser
- def run(self, simulator):
+ def run(self, reactor, simulator):
for i in range(self.groups):
- self.reactor.callLater(
+ reactor.callLater(
self.interval * i, simulator.add, self.groupSize, self.clientsPerUser)
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/profiles.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/profiles.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -23,6 +23,7 @@
import random
from uuid import uuid4
+from numbers import Number
from caldavclientlibrary.protocol.caldav.definitions import caldavxml
@@ -37,7 +38,7 @@
from contrib.performance.loadtest.distributions import (
NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, BernoulliDistribution,
- LogNormalDistribution, RecurrenceDistribution
+ LogNormalDistribution, RecurrenceDistribution, FixedDistribution
)
from contrib.performance.loadtest.ical import IncorrectResponseCode
from contrib.performance.loadtest.resources import Calendar, Event
@@ -47,6 +48,23 @@
from pycalendar.duration import Duration
from pycalendar.value import Value
+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
+
class ProfileBase(object):
"""
Base class which provides some conveniences for profile
@@ -55,11 +73,19 @@
random = random
def __init__(self, enabled, interval, **params):
+ print("Creating new profile: %s" % (self.__class__.__name__,))
self.enabled = enabled
+ if isinstance(interval, Number):
+ interval = FixedDistribution(interval)
self._interval = interval
- self.setParameters(**params)
+ print "**" + str(self._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):
self._reactor = reactor
self._sim = simulator
@@ -67,49 +93,19 @@
self._record = record
self._initialized = True
- def setParameters(self):
+ def setDistributions(self):
pass
+ def _wrapper(self):
+ if not self.enabled:
+ return succeed(None)
+ if not self._client.started:
+ return succeed(None)
+ return self.action()
+
def run(self):
- # def action(self):
- # if self.enabled:
- # return self.action()
+ return _loopWithDistribution(self._reactor, self._interval, self._wrapper)
- print("Hello from run")
- _call = LoopingCall(self.action)
- _call.clock = self._reactor
- return _call.start(self._interval)
-
- # OR
-
- # return self._loopWithDistribution(
- # self._reactor, self._sendInvitationDistribution, self._invite)
-
- # def _loopWithDistribution(reactor, distribution, function):
- # result = Deferred()
-
- # 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
-
- def initialize(self):
- """
- Called before the profile runs for real. Can be used to initialize client state.
-
- @return: a L{Deferred} that fires when initialization is done
- """
- return succeed(None)
-
-
def _calendarsOfType(self, calendarType, componentType):
return [
cal
@@ -127,12 +123,12 @@
return attendee.parameterValue('EMAIL') == self._client.email[len('mailto:'):]
- def _getRandomCalendar(self):
+ def _getRandomCalendarOfType(self, component_type):
"""
Return a random L{Calendar} object from the current user
or C{None} if there are no calendars to work with
"""
- calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+ 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
@@ -140,12 +136,12 @@
return calendar
- def _getRandomEvent(self):
+ def _getRandomEventOfType(self, component_type):
"""
Return a random L{Event} object from the current user
or C{None} if there are no events to work with
"""
- calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+ calendars = self._calendarsOfType(caldavxml.calendar, component_type)
while calendars:
calendar = self.random.choice(calendars)
calendars.remove(calendar)
@@ -232,50 +228,42 @@
"""
pass
- """ Event-Interaction Profiles
-Event Creation - Eventer
-Event Changing - EventUpdaterBase
- TitlerMixin
- RelocaterMixin
- ReschedulerMixin
- RepeaterMixin
- AlerterMixin
- InviterMixin
- NoterMixin
- InviterMixin
- LinkerMixin
- AttacherMixin
-Event Deletion - EventerDeleter
-"""
#####################
-# Eventer Behaviors #
+# Eventer Hierarchy #
+# ----------------- #
+# EventBase #
+# Eventer #
+# EventUpdaterBase#
+# Titler #
+# Noter #
+# Linker #
+# Relocater #
+# Repeater #
+# Rescheduler #
+# ~Alerter~ #
+# Attacher #
+# InviterBase #
+# Inviter #
+# Relocater #
+# EventDeleter #
#####################
-class EventerBase(ProfileBase):
+class EventBase(ProfileBase):
"""
Base profile for a calendar user who interacts with events
"""
- def setParameters(
- self,
- enabled=True,
- interval=25,
- **params
- ):
- self.enabled = enabled
- self._interval = interval
- self.setDistributions(**params)
+ def _getRandomCalendar(self):
+ return self._getRandomCalendarOfType('VEVENT')
+ def _getRandomEvent(self):
+ return self._getRandomEventOfType('VEVENT')
-class Eventer(EventerBase):
+class Eventer(EventBase):
"""
A Calendar user who creates new events.
"""
- def initialize(self):
- self.action = self._addEvent
- return succeed(None)
-
def setDistributions(
self,
eventStartDistribution=NearFutureDistribution(),
@@ -285,30 +273,25 @@
120 * 60
])
):
- self._eventStartDistribution = eventStartDistribution
- self._eventDurationDistribution = eventDurationDistribution
+ """
+ @param eventStartDistribution: Generates datetimes at which an event starts
+ @param eventDurationDistribution: Generates length of event (in seconds)
+ """
+ self._eventStart = eventStartDistribution
+ self._eventDuration = eventDurationDistribution
def _addEvent(self):
- print "Hello a bit"
-
- if not self._client.started:
- return succeed(None)
-
-
-
+ # Choose a random calendar on which to add an event
calendar = self._getRandomCalendar()
if not calendar:
return succeed(None)
- print "Made it"
-
- # Copy the template event and fill in some of its fields
- # to make a new event to create on the calendar.
+ # Form a new event by modifying fields of the template event
vcalendar = eventTemplate.duplicate()
vevent = vcalendar.mainComponent()
uid = str(uuid4())
- dtstart = self._eventStartDistribution.sample()
- dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
+ dtstart = self._eventStart.sample()
+ dtend = dtstart + Duration(seconds=self._eventDuration.sample())
vevent.replaceProperty(Property("UID", uid))
vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
@@ -319,17 +302,18 @@
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("create", d)
+ return self._newOperation("create{event}", d)
+ action = _addEvent
+
# Could have better handling for not changing events once they're modified
# esp re: repeating
-class EventUpdaterBase(EventerBase):
+class EventUpdaterBase(EventBase):
"""Superclass of all event mixins.
Accepts two parameters
enabled: bool on or off
interval: distibution that generates integers representing delays
"""
- # COMPONENT_TYPE = None
def action(self):
event = self._getRandomEvent()
if not event:
@@ -346,7 +330,7 @@
return self._newOperation(label, d)
- def modifyEvent(self):
+ def modifyEvent(self, href, vevent):
"""Overriden by subclasses"""
pass
@@ -450,19 +434,21 @@
return "reschedule{event}"
# class Alerter(EventUpdaterBase):
+# component.replaceProperty(Property("ACKNOWLEDGED", DateTime.getNowUTC()))
# pass
class Attacher(EventUpdaterBase):
def setDistributions(
self,
+ filesizeDistribution=NormalDistribution(24, 3),
+ numAttachmentsDistribution=LogNormalDistribution(2, 1),
+ attachLikelihoodDistribution=BernoulliDistribution(0.9),
+
):
- # filesizeDistribution=NormalDistribution(24, 3),
- # numAttachmentsDistribution=LogNormalDistribution(2, 1),
- # attachLikelihoodDistribution=BernoulliDistribution(0.9)
- # self._filesize = filesizeDistribution
+ self._filesize = filesizeDistribution
# self._numAttachments = numAttachmentsDistribution
# self._attachLikelihood = attachLikelihoodDistribution
- pass
+ # pass
def modifyEvent(self, href, vevent):
d = self._client.postAttachment(href, 'x' * 1024)
@@ -487,405 +473,52 @@
def unattachFile(self):
pass
-class InviterBase(EventerBase):
- """
- Base profile for a calendar user that invites and deinvites other principals to events
- """
- def setParameters(
+class InviterBase(EventUpdaterBase):
+ def setDistributions(
self,
- enabled=True,
+ numInvitees=NormalDistribution(7, 2),
sendInvitationDistribution=NormalDistribution(600, 60),
- inviteeDistribution=UniformDiscreteDistribution(range(-10, 11)),
- **params
+ numInviteesDistribution=UniformDiscreteDistribution(range(-10, 11))
):
self.enabled = enabled
self._sendInvitationDistribution = sendInvitationDistribution
- self._inviteeDistribution = inviteeDistribution
- if len(params) > 0:
- pass
+ self._numInvitees = inviteeDistribution
- def getAttendees():
+ def _findUninvitedRecord(self, vevent):
pass
- # def _invitePrincipal(self, ...):
+ def _addAttendee(self, some_id):
+ attendeeProp = self._buildAttendee()
+ self._client.attendeeAutocomplete
- # def _uninvitePrincipal(self, ...):
-
-
-
-
- def _loopWithDistribution(reactor, distribution, function):
- result = Deferred()
-
- 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
-
# 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("utf-8"),
+ params={
+ 'CN': commonname,
+ 'CUTYPE': 'INDIVIDUAL' if isIndividual else 'ROOM',
+ 'PARTSTAT': 'NEEDS-ACTION',
+ 'ROLE': 'REQ-PARTICIPANT',
+ 'RSVP': 'TRUE',
+ },
+ )
- # def _buildIndividualAttendee(self, commonName, record, ):
+ def _getAttendees(self, vevent):
+ return vevent.properties('ATTENDEE')
- # # ATTENDEE;CN="Super User";CUTYPE=INDIVIDUAL;EMAIL="admin at example.com":mailto:admin at example.com
- # # ATTENDEE;CN="User 04";CUTYPE=INDIVIDUAL;EMAIL="user04 at example.com":mailto:user04 at example.com
-
- # role = ['REQ-PARTICIPANT', '']
- # attendee = Property(
- # name=u'ATTENDEE',
- # value=cuaddr.encode("utf-8"),
- # params={
- # 'CN': commonName,
- # 'CUTYPE': 'INDIVIDUAL',
- # 'PARTSTAT': 'NEEDS-ACTION',
- # 'ROLE': 'REQ-PARTICIPANT',
- # 'RSVP': 'TRUE',
- # },
- # )
- # return attendee
-
- # def _buildLocationAttendee(self, cn, cuaddr):
- # """
- # Example Usage: profile._buildLocationAttendee("Location 01", "urn:uuid:...")
- # """
- # role = ['REQ-PARTICIPANT', '']
- # attendee = Property(
- # name=u'ATTENDEE',
- # value=cuaddr.encode("utf-8"),
- # params={
- # 'CN': commonName,
- # 'CUTYPE': 'ROOM',
- # 'PARTSTAT': 'NEEDS-ACTION',
- # 'ROLE': 'REQ-PARTICIPANT',
- # 'RSVP': 'TRUE',
- # },
- # )
- # return attendee
-
def _invite():
raise NotImplementedError
def _addAttendee():
raise NotImplementedError
-# TODO - invite groups
-# class Inviter(EventUpdaterBase): pass
+class Inviter(InviterBase):
-class Relocater(EventUpdaterBase):
- def setParameters(
- self,
- ):
- pass
-
-class AppleEventer(EventerBase):
- """
- Calendar user who makes events in the form of Apple logo
- """
def initialize(self):
- self.action = self._addEvent
- return succeed(None)
-
- def setDistributions(
- self
- ):
- pass
-
- def _addEvent(self):
- if not self._client.started:
- return succeed(None)
-
- calendar = self._getRandomCalendar()
- if not calendar:
- return succeed(None)
-
- # Copy the template event and fill in some of its fields
- # to make a new event to create on the calendar.
- vcalendar = eventTemplate.duplicate()
- vevent = vcalendar.mainComponent()
- uid = str(uuid4())
- dtstart = self._eventStartDistribution.sample()
- dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
-
- vevent.replaceProperty(Property("UID", uid))
- vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTART", dtstart))
- vevent.replaceProperty(Property("DTEND", dtend))
-
- 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("create", d)
-
-class HappyEventer(EventerBase):
- """
- Calendar user who makes events in the form of Apple logo
- """
- def initialize(self):
- self.action = self._addEvent
- return succeed(None)
-
- def setDistributions(
- self
- ):
- self._eventStart = UniformDiscreteDistribution([
- # August 3
- DateTime(year=2015, month=8, day=3, hours=15, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=3, hours=15, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=3, hours=16, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=3, hours=16, minutes=30, seconds=0, tzid=None, utcoffset=None),
-
- # August 4
- DateTime(year=2015, month=8, day=4, hours=10, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=4, hours=10, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=4, hours=11, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=4, hours=11, minutes=30, seconds=0, tzid=None, utcoffset=None),
-
- DateTime(year=2015, month=8, day=4, hours=16, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=4, hours=16, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=4, hours=17, minutes=0, seconds=0, tzid=None, utcoffset=None),
-
- # August 5
- DateTime(year=2015, month=8, day=5, hours=13, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=5, hours=13, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=5, hours=17, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=5, hours=17, minutes=30, seconds=0, tzid=None, utcoffset=None),
-
- # August 6
- DateTime(year=2015, month=8, day=6, hours=10, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=6, hours=10, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=6, hours=11, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=6, hours=11, minutes=30, seconds=0, tzid=None, utcoffset=None),
-
- DateTime(year=2015, month=8, day=6, hours=16, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=6, hours=16, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=6, hours=17, minutes=0, seconds=0, tzid=None, utcoffset=None),
-
- # August 7
- DateTime(year=2015, month=8, day=7, hours=15, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=7, hours=15, minutes=30, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=7, hours=16, minutes=0, seconds=0, tzid=None, utcoffset=None),
- DateTime(year=2015, month=8, day=7, hours=16, minutes=30, seconds=0, tzid=None, utcoffset=None),
- ])
- self._eventDuration = UniformDiscreteDistribution([
- 30 * 60,
- 60 * 60
- ])
-
- def _addEvent(self):
- if not self._client.started:
- return succeed(None)
-
- calendar = self._getRandomCalendar()
- if not calendar:
- return succeed(None)
-
- # Copy the template event and fill in some of its fields
- # to make a new event to create on the calendar.
- vcalendar = eventTemplate.duplicate()
- vevent = vcalendar.mainComponent()
- uid = str(uuid4())
- dtstart = self._eventStart.sample()
- dtend = dtstart + Duration(seconds=self._eventDuration.sample())
-
- vevent.replaceProperty(Property("UID", uid))
- vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
-
- vevent.replaceProperty(Property("DTSTART", dtstart))
- vevent.replaceProperty(Property("DTEND", dtend))
-
- 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("create", d)
-
-
-
-# class EventDeleter(ProfileBase):
-
-
-
-""" TEST """
-# class Intern(object):
-# def __init__(self):
-# self.behaviors = [
-# Eventer(asdfjadsf),
-# Attacher(asjadsfjasdf),
-# Inviter(enabled=True, **params)
-# ]
-
-# def run(self):
-# deferreds = []
-# for behavior in self.behaviors:
-# deferreds.append(behavior.run())
-# return DeferredList(deferreds)
-
-
-
-####################
-# Class Hierarchy
-# ---------------
-# TaskBase
-# Tasker
-# TaskUpdaterBase
-# Titler
-# Alerter
-# Noter
-# Prioritizer
-# Completer
-# TaskDeleter
-####################
-class TaskBase(ProfileBase):
- """
- Base profile for a calendar user who interacts with tasks
- """
- def setParameters(self, enabled=True, interval=25, **params):
- self.enabled = enabled
- self._interval = interval
-
-class Tasker(TaskBase):
- """
- A Calendar user who creates new tasks.
- """
- def initialize(self):
- self.action = self._addTask
- return succeed(None)
-
- def _addTask(self, title="Simple Task"):
- if not self._client.started:
- return succeed(None)
-
- calendars = self._calendarsOfType(caldavxml.calendar, "VTODO")
-
- while calendars:
- calendar = self.random.choice(calendars)
- calendars.remove(calendar)
-
- # Copy the template task and fill in some of its fields
- # to make a new task to create on the calendar.
- vcalendar = taskTemplate.duplicate()
- vtodo = vcalendar.mainComponent()
- uid = str(uuid4())
- vtodo.replaceProperty(Property("UID", uid))
- vtodo.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
- vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
-
- href = '%s%s.ics' % (calendar.url, uid)
- d = self._client.addEvent(href, vcalendar)
- return self._newOperation("create", d)
-
-
-class TaskUpdaterBase(TaskBase):
- def action(self):
- task = self._getRandomTask()
- if not task:
- return succeed(None)
- component = task.component
- vtodo = component.mainComponent()
-
- label = self.modifyEvent(task.url, vtodo)
- vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
-
- task.component = component
- d = self._client.updateEvent(task)
- return self._newOperation(label, d)
-
- def modifyEvent(self):
- """Overriden by subclasses"""
- pass
-
-
-
-
-class TaskUpdater(TaskBase):
- """
- A Calendar user who creates and updates complex tasks with:
- Mark as completed/not completed
- Change name
- Change priority
- Change notes
- Sets due dates
- """
-
- def setParameters(
- self,
- taskDueDistribution=NearFutureDistribution(),
- ):
- pass
-
-
- def _addTask(self):
- if not self._client.started:
- return succeed(None)
-
- calendars = self._calendarsOfType(caldavxml.calendar, "VTODO")
-
- while calendars:
- calendar = self.random.choice(calendars)
- calendars.remove(calendar)
-
- # Copy the template task and fill in some of its fields
- # to make a new task to create on the calendar.
- vcalendar = taskTemplate.duplicate()
- vtodo = vcalendar.mainComponent()
- uid = str(uuid4())
- due = self._taskStartDistribution.sample()
- vtodo.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
- vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
- vtodo.replaceProperty(Property("UID", uid))
-
- # vtodo.replaceProperty(Property("SUMMARY", title))
-
- href = '%s%s.ics' % (calendar.url, uid)
- d = self._client.addEvent(href, vcalendar)
- return self._newOperation("create", d)
-
-
-
-# class TaskTitlerMixin = TitlerMixin
-# class TaskAlerterMixin = AlerterMixin
-# self._taskStartDistribution = taskDueDistribution
-# vtodo.replaceProperty(Property("DUE", due))
-# class TaskNoterMixin = NoterMixin
-
-class Prioritizer(TaskBase):
- PRIORITY_NONE = 0
- PRIORITY_HIGH = 1
- PRIORITY_MEDIUM = 5
- PRIORITY_LOW = 9
-
- def _setPriority(self, priority, vtodo):
- vtodo.replaceProperty(Property("PRIORITY", priority))
-
-class Completer(TaskBase):
- def _markTaskComplete(vtodo):
- """ task is a Component representing a VTODO """
- vtodo.replaceProperty(Property("COMPLETED", DateTime.getNowUTC()))
- vtodo.replaceProperty(Property("PERCENT-COMPLETE", 100))
- vtodo.replaceProperty(Property("STATUS", "COMPLETED"))
-
-
- def _markTaskIncomplete(vtodo):
- """ mark a VTODO as incomplete """
- vtodo.removeProperty("COMPLETED")
- vtodo.removeProperty("PERCENT-COMPLETE")
- vtodo.replaceProperty(Property("STATUS", "NEEDS-ACTION"))
-
-# class TaskDeleter(ProfileBase): pass
-
-
-class Inviter(ProfileBase):
-
- def initialize(self):
self.action = self.test
return succeed(None)
@@ -1014,135 +647,198 @@
# Oops, either no events or no calendars to play with.
return succeed(None)
+class Relocater(InviterBase):
+ def setDistributions(
+ self,
+ ):
+ pass
+class EventDeleter(EventBase):
+ """
+ A calendar user who deletes events at random
+ """
+ def _deleteEvent(self):
+ event = self._getRandomEvent()
+ if event is None:
+ return succeed(None)
+ d = self._client.deleteEvent(event.url)
+ return self._newOperation("delete{event}", d)
-class RealisticInviter(ProfileBase):
+ action = _deleteEvent
+
+
+
+
+""" TEST """
+# class Intern(object):
+# def __init__(self):
+# self.behaviors = [
+# Eventer(asdfjadsf),
+# Attacher(asjadsfjasdf),
+# Inviter(enabled=True, **params)
+# ]
+
+# def run(self):
+# deferreds = []
+# for behavior in self.behaviors:
+# deferreds.append(behavior.run())
+# return DeferredList(deferreds)
+
+
+
+####################
+# Tasker Hierarchy #
+# ---------------- #
+# TaskBase #
+# Tasker #
+# TaskDeleter #
+# TaskUpdaterBase#
+# Titler #
+# Noter #
+# Prioritizer #
+# Completer #
+# Alerter #
+####################
+
+
+class TaskBase(ProfileBase):
"""
- A Calendar user who invites other users to new events.
+ Base profile for a calendar user who interacts with tasks
"""
- 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
+ def _getRandomCalendar(self):
+ return self._getRandomCalendarOfType('VTODO')
+ def _getRandomEvent(self):
+ return self._getRandomEventOfType('VTODO')
- def _addAttendee(self, event, attendees):
- """
- Create a new attendee to add to the list of attendees for the
- given event.
- """
- selfRecord = self._sim.getUserRecord(self._number)
- invitees = set([u'mailto:%s' % (selfRecord.email,)])
- for att in attendees:
- invitees.add(att.value())
- for _ignore_i in range(10):
+class Tasker(TaskBase):
+ """
+ A Calendar user who creates new tasks.
+ """
+ def _addTask(self):
+ calendar = self._getRandomCalendar()
+ if not calendar:
+ return succeed(None)
- sample = self._inviteeDistribution.sample()
- if self._inviteeClumping:
- sample = self._number + sample
- invitee = max(0, sample)
+ # Form a new event by modifying fields of the template event
+ vcalendar = taskTemplate.duplicate()
+ vtodo = vcalendar.mainComponent()
+ uid = str(uuid4())
- try:
- record = self._sim.getUserRecord(invitee)
- except IndexError:
- continue
- cuaddr = u'mailto:%s' % (record.email,)
- if cuaddr not in invitees:
- break
- else:
- raise CannotAddAttendee("Can't find uninvited user to invite.")
+ vtodo.replaceProperty(Property("UID", uid))
+ vtodo.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
+ vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
- attendee = Property(
- name=u'ATTENDEE',
- value=cuaddr.encode("utf-8"),
- params={
- 'CN': record.commonName,
- 'CUTYPE': 'INDIVIDUAL',
- 'PARTSTAT': 'NEEDS-ACTION',
- 'ROLE': 'REQ-PARTICIPANT',
- 'RSVP': 'TRUE',
- },
- )
+ 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("create{task}", d)
- event.addProperty(attendee)
- attendees.append(attendee)
+ action = _addTask
+class TaskDeleter(TaskBase):
+ def _deleteTask(self):
+ event = self._getRandomEvent()
+ if event is None:
+ return succeed(None)
- def _invite(self):
- """
- Try to add a new event, or perhaps remove an
- existing attendee from an event.
+ d = self._client.deleteEvent(event.url)
+ return self._newOperation("delete{task}", d)
- @return: C{None} if there are no events to play with,
- otherwise a L{Deferred} which fires when the attendee
- change has been made.
- """
+ action = _deleteTask
- if not self._client.started:
+
+class TaskUpdaterBase(TaskBase):
+ def action(self):
+ task = self._getRandomEvent()
+ if not task:
return succeed(None)
+ component = task.component
+ vtodo = component.mainComponent()
- # Find calendars which are eligible for invites
- calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+ label = self.modifyEvent(task.url, vtodo)
+ vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
- while calendars:
- # Pick one at random from which to try to create an event
- # to modify.
- calendar = self.random.choice(calendars)
- calendars.remove(calendar)
+ task.component = component
+ d = self._client.updateEvent(task, method_label="update{task}")
+ return self._newOperation(label, d)
- # Copy the template event and fill in some of its fields
- # to make a new event to create on the calendar.
- vcalendar = eventTemplate.duplicate()
- vevent = vcalendar.mainComponent()
- uid = str(uuid4())
- dtstart = self._eventStartDistribution.sample()
- dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
- vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTART", dtstart))
- vevent.replaceProperty(Property("DTEND", dtend))
- vevent.replaceProperty(Property("UID", uid))
+ def modifyEvent(self, href, vtodo):
+ """Overriden by subclasses"""
+ pass
- rrule = self._recurrenceDistribution.sample()
- if rrule is not None:
- vevent.addProperty(Property(None, None, None, pycalendar=rrule))
- vevent.addProperty(self._client._makeSelfOrganizer())
- vevent.addProperty(self._client._makeSelfAttendee())
+class TaskTitler(TaskUpdaterBase, Titler):
+ """
+ Changes the SUMMARY of a random VTODO
+ """
+ def modifyEvent(self, _ignore_href, vtodo):
+ vtodo.replaceProperty(Property("SUMMARY", "." * 5))
+ return "update{title}"
- attendees = list(vevent.properties('ATTENDEE'))
- for _ignore in range(int(self._inviteeCountDistribution.sample())):
- try:
- self._addAttendee(vevent, attendees)
- except CannotAddAttendee:
- self._failedOperation("invite", "Cannot add attendee")
- return succeed(None)
+class TaskNoter(TaskUpdaterBase, Noter):
+ """
+ Changes the NOTES of a random VTODO
+ """
+ def modifyEvent(self, _ignore_href, vtodo):
+ vtodo.replaceProperty(Property("DESCRIPTION", "." * 5))
+ return "update{notes}"
- href = '%s%s.ics' % (calendar.url, uid)
- d = self._client.addInvite(href, vcalendar)
- return self._newOperation("invite", d)
+# class TaskAlerterMixin = AlerterMixin (alarm AND due)
+# self._taskStartDistribution = taskDueDistribution
+# vtodo.replaceProperty(Property("DUE", 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):
+ """ Set the PRIORITY of a VTODO """
+ vtodo.replaceProperty(Property("PRIORITY", 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):
+ """ Mark a VTODO as complete """
+ vtodo.replaceProperty(Property("COMPLETED", DateTime.getNowUTC()))
+ vtodo.replaceProperty(Property("PERCENT-COMPLETE", 100))
+ vtodo.replaceProperty(Property("STATUS", "COMPLETED"))
+
+ def _markTaskIncomplete(self, vtodo):
+ """ Mark a VTODO as incomplete """
+ vtodo.removeProperty("COMPLETED")
+ vtodo.removeProperty("PERCENT-COMPLETE")
+ vtodo.replaceProperty(Property("STATUS", "NEEDS-ACTION"))
+
+
##########################
# Notification Behaviors #
##########################
@@ -1151,7 +847,7 @@
A Calendar user who accepts invitations to events. As well as accepting requests, this
will also remove cancels and replies.
"""
- def setParameters(
+ def setDistributions(
self,
enabled=True,
acceptDelayDistribution=NormalDistribution(1200, 60)
@@ -1298,107 +994,6 @@
-class EventUpdater(EventerBase):
- """
- A Calendar user who creates a new event, and then updates its alarm.
- """
- def initialize(self):
- """
- Called before the profile runs for real. Can be used to initialize client state.
-
- @return: a L{Deferred} that fires when initialization is done
- """
- self.action = self._updateEvent
- return self._initEvent()
-
-
- def _initEvent(self):
- if not self._client.started:
- return succeed(None)
-
- # If it already exists, don't re-create
- calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
- if calendar.events:
- events = [event for event in calendar.events.values() if event.url.endswith("event_to_update.ics")]
- if events:
- return succeed(None)
-
- # 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("CREATED", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
- vevent.replaceProperty(Property("DTSTART", dtstart))
- vevent.replaceProperty(Property("DTEND", dtend))
- vevent.replaceProperty(Property("UID", uid))
-
- rrule = self._recurrenceDistribution.sample()
- if rrule is not None:
- vevent.addProperty(Property(None, None, None, pycalendar=rrule))
-
- href = '%s%s' % (calendar.url, "event_to_update.ics")
- d = self._client.addEvent(href, vcalendar)
- return self._newOperation("create", d)
-
-
- def _updateEvent(self):
- """
- Try to add a new attendee to an event, or perhaps remove an
- existing attendee from an event.
-
- @return: C{None} if there are no events to play with,
- otherwise a L{Deferred} which fires when the attendee
- change has been made.
- """
-
- if not self._client.started:
- return succeed(None)
-
- # If it does not exist, try to create it
- calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
- if not calendar.events:
- return self._initEvent()
- events = [event for event in calendar.events.values() if event.url.endswith("event_to_update.ics")]
- if not events:
- return self._initEvent()
- event = events[0]
-
- # Add/update the ACKNOWLEDGED property
- component = event.component.mainComponent()
- component.replaceProperty(Property("ACKNOWLEDGED", DateTime.getNowUTC()))
- d = self._client.changeEvent(event.url)
- return self._newOperation("update", d)
-
- # def _changeEventTitle(self, event, title):
- # event.component = self._setEventTitle(event.component, title)
- # event.
-
-
-
-
-
-class EventDeleter(EventerBase):
- """
- A calendar user who deletes events at random
- """
- def initialize(self):
- self.action = self._deleteEvent
-
- def _deleteEvent(self):
- event = self._getRandomEvent()
- if event is None:
- return succeed(None)
- d = self._client.deleteEvent(event)
- return self._newOperation("delete", d)
-
-
-
-
-
######################
# Calendar Behaviors #
######################
@@ -1411,7 +1006,7 @@
return succeed(None)
- def setParameters(self, enabled=True, interval=25):
+ def setDistributions(self, enabled=True, interval=25):
self.enabled = enabled
self._interval = interval
@@ -1419,26 +1014,26 @@
class CalendarMaker(CalendarBase):
""" A Calendar user who adds new Calendars """
- def initialize(self):
- self.action = self._addCalendar
- return succeed(None)
-
def _addCalendar(self):
- if not self._client.started:
- return None
+ print "Adding a calendar"
+ # if not self._client.started:
+ # return None
- uid = str(uuid4())
+ # uid = str(uuid4())
- body = Calendar.buildCalendarXML(order=0, component_type="VEVENT", rgba_color='FB524FFF', name='Sample Calendar')
- print("Making new calendar with uid: " + uid)
- # XXX Just for testing! remove this soon
- path = "/calendars/__uids__/" + self._client.record.guid + "/" + uid + "/"
- d = self._client.addCalendar(path, body)
+ # body = Calendar.buildCalendarXML(order=0, component_type="VEVENT", rgba_color='FB524FFF', name='Sample Calendar')
+ # print("Making new calendar with uid: " + uid)
+ # # XXX Just for testing! remove this soon
+ # path = "/calendars/__uids__/" + self._client.record.guid + "/" + uid + "/"
+ # d = self._client.addCalendar(path, body)
+ d = succeed('calendar created')
return self._newOperation("create", d)
+ action = _addCalendar
+
class CalendarUpdater(CalendarBase):
"""
A calendar user who updates random calendars
@@ -1545,3 +1140,16 @@
print("Deleting " + calendar.url)
d = self._client.deleteCalendar(calendar.url)
return self._newOperation("delete", d)
+
+if __name__ == '__main__':
+ class TestProfile(ProfileBase):
+ def sayHello(self):
+ print("Hello!")
+ action = sayHello
+
+ from twisted.internet import reactor
+
+ profile = TestProfile(enabled=True, interval=1)
+ profile.setUp(reactor, None, None, None)
+ profile.run()
+ reactor.run()
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/push.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/push.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/push.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -1,6 +1,10 @@
-from calendarserver.push.amppush import subscribeToIDs
-from twisted.internet.defer import succeed
+import uuid
+from twisted.internet.endpoints import TCP4ClientEndpoint
+from twisted.internet.defer import inlineCallbacks
+
+from calendarserver.push.amppush import SubscribeToID, UnsubscribeFromID, AMPPushClientFactory
+
class PushMonitor(object):
"""
Watchguard that monitors push notifications (AMP Push)
@@ -21,71 +25,107 @@
@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 hrefupon receipt of a push notification
+ with a calendar href upon receipt of a push notification
for that resource
@type callback: one-argument callable
"""
+
+ 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 calendar home URLs as keys and pushkeys as
- # values.
+ # 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):
"""
Start monitoring for AMP-based push notifications
"""
- self._subscribeToIDs(self.ampPushkeys)
+ self._protocol = yield self._endpoint.connect(self._factory)
+ self._connected = True
+ pushkeys = self._ampPushkeys.keys()
+ yield self._subscribeToPushkeys(pushkeys)
+ @inlineCallbacks
def end(self):
"""
- Finish monitoring push notifications. Any other cleanup should be done here
+ Finish monitoring push notifications.
"""
- self._unsubscribeFromAll()
+ pushkeys = self._ampPushkeys.keys()
+ self._ampPushkeys = {}
+ yield self._unsubscribeFromPushkeys(pushkeys)
- def _subscribeToIDs(self, ids):
+ # Close the connection between client and server
+ yield self._protocol.transport.loseConnection()
+ self._connected = False
- subscribeToIDs(
- self._ampPushHost,
- self._ampPushPort,
- ids,
- self._receivedAmpPush,
- self._reactor
- )
- def _receivedAMPPush(self, inboundID, dataChangedTimestamp, priority=5):
- print("-" * 64)
- print("{} received a PUSH with ID={}, timestamp={}, priority={}".format(self.record.commonName, inboundID, dataChangedTimestamp, priority))
- print("By the way, my AMP keys are {}".format(self._ampPushkeys))
- print("-" * 64)
+ def addPushkey(self, pushkey, href):
+ """
+ Register a pushkey associated with a specific calendar href.
- for href, calendar_id in self.ampPushkeys.iteritems():
- if inboundID == calendar_id:
- self.callback(href)
- break
- else:
- # Somehow we are not subscribed to this inboundID
- print("*" * 16 + "Oh no - we're not subscribed to " + str(inboundID) + " but we received a notification anyway!")
- pass
+ @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}
- def _unsubscribeFromAll(self):
- # For now, the server doesn't support unsubscribing from pushkeys, so we simply
- # "forget" about our registered pushkeys
- self._ampPushkeys = {}
+ Example Usage:
+ monitor.addPushkey('/CalDAV/localhost/<uid>', '/calendars/__uids__/<uid>')
+ """
+ self._ampPushkeys[pushkey] = href
+ if self._connected:
+ return self._subscribeToPushkey(pushkey)
+ def removePushkey(self, pushkey):
+ """
+ Unregister the calendar home associated with the specified pushkey
+ """
+ if pushkey in self._ampPushkeys:
+ del self._ampPushkeys[pushkey]
+ if self._connected:
+ return self._unsubscribeFromPushkey(pushkey)
- def addPushkey(self, href, pushkey):
- self._ampPushkeys[href] = pushkey
- self.subscribeToIDs()
+ def isSubscribedTo(self, href):
+ """
+ Returns true if and only if the given calendar href is actively being monitored
+ """
+ return href in self._ampPushkeys.itervalues()
+ @inlineCallbacks
+ def _subscribeToPushkeys(self, pushkeys):
+ for pushkey in pushkeys:
+ yield self._subscribeToPushkey(pushkey)
- def removePushkey(self, pushkey):
- # if self.ampPushkeys.has_value(pushkey):
- # del self.ampPushKeys
- pass
+ @inlineCallbacks
+ def _unsubscribeFromPushkeys(self, pushkeys):
+ for pushkey in pushkeys:
+ yield self._unsubscribeFromPushkey(pushkey)
- def isSubscribedTo(self, href):
- return href in self.ampPushkeys
+ def _subscribeToPushkey(self, pushkey):
+ return self._protocol.callRemote(SubscribeToID, token=self._token, id=pushkey)
+
+ def _unsubscribeFromPushkey(self, pushkey):
+ 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
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/records.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/records.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/records.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -11,6 +11,19 @@
def __repr__(self):
return "Record(%s:%s %s %s %s)" % (self.uid, self.password, self.commonName, self.email, self.guid)
+# def generateRecords(
+# count, uidPattern="user%d", passwordPattern="user%d",
+# namePattern="User %d", emailPattern="user%d at example.com",
+# guidPattern="user%d"
+# ):
+# 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:
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/requester.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/requester.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/requester.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -24,7 +24,7 @@
class Requester(object):
"""
Utility to create requests on behalf of a client. Public methods are:
- method url body headers status method_label
+ method url body headers status method_label
------------------------------------------------------------------------
GET req ---
POST req req
@@ -100,6 +100,7 @@
client_id=self._client_id,
)
+
before = self._reactor.seconds()
response = yield self._agent.request(method, url, headers, StringProducer(body) if body else None)
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/resources.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/resources.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/resources.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -114,13 +114,6 @@
self.url = url
self.changeToken = changeToken
self.events = {}
- # print("----\nNew Calendar")
- # print("Resource Type: ", self.resourceType)
- # print("Component Types: ", self.componentTypes)
- # print("Name: ", self.name)
- # print("URL: ", self.url)
- # print("Change Token: ", self.changeToken)
- # print("Events: ", self.events)
def serialize(self):
@@ -156,20 +149,7 @@
calendar.changeToken = ""
return calendar
- @staticmethod
- def buildCalendarXML(order=0, component_type='VEVENT', rgba_color='FB524FFF', name='Simple Calendar'):
- # TODO add timezone information
- # MakeCalendar(None, '/', name, )
-
- # body = _STARTUP_CREATE_CALENDAR.format(
- # order=order,
- # component_type=component_type,
- # color=rgba_color,
- # name=name)
- # return body
- return ""
-
@staticmethod
def addInviteeXML(uid, summary, readwrite=True):
return AddInvitees(None, '/', [uid], readwrite, summary=summary).request_data.text
@@ -231,11 +211,3 @@
el = ElementTree.Element(qn)
el.text = order
return Calendar._buildPropPatchXML(el)
-
- # @inlineCallbacks
- # def setCalendarProperty(self, calendar):
-
- # def do_stuff(...):
- # body =
- # yield self.requester.proppatch(href, body, method_label="PROPPATCH{calendar}")
- #
\ No newline at end of file
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/alt-settings/plist/clients.plist 2015-08-31 20:37:45 UTC (rev 15081)
@@ -38,7 +38,7 @@
<dict>
<!-- Name that appears in logs. -->
<key>title</key>
- <string>10.11 Intern</string>
+ <string>10.11</string>
<!-- OS_X_10_7 can poll the calendar home at some interval. This is
in seconds. -->
@@ -64,7 +64,7 @@
<array>
<dict>
<key>class</key>
- <string>contrib.performance.loadtest.profiles.Inviter</string>
+ <string>contrib.performance.loadtest.profiles.Eventer</string>
<key>params</key>
<dict>
@@ -74,7 +74,7 @@
<!-- Define the interval (in seconds) at which this profile will use
its client to create a new event. -->
<key>interval</key>
- <integer>1</integer>
+ <integer>2</integer>
</dict>
</dict>
</array>
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/clients.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/clients.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/clients.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -1,35 +1,26 @@
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 CalendarMaker, CalendarUpdater, CalendarSharer, CalendarDeleter
-from contrib.performance.loadtest.population import ProfileType
+from contrib.performance.loadtest.profiles import (
+ Eventer, EventDeleter,
+ Titler,
+ Tasker, TaskDeleter,
+ TaskTitler, TaskNoter, Completer, Prioritizer,
-from preset_distributions import STANDARD_WORK_DISTRIBUTION, LOW_RECURRENCE_DISTRIBUTION, MEDIUM_RECURRENCE_DISTRIBUTION
+ CalendarMaker, CalendarUpdater, CalendarSharer, CalendarDeleter
+)
+from contrib.performance.loadtest.distributions import FixedDistribution, BernoulliDistribution
-# We have to roll our own deep copy method because you can't deep copy Twisted's reactor
-class ClientFactory(object):
- def __init__(self, client, weight):
- pass
+from preset_distributions import STANDARD_WORK_DISTRIBUTION, LOW_RECURRENCE_DISTRIBUTION, MEDIUM_RECURRENCE_DISTRIBUTION
- @staticmethod
- def _duplicateClient(client):
- return type(client)(
- # some params
- )
-
- def new(reactor, ):
- pass
-
-class ProfileFactory(object):
- def __init__(self, profile):
- pass
-
- @staticmethod
- def _duplicateProfile(profile):
- return type(profile)()
-
-calendars_only = [
+config = [
{
"software": OS_X_10_11,
+ # title="10.11",
+ # calendarHomePollInterval=5,
+ # supportAmpPush=True,
+ # ampPushHost="localhost",
+ # ampPushPort62311
+ # )
"params": {
"title": "10.11",
"calendarHomePollInterval": 5,
@@ -38,33 +29,41 @@
"ampPushPort": 62311
},
"profiles": [
- ProfileType(CalendarMaker, dict(enabled=True, interval=15)),
+ # Eventer(enabled=True, interval=0.01, eventStartDistribution=STANDARD_WORK_DISTRIBUTION),
+ # Titler(enabled=True, interval=1, titleLengthDistribution=FixedDistribution(10)),
- # CalendarMaker(enabled=True, interval=15),
+ # 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)
],
- "weight": 1
+ "weight": 3
}
]
-# TBD what about multiple weights?
-calendars_only_ideal = [
- OS_X_10_11(
- title="10.11",
- calendarHomePollInterval=5,
- supportAmpPush=True,
- ampPushHost="localhost",
- ampPushPort=62311,
- profiles=[
- CalendarMaker(enabled=True, interval=15),
- # CalendarUpdater(enabled=True, interval=5),
- # CalendarSharer(enabled=False, interval=30),
- # CalendarDeleter(enabled=False, interval=30)
- ]
- )
-]
+# # TBD what about multiple weights?
+# calendars_only_ideal = [
+# OS_X_10_11(
+# title="10.11",
+# calendarHomePollInterval=5,
+# supportAmpPush=True,
+# ampPushHost="localhost",
+# 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 = [
# {
@@ -111,7 +110,3 @@
# "weight": 1
# }
# ]
-
-
-# clientConfiguration = calendars_only
-# __all__ = [clientConfiguration]
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/config.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/config.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/settings/config.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -1,22 +1,13 @@
-# Generally, the defaults are good enough for us.
+from contrib.performance.loadtest.settings.defaults import arrival, requestLogger, operationLogger, statisticsReporter, accounts
-config = dict(
- server=server,
- webadminPort=8080,
- serverStatsPort=8100,
- serializationPath='/tmp/sim',
- arrival=arrival,
- observers=[_requestLogger, _operationLogger, _statisticsReporter],
- records=accounts
-)
+from contrib.performance.loadtest.logger import EverythingLogger, MessageLogger
-config_dist = dict(
- server=server,
- webadminPort=8080,
- serverStatsPort=8100,
- serializationPath='/tmp/sim',
- arrival=arrival,
- observers=[_requestLogger, _operationLogger, _statisticsReporter],
- records=accounts,
- workers=["./bin/python contrib/performance/loadtest/ampsim.py"] * 6,
-)
\ No newline at end of file
+config = {
+ "server": 'https://127.0.0.1:8443',
+ "webadminPort": 8080,
+ "serverStatsPort": {'server': 'localhost', 'Port': 8100},
+ "serializationPath": '/tmp/sim',
+ "arrival": arrival,
+ "observers": [requestLogger, operationLogger, statisticsReporter, EverythingLogger(), MessageLogger()],
+ "records": accounts
+}
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/sim.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/sim.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -48,8 +48,9 @@
from contrib.performance.loadtest.clients import OS_X_10_6
from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
from contrib.performance.loadtest.population import (
- Populator, ProfileType, ClientType, PopulationParameters, SmoothRampUp,
+ ClientFactory, PopulationParameters, SmoothRampUp,
CalendarClientSimulator)
+from contrib.performance.loadtest.config import Config
from contrib.performance.loadtest.webadmin import LoadSimAdminResource
def safeDivision(value, total, factor=1):
@@ -88,19 +89,30 @@
"""
config = None
settings = FilePath(__file__).sibling("settings")
- _defaultConfig = settings.child("config.plist")
- _defaultClients = settings.child("clients.plist")
+ # plists = settings.child('alt-settings').child('plist')
+ # _defaultConfig = plists.child("config.plist")
+ # _defaultClients = plists.child("clients.plist")
+ # _defaultConfig = settings.child('settings.config')
+ # _defaultClients = settings.child('settings.clients')
+ _defaultConfig = 'contrib.performance.loadtest.settings.config'
+ _defaultClients = 'contrib.performance.loadtest.settings.clients'
+ optFlags = [
+ ("debug", "d", "Enable Deferred and Failure debugging"),
+ ("debug-deferred", None, "Enable only Deferred debugging"),
+ ("debug-failure", None, "Enable only Failure debugging"),
+ ("use-plist", None, "Interpret configuration files as XML property lists (default false)")
+ ]
+
optParameters = [
("runtime", "t", None,
"Specify the limit (seconds) on the time to run the simulation.",
int),
("config", None, _defaultConfig,
- "Configuration plist file name from which to read simulation parameters.",
- FilePath),
+ "Configuration plist file name from which to read simulation parameters."),
("clients", None, _defaultClients,
- "Configuration plist file name from which to read client parameters.",
- FilePath),
+ "Configuration plist file name from which to read client parameters."),
+ ("logfile", "l", '-', FilePath)
]
@@ -140,40 +152,48 @@
def postOptions(self):
- try:
- configFile = self['config'].open()
- except IOError, e:
- raise UsageError("--config %s: %s" % (
- self['config'].path, e.strerror))
- try:
+ """
+ Convert the given configuration files to dictionaries, respectively in
+ self.config and self.clients
+ """
+ configPath = self["config"]
+ clientsPath = self["clients"]
+ if self['use-plist']:
try:
- self.config = readPlist(configFile)
+ with open(configPath) as configFile: # Could raise an IOError
+ self.config = readPlist(configFile) # Could raise an ExpatError
+ except IOError, e:
+ raise UsageError("--config %s: %s" % (configPath.path, e.strerror))
except ExpatError, e:
- raise UsageError("--config %s: %s" % (self['config'].path, e))
- finally:
- configFile.close()
+ raise UsageError("--config %s: %s" % (configPath.path, e))
- try:
- # from importlib import import_module
- # client_config = import_module("contrib.performance.loadtest.settings.clients")
- 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.calendars_only# client_config["clients"]
- self.config["clients"] = client_config["clients"]
- if "arrivalInterval" in client_config:
- self.config["arrival"]["params"]["interval"] = client_config["arrivalInterval"]
+ with open(clientsPath) as clientFile: # Could raise an IOError
+ self.clients = readPlist(clientFile) # Could raise an ExpatError
+ except IOError, e:
+ raise UsageError("--clients %s: %s" % (clientsPath.path, e.strerror))
except ExpatError, e:
- raise UsageError("--clients %s: %s" % (self['clients'].path, e))
- finally:
- # clientFile.close()
- pass
+ raise UsageError("--clients %s: %s" % (clientsPath.path, e))
+ self.config["clients"] = self.clients["clients"]
+ 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("--config %s: %s" % (configPath, e))
+ try:
+ clients = import_module(clientsPath) # Could raise ImportError
+ self.clients = clients.config # Could raise attribute error
+ except (ImportError, AttributeError), e:
+ raise UsageError("--clients %s: %s" % (clientsPath, e))
+
+ self.configObj = Config()
+ self.configObj.populateFrom(self.config, self.clients)
+
+
Arrival = namedtuple('Arrival', 'factory parameters')
@@ -194,6 +214,8 @@
def __init__(self, server, webadminPort, serverStats, serializationPath, arrival, parameters, observers=None,
records=None, reactor=None, runtime=None, workers=None,
configTemplate=None, workerID=None, workerCount=1):
+ if configTemplate == {}:
+ raise Exception('Got here!')
if reactor is None:
from twisted.internet import reactor
self.server = server
@@ -224,7 +246,7 @@
except UsageError, e:
raise SystemExit(str(e))
- return cls.fromConfig(options.config, options['runtime'], output)
+ return cls.fromConfigObject(options.configObj, options['runtime'], output)
@classmethod
@@ -338,7 +360,6 @@
observers=observers,
records=records,
runtime=runtime,
- reactor=reactor,
workers=workers,
configTemplate=configTemplate,
workerID=workerID,
@@ -348,138 +369,47 @@
@classmethod
def fromConfigObject(cls, config, runtime=None, output=stdout):
- workers = config['workers']
- if workers is None:
- # Client / place where the simulator actually runs configuration
- workerID = config.get("workerID", 0)
- workerCount = config.get("workerCount", 1)
- configTemplate = None
- server = config.get('server', 'http://127.0.0.1:8008')
- serializationPath = None
+ # if config.isManaging:
+ # observers =
- serializationPath = config['serializationPath']
+ # if config.isWorking:
+ # simulator = CalendarClientSimulator(
+ # config.records,
+ # config.parameters,
+ # config.reactor,
+ # config.server,
+ # config.serializationPath,
+ # config.workerID,
+ # config.workerCount,
+ # )
+ # arrival = config.arrival
- 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["weight"],
- ClientType(
- clientConfig["software"],
- clientConfig["params"],
- clientConfig["profiles"]
- )
- )
- # ClientType(
- # namedAny(clientConfig["software"]),
- # cls._convertParams(clientConfig["params"]),
- # [
- # ProfileType(
- # namedAny(profile["class"]),
- # cls._convertParams(profile["params"])
- # ) for profile in clientConfig["profiles"]
- # ]))
- 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["type"]
- observerParams = observer["params"]
- 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("Loaded {0} accounts.\n".format(len(records)))
-
return cls(
- server,
- webadminPort,
- serverStats,
- serializationPath,
- arrival,
- parameters,
- observers=observers,
- records=records,
+ config.server,
+ config.webadminPort,
+ config.serverStats,
+ config.serializationPath,
+ config.arrival,
+ config.parameters,
+ observers=config.observers,
+ records=config.records,
runtime=runtime,
- reactor=reactor,
- workers=workers,
- configTemplate=configTemplate,
- workerID=workerID,
- workerCount=workerCount,
+ configTemplate=config.serializeForWorker,
+ workers=config.workers,
+ workerID=config.workerID,
+ workerCount=config.workerCount,
)
- @classmethod
- def _convertParams(cls, params):
- """
- Find parameter values which should be more structured than plistlib is
- capable of constructing and replace them with the more structured form.
- Specifically, find keys that end with C{"Distribution"} and convert
- them into some kind of distribution object using the associated
- dictionary of keyword arguments.
- """
- for k, v in params.iteritems():
- if k.endswith('Distribution'):
- params[k] = cls._convertDistribution(v)
- return params
-
-
@classmethod
- def _convertDistribution(cls, value):
- """
- Construct and return a new distribution object using the type and
- params specified by C{value}.
- """
- return namedAny(value['type'])(**value['params'])
-
-
- @classmethod
def main(cls, args=None):
simulator = cls.fromCommandLine(args)
raise SystemExit(simulator.run())
def createSimulator(self):
- populator = Populator(Random())
return CalendarClientSimulator(
self.records,
- populator,
self.parameters,
self.reactor,
self.server,
@@ -491,7 +421,8 @@
def createArrivalPolicy(self):
# print(self.arrival.parameters)
- return self.arrival.factory(self.reactor, **self.arrival.parameters)
+ # return zory(self.reactor, **self.arrival.parameters)
+ return self.arrival
def serviceClasses(self):
@@ -503,6 +434,7 @@
PrimaryService = WorkerSpawnerService
else:
PrimaryService = SimulatorService
+
return [
ObserverService,
ReporterService,
@@ -575,6 +507,7 @@
data = ""
while not data.endswith("\n"):
d = s.recv(1024)
+ print("Received data from stats socket: ", d)
if d:
data += d
else:
@@ -622,6 +555,9 @@
"""
super(ObserverService, self).startService()
for obs in self.loadsim.observers:
+ import random
+ val = random.random()
+ msg(type='log', text='Adding observer: ' + obs.__class__.__name__, val=str(val))
addObserver(obs.observe)
@@ -639,16 +575,19 @@
"""
def startService(self):
+ print("Starting simulator service")
super(SimulatorService, self).startService()
self.clientsim = self.loadsim.createSimulator()
arrivalPolicy = self.loadsim.createArrivalPolicy()
- arrivalPolicy.run(self.clientsim)
+ arrivalPolicy.run(self.loadsim.reactor, self.clientsim)
+ # self.loadsim.arrival.run(self.loadsim.reactor, self.loadsim.simulator)
@inlineCallbacks
def stopService(self):
yield super(SimulatorService, self).stopService()
yield self.clientsim.stop()
+ # yield self.loadsim.simulator.stop()
@@ -662,6 +601,7 @@
"""
Start observing.
"""
+ print("Starting reporter service")
super(ReporterService, self).startService()
self.loadsim.reporter = self
@@ -736,6 +676,7 @@
super(WorkerSpawnerService, self).startService()
self.bridges = []
for workerID, worker in enumerate(self.loadsim.workers):
+ print("Building bridge for #" + str(workerID))
bridge = ProcessProtocolBridge(
self, Manager(self.loadsim, workerID, len(self.loadsim.workers),
self.output)
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_distributions.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -1,25 +1,91 @@
from twisted.trial.unittest import TestCase
from contrib.performance.loadtest.distributions import (
- LogNormalDistribution, UniformDiscreteDistribution,
- UniformIntegerDistribution, WorkDistribution, RecurrenceDistribution
+ # 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
+
+"""
+Disclaimer: These tests are nondeterministic, so be careful
+"""
+
class DistributionTestBase(TestCase):
- def getSamples(self, n):
+ def get_n_samples(self, dist, n):
samples = []
for _ignore_i in xrange(n):
- samples.append()
+ samples.append(dist.sample())
+ return samples
- def close(self, n):
- pass
+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)
-class DistributionTests(TestCase):
+ # 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 <= 0.01, "%d/%d, expected %f" % (successes, sample_count, prop))
+ 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 <= 0.01, "%d/%d, expected %f" % (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
+
+ def test_uniform(self):
+ dist = UniformIntegerDistribution(-5, 10)
+ for _ignore_i in range(100):
+ value = dist.sample()
+ self.assertTrue(-5 <= value < 10)
+ self.assertIsInstance(value, int)
+
+class ContinuousDistributionTests(TestCase):
+ def is_fit(self, pdf, xdata, ydata, pexp):
+ """
+ expected parameters
+ """
+ popt, pcov = curve_fit(pdf, xdata, ydata)
+ print popt
+
+ def test_normal(self):
+ dist = NormalDistribution()
+
def test_lognormal(self):
dist = LogNormalDistribution(mu=1, sigma=1)
for _ignore_i in range(100):
@@ -48,16 +114,8 @@
self.assertRaises(ValueError, LogNormalDistribution, mean=1)
self.assertRaises(ValueError, LogNormalDistribution, median=1)
+class CalendarDistributionTests(TestCase):
- def test_uniformdiscrete(self):
- population = [1, 5, 6, 9]
- 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)
-
-
def test_workdistribution(self):
tzname = "US/Eastern"
dist = WorkDistribution(["mon", "wed", "thu", "sat"], 10, 20, tzname)
@@ -91,7 +149,7 @@
self.assertTrue(value is None)
dist = RecurrenceDistribution(True, {"daily": 1, "none": 2, "weekly": 1})
- dist._helperDistribution = UniformDiscreteDistribution([0, 3, 2, 1, 0], randomize=False)
+ dist._helperDistribution = UniformDiscreteDistribution([0, 3, 2, 1, 0])
value = dist.sample()
self.assertTrue(value is not None)
value = dist.sample()
@@ -102,11 +160,3 @@
self.assertTrue(value is not None)
value = dist.sample()
self.assertTrue(value is not None)
-
-
- def test_uniform(self):
- dist = UniformIntegerDistribution(-5, 10)
- for _ignore_i in range(100):
- value = dist.sample()
- self.assertTrue(-5 <= value < 10)
- self.assertIsInstance(value, int)
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_push.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_push.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_push.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -1,15 +1,19 @@
from twisted.trial.unittest import TestCase
+from twisted.internet.defer import inlineCallbacks
-from calendarserver.push.amppush import AMPPushMaster
-
from contrib.performance.loadtest.push import PushMonitor
class PushMonitorTests(TestCase):
- def fakePush(self, inboundID, dataChangedTimestamp, priority=5):
- self.monitor._receivedAMPPush(inboundID, dataChangedTimestamp, priority)
+ 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):
- self.pushMaster = AMPPushMaster()
+ """
+ Creates and begins a PushMonitor with a history-tracking callback
+ """
self.monitor = PushMonitor(
None,
@@ -17,16 +21,94 @@
62311,
self.receivedPush
)
- self.monitor.begin()
+ self.history = []
+ return self.monitor.begin()
- def sendNotification(self, href, pushkey):
- self.pushMaster.notify(href, pushkey, None, None)
+ def test_noPushkey(self):
+ """
+ Monitor will not react to a push if there are no registered pushkeys
+ """
+ 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):
- pass
+ """
+ Adding a pushkey triggers a notification for the corresponding calendar home
+ """
+ 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):
- pass
+ """
+ Pushkeys can be unregistered
+ """
+ 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):
+ """
+ Adding the same pushkey twice only registers it once
+ """
+ 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):
+ """
+ Adding the same pushkey with a different calendar home
+ unregisters the original
+ """
+ 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):
+ """
+ Monitor supports registering multiple pushkeys
+ """
+ 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):
- self.monitor.end()
+ return self.monitor.end()
Modified: CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_resources.py
===================================================================
--- CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_resources.py 2015-08-31 20:06:29 UTC (rev 15080)
+++ CalendarServer/branches/users/sredmond/clientsim/contrib/performance/loadtest/tests/test_resources.py 2015-08-31 20:37:45 UTC (rev 15081)
@@ -37,4 +37,3 @@
"""
event = Event(None, u'/bar/baz', u'etag')
self.assertIdentical(event.getUID(), None)
-
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150831/1bd838f8/attachment-0001.html>
More information about the calendarserver-changes
mailing list