def _defaultSorter(self, master, builders): timer = metrics.Timer("BuildRequestDistributor._defaultSorter()") timer.start() # perform an asynchronous schwarzian transform, transforming None # into sys.maxint so that it sorts to the end def xform(bldr): d = defer.maybeDeferred(lambda: bldr.getOldestRequestTime()) d.addCallback(lambda time: (((time is None) and None or time), bldr)) return d xformed = yield defer.gatherResults([xform(bldr) for bldr in builders]) # sort the transformed list synchronously, comparing None to the end of # the list def nonecmp(a, b): if a[0] is None: return 1 if b[0] is None: return -1 return cmp(a, b) xformed.sort(cmp=nonecmp) # and reverse the transform rv = [xf[1] for xf in xformed] timer.stop() defer.returnValue(rv)
def _activityLoop(self): self.active = True timer = metrics.Timer('BuildRequestDistributor._activityLoop()') timer.start() while 1: yield self.activity_lock.acquire() # lock pending_builders, pop an element from it, and release yield self.pending_builders_lock.acquire() # bail out if we shouldn't keep looping if not self.running or not self._pending_builders: self.pending_builders_lock.release() self.activity_lock.release() break bldr_name = self._pending_builders.pop(0) self.pending_builders_lock.release() try: yield self._callABuilder(bldr_name) except Exception: log.err( Failure(), "from maybeStartBuild for builder '%s'" % (bldr_name, )) self.activity_lock.release() timer.stop() self.active = False self._quiet()
def reconfig(self): # this method wraps doConfig, ensuring it is only ever called once at # a time, and alerting the user if the reconfig takes too long if self.reconfig_active: log.msg("reconfig already active; will reconfig again after") self.reconfig_requested = True return self.reconfig_active = self.reactor.seconds() metrics.MetricCountEvent.log("loaded_config", 1) # notify every 10 seconds that the reconfig is still going on, the duration of reconfigs is # longer on larger installations and may take a while. self.reconfig_notifier = task.LoopingCall( lambda: log.msg("reconfig is ongoing for " f"{self.reactor.seconds() - self.reconfig_active:.3f} s")) self.reconfig_notifier.start(10, now=False) timer = metrics.Timer("BuildMaster.reconfig") timer.start() try: yield self.doReconfig() except Exception as e: log.err(e, 'while reconfiguring') finally: timer.stop() self.reconfig_notifier.stop() self.reconfig_notifier = None self.reconfig_active = False if self.reconfig_requested: self.reconfig_requested = False self.reconfig()
def reconfigServiceWithBuildbotConfig(self, new_config): timer = metrics.Timer( "ChangeManager.reconfigServiceWithBuildbotConfig") timer.start() removed, added = util.diffSets(set(self), new_config.change_sources) if removed or added: log.msg("adding %d new changesources, removing %d" % (len(added), len(removed))) for src in removed: yield defer.maybeDeferred(src.disownServiceParent) for src in added: yield src.setServiceParent(self) num_sources = len(list(self)) assert num_sources == len(new_config.change_sources) metrics.MetricCountEvent.log("num_sources", num_sources, absolute=True) # reconfig any newly-added change sources, as well as existing yield service.ReconfigurableServiceMixin.reconfigServiceWithBuildbotConfig( self, new_config) timer.stop()
def _sortBuilders(self, buildernames): timer = metrics.Timer("BuildRequestDistributor._sortBuilders()") timer.start() # note that this takes and returns a list of builder names # convert builder names to builders builders_dict = self.botmaster.builders builders = [ builders_dict.get(n) for n in buildernames if n in builders_dict ] # find a sorting function sorter = self.botmaster.prioritizeBuilders if not sorter: sorter = self._defaultSorter # run it try: wfd = defer.waitForDeferred( defer.maybeDeferred(lambda: sorter(self.master, builders))) yield wfd builders = wfd.getResult() except: log.msg("Exception prioritizing builders; order unspecified") log.err(Failure()) # and return the names yield [b.name for b in builders] timer.stop()
def reconfigService(self, new_config): timer = metrics.Timer("BotMaster.reconfigService") timer.start() # reconfigure slaves wfd = defer.waitForDeferred(self.reconfigServiceSlaves(new_config)) yield wfd wfd.getResult() # reconfigure builders wfd = defer.waitForDeferred(self.reconfigServiceBuilders(new_config)) yield wfd wfd.getResult() wfd = defer.waitForDeferred( config.ReconfigurableServiceMixin.reconfigService( self, new_config)) yield wfd wfd.getResult() # try to start a build for every builder; this is necessary at master # startup, and a good idea in any other case self.maybeStartBuildsForAllBuilders() timer.stop()
def reconfigServiceWithBuildbotConfig(self, new_config): timer = metrics.Timer("f{self.name}.reconfigServiceWithBuildbotConfig") timer.start() yield super().reconfigServiceWithBuildbotConfig(new_config) metrics.MetricCountEvent.log(f"num_{self.managed_services_name}", len(list(self)), absolute=True) timer.stop()
def _sortBuilders(self, buildernames): timer = metrics.Timer("BuildRequestDistributor._sortBuilders()") timer.start() # note that this takes and returns a list of builder names # convert builder names to builders builders_dict = self.botmaster.builders builders = [ builders_dict.get(n) for n in buildernames if n in builders_dict ] # find a sorting function sorter = self.master.config.prioritizeBuilders if not sorter: sorter = self._defaultSorter # run it try: builders = yield defer.maybeDeferred(sorter, self.master, builders) except Exception: log.err(Failure(), "prioritizing builders; order unspecified") # and return the names rv = [b.name for b in builders] timer.stop() defer.returnValue(rv)
def reconfigServiceWithBuildbotConfig(self, new_config): timer = metrics.Timer( "{0}.reconfigServiceWithBuildbotConfig".format(self.name)) timer.start() yield super(MeasuredBuildbotServiceManager, self).reconfigServiceWithBuildbotConfig(new_config) metrics.MetricCountEvent.log("num_{0}".format(self.managed_services_name), len(list(self)), absolute=True) timer.stop()
def pollDatabaseChanges(self): # Older versions of Buildbot had each scheduler polling the database # independently, and storing a "last_processed" state indicating the # last change it had processed. This had the advantage of allowing # schedulers to pick up changes that arrived in the database while # the scheduler was not running, but was horribly inefficient. # This version polls the database on behalf of the schedulers, using a # similar state at the master level. timer = metrics.Timer("BuildMaster.pollDatabaseChanges()") timer.start() need_setState = False # get the last processed change id if self._last_processed_change is None: self._last_processed_change = \ yield self._getState('last_processed_change') # if it's still None, assume we've processed up to the latest changeid if self._last_processed_change is None: lpc = yield self.db.changes.getLatestChangeid() # if there *are* no changes, count the last as '0' so that we don't # skip the first change if lpc is None: lpc = 0 self._last_processed_change = lpc need_setState = True if self._last_processed_change is None: timer.stop() return while True: changeid = self._last_processed_change + 1 chdict = yield self.db.changes.getChange(changeid) # if there's no such change, we've reached the end and can # stop polling if not chdict: break change = yield changes.Change.fromChdict(self, chdict) self._change_subs.deliver(change) self._last_processed_change = changeid need_setState = True # write back the updated state, if it's changed if need_setState: yield self._setState('last_processed_change', self._last_processed_change) timer.stop()
def reconfigServiceBuilders(self, new_config): timer = metrics.Timer("BotMaster.reconfigServiceBuilders") timer.start() # arrange builders by name old_by_name = dict([(b.name, b) for b in list(self) if isinstance(b, Builder)]) old_set = set(old_by_name.iterkeys()) new_by_name = dict([(bc.name, bc) for bc in new_config.builders]) new_set = set(new_by_name.iterkeys()) # calculate new builders, by name, and removed builders removed_names, added_names = util.diffSets(old_set, new_set) if removed_names or added_names: log.msg("adding %d new builders, removing %d" % (len(added_names), len(removed_names))) for n in removed_names: builder = old_by_name[n] del self.builders[n] builder.master = None builder.botmaster = None yield defer.maybeDeferred( lambda: builder.disownServiceParent()) for n in added_names: builder = Builder(n) self.builders[n] = builder builder.botmaster = self builder.master = self.master builder.setServiceParent(self) self.builderNames = self.builders.keys() metrics.MetricCountEvent.log("num_builders", len(self.builders), absolute=True) # remove unclaimed builds if the builder has been removed from configuration if len(self.master.config.projects) > 1: queued_builds = yield self.master.db.buildrequests.getBuildRequestInQueue( sorted=True) # TODO: if we are running in multimaster mode with multiple instance of katana we need # to check for the project key as well removed_builders = [ b for b in queued_builds if b["buildername"] not in new_set ] self.removeQueuedBuilds(removed_builders) timer.stop()
def testTimer(self): clock = task.Clock() t = metrics.Timer('foo_time') t._reactor = clock t.start() clock.advance(5) t.stop() report = self.observer.asDict() self.assertEquals(report['timers']['foo_time'], 5)
def reconfigServiceSlaves(self, new_config): timer = metrics.Timer("BotMaster.reconfigServiceSlaves") timer.start() # arrange slaves by name old_by_name = dict([(s.slavename, s) for s in list(self) if interfaces.IBuildSlave.providedBy(s)]) old_set = set(old_by_name.iterkeys()) new_by_name = dict([(s.slavename, s) for s in new_config.slaves]) new_set = set(new_by_name.iterkeys()) # calculate new slaves, by name, and removed slaves removed_names, added_names = util.diffSets(old_set, new_set) # find any slaves for which the fully qualified class name has # changed, and treat those as an add and remove for n in old_set & new_set: old = old_by_name[n] new = new_by_name[n] # detect changed class name if reflect.qual(old.__class__) != reflect.qual(new.__class__): removed_names.add(n) added_names.add(n) if removed_names or added_names: log.msg("adding %d new slaves, removing %d" % (len(added_names), len(removed_names))) for n in removed_names: slave = old_by_name[n] del self.slaves[n] slave.master = None slave.botmaster = None wfd = defer.waitForDeferred( defer.maybeDeferred(lambda: slave.disownServiceParent())) yield wfd wfd.getResult() for n in added_names: slave = new_by_name[n] slave.setServiceParent(self) slave.botmaster = self slave.master = self.master self.slaves[n] = slave metrics.MetricCountEvent.log("num_slaves", len(self.slaves), absolute=True) timer.stop()
def reconfigServiceWithBuildbotConfig(self, new_config): timer = metrics.Timer("BuildSlaveManager.reconfigServiceSlaves") timer.start() yield service.BuildbotServiceManager.reconfigServiceWithBuildbotConfig(self, new_config) metrics.MetricCountEvent.log("num_slaves", len(self.slaves), absolute=True) timer.stop()
def reconfigServiceSlaves(self, new_config): timer = metrics.Timer("BuildSlaveManager.reconfigServiceSlaves") timer.start() # first we deconfigure everything to let the slaves register again yield self.master.data.updates.deconfigureAllBuidslavesForMaster( self.master.masterid) # arrange slaves by name old_by_name = dict([(s.slavename, s) for s in list(self) if interfaces.IBuildSlave.providedBy(s)]) old_set = set(old_by_name.iterkeys()) new_by_name = dict([(s.slavename, s) for s in new_config.slaves]) new_set = set(new_by_name.iterkeys()) # calculate new slaves, by name, and removed slaves removed_names, added_names = util.diffSets(old_set, new_set) # find any slaves for which the fully qualified class name has # changed, and treat those as an add and remove for n in old_set & new_set: old = old_by_name[n] new = new_by_name[n] # detect changed class name if reflect.qual(old.__class__) != reflect.qual(new.__class__): removed_names.add(n) added_names.add(n) if removed_names or added_names: log.msg("adding %d new slaves, removing %d" % (len(added_names), len(removed_names))) for n in removed_names: slave = old_by_name[n] del self.slaves[n] slave.master = None yield slave.disownServiceParent() for n in added_names: slave = new_by_name[n] yield slave.setServiceParent(self) self.slaves[n] = slave metrics.MetricCountEvent.log("num_slaves", len(self.slaves), absolute=True) timer.stop()
def reconfigServiceBuilders(self, new_config): timer = metrics.Timer("BotMaster.reconfigServiceBuilders") timer.start() # arrange builders by name old_by_name = {b.name: b for b in list(self) if isinstance(b, Builder)} old_set = set(old_by_name) new_by_name = {bc.name: bc for bc in new_config.builders} new_set = set(new_by_name) # calculate new builders, by name, and removed builders removed_names, added_names = util.diffSets(old_set, new_set) if removed_names or added_names: log.msg("adding %d new builders, removing %d" % (len(added_names), len(removed_names))) for n in removed_names: builder = old_by_name[n] del self.builders[n] builder.master = None builder.botmaster = None # pylint: disable=cell-var-from-loop yield defer.maybeDeferred(lambda: builder.disownServiceParent()) for n in added_names: builder = Builder(n) self.builders[n] = builder builder.botmaster = self builder.master = self.master yield builder.setServiceParent(self) self.builderNames = list(self.builders) yield self.master.data.updates.updateBuilderList( self.master.masterid, [util.bytes2unicode(n) for n in self.builderNames]) metrics.MetricCountEvent.log("num_builders", len(self.builders), absolute=True) timer.stop()
def pollDatabaseBuildRequests(self): # deal with cleaning up unclaimed requests, and (if necessary) # requests from a previous instance of this master timer = metrics.Timer("BuildMaster.pollDatabaseBuildRequests()") timer.start() # cleanup unclaimed builds since_last_cleanup = reactor.seconds() - self._last_claim_cleanup if since_last_cleanup < self.RECLAIM_BUILD_INTERVAL: unclaimed_age = (self.RECLAIM_BUILD_INTERVAL * self.UNCLAIMED_BUILD_FACTOR) wfd = defer.waitForDeferred( self.db.buildrequests.unclaimExpiredRequests(unclaimed_age)) yield wfd wfd.getResult() self._last_claim_cleanup = reactor.seconds() # _last_unclaimed_brids_set tracks the state of unclaimed build # requests; whenever it sees a build request which was not claimed on # the last poll, it notifies the subscribers. It only tracks that # state within the master instance, though; on startup, it notifies for # all unclaimed requests in the database. last_unclaimed = self._last_unclaimed_brids_set or set() if len(last_unclaimed) > self.WARNING_UNCLAIMED_COUNT: log.msg("WARNING: %d unclaimed buildrequests - is a scheduler " "producing builds for which no builder is running?" % len(last_unclaimed)) # get the current set of unclaimed buildrequests wfd = defer.waitForDeferred( self.db.buildrequests.getBuildRequests(claimed=False)) yield wfd now_unclaimed_brdicts = wfd.getResult() now_unclaimed = set([ brd['brid'] for brd in now_unclaimed_brdicts ]) # and store that for next time self._last_unclaimed_brids_set = now_unclaimed # see what's new, and notify if anything is new_unclaimed = now_unclaimed - last_unclaimed if new_unclaimed: brdicts = dict((brd['brid'], brd) for brd in now_unclaimed_brdicts) for brid in new_unclaimed: brd = brdicts[brid] self.buildRequestAdded(brd['buildsetid'], brd['brid'], brd['buildername']) timer.stop()
def reconfigServiceWithBuildbotConfig(self, new_config): timer = metrics.Timer("BotMaster.reconfigServiceWithBuildbotConfig") timer.start() # reconfigure builders yield self.reconfigServiceBuilders(new_config) # call up yield super().reconfigServiceWithBuildbotConfig(new_config) # try to start a build for every builder; this is necessary at master # startup, and a good idea in any other case self.maybeStartBuildsForAllBuilders() timer.stop()
def reconfigService(self, new_config): timer = metrics.Timer("BotMaster.reconfigService") timer.start() # reconfigure slaves yield self.reconfigServiceSlaves(new_config) # reconfigure builders yield self.reconfigServiceBuilders(new_config) # call up yield config.ReconfigurableServiceMixin.reconfigService( self, new_config) timer.stop()
def reconfigServiceBuilders(self, new_config): timer = metrics.Timer("BotMaster.reconfigServiceBuilders") timer.start() # arrange builders by name old_by_name = dict([(b.name, b) for b in list(self) if isinstance(b, Builder)]) old_set = set(old_by_name.iterkeys()) new_by_name = dict([(bc.name, bc) for bc in new_config.builders]) new_set = set(new_by_name.iterkeys()) # calculate new builders, by name, and removed builders removed_names, added_names = util.diffSets(old_set, new_set) if removed_names or added_names: log.msg("adding %d new builders, removing %d" % (len(added_names), len(removed_names))) for n in removed_names: builder = old_by_name[n] del self.builders[n] builder.master = None builder.botmaster = None wfd = defer.waitForDeferred( defer.maybeDeferred(lambda: builder.disownServiceParent())) yield wfd wfd.getResult() for n in added_names: builder = Builder(n) self.builders[n] = builder builder.botmaster = self builder.master = self.master builder.setServiceParent(self) self.builderNames = self.builders.keys() metrics.MetricCountEvent.log("num_builders", len(self.builders), absolute=True) timer.stop()
def _updateAllSlaves(self): """Notify all buildslaves about changes in their Builders.""" timer = metrics.Timer("BotMaster._updateAllSlaves()") timer.start() dl = [] for s in self.slaves.values(): d = s.updateSlave() d.addErrback(log.err) dl.append(d) d = defer.DeferredList(dl) def stop(_): timer.stop() return _ d.addBoth(stop) return d
def _activityLoop(self): self.active = True timer = metrics.Timer('BuildRequestDistributor._activityLoop()') timer.start() pending_builders = [] while True: yield self.activity_lock.acquire() if not self.running: self.activity_lock.release() break if not pending_builders: # lock pending_builders, pop an element from it, and release yield self.pending_builders_lock.acquire() # bail out if we shouldn't keep looping if not self._pending_builders: self.pending_builders_lock.release() self.activity_lock.release() break # take that builder list, and run it until the end # we make a copy of it, as it could be modified meanwhile pending_builders = copy.copy(self._pending_builders) self._pending_builders = [] self.pending_builders_lock.release() bldr_name = pending_builders.pop(0) # get the actual builder object bldr = self.botmaster.builders.get(bldr_name) try: if bldr: yield self._maybeStartBuildsOnBuilder(bldr) except Exception: log.err( Failure(), "from maybeStartBuild for builder '%s'" % (bldr_name, )) self.activity_lock.release() timer.stop() self.active = False self._quiet()
def _defaultSorter(self, master, builders): timer = metrics.Timer("BuildRequestDistributor._defaultSorter()") timer.start() # perform an asynchronous schwarzian transform, transforming None # into sys.maxint so that it sorts to the end def xform(bldr): d = defer.maybeDeferred(bldr.getOldestRequestTime) d.addCallback(lambda time: (((time is None) and None or time), bldr)) return d xformed = yield defer.gatherResults([xform(bldr) for bldr in builders]) # sort the transformed list synchronously, comparing None to the end of # the list def xformedKey(a): """ Key function can be used to sort a list where each list element is a tuple: (datetime.datetime, Builder) @return: a tuple of (date, builder name) """ (date, builder) = a if date is None: # Choose a really big date, so that any # date set to 'None' will appear at the # end of the list during comparisons. date = datetime.max # Need to set the timezone on the date, in order # to perform comparisons with other dates which # have the time zone set. date = date.replace(tzinfo=tzutc()) return (date, builder.name) xformed.sort(key=xformedKey) # and reverse the transform rv = [xf[1] for xf in xformed] timer.stop() defer.returnValue(rv)
def testStartStopDecorators(self): clock = task.Clock() t = metrics.Timer('foo_time') t._reactor = clock @t.startTimer def foo(): clock.advance(5) return "foo!" @t.stopTimer def bar(): clock.advance(5) return "bar!" foo() bar() report = self.observer.asDict() self.assertEquals(report['timers']['foo_time'], 10)
def reconfigService(self, new_config): timer = metrics.Timer("BotMaster.reconfigService") timer.start() # reconfigure slaves yield self.reconfigServiceSlaves(new_config) # reconfigure builders yield self.reconfigServiceBuilders(new_config) # call up yield config.ReconfigurableServiceMixin.reconfigService( self, new_config) # try to start a build for every builder; this is necessary at master # startup, and a good idea in any other case self.maybeStartBuildsForAllBuilders() timer.stop()
def reconfig(self): # this method wraps doConfig, ensuring it is only ever called once at # a time, and alerting the user if the reconfig takes too long if self.reconfig_active: log.msg("reconfig already active; will reconfig again after") self.reconfig_requested = True return self.reconfig_active = self.reactor.seconds() metrics.MetricCountEvent.log("loaded_config", 1) # notify every 10 seconds that the reconfig is still going on, although # reconfigs should not take that long! self.reconfig_notifier = task.LoopingCall( lambda: log.msg("reconfig is ongoing for %d s" % (self.reactor.seconds() - self.reconfig_active))) self.reconfig_notifier.start(10, now=False) timer = metrics.Timer("BuildMaster.reconfig") timer.start() d = self.doReconfig() @d.addBoth def cleanup(res): timer.stop() self.reconfig_notifier.stop() self.reconfig_notifier = None self.reconfig_active = False if self.reconfig_requested: self.reconfig_requested = False self.reconfig() return res @d.addCallback def advertiseItself(res): if self.masterHeartbeatService.parent is None: self.masterHeartbeatService.setServiceParent(self) d.addErrback(log.err, 'while reconfiguring') return d # for tests
def _defaultSorter(self, master, builders): timer = metrics.Timer("BuildRequestDistributor._defaultSorter()") timer.start() @defer.inlineCallbacks def key(bldr): # Sort by time of oldest build request time = yield bldr.getOldestRequestTime() if time is None: # for builders that do not have pending buildrequest, we just use large number time = math.inf else: if isinstance(time, datetime): time = time.timestamp() return (time, bldr.name) yield async_sort(builders, key) timer.stop() return builders
def _defaultSorter(self, master, builders): timer = metrics.Timer("BuildRequestDistributor._defaultSorter()") timer.start() # perform an asynchronous schwarzian transform, transforming None # into sys.maxint so that it sorts to the end def xform(bldr): d = defer.maybeDeferred(lambda: bldr.getOldestRequestTime()) d.addCallback(lambda time: (((time is None) and None or time), bldr)) return d xformed = yield defer.gatherResults([xform(bldr) for bldr in builders]) # sort the transformed list synchronously, comparing None to the end of # the list def nonekey(a): if a[0] is None: b = list(a) # Choose a really big date, so that any # date set to 'None' will appear at the # end of the list during comparisons. b[0] = datetime.max # Need to set the timezone on the date, in order # to perform comparisons with other dates which # have the time zone set. b[0] = b[0].replace(tzinfo=tzutc()) return tuple(b) return a xformed.sort(key=nonekey) # and reverse the transform rv = [xf[1] for xf in xformed] timer.stop() defer.returnValue(rv)
def reconfigService(self, new_config): timer = metrics.Timer("ChangeManager.reconfigService") timer.start() removed, added = util.diffSets( set(self), new_config.change_sources) if removed or added: log.msg("adding %d new changesources, removing %d" % (len(added), len(removed))) for src in removed: wfd = defer.waitForDeferred( defer.maybeDeferred( src.disownServiceParent)) yield wfd wfd.getResult() src.master = None for src in added: src.master = self.master src.setServiceParent(self) num_sources = len(list(self)) assert num_sources == len(new_config.change_sources) metrics.MetricCountEvent.log("num_sources", num_sources, absolute=True) # reconfig any newly-added change sources, as well as existing wfd = defer.waitForDeferred( config.ReconfigurableServiceMixin.reconfigService(self, new_config)) yield wfd wfd.getResult() timer.stop()
def reconfigService(self, new_config): timer = metrics.Timer("SchedulerManager.reconfigService") timer.start() old_by_name = dict((sch.name, sch) for sch in self) old_set = set(old_by_name.iterkeys()) new_by_name = new_config.schedulers new_set = set(new_by_name.iterkeys()) removed_names, added_names = util.diffSets(old_set, new_set) # find any schedulers that don't know how to reconfig, and, if they # have changed, add them to both removed and added, so that we # run the new version. While we're at it, find any schedulers whose # fully qualified class name has changed, and consider those a removal # and re-add as well. for n in old_set & new_set: old = old_by_name[n] new = new_by_name[n] # detect changed class name if reflect.qual(old.__class__) != reflect.qual(new.__class__): removed_names.add(n) added_names.add(n) # compare using ComparableMixin if they don't support reconfig elif not hasattr(old, 'reconfigService'): if old != new: removed_names.add(n) added_names.add(n) # removals first for sch_name in removed_names: log.msg("removing scheduler '%s'" % (sch_name, )) sch = old_by_name[sch_name] yield defer.maybeDeferred(lambda: sch.disownServiceParent()) sch.master = None # .. then additions for sch_name in added_names: log.msg("adding scheduler '%s'" % (sch_name, )) sch = new_by_name[sch_name] # get the scheduler's objectid class_name = '%s.%s' % (sch.__class__.__module__, sch.__class__.__name__) objectid = yield self.master.db.state.getObjectId( sch.name, class_name) # set up the scheduler sch.objectid = objectid sch.master = self.master # *then* attacah and start it yield sch.setServiceParent(self) metrics.MetricCountEvent.log("num_schedulers", len(list(self)), absolute=True) # reconfig any newly-added schedulers, as well as existing yield config.ReconfigurableServiceMixin.reconfigService( self, new_config) timer.stop()