launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #16158
[Merge] lp:~wgrant/launchpad/buildqueue-estimation-extract into lp:launchpad
William Grant has proposed merging lp:~wgrant/launchpad/buildqueue-estimation-extract into lp:launchpad.
Commit message:
Extract job start time estimation from the main BuildQueue module.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~wgrant/launchpad/buildqueue-estimation-extract/+merge/193378
A majority of buildqueue.py and test_buildqueue.py consists of job start time estimation. Let's extract that to its own module to make things clearer.
--
https://code.launchpad.net/~wgrant/launchpad/buildqueue-estimation-extract/+merge/193378
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/buildqueue-estimation-extract into lp:launchpad.
=== modified file 'lib/lp/buildmaster/model/buildqueue.py'
--- lib/lp/buildmaster/model/buildqueue.py 2013-09-02 08:11:58 +0000
+++ lib/lp/buildmaster/model/buildqueue.py 2013-10-31 06:45:03 +0000
@@ -9,11 +9,7 @@
'specific_job_classes',
]
-from collections import defaultdict
-from datetime import (
- datetime,
- timedelta,
- )
+from datetime import datetime
from itertools import groupby
from operator import attrgetter
@@ -37,11 +33,7 @@
from lp.services.database.bulk import load_related
from lp.services.database.constants import DEFAULT
from lp.services.database.enumcol import EnumCol
-from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import (
- SQLBase,
- sqlvalues,
- )
+from lp.services.database.sqlbase import SQLBase
from lp.services.job.interfaces.job import JobStatus
from lp.services.job.model.job import Job
from lp.services.propertycache import (
@@ -50,12 +42,6 @@
)
-def normalize_virtualization(virtualized):
- """Jobs with NULL virtualization settings should be treated the
- same way as virtualized jobs."""
- return virtualized is None or virtualized
-
-
def specific_job_classes():
"""Job classes that may run on the build farm."""
job_classes = dict()
@@ -72,31 +58,6 @@
return job_classes
-def get_builder_data():
- """How many working builders are there, how are they configured?"""
- builder_data = """
- SELECT processor, virtualized, COUNT(id) FROM builder
- WHERE builderok = TRUE AND manual = FALSE
- GROUP BY processor, virtualized;
- """
- results = IStore(BuildQueue).execute(builder_data).get_all()
- builders_in_total = virtualized_total = 0
-
- builder_stats = defaultdict(int)
- for processor, virtualized, count in results:
- builders_in_total += count
- if virtualized:
- virtualized_total += count
- builder_stats[(processor, virtualized)] = count
-
- builder_stats[(None, True)] = virtualized_total
- # Jobs with a NULL virtualized flag should be treated the same as
- # jobs where virtualized=TRUE.
- builder_stats[(None, None)] = virtualized_total
- builder_stats[(None, False)] = builders_in_total - virtualized_total
- return builder_stats
-
-
class BuildQueue(SQLBase):
implements(IBuildQueue)
_table = "BuildQueue"
@@ -219,295 +180,15 @@
self.specific_job.jobCancel()
self.destroySelf()
- def _getFreeBuildersCount(self, processor, virtualized):
- """How many builders capable of running jobs for the given processor
- and virtualization combination are idle/free at present?"""
- query = """
- SELECT COUNT(id) FROM builder
- WHERE
- builderok = TRUE AND manual = FALSE
- AND id NOT IN (
- SELECT builder FROM BuildQueue WHERE builder IS NOT NULL)
- AND virtualized = %s
- """ % sqlvalues(normalize_virtualization(virtualized))
- if processor is not None:
- query += """
- AND processor = %s
- """ % sqlvalues(processor)
- result_set = IStore(BuildQueue).execute(query)
- free_builders = result_set.get_one()[0]
- return free_builders
-
- def _estimateTimeToNextBuilder(self):
- """Estimate time until next builder becomes available.
-
- For the purpose of estimating the dispatch time of the job of interest
- (JOI) we need to know how long it will take until the job at the head
- of JOI's queue is dispatched.
-
- There are two cases to consider here: the head job is
-
- - processor dependent: only builders with the matching
- processor/virtualization combination should be considered.
- - *not* processor dependent: all builders with the matching
- virtualization setting should be considered.
-
- :return: The estimated number of seconds untils a builder capable of
- running the head job becomes available.
- """
- head_job_platform = self._getHeadJobPlatform()
-
- # Return a zero delay if we still have free builders available for the
- # given platform/virtualization combination.
- free_builders = self._getFreeBuildersCount(*head_job_platform)
- if free_builders > 0:
- return 0
-
- head_job_processor, head_job_virtualized = head_job_platform
-
- now = self._now()
- delay_query = """
- SELECT MIN(
- CASE WHEN
- EXTRACT(EPOCH FROM
- (BuildQueue.estimated_duration -
- (((%s AT TIME ZONE 'UTC') - Job.date_started)))) >= 0
- THEN
- EXTRACT(EPOCH FROM
- (BuildQueue.estimated_duration -
- (((%s AT TIME ZONE 'UTC') - Job.date_started))))
- ELSE
- -- Assume that jobs that have overdrawn their estimated
- -- duration time budget will complete within 2 minutes.
- -- This is a wild guess but has worked well so far.
- --
- -- Please note that this is entirely innocuous i.e. if our
- -- guess is off nothing bad will happen but our estimate will
- -- not be as good as it could be.
- 120
- END)
- FROM
- BuildQueue, Job, Builder
- WHERE
- BuildQueue.job = Job.id
- AND BuildQueue.builder = Builder.id
- AND Builder.manual = False
- AND Builder.builderok = True
- AND Job.status = %s
- AND Builder.virtualized = %s
- """ % sqlvalues(
- now, now, JobStatus.RUNNING,
- normalize_virtualization(head_job_virtualized))
-
- if head_job_processor is not None:
- # Only look at builders with specific processor types.
- delay_query += """
- AND Builder.processor = %s
- """ % sqlvalues(head_job_processor)
-
- result_set = IStore(BuildQueue).execute(delay_query)
- head_job_delay = result_set.get_one()[0]
- return (0 if head_job_delay is None else int(head_job_delay))
-
- def _getPendingJobsClauses(self):
- """WHERE clauses for pending job queries, used for dipatch time
- estimation."""
- virtualized = normalize_virtualization(self.virtualized)
- clauses = """
- BuildQueue.job = Job.id
- AND Job.status = %s
- AND (
- -- The score must be either above my score or the
- -- job must be older than me in cases where the
- -- score is equal.
- BuildQueue.lastscore > %s OR
- (BuildQueue.lastscore = %s AND Job.id < %s))
- -- The virtualized values either match or the job
- -- does not care about virtualization and the job
- -- of interest (JOI) is to be run on a virtual builder
- -- (we want to prevent the execution of untrusted code
- -- on native builders).
- AND COALESCE(buildqueue.virtualized, TRUE) = %s
- """ % sqlvalues(
- JobStatus.WAITING, self.lastscore, self.lastscore, self.job,
- virtualized)
- processor_clause = """
- AND (
- -- The processor values either match or the candidate
- -- job is processor-independent.
- buildqueue.processor = %s OR
- buildqueue.processor IS NULL)
- """ % sqlvalues(self.processor)
- # We don't care about processors if the estimation is for a
- # processor-independent job.
- if self.processor is not None:
- clauses += processor_clause
- return clauses
-
- def _getHeadJobPlatform(self):
- """Find the processor and virtualization setting for the head job.
-
- Among the jobs that compete with the job of interest (JOI) for
- builders and are queued ahead of it the head job is the one in pole
- position i.e. the one to be dispatched to a builder next.
-
- :return: A (processor, virtualized) tuple which is the head job's
- platform or None if the JOI is the head job.
- """
- my_platform = (
- getattr(self.processor, 'id', None),
- normalize_virtualization(self.virtualized))
- query = """
- SELECT
- processor,
- virtualized
- FROM
- BuildQueue, Job
- WHERE
- """
- query += self._getPendingJobsClauses()
- query += """
- ORDER BY lastscore DESC, job LIMIT 1
- """
- result = IStore(BuildQueue).execute(query).get_one()
- return (my_platform if result is None else result)
-
- def _estimateJobDelay(self, builder_stats):
- """Sum of estimated durations for *pending* jobs ahead in queue.
-
- For the purpose of estimating the dispatch time of the job of
- interest (JOI) we need to know the delay caused by all the pending
- jobs that are ahead of the JOI in the queue and that compete with it
- for builders.
-
- :param builder_stats: A dictionary with builder counts where the
- key is a (processor, virtualized) combination (aka "platform") and
- the value is the number of builders that can take on jobs
- requiring that combination.
- :return: An integer value holding the sum of delays (in seconds)
- caused by the jobs that are ahead of and competing with the JOI.
- """
- def jobs_compete_for_builders(a, b):
- """True if the two jobs compete for builders."""
- a_processor, a_virtualized = a
- b_processor, b_virtualized = b
- if a_processor is None or b_processor is None:
- # If either of the jobs is platform-independent then the two
- # jobs compete for the same builders if the virtualization
- # settings match.
- if a_virtualized == b_virtualized:
- return True
- else:
- # Neither job is platform-independent, match processor and
- # virtualization settings.
- return a == b
-
- my_platform = (
- getattr(self.processor, 'id', None),
- normalize_virtualization(self.virtualized))
- query = """
- SELECT
- BuildQueue.processor,
- BuildQueue.virtualized,
- COUNT(BuildQueue.job),
- CAST(EXTRACT(
- EPOCH FROM
- SUM(BuildQueue.estimated_duration)) AS INTEGER)
- FROM
- BuildQueue, Job
- WHERE
- """
- query += self._getPendingJobsClauses()
- query += """
- GROUP BY BuildQueue.processor, BuildQueue.virtualized
- """
-
- delays_by_platform = IStore(BuildQueue).execute(query).get_all()
-
- # This will be used to capture per-platform delay totals.
- delays = defaultdict(int)
- # This will be used to capture per-platform job counts.
- job_counts = defaultdict(int)
-
- # Divide the estimated duration of the jobs as follows:
- # - if a job is tied to a processor TP then divide the estimated
- # duration of that job by the number of builders that target TP
- # since only these can build the job.
- # - if the job is processor-independent then divide its estimated
- # duration by the total number of builders with the same
- # virtualization setting because any one of them may run it.
- for processor, virtualized, job_count, delay in delays_by_platform:
- virtualized = normalize_virtualization(virtualized)
- platform = (processor, virtualized)
- builder_count = builder_stats.get(platform, 0)
- if builder_count == 0:
- # There is no builder that can run this job, ignore it
- # for the purpose of dispatch time estimation.
- continue
-
- if jobs_compete_for_builders(my_platform, platform):
- # The jobs that target the platform at hand compete with
- # the JOI for builders, add their delays.
- delays[platform] += delay
- job_counts[platform] += job_count
-
- sum_of_delays = 0
- # Now devide the delays based on a jobs/builders comparison.
- for platform, duration in delays.iteritems():
- jobs = job_counts[platform]
- builders = builder_stats[platform]
- # If there are less jobs than builders that can take them on,
- # the delays should be averaged/divided by the number of jobs.
- denominator = (jobs if jobs < builders else builders)
- if denominator > 1:
- duration = int(duration / float(denominator))
-
- sum_of_delays += duration
-
- return sum_of_delays
-
- def getEstimatedJobStartTime(self):
- """See `IBuildQueue`.
-
- The estimated dispatch time for the build farm job at hand is
- calculated from the following ingredients:
- * the start time for the head job (job at the
- head of the respective build queue)
- * the estimated build durations of all jobs that
- precede the job of interest (JOI) in the build queue
- (divided by the number of machines in the respective
- build pool)
- """
- # This method may only be invoked for pending jobs.
- if self.job.status != JobStatus.WAITING:
- raise AssertionError(
- "The start time is only estimated for pending jobs.")
-
- builder_stats = get_builder_data()
- platform = (getattr(self.processor, 'id', None), self.virtualized)
- if builder_stats[platform] == 0:
- # No builders that can run the job at hand
- # -> no dispatch time estimation available.
- return None
-
- # Get the sum of the estimated run times for *pending* jobs that are
- # ahead of us in the queue.
- sum_of_delays = self._estimateJobDelay(builder_stats)
-
- # Get the minimum time duration until the next builder becomes
- # available.
- min_wait_time = self._estimateTimeToNextBuilder()
-
- # A job will not get dispatched in less than 5 seconds no matter what.
- start_time = max(5, min_wait_time + sum_of_delays)
- result = self._now() + timedelta(seconds=start_time)
-
- return result
+ def getEstimatedJobStartTime(self, now=None):
+ """See `IBuildQueue`."""
+ from lp.buildmaster.queuedepth import estimate_job_start_time
+ return estimate_job_start_time(self, now)
@staticmethod
def _now():
"""Return current time (UTC). Overridable for test purposes."""
- return datetime.now(pytz.UTC)
+ return datetime.now(pytz.utc)
class BuildQueueSet(object):
=== added file 'lib/lp/buildmaster/queuedepth.py'
--- lib/lp/buildmaster/queuedepth.py 1970-01-01 00:00:00 +0000
+++ lib/lp/buildmaster/queuedepth.py 2013-10-31 06:45:03 +0000
@@ -0,0 +1,342 @@
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+__all__ = [
+ 'estimate_job_start_time',
+ ]
+
+from collections import defaultdict
+from datetime import (
+ datetime,
+ timedelta,
+ )
+
+from pytz import utc
+
+from lp.buildmaster.model.buildqueue import BuildQueue
+from lp.services.database.interfaces import IStore
+from lp.services.database.sqlbase import sqlvalues
+from lp.services.job.interfaces.job import JobStatus
+
+
+def get_builder_data():
+ """How many working builders are there, how are they configured?"""
+ builder_data = """
+ SELECT processor, virtualized, COUNT(id) FROM builder
+ WHERE builderok = TRUE AND manual = FALSE
+ GROUP BY processor, virtualized;
+ """
+ results = IStore(BuildQueue).execute(builder_data).get_all()
+ builders_in_total = virtualized_total = 0
+
+ builder_stats = defaultdict(int)
+ for processor, virtualized, count in results:
+ builders_in_total += count
+ if virtualized:
+ virtualized_total += count
+ builder_stats[(processor, virtualized)] = count
+
+ builder_stats[(None, True)] = virtualized_total
+ # Jobs with a NULL virtualized flag should be treated the same as
+ # jobs where virtualized=TRUE.
+ builder_stats[(None, None)] = virtualized_total
+ builder_stats[(None, False)] = builders_in_total - virtualized_total
+ return builder_stats
+
+
+def normalize_virtualization(virtualized):
+ """Jobs with NULL virtualization settings should be treated the
+ same way as virtualized jobs."""
+ return virtualized is None or virtualized
+
+
+def get_free_builders_count(processor, virtualized):
+ """How many builders capable of running jobs for the given processor
+ and virtualization combination are idle/free at present?"""
+ query = """
+ SELECT COUNT(id) FROM builder
+ WHERE
+ builderok = TRUE AND manual = FALSE
+ AND id NOT IN (
+ SELECT builder FROM BuildQueue WHERE builder IS NOT NULL)
+ AND virtualized = %s
+ """ % sqlvalues(normalize_virtualization(virtualized))
+ if processor is not None:
+ query += """
+ AND processor = %s
+ """ % sqlvalues(processor)
+ result_set = IStore(BuildQueue).execute(query)
+ free_builders = result_set.get_one()[0]
+ return free_builders
+
+
+def get_head_job_platform(bq):
+ """Find the processor and virtualization setting for the head job.
+
+ Among the jobs that compete with the job of interest (JOI) for
+ builders and are queued ahead of it the head job is the one in pole
+ position i.e. the one to be dispatched to a builder next.
+
+ :return: A (processor, virtualized) tuple which is the head job's
+ platform or None if the JOI is the head job.
+ """
+ my_platform = (
+ getattr(bq.processor, 'id', None),
+ normalize_virtualization(bq.virtualized))
+ query = """
+ SELECT
+ processor,
+ virtualized
+ FROM
+ BuildQueue, Job
+ WHERE
+ """
+ query += get_pending_jobs_clauses(bq)
+ query += """
+ ORDER BY lastscore DESC, job LIMIT 1
+ """
+ result = IStore(BuildQueue).execute(query).get_one()
+ return (my_platform if result is None else result)
+
+
+def estimate_time_to_next_builder(bq, now=None):
+ """Estimate time until next builder becomes available.
+
+ For the purpose of estimating the dispatch time of the job of interest
+ (JOI) we need to know how long it will take until the job at the head
+ of JOI's queue is dispatched.
+
+ There are two cases to consider here: the head job is
+
+ - processor dependent: only builders with the matching
+ processor/virtualization combination should be considered.
+ - *not* processor dependent: all builders with the matching
+ virtualization setting should be considered.
+
+ :return: The estimated number of seconds untils a builder capable of
+ running the head job becomes available.
+ """
+ head_job_platform = get_head_job_platform(bq)
+
+ # Return a zero delay if we still have free builders available for the
+ # given platform/virtualization combination.
+ free_builders = get_free_builders_count(*head_job_platform)
+ if free_builders > 0:
+ return 0
+
+ head_job_processor, head_job_virtualized = head_job_platform
+
+ now = now or datetime.datetime.now(utc)
+ delay_query = """
+ SELECT MIN(
+ CASE WHEN
+ EXTRACT(EPOCH FROM
+ (BuildQueue.estimated_duration -
+ (((%s AT TIME ZONE 'UTC') - Job.date_started)))) >= 0
+ THEN
+ EXTRACT(EPOCH FROM
+ (BuildQueue.estimated_duration -
+ (((%s AT TIME ZONE 'UTC') - Job.date_started))))
+ ELSE
+ -- Assume that jobs that have overdrawn their estimated
+ -- duration time budget will complete within 2 minutes.
+ -- This is a wild guess but has worked well so far.
+ --
+ -- Please note that this is entirely innocuous i.e. if our
+ -- guess is off nothing bad will happen but our estimate will
+ -- not be as good as it could be.
+ 120
+ END)
+ FROM
+ BuildQueue, Job, Builder
+ WHERE
+ BuildQueue.job = Job.id
+ AND BuildQueue.builder = Builder.id
+ AND Builder.manual = False
+ AND Builder.builderok = True
+ AND Job.status = %s
+ AND Builder.virtualized = %s
+ """ % sqlvalues(
+ now, now, JobStatus.RUNNING,
+ normalize_virtualization(head_job_virtualized))
+
+ if head_job_processor is not None:
+ # Only look at builders with specific processor types.
+ delay_query += """
+ AND Builder.processor = %s
+ """ % sqlvalues(head_job_processor)
+
+ result_set = IStore(BuildQueue).execute(delay_query)
+ head_job_delay = result_set.get_one()[0]
+ return (0 if head_job_delay is None else int(head_job_delay))
+
+
+def get_pending_jobs_clauses(bq):
+ """WHERE clauses for pending job queries, used for dipatch time
+ estimation."""
+ virtualized = normalize_virtualization(bq.virtualized)
+ clauses = """
+ BuildQueue.job = Job.id
+ AND Job.status = %s
+ AND (
+ -- The score must be either above my score or the
+ -- job must be older than me in cases where the
+ -- score is equal.
+ BuildQueue.lastscore > %s OR
+ (BuildQueue.lastscore = %s AND Job.id < %s))
+ -- The virtualized values either match or the job
+ -- does not care about virtualization and the job
+ -- of interest (JOI) is to be run on a virtual builder
+ -- (we want to prevent the execution of untrusted code
+ -- on native builders).
+ AND COALESCE(buildqueue.virtualized, TRUE) = %s
+ """ % sqlvalues(
+ JobStatus.WAITING, bq.lastscore, bq.lastscore, bq.job,
+ virtualized)
+ processor_clause = """
+ AND (
+ -- The processor values either match or the candidate
+ -- job is processor-independent.
+ buildqueue.processor = %s OR
+ buildqueue.processor IS NULL)
+ """ % sqlvalues(bq.processor)
+ # We don't care about processors if the estimation is for a
+ # processor-independent job.
+ if bq.processor is not None:
+ clauses += processor_clause
+ return clauses
+
+
+def estimate_job_delay(bq, builder_stats):
+ """Sum of estimated durations for *pending* jobs ahead in queue.
+
+ For the purpose of estimating the dispatch time of the job of
+ interest (JOI) we need to know the delay caused by all the pending
+ jobs that are ahead of the JOI in the queue and that compete with it
+ for builders.
+
+ :param builder_stats: A dictionary with builder counts where the
+ key is a (processor, virtualized) combination (aka "platform") and
+ the value is the number of builders that can take on jobs
+ requiring that combination.
+ :return: An integer value holding the sum of delays (in seconds)
+ caused by the jobs that are ahead of and competing with the JOI.
+ """
+ def jobs_compete_for_builders(a, b):
+ """True if the two jobs compete for builders."""
+ a_processor, a_virtualized = a
+ b_processor, b_virtualized = b
+ if a_processor is None or b_processor is None:
+ # If either of the jobs is platform-independent then the two
+ # jobs compete for the same builders if the virtualization
+ # settings match.
+ if a_virtualized == b_virtualized:
+ return True
+ else:
+ # Neither job is platform-independent, match processor and
+ # virtualization settings.
+ return a == b
+
+ my_platform = (
+ getattr(bq.processor, 'id', None),
+ normalize_virtualization(bq.virtualized))
+ query = """
+ SELECT
+ BuildQueue.processor,
+ BuildQueue.virtualized,
+ COUNT(BuildQueue.job),
+ CAST(EXTRACT(
+ EPOCH FROM
+ SUM(BuildQueue.estimated_duration)) AS INTEGER)
+ FROM
+ BuildQueue, Job
+ WHERE
+ """
+ query += get_pending_jobs_clauses(bq)
+ query += """
+ GROUP BY BuildQueue.processor, BuildQueue.virtualized
+ """
+
+ delays_by_platform = IStore(BuildQueue).execute(query).get_all()
+
+ # This will be used to capture per-platform delay totals.
+ delays = defaultdict(int)
+ # This will be used to capture per-platform job counts.
+ job_counts = defaultdict(int)
+
+ # Divide the estimated duration of the jobs as follows:
+ # - if a job is tied to a processor TP then divide the estimated
+ # duration of that job by the number of builders that target TP
+ # since only these can build the job.
+ # - if the job is processor-independent then divide its estimated
+ # duration by the total number of builders with the same
+ # virtualization setting because any one of them may run it.
+ for processor, virtualized, job_count, delay in delays_by_platform:
+ virtualized = normalize_virtualization(virtualized)
+ platform = (processor, virtualized)
+ builder_count = builder_stats.get(platform, 0)
+ if builder_count == 0:
+ # There is no builder that can run this job, ignore it
+ # for the purpose of dispatch time estimation.
+ continue
+
+ if jobs_compete_for_builders(my_platform, platform):
+ # The jobs that target the platform at hand compete with
+ # the JOI for builders, add their delays.
+ delays[platform] += delay
+ job_counts[platform] += job_count
+
+ sum_of_delays = 0
+ # Now devide the delays based on a jobs/builders comparison.
+ for platform, duration in delays.iteritems():
+ jobs = job_counts[platform]
+ builders = builder_stats[platform]
+ # If there are less jobs than builders that can take them on,
+ # the delays should be averaged/divided by the number of jobs.
+ denominator = (jobs if jobs < builders else builders)
+ if denominator > 1:
+ duration = int(duration / float(denominator))
+
+ sum_of_delays += duration
+
+ return sum_of_delays
+
+
+def estimate_job_start_time(bq, now=None):
+ """Estimate the start time of the given `IBuildQueue`.
+
+ The estimated dispatch time for the build farm job at hand is
+ calculated from the following ingredients:
+ * the start time for the head job (job at the
+ head of the respective build queue)
+ * the estimated build durations of all jobs that
+ precede the job of interest (JOI) in the build queue
+ (divided by the number of machines in the respective
+ build pool)
+ """
+ # This method may only be invoked for pending jobs.
+ if bq.job.status != JobStatus.WAITING:
+ raise AssertionError(
+ "The start time is only estimated for pending jobs.")
+
+ builder_stats = get_builder_data()
+ platform = (getattr(bq.processor, 'id', None), bq.virtualized)
+ if builder_stats[platform] == 0:
+ # No builders that can run the job at hand
+ # -> no dispatch time estimation available.
+ return None
+
+ # Get the sum of the estimated run times for *pending* jobs that are
+ # ahead of us in the queue.
+ sum_of_delays = estimate_job_delay(bq, builder_stats)
+
+ # Get the minimum time duration until the next builder becomes
+ # available.
+ min_wait_time = estimate_time_to_next_builder(bq, now=now)
+
+ # A job will not get dispatched in less than 5 seconds no matter what.
+ start_time = max(5, min_wait_time + sum_of_delays)
+ result = (now or datetime.now(utc)) + timedelta(seconds=start_time)
+ return result
=== modified file 'lib/lp/buildmaster/tests/test_buildqueue.py'
--- lib/lp/buildmaster/tests/test_buildqueue.py 2013-09-13 06:20:49 +0000
+++ lib/lp/buildmaster/tests/test_buildqueue.py 2013-10-31 06:45:03 +0000
@@ -2,40 +2,27 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test BuildQueue features."""
-from datetime import (
- datetime,
- timedelta,
- )
+from datetime import timedelta
-from pytz import utc
from storm.sqlobject import SQLObjectNotFound
from storm.store import Store
from zope import component
-from zope.component import (
- getGlobalSiteManager,
- getUtility,
- )
-from zope.security.proxy import removeSecurityProxy
+from zope.component import getGlobalSiteManager
from lp.buildmaster.enums import (
BuildFarmJobType,
BuildStatus,
)
-from lp.buildmaster.interfaces.builder import IBuilderSet
from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
from lp.buildmaster.model.builder import specific_job_classes
from lp.buildmaster.model.buildfarmjob import BuildFarmJobMixin
-from lp.buildmaster.model.buildqueue import (
- BuildQueue,
- get_builder_data,
- )
+from lp.buildmaster.model.buildqueue import BuildQueue
from lp.services.database.interfaces import IStore
from lp.soyuz.enums import (
ArchivePurpose,
PackagePublishingStatus,
)
from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
-from lp.soyuz.interfaces.processor import IProcessorSet
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import TestCaseWithFactory
from lp.testing.fakemethod import FakeMethod
@@ -66,31 +53,6 @@
return (None, None)
-def nth_builder(test, bq, n):
- """Find nth builder that can execute the given build."""
-
- def builder_key(job):
- """Access key for builders capable of running the given job."""
- return (getattr(job.processor, 'id', None), job.virtualized)
-
- builder = None
- builders = test.builders.get(builder_key(bq), [])
- try:
- for builder in builders[n - 1:]:
- if builder.builderok:
- break
- except IndexError:
- pass
- return builder
-
-
-def assign_to_builder(test, job_name, builder_number, processor='386'):
- """Simulate assigning a build to a builder."""
- build, bq = find_job(test, job_name, processor)
- builder = nth_builder(test, bq, builder_number)
- bq.markAsBuilding(builder)
-
-
def print_build_setup(builds):
"""Show the build set-up for a particular test."""
@@ -114,389 +76,6 @@
queue_entry.lastscore)
-def check_mintime_to_builder(test, bq, min_time):
- """Test the estimated time until a builder becomes available."""
- # Monkey-patch BuildQueueSet._now() so it returns a constant time stamp
- # that's not too far in the future. This avoids spurious test failures.
- monkey_patch_the_now_property(bq)
- delay = removeSecurityProxy(bq)._estimateTimeToNextBuilder()
- test.assertTrue(
- delay <= min_time,
- "Wrong min time to next available builder (%s > %s)"
- % (delay, min_time))
-
-
-def set_remaining_time_for_running_job(bq, remainder):
- """Set remaining running time for job."""
- offset = bq.estimated_duration.seconds - remainder
- removeSecurityProxy(bq.job).date_started = (
- datetime.now(utc) - timedelta(seconds=offset))
-
-
-def check_delay_for_job(test, the_job, delay):
- # Obtain the builder statistics pertaining to this job.
- builder_data = get_builder_data()
- estimated_delay = removeSecurityProxy(the_job)._estimateJobDelay(
- builder_data)
- test.assertEqual(delay, estimated_delay)
-
-
-def total_builders():
- """How many available builders do we have in total?"""
- builder_data = get_builder_data()
- return builder_data[(None, False)] + builder_data[(None, True)]
-
-
-def builders_for_job(job):
- """How many available builders can run the given job?"""
- builder_data = get_builder_data()
- return builder_data[(getattr(job.processor, 'id', None), job.virtualized)]
-
-
-def monkey_patch_the_now_property(buildqueue):
- """Patch BuildQueue._now() so it returns a constant time stamp.
-
- This avoids spurious test failures.
- """
- # Use the date/time the job started if available.
- naked_buildqueue = removeSecurityProxy(buildqueue)
- if buildqueue.job.date_started:
- time_stamp = buildqueue.job.date_started
- else:
- time_stamp = naked_buildqueue._now()
-
- naked_buildqueue._now = FakeMethod(result=time_stamp)
- return time_stamp
-
-
-def check_estimate(test, job, delay_in_seconds):
- """Does the dispatch time estimate match the expectation?"""
- # Monkey-patch BuildQueueSet._now() so it returns a constant time stamp.
- # This avoids spurious test failures.
- time_stamp = monkey_patch_the_now_property(job)
- estimate = job.getEstimatedJobStartTime()
- if delay_in_seconds is None:
- test.assertEquals(
- delay_in_seconds, estimate,
- "An estimate should not be possible at present but one was "
- "returned (%s) nevertheless." % estimate)
- else:
- estimate -= time_stamp
- test.assertTrue(
- estimate.seconds <= delay_in_seconds,
- "The estimated delay deviates from the expected one (%s > %s)" %
- (estimate.seconds, delay_in_seconds))
-
-
-def disable_builders(test, processor_name, virtualized):
- """Disable bulders with the given processor and virtualization setting."""
- if processor_name is not None:
- processor = getUtility(IProcessorSet).getByName(processor_name)
- for builder in test.builders[(processor.id, virtualized)]:
- builder.builderok = False
-
-
-class TestBuildQueueBase(TestCaseWithFactory):
- """Setup the test publisher and some builders."""
-
- layer = LaunchpadZopelessLayer
-
- def setUp(self):
- super(TestBuildQueueBase, self).setUp()
- self.publisher = SoyuzTestPublisher()
- self.publisher.prepareBreezyAutotest()
-
- # First make nine 'i386' builders.
- self.i1 = self.factory.makeBuilder(name='i386-v-1')
- self.i2 = self.factory.makeBuilder(name='i386-v-2')
- self.i3 = self.factory.makeBuilder(name='i386-v-3')
- self.i4 = self.factory.makeBuilder(name='i386-v-4')
- self.i5 = self.factory.makeBuilder(name='i386-v-5')
- self.i6 = self.factory.makeBuilder(name='i386-n-6', virtualized=False)
- self.i7 = self.factory.makeBuilder(name='i386-n-7', virtualized=False)
- self.i8 = self.factory.makeBuilder(name='i386-n-8', virtualized=False)
- self.i9 = self.factory.makeBuilder(name='i386-n-9', virtualized=False)
-
- # Next make seven 'hppa' builders.
- self.hppa_proc = getUtility(IProcessorSet).getByName('hppa')
- self.h1 = self.factory.makeBuilder(
- name='hppa-v-1', processor=self.hppa_proc)
- self.h2 = self.factory.makeBuilder(
- name='hppa-v-2', processor=self.hppa_proc)
- self.h3 = self.factory.makeBuilder(
- name='hppa-v-3', processor=self.hppa_proc)
- self.h4 = self.factory.makeBuilder(
- name='hppa-v-4', processor=self.hppa_proc)
- self.h5 = self.factory.makeBuilder(
- name='hppa-n-5', processor=self.hppa_proc, virtualized=False)
- self.h6 = self.factory.makeBuilder(
- name='hppa-n-6', processor=self.hppa_proc, virtualized=False)
- self.h7 = self.factory.makeBuilder(
- name='hppa-n-7', processor=self.hppa_proc, virtualized=False)
-
- # Finally make five 'amd64' builders.
- self.amd_proc = getUtility(IProcessorSet).getByName('amd64')
- self.a1 = self.factory.makeBuilder(
- name='amd64-v-1', processor=self.amd_proc)
- self.a2 = self.factory.makeBuilder(
- name='amd64-v-2', processor=self.amd_proc)
- self.a3 = self.factory.makeBuilder(
- name='amd64-v-3', processor=self.amd_proc)
- self.a4 = self.factory.makeBuilder(
- name='amd64-n-4', processor=self.amd_proc, virtualized=False)
- self.a5 = self.factory.makeBuilder(
- name='amd64-n-5', processor=self.amd_proc, virtualized=False)
-
- self.builders = dict()
- self.x86_proc = getUtility(IProcessorSet).getByName('386')
- # x86 native
- self.builders[(self.x86_proc.id, False)] = [
- self.i6, self.i7, self.i8, self.i9]
- # x86 virtual
- self.builders[(self.x86_proc.id, True)] = [
- self.i1, self.i2, self.i3, self.i4, self.i5]
-
- # amd64 native
- self.builders[(self.amd_proc.id, False)] = [self.a4, self.a5]
- # amd64 virtual
- self.builders[(self.amd_proc.id, True)] = [self.a1, self.a2, self.a3]
-
- # hppa native
- self.builders[(self.hppa_proc.id, False)] = [
- self.h5,
- self.h6,
- self.h7,
- ]
- # hppa virtual
- self.builders[(self.hppa_proc.id, True)] = [
- self.h1, self.h2, self.h3, self.h4]
-
- # Ensure all builders are operational.
- for builders in self.builders.values():
- for builder in builders:
- builder.builderok = True
- builder.manual = False
-
- # Native builders irrespective of processor.
- self.builders[(None, False)] = []
- self.builders[(None, False)].extend(
- self.builders[(self.x86_proc.id, False)])
- self.builders[(None, False)].extend(
- self.builders[(self.amd_proc.id, False)])
- self.builders[(None, False)].extend(
- self.builders[(self.hppa_proc.id, False)])
-
- # Virtual builders irrespective of processor.
- self.builders[(None, True)] = []
- self.builders[(None, True)].extend(
- self.builders[(self.x86_proc.id, True)])
- self.builders[(None, True)].extend(
- self.builders[(self.amd_proc.id, True)])
- self.builders[(None, True)].extend(
- self.builders[(self.hppa_proc.id, True)])
-
- # Disable the sample data builders.
- getUtility(IBuilderSet)['bob'].builderok = False
- getUtility(IBuilderSet)['frog'].builderok = False
-
-
-class SingleArchBuildsBase(TestBuildQueueBase):
- """Set up a test environment with builds that target a single
- processor."""
-
- def setUp(self):
- """Set up some native x86 builds for the test archive."""
- super(SingleArchBuildsBase, self).setUp()
- # The builds will be set up as follows:
- #
- # gedit, p: 386, v:False e:0:01:00 *** s: 1001
- # firefox, p: 386, v:False e:0:02:00 *** s: 1002
- # apg, p: 386, v:False e:0:03:00 *** s: 1003
- # vim, p: 386, v:False e:0:04:00 *** s: 1004
- # gcc, p: 386, v:False e:0:05:00 *** s: 1005
- # bison, p: 386, v:False e:0:06:00 *** s: 1006
- # flex, p: 386, v:False e:0:07:00 *** s: 1007
- # postgres, p: 386, v:False e:0:08:00 *** s: 1008
- #
- # p=processor, v=virtualized, e=estimated_duration, s=score
-
- # First mark all builds in the sample data as already built.
- sample_data = IStore(BinaryPackageBuild).find(BinaryPackageBuild)
- for build in sample_data:
- build.buildstate = BuildStatus.FULLYBUILT
- IStore(BinaryPackageBuild).flush()
-
- # We test builds that target a primary archive.
- self.non_ppa = self.factory.makeArchive(
- name="primary", purpose=ArchivePurpose.PRIMARY)
- self.non_ppa.require_virtualized = False
-
- self.builds = []
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="firefox",
- status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="postgres",
- status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa).createMissingBuilds())
- # Set up the builds for test.
- score = 1000
- duration = 0
- for build in self.builds:
- score += 1
- duration += 60
- bq = build.buildqueue_record
- bq.lastscore = score
- bq.estimated_duration = timedelta(seconds=duration)
-
-
-class TestBuilderData(SingleArchBuildsBase):
- """Test the retrieval of builder related data. The latter is required
- for job dispatch time estimations irrespective of job processor
- architecture and virtualization setting."""
-
- def test_builder_data(self):
- # Make sure the builder numbers are correct. The builder data will
- # be the same for all of our builds.
- bq = self.builds[0].buildqueue_record
- self.assertEqual(
- 21, total_builders(),
- "The total number of builders is wrong.")
- self.assertEqual(
- 4, builders_for_job(bq),
- "[1] The total number of builders that can build the job in "
- "question is wrong.")
- builder_stats = get_builder_data()
- self.assertEqual(
- 4, builder_stats[(self.x86_proc.id, False)],
- "The number of native x86 builders is wrong")
- self.assertEqual(
- 5, builder_stats[(self.x86_proc.id, True)],
- "The number of virtual x86 builders is wrong")
- self.assertEqual(
- 2, builder_stats[(self.amd_proc.id, False)],
- "The number of native amd64 builders is wrong")
- self.assertEqual(
- 3, builder_stats[(self.amd_proc.id, True)],
- "The number of virtual amd64 builders is wrong")
- self.assertEqual(
- 3, builder_stats[(self.hppa_proc.id, False)],
- "The number of native hppa builders is wrong")
- self.assertEqual(
- 4, builder_stats[(self.hppa_proc.id, True)],
- "The number of virtual hppa builders is wrong")
- self.assertEqual(
- 9, builder_stats[(None, False)],
- "The number of *virtual* builders across all processors is wrong")
- self.assertEqual(
- 12, builder_stats[(None, True)],
- "The number of *native* builders across all processors is wrong")
- # Disable the native x86 builders.
- for builder in self.builders[(self.x86_proc.id, False)]:
- builder.builderok = False
- # Since all native x86 builders were disabled there are none left
- # to build the job.
- self.assertEqual(
- 0, builders_for_job(bq),
- "[2] The total number of builders that can build the job in "
- "question is wrong.")
- # Re-enable one of them.
- for builder in self.builders[(self.x86_proc.id, False)]:
- builder.builderok = True
- break
- # Now there should be one builder available to build the job.
- self.assertEqual(
- 1, builders_for_job(bq),
- "[3] The total number of builders that can build the job in "
- "question is wrong.")
- # Disable the *virtual* x86 builders -- should not make any
- # difference.
- for builder in self.builders[(self.x86_proc.id, True)]:
- builder.builderok = False
- # There should still be one builder available to build the job.
- self.assertEqual(
- 1, builders_for_job(bq),
- "[4] The total number of builders that can build the job in "
- "question is wrong.")
-
- def test_free_builder_counts(self):
- # Make sure the builder numbers are correct. The builder data will
- # be the same for all of our builds.
- build = self.builds[0]
- # The build in question is an x86/native one.
- self.assertEqual(self.x86_proc.id, build.processor.id)
- self.assertEqual(False, build.is_virtualized)
-
- # To test this non-interface method, we need to remove the
- # security proxy.
- bq = removeSecurityProxy(build.buildqueue_record)
- builder_stats = get_builder_data()
- # We have 4 x86 native builders.
- self.assertEqual(
- 4, builder_stats[(self.x86_proc.id, False)],
- "The number of native x86 builders is wrong")
- # Initially all 4 builders are free.
- free_count = bq._getFreeBuildersCount(
- build.processor, build.is_virtualized)
- self.assertEqual(4, free_count)
- # Once we assign a build to one of them we should see the free
- # builders count drop by one.
- assign_to_builder(self, 'postgres', 1)
- free_count = bq._getFreeBuildersCount(
- build.processor, build.is_virtualized)
- self.assertEqual(3, free_count)
- # When we assign another build to one of them we should see the free
- # builders count drop by one again.
- assign_to_builder(self, 'gcc', 2)
- free_count = bq._getFreeBuildersCount(
- build.processor, build.is_virtualized)
- self.assertEqual(2, free_count)
- # Let's use up another builder.
- assign_to_builder(self, 'apg', 3)
- free_count = bq._getFreeBuildersCount(
- build.processor, build.is_virtualized)
- self.assertEqual(1, free_count)
- # And now for the last one.
- assign_to_builder(self, 'flex', 4)
- free_count = bq._getFreeBuildersCount(
- build.processor, build.is_virtualized)
- self.assertEqual(0, free_count)
- # If we reset the 'flex' build the builder that was assigned to it
- # will be free again.
- build, bq = find_job(self, 'flex')
- bq.reset()
- free_count = removeSecurityProxy(bq)._getFreeBuildersCount(
- build.processor, build.is_virtualized)
- self.assertEqual(1, free_count)
-
-
class TestBuildCancellation(TestCaseWithFactory):
"""Test cases for cancelling builds."""
@@ -536,303 +115,6 @@
self.assertCancelled(build, bq)
-class TestMinTimeToNextBuilder(SingleArchBuildsBase):
- """Test estimated time-to-builder with builds targetting a single
- processor."""
-
- def test_min_time_to_next_builder(self):
- """When is the next builder capable of running the job at the head of
- the queue becoming available?"""
- # Test the estimation of the minimum time until a builder becomes
- # available.
-
- # The builds will be set up as follows:
- #
- # gedit, p: 386, v:False e:0:01:00 *** s: 1001
- # firefox, p: 386, v:False e:0:02:00 *** s: 1002
- # apg, p: 386, v:False e:0:03:00 *** s: 1003
- # vim, p: 386, v:False e:0:04:00 *** s: 1004
- # gcc, p: 386, v:False e:0:05:00 *** s: 1005
- # bison, p: 386, v:False e:0:06:00 *** s: 1006
- # flex, p: 386, v:False e:0:07:00 *** s: 1007
- # postgres, p: 386, v:False e:0:08:00 *** s: 1008
- #
- # p=processor, v=virtualized, e=estimated_duration, s=score
-
- # This will be the job of interest.
- apg_build, apg_job = find_job(self, 'apg')
- # One of four builders for the 'apg' build is immediately available.
- check_mintime_to_builder(self, apg_job, 0)
-
- # Assign the postgres job to a builder.
- assign_to_builder(self, 'postgres', 1)
- # Now one builder is gone. But there should still be a builder
- # immediately available.
- check_mintime_to_builder(self, apg_job, 0)
-
- assign_to_builder(self, 'flex', 2)
- check_mintime_to_builder(self, apg_job, 0)
-
- assign_to_builder(self, 'bison', 3)
- check_mintime_to_builder(self, apg_job, 0)
-
- assign_to_builder(self, 'gcc', 4)
- # Now that no builder is immediately available, the shortest
- # remaing build time (based on the estimated duration) is returned:
- # 300 seconds
- # This is equivalent to the 'gcc' job's estimated duration.
- check_mintime_to_builder(self, apg_job, 300)
-
- # Now we pretend that the 'postgres' started 6 minutes ago. Its
- # remaining execution time should be 2 minutes = 120 seconds and
- # it now becomes the job whose builder becomes available next.
- build, bq = find_job(self, 'postgres')
- set_remaining_time_for_running_job(bq, 120)
- check_mintime_to_builder(self, apg_job, 120)
-
- # What happens when jobs overdraw the estimated duration? Let's
- # pretend the 'flex' job started 8 minutes ago.
- build, bq = find_job(self, 'flex')
- set_remaining_time_for_running_job(bq, -60)
- # In such a case we assume that the job will complete within 2
- # minutes, this is a guess that has worked well so far.
- check_mintime_to_builder(self, apg_job, 120)
-
- # If there's a job that will complete within a shorter time then
- # we expect to be given that time frame.
- build, bq = find_job(self, 'postgres')
- set_remaining_time_for_running_job(bq, 30)
- check_mintime_to_builder(self, apg_job, 30)
-
- # Disable the native x86 builders.
- for builder in self.builders[(self.x86_proc.id, False)]:
- builder.builderok = False
-
- # No builders capable of running the job at hand are available now.
- self.assertEquals(0, builders_for_job(apg_job))
- # The "minimum time to builder" estimation logic is not aware of this
- # though.
- check_mintime_to_builder(self, apg_job, 0)
-
- # The following job can only run on a native builder.
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=111, sourcename=u'xxr-gftp', score=1055,
- virtualized=False)
- self.builds.append(job.specific_job.build)
-
- # Disable all native builders.
- for builder in self.builders[(None, False)]:
- builder.builderok = False
-
- # All native builders are disabled now. No builders capable of
- # running the job at hand are available.
- self.assertEquals(0, builders_for_job(job))
- # The "minimum time to builder" estimation logic is not aware of the
- # fact that no builders capable of running the job are available.
- check_mintime_to_builder(self, job, 0)
-
-
-class MultiArchBuildsBase(TestBuildQueueBase):
- """Set up a test environment with builds and multiple processors."""
-
- def setUp(self):
- """Set up some native x86 builds for the test archive."""
- super(MultiArchBuildsBase, self).setUp()
- # The builds will be set up as follows:
- #
- # gedit, p: hppa, v:False e:0:01:00 *** s: 1001
- # gedit, p: 386, v:False e:0:02:00 *** s: 1002
- # firefox, p: hppa, v:False e:0:03:00 *** s: 1003
- # firefox, p: 386, v:False e:0:04:00 *** s: 1004
- # apg, p: hppa, v:False e:0:05:00 *** s: 1005
- # apg, p: 386, v:False e:0:06:00 *** s: 1006
- # vim, p: hppa, v:False e:0:07:00 *** s: 1007
- # vim, p: 386, v:False e:0:08:00 *** s: 1008
- # gcc, p: hppa, v:False e:0:09:00 *** s: 1009
- # gcc, p: 386, v:False e:0:10:00 *** s: 1010
- # bison, p: hppa, v:False e:0:11:00 *** s: 1011
- # bison, p: 386, v:False e:0:12:00 *** s: 1012
- # flex, p: hppa, v:False e:0:13:00 *** s: 1013
- # flex, p: 386, v:False e:0:14:00 *** s: 1014
- # postgres, p: hppa, v:False e:0:15:00 *** s: 1015
- # postgres, p: 386, v:False e:0:16:00 *** s: 1016
- #
- # p=processor, v=virtualized, e=estimated_duration, s=score
-
- # First mark all builds in the sample data as already built.
- sample_data = IStore(BinaryPackageBuild).find(BinaryPackageBuild)
- for build in sample_data:
- build.buildstate = BuildStatus.FULLYBUILT
- IStore(BinaryPackageBuild).flush()
-
- # We test builds that target a primary archive.
- self.non_ppa = self.factory.makeArchive(
- name="primary", purpose=ArchivePurpose.PRIMARY)
- self.non_ppa.require_virtualized = False
-
- self.builds = []
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="firefox",
- status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- self.builds.extend(
- self.publisher.getPubSource(
- sourcename="postgres",
- status=PackagePublishingStatus.PUBLISHED,
- archive=self.non_ppa,
- architecturehintlist='any').createMissingBuilds())
- # Set up the builds for test.
- score = 1000
- duration = 0
- for build in self.builds:
- score += getattr(self, 'score_increment', 1)
- score += 1
- duration += 60
- bq = build.buildqueue_record
- bq.lastscore = score
- bq.estimated_duration = timedelta(seconds=duration)
-
-
-class TestMinTimeToNextBuilderMulti(MultiArchBuildsBase):
- """Test estimated time-to-builder with builds and multiple processors."""
-
- def disabled_test_min_time_to_next_builder(self):
- """When is the next builder capable of running the job at the head of
- the queue becoming available?"""
- # XXX AaronBentley 2010-03-19 bug=541914: Fails spuriously
- # One of four builders for the 'apg' build is immediately available.
- apg_build, apg_job = find_job(self, 'apg', 'hppa')
- check_mintime_to_builder(self, apg_job, 0)
-
- # Assign the postgres job to a builder.
- assign_to_builder(self, 'postgres', 1, 'hppa')
- # Now one builder is gone. But there should still be a builder
- # immediately available.
- check_mintime_to_builder(self, apg_job, 0)
-
- assign_to_builder(self, 'flex', 2, 'hppa')
- check_mintime_to_builder(self, apg_job, 0)
-
- assign_to_builder(self, 'bison', 3, 'hppa')
- # Now that no builder is immediately available, the shortest
- # remaing build time (based on the estimated duration) is returned:
- # 660 seconds
- # This is equivalent to the 'bison' job's estimated duration.
- check_mintime_to_builder(self, apg_job, 660)
-
- # Now we pretend that the 'postgres' started 13 minutes ago. Its
- # remaining execution time should be 2 minutes = 120 seconds and
- # it now becomes the job whose builder becomes available next.
- build, bq = find_job(self, 'postgres', 'hppa')
- set_remaining_time_for_running_job(bq, 120)
- check_mintime_to_builder(self, apg_job, 120)
-
- # What happens when jobs overdraw the estimated duration? Let's
- # pretend the 'flex' job started 14 minutes ago.
- build, bq = find_job(self, 'flex', 'hppa')
- set_remaining_time_for_running_job(bq, -60)
- # In such a case we assume that the job will complete within 2
- # minutes, this is a guess that has worked well so far.
- check_mintime_to_builder(self, apg_job, 120)
-
- # If there's a job that will complete within a shorter time then
- # we expect to be given that time frame.
- build, bq = find_job(self, 'postgres', 'hppa')
- set_remaining_time_for_running_job(bq, 30)
- check_mintime_to_builder(self, apg_job, 30)
-
- # Disable the native hppa builders.
- for builder in self.builders[(self.hppa_proc.id, False)]:
- builder.builderok = False
-
- # No builders capable of running the job at hand are available now.
- self.assertEquals(0, builders_for_job(apg_job))
- check_mintime_to_builder(self, apg_job, 0)
-
- # Let's add a processor-independent job to the mix.
- job = self.factory.makeSourcePackageRecipeBuildJob(
- virtualized=False, estimated_duration=22,
- sourcename='my-recipe-digikam', score=9999)
- # There are still builders available for the processor-independent
- # job.
- self.assertEquals(6, builders_for_job(job))
- # Even free ones.
- self.assertTrue(
- bq._getFreeBuildersCount(job.processor, job.virtualized) > 0,
- "Builders are immediately available for processor-independent "
- "jobs.")
- check_mintime_to_builder(self, job, 0)
-
- # Let's disable all builders.
- for builders in self.builders.itervalues():
- for builder in builders:
- builder.builderok = False
-
- # There are no builders capable of running even the processor
- # independent jobs now.
- self.assertEquals(0, builders_for_job(job))
- check_mintime_to_builder(self, job, 0)
-
- # Re-enable the native hppa builders.
- for builder in self.builders[(self.hppa_proc.id, False)]:
- builder.builderok = True
-
- # The builder that's becoming available next is the one that's
- # running the 'postgres' build.
- check_mintime_to_builder(self, apg_job, 30)
-
- # Make sure we'll find an x86 builder as well.
- builder = self.builders[(self.x86_proc.id, False)][0]
- builder.builderok = True
-
- # Now this builder is the one that becomes available next (29 minutes
- # remaining build time).
- assign_to_builder(self, 'gcc', 1, '386')
- build, bq = find_job(self, 'gcc', '386')
- set_remaining_time_for_running_job(bq, 29)
-
- check_mintime_to_builder(self, apg_job, 29)
-
- # Make a second, idle x86 builder available.
- builder = self.builders[(self.x86_proc.id, False)][1]
- builder.builderok = True
-
- # That builder should be available immediately since it's idle.
- check_mintime_to_builder(self, apg_job, 0)
-
-
class TestBuildQueueDuration(TestCaseWithFactory):
layer = ZopelessDatabaseLayer
@@ -983,386 +265,6 @@
"The 'virtualized' property deviates.")
-class TestMultiArchJobDelayEstimation(MultiArchBuildsBase):
- """Test estimated job delays with various processors."""
- score_increment = 2
-
- def setUp(self):
- """Add 2 'build source package from recipe' builds to the mix.
-
- The two platform-independent jobs will have a score of 1025 and 1053
- respectively.
- In case of jobs with equal scores the one with the lesser 'job' value
- (i.e. the older one wins).
-
- 3, gedit, p: hppa, v:False e:0:01:00 *** s: 1003
- 4, gedit, p: 386, v:False e:0:02:00 *** s: 1006
- 5, firefox, p: hppa, v:False e:0:03:00 *** s: 1009
- 6, firefox, p: 386, v:False e:0:04:00 *** s: 1012
- 7, apg, p: hppa, v:False e:0:05:00 *** s: 1015
- 9, vim, p: hppa, v:False e:0:07:00 *** s: 1021
- 10, vim, p: 386, v:False e:0:08:00 *** s: 1024
- 8, apg, p: 386, v:False e:0:06:00 *** s: 1024
- --> 19, xx-recipe-bash, p: None, v:False e:0:00:22 *** s: 1025
- 11, gcc, p: hppa, v:False e:0:09:00 *** s: 1027
- 12, gcc, p: 386, v:False e:0:10:00 *** s: 1030
- 13, bison, p: hppa, v:False e:0:11:00 *** s: 1033
- 14, bison, p: 386, v:False e:0:12:00 *** s: 1036
- 15, flex, p: hppa, v:False e:0:13:00 *** s: 1039
- 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
- 17, postgres, p: hppa, v:False e:0:15:00 *** s: 1045
- 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
- --> 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
-
- p=processor, v=virtualized, e=estimated_duration, s=score
- """
- super(TestMultiArchJobDelayEstimation, self).setUp()
-
- job = self.factory.makeSourcePackageRecipeBuildJob(
- virtualized=False, estimated_duration=22,
- sourcename=u'xx-recipe-bash', score=1025)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- virtualized=False, estimated_duration=222,
- sourcename=u'xx-recipe-zsh', score=1053)
- self.builds.append(job.specific_job.build)
-
- # Assign the same score to the '386' vim and apg build jobs.
- _apg_build, apg_job = find_job(self, 'apg', '386')
- apg_job.lastscore = 1024
-
- def disabled_test_job_delay_for_binary_builds(self):
- # One of four builders for the 'flex' build is immediately available.
- flex_build, flex_job = find_job(self, 'flex', 'hppa')
- check_mintime_to_builder(self, flex_job, 0)
-
- # The delay will be 900 (= 15*60) + 222 seconds
- check_delay_for_job(self, flex_job, 1122)
-
- # Assign the postgres job to a builder.
- assign_to_builder(self, 'postgres', 1, 'hppa')
- # The 'postgres' job is not pending any more. Now only the 222
- # seconds (the estimated duration of the platform-independent job)
- # should be returned.
- check_delay_for_job(self, flex_job, 222)
-
- # How about some estimates for x86 builds?
- _bison_build, bison_job = find_job(self, 'bison', '386')
- check_mintime_to_builder(self, bison_job, 0)
- # The delay will be 900 (= (14+16)*60/2) + 222 seconds.
- check_delay_for_job(self, bison_job, 1122)
-
- # The 2 tests that follow exercise the estimation in conjunction with
- # longer pending job queues. Please note that the sum of estimates for
- # the '386' jobs is divided by 4 which is the number of native '386'
- # builders.
-
- # Also, this tests that jobs with equal score but a lower 'job' value
- # (i.e. older jobs) are queued ahead of the job of interest (JOI).
- _vim_build, vim_job = find_job(self, 'vim', '386')
- check_mintime_to_builder(self, vim_job, 0)
- # The delay will be 870 (= (6+10+12+14+16)*60/4) + 122 (= (222+22)/2)
- # seconds.
- check_delay_for_job(self, vim_job, 992)
-
- _gedit_build, gedit_job = find_job(self, 'gedit', '386')
- check_mintime_to_builder(self, gedit_job, 0)
- # The delay will be
- # 1080 (= (4+6+8+10+12+14+16)*60/4) + 122 (= (222+22)/2)
- # seconds.
- check_delay_for_job(self, gedit_job, 1172)
-
- def disabled_test_job_delay_for_recipe_builds(self):
- # One of the 9 builders for the 'bash' build is immediately available.
- bash_build, bash_job = find_job(self, 'xx-recipe-bash', None)
- check_mintime_to_builder(self, bash_job, 0)
-
- # The delay will be 960 + 780 + 222 = 1962, where
- # hppa job delays: 960 = (9+11+13+15)*60/3
- # 386 job delays: 780 = (10+12+14+16)*60/4
- check_delay_for_job(self, bash_job, 1962)
-
- # One of the 9 builders for the 'zsh' build is immediately available.
- zsh_build, zsh_job = find_job(self, 'xx-recipe-zsh', None)
- check_mintime_to_builder(self, zsh_job, 0)
-
- # The delay will be 0 since this is the head job.
- check_delay_for_job(self, zsh_job, 0)
-
- # Assign the zsh job to a builder.
- self.assertEquals((None, False), bash_job._getHeadJobPlatform())
- assign_to_builder(self, 'xx-recipe-zsh', 1, None)
- self.assertEquals((1, False), bash_job._getHeadJobPlatform())
-
- # Now that the highest-scored job is out of the way, the estimation
- # for the 'bash' recipe build is 222 seconds shorter.
-
- # The delay will be 960 + 780 = 1740, where
- # hppa job delays: 960 = (9+11+13+15)*60/3
- # 386 job delays: 780 = (10+12+14+16)*60/4
- check_delay_for_job(self, bash_job, 1740)
-
- _postgres_build, postgres_job = find_job(self, 'postgres', '386')
- # The delay will be 0 since this is the head job now.
- check_delay_for_job(self, postgres_job, 0)
- # Also, the platform of the postgres job is returned since it *is*
- # the head job now.
- pg_platform = (postgres_job.processor.id, postgres_job.virtualized)
- self.assertEquals(pg_platform, postgres_job._getHeadJobPlatform())
-
- def test_job_delay_for_unspecified_virtualization(self):
- # Make sure that jobs with a NULL 'virtualized' flag get the same
- # treatment as the ones with virtualized=TRUE.
- # First toggle the 'virtualized' flag for all hppa jobs.
- for build in self.builds:
- bq = build.buildqueue_record
- if bq.processor == self.hppa_proc:
- removeSecurityProxy(bq).virtualized = True
- job = self.factory.makeSourcePackageRecipeBuildJob(
- virtualized=True, estimated_duration=332,
- sourcename=u'xxr-openssh-client', score=1050)
- self.builds.append(job.specific_job.build)
- # print_build_setup(self.builds)
- # ...
- # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039
- # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
- # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045
- # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
- # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050
- # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
-
- flex_build, flex_job = find_job(self, 'flex', 'hppa')
- # The head job platform is the one of job #21 (xxr-openssh-client).
- self.assertEquals(
- (None, True), removeSecurityProxy(flex_job)._getHeadJobPlatform())
- # The delay will be 900 (= 15*60) + 332 seconds
- check_delay_for_job(self, flex_job, 1232)
-
- # Now add a job with a NULL 'virtualized' flag. It should be treated
- # like jobs with virtualized=TRUE.
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=111, sourcename=u'xxr-gwibber', score=1051,
- virtualized=None)
- self.builds.append(job.specific_job.build)
- # print_build_setup(self.builds)
- self.assertEqual(None, job.virtualized)
- # ...
- # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039
- # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
- # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045
- # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
- # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050
- # 22, xxr-gwibber, p: None, v: None e:0:01:51 *** s: 1051
- # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
-
- # The newly added 'xxr-gwibber' job is the new head job now.
- self.assertEquals(
- (None, None), removeSecurityProxy(flex_job)._getHeadJobPlatform())
- # The newly added 'xxr-gwibber' job now weighs in as well and the
- # delay is 900 (= 15*60) + (332+111)/2 seconds
- check_delay_for_job(self, flex_job, 1121)
-
- # The '386' flex job does not care about the 'xxr-gwibber' and
- # 'xxr-openssh-client' jobs since the 'virtualized' values do not
- # match.
- flex_build, flex_job = find_job(self, 'flex', '386')
- self.assertEquals(
- (None, False),
- removeSecurityProxy(flex_job)._getHeadJobPlatform())
- # delay is 960 (= 16*60) + 222 seconds
- check_delay_for_job(self, flex_job, 1182)
-
-
-class TestJobDispatchTimeEstimation(MultiArchBuildsBase):
- """Test estimated job delays with various processors."""
- score_increment = 2
-
- def setUp(self):
- """Add more processor-independent jobs to the mix, make the '386' jobs
- virtual.
-
- 3, gedit, p: hppa, v:False e:0:01:00 *** s: 1003
- 4, gedit, p: 386, v: True e:0:02:00 *** s: 1006
- 5, firefox, p: hppa, v:False e:0:03:00 *** s: 1009
- 6, firefox, p: 386, v: True e:0:04:00 *** s: 1012
- 7, apg, p: hppa, v:False e:0:05:00 *** s: 1015
- 9, vim, p: hppa, v:False e:0:07:00 *** s: 1021
- 10, vim, p: 386, v: True e:0:08:00 *** s: 1024
- 8, apg, p: 386, v: True e:0:06:00 *** s: 1024
- 19, xxr-aptitude, p: None, v:False e:0:05:32 *** s: 1025
- 11, gcc, p: hppa, v:False e:0:09:00 *** s: 1027
- 12, gcc, p: 386, v: True e:0:10:00 *** s: 1030
- 13, bison, p: hppa, v:False e:0:11:00 *** s: 1033
- 14, bison, p: 386, v: True e:0:12:00 *** s: 1036
- 15, flex, p: hppa, v:False e:0:13:00 *** s: 1039
- 16, flex, p: 386, v: True e:0:14:00 *** s: 1042
- 23, xxr-apt-build, p: None, v: True e:0:12:56 *** s: 1043
- 22, xxr-cron-apt, p: None, v: True e:0:11:05 *** s: 1043
- 26, xxr-cupt, p: None, v: None e:0:18:30 *** s: 1044
- 25, xxr-apt, p: None, v: None e:0:16:38 *** s: 1044
- 24, xxr-debdelta, p: None, v: None e:0:14:47 *** s: 1044
- 17, postgres, p: hppa, v:False e:0:15:00 *** s: 1045
- 18, postgres, p: 386, v: True e:0:16:00 *** s: 1048
- 21, xxr-daptup, p: None, v: None e:0:09:14 *** s: 1051
- 20, xxr-auto-apt, p: None, v:False e:0:07:23 *** s: 1053
-
- p=processor, v=virtualized, e=estimated_duration, s=score
- """
- super(TestJobDispatchTimeEstimation, self).setUp()
-
- job = self.factory.makeSourcePackageRecipeBuildJob(
- virtualized=False, estimated_duration=332,
- sourcename=u'xxr-aptitude', score=1025)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- virtualized=False, estimated_duration=443,
- sourcename=u'xxr-auto-apt', score=1053)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=554, sourcename=u'xxr-daptup', score=1051,
- virtualized=None)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=665, sourcename=u'xxr-cron-apt', score=1043)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=776, sourcename=u'xxr-apt-build', score=1043)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=887, sourcename=u'xxr-debdelta', score=1044,
- virtualized=None)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=998, sourcename=u'xxr-apt', score=1044,
- virtualized=None)
- self.builds.append(job.specific_job.build)
- job = self.factory.makeSourcePackageRecipeBuildJob(
- estimated_duration=1110, sourcename=u'xxr-cupt', score=1044,
- virtualized=None)
- self.builds.append(job.specific_job.build)
-
- # Assign the same score to the '386' vim and apg build jobs.
- _apg_build, apg_job = find_job(self, 'apg', '386')
- apg_job.lastscore = 1024
-
- # Also, toggle the 'virtualized' flag for all '386' jobs.
- for build in self.builds:
- bq = build.buildqueue_record
- if bq.processor == self.x86_proc:
- removeSecurityProxy(bq).virtualized = True
-
- def test_pending_jobs_only(self):
- # Let's see the assertion fail for a job that's not pending any more.
- assign_to_builder(self, 'gedit', 1, 'hppa')
- gedit_build, gedit_job = find_job(self, 'gedit', 'hppa')
- self.assertRaises(AssertionError, gedit_job.getEstimatedJobStartTime)
-
- def test_estimation_binary_virtual(self):
- gcc_build, gcc_job = find_job(self, 'gcc', '386')
- # The delay of 1671 seconds is calculated as follows:
- # 386 jobs: (12+14+16)*60/3 = 840
- # processor-independent jobs:
- # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
- check_estimate(self, gcc_job, 1671)
- self.assertEquals(5, builders_for_job(gcc_job))
-
- def test_proc_indep_virtual_true(self):
- xxr_build, xxr_job = find_job(self, 'xxr-apt-build', None)
- # The delay of 1802 seconds is calculated as follows:
- # 386 jobs: 16*60 = 960
- # processor-independent jobs:
- # (11:05 + 18:30 + 16:38 + 14:47 + 9:14)/5 = 842
- check_estimate(self, xxr_job, 1802)
-
- def test_estimation_binary_virtual_long_queue(self):
- gedit_build, gedit_job = find_job(self, 'gedit', '386')
- # The delay of 1671 seconds is calculated as follows:
- # 386 jobs:
- # (4+6+8+10+12+14+16)*60/5 = 840
- # processor-independent jobs:
- # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
- check_estimate(self, gedit_job, 1671)
-
- def test_proc_indep_virtual_null_headjob(self):
- xxr_build, xxr_job = find_job(self, 'xxr-daptup', None)
- # This job is at the head of the queue for virtualized builders and
- # will get dispatched within the next 5 seconds.
- check_estimate(self, xxr_job, 5)
-
- def test_proc_indep_virtual_false(self):
- xxr_build, xxr_job = find_job(self, 'xxr-aptitude', None)
- # The delay of 1403 seconds is calculated as follows:
- # hppa jobs: (9+11+13+15)*60/3 = 960
- # processor-independent jobs: 7:23 = 443
- check_estimate(self, xxr_job, 1403)
-
- def test_proc_indep_virtual_false_headjob(self):
- xxr_build, xxr_job = find_job(self, 'xxr-auto-apt', None)
- # This job is at the head of the queue for native builders and
- # will get dispatched within the next 5 seconds.
- check_estimate(self, xxr_job, 5)
-
- def test_estimation_binary_virtual_same_score(self):
- vim_build, vim_job = find_job(self, 'vim', '386')
- # The apg job is ahead of the vim job.
- # The delay of 1527 seconds is calculated as follows:
- # 386 jobs: (6+10+12+14+16)*60/5 = 696
- # processor-independent jobs:
- # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
- check_estimate(self, vim_job, 1527)
-
- def test_no_builder_no_estimate(self):
- # No dispatch estimate is provided in the absence of builders that
- # can run the job of interest (JOI).
- disable_builders(self, '386', True)
- vim_build, vim_job = find_job(self, 'vim', '386')
- check_estimate(self, vim_job, None)
-
- def disabled_test_estimates_with_small_builder_pool(self):
- # Test that a reduced builder pool results in longer dispatch time
- # estimates.
- vim_build, vim_job = find_job(self, 'vim', '386')
- disable_builders(self, '386', True)
- # Re-enable one builder.
- builder = self.builders[(self.x86_proc.id, True)][0]
- builder.builderok = True
- # Dispatch the firefox job to it.
- assign_to_builder(self, 'firefox', 1, '386')
- # Dispatch the head job, making postgres/386 the new head job and
- # resulting in a 240 seconds head job dispatch delay.
- assign_to_builder(self, 'xxr-daptup', 1, None)
- check_mintime_to_builder(self, vim_job, 240)
- # Re-enable another builder.
- builder = self.builders[(self.x86_proc.id, True)][1]
- builder.builderok = True
- # Assign a job to it.
- assign_to_builder(self, 'gedit', 2, '386')
- check_mintime_to_builder(self, vim_job, 120)
-
- xxr_build, xxr_job = find_job(self, 'xxr-apt', None)
- # The delay of 2627+120 seconds is calculated as follows:
- # 386 jobs : (6+10+12+14+16)*60/2 = 1740
- # processor-independent jobs :
- # (12:56 + 11:05 + 18:30 + 16:38 + 14:47)/5 = 887
- # waiting time for next builder: = 120
- self.assertEquals(2, builders_for_job(vim_job))
- self.assertEquals(9, builders_for_job(xxr_job))
- check_estimate(self, vim_job, 2747)
-
- def test_estimation_binary_virtual_headjob(self):
- # The head job only waits for the next builder to become available.
- disable_builders(self, '386', True)
- # Re-enable one builder.
- builder = self.builders[(self.x86_proc.id, True)][0]
- builder.builderok = True
- # Assign a job to it.
- assign_to_builder(self, 'gedit', 1, '386')
- # Dispatch the head job, making postgres/386 the new head job.
- assign_to_builder(self, 'xxr-daptup', 1, None)
- postgres_build, postgres_job = find_job(self, 'postgres', '386')
- check_estimate(self, postgres_job, 120)
-
-
class TestBuildQueueManual(TestCaseWithFactory):
layer = ZopelessDatabaseLayer
=== added file 'lib/lp/buildmaster/tests/test_queuedepth.py'
--- lib/lp/buildmaster/tests/test_queuedepth.py 1970-01-01 00:00:00 +0000
+++ lib/lp/buildmaster/tests/test_queuedepth.py 2013-10-31 06:45:03 +0000
@@ -0,0 +1,1098 @@
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+"""Test BuildQueue start time estimation."""
+
+from datetime import (
+ datetime,
+ timedelta,
+ )
+
+from pytz import utc
+from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+
+from lp.buildmaster.enums import BuildStatus
+from lp.buildmaster.interfaces.builder import IBuilderSet
+from lp.buildmaster.queuedepth import (
+ estimate_job_delay,
+ estimate_time_to_next_builder,
+ get_builder_data,
+ get_free_builders_count,
+ get_head_job_platform,
+ )
+from lp.buildmaster.tests.test_buildqueue import find_job
+from lp.services.database.interfaces import IStore
+from lp.soyuz.enums import (
+ ArchivePurpose,
+ PackagePublishingStatus,
+ )
+from lp.soyuz.interfaces.processor import IProcessorSet
+from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
+from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import LaunchpadZopelessLayer
+
+
+def check_mintime_to_builder(test, bq, min_time):
+ """Test the estimated time until a builder becomes available."""
+ time_stamp = bq.job.date_started or datetime.now(utc)
+ delay = estimate_time_to_next_builder(
+ removeSecurityProxy(bq), now=time_stamp)
+ test.assertTrue(
+ delay <= min_time,
+ "Wrong min time to next available builder (%s > %s)"
+ % (delay, min_time))
+
+
+def set_remaining_time_for_running_job(bq, remainder):
+ """Set remaining running time for job."""
+ offset = bq.estimated_duration.seconds - remainder
+ removeSecurityProxy(bq.job).date_started = (
+ datetime.now(utc) - timedelta(seconds=offset))
+
+
+def check_delay_for_job(test, the_job, delay):
+ # Obtain the builder statistics pertaining to this job.
+ builder_data = get_builder_data()
+ estimated_delay = estimate_job_delay(
+ removeSecurityProxy(the_job), builder_data)
+ test.assertEqual(delay, estimated_delay)
+
+
+def total_builders():
+ """How many available builders do we have in total?"""
+ builder_data = get_builder_data()
+ return builder_data[(None, False)] + builder_data[(None, True)]
+
+
+def builders_for_job(job):
+ """How many available builders can run the given job?"""
+ builder_data = get_builder_data()
+ return builder_data[(getattr(job.processor, 'id', None), job.virtualized)]
+
+
+def check_estimate(test, job, delay_in_seconds):
+ time_stamp = job.job.date_started or datetime.now(utc)
+ estimate = job.getEstimatedJobStartTime(now=time_stamp)
+ if delay_in_seconds is None:
+ test.assertEquals(
+ delay_in_seconds, estimate,
+ "An estimate should not be possible at present but one was "
+ "returned (%s) nevertheless." % estimate)
+ else:
+ estimate -= time_stamp
+ test.assertTrue(
+ estimate.seconds <= delay_in_seconds,
+ "The estimated delay deviates from the expected one (%s > %s)" %
+ (estimate.seconds, delay_in_seconds))
+
+
+def disable_builders(test, processor_name, virtualized):
+ """Disable bulders with the given processor and virtualization setting."""
+ if processor_name is not None:
+ processor = getUtility(IProcessorSet).getByName(processor_name)
+ for builder in test.builders[(processor.id, virtualized)]:
+ builder.builderok = False
+
+
+def nth_builder(test, bq, n):
+ """Find nth builder that can execute the given build."""
+
+ def builder_key(job):
+ """Access key for builders capable of running the given job."""
+ return (getattr(job.processor, 'id', None), job.virtualized)
+
+ builder = None
+ builders = test.builders.get(builder_key(bq), [])
+ try:
+ for builder in builders[n - 1:]:
+ if builder.builderok:
+ break
+ except IndexError:
+ pass
+ return builder
+
+
+def assign_to_builder(test, job_name, builder_number, processor='386'):
+ """Simulate assigning a build to a builder."""
+ build, bq = find_job(test, job_name, processor)
+ builder = nth_builder(test, bq, builder_number)
+ bq.markAsBuilding(builder)
+
+
+class TestBuildQueueBase(TestCaseWithFactory):
+ """Setup the test publisher and some builders."""
+
+ layer = LaunchpadZopelessLayer
+
+ def setUp(self):
+ super(TestBuildQueueBase, self).setUp()
+ self.publisher = SoyuzTestPublisher()
+ self.publisher.prepareBreezyAutotest()
+
+ # First make nine 'i386' builders.
+ self.i1 = self.factory.makeBuilder(name='i386-v-1')
+ self.i2 = self.factory.makeBuilder(name='i386-v-2')
+ self.i3 = self.factory.makeBuilder(name='i386-v-3')
+ self.i4 = self.factory.makeBuilder(name='i386-v-4')
+ self.i5 = self.factory.makeBuilder(name='i386-v-5')
+ self.i6 = self.factory.makeBuilder(name='i386-n-6', virtualized=False)
+ self.i7 = self.factory.makeBuilder(name='i386-n-7', virtualized=False)
+ self.i8 = self.factory.makeBuilder(name='i386-n-8', virtualized=False)
+ self.i9 = self.factory.makeBuilder(name='i386-n-9', virtualized=False)
+
+ # Next make seven 'hppa' builders.
+ self.hppa_proc = getUtility(IProcessorSet).getByName('hppa')
+ self.h1 = self.factory.makeBuilder(
+ name='hppa-v-1', processor=self.hppa_proc)
+ self.h2 = self.factory.makeBuilder(
+ name='hppa-v-2', processor=self.hppa_proc)
+ self.h3 = self.factory.makeBuilder(
+ name='hppa-v-3', processor=self.hppa_proc)
+ self.h4 = self.factory.makeBuilder(
+ name='hppa-v-4', processor=self.hppa_proc)
+ self.h5 = self.factory.makeBuilder(
+ name='hppa-n-5', processor=self.hppa_proc, virtualized=False)
+ self.h6 = self.factory.makeBuilder(
+ name='hppa-n-6', processor=self.hppa_proc, virtualized=False)
+ self.h7 = self.factory.makeBuilder(
+ name='hppa-n-7', processor=self.hppa_proc, virtualized=False)
+
+ # Finally make five 'amd64' builders.
+ self.amd_proc = getUtility(IProcessorSet).getByName('amd64')
+ self.a1 = self.factory.makeBuilder(
+ name='amd64-v-1', processor=self.amd_proc)
+ self.a2 = self.factory.makeBuilder(
+ name='amd64-v-2', processor=self.amd_proc)
+ self.a3 = self.factory.makeBuilder(
+ name='amd64-v-3', processor=self.amd_proc)
+ self.a4 = self.factory.makeBuilder(
+ name='amd64-n-4', processor=self.amd_proc, virtualized=False)
+ self.a5 = self.factory.makeBuilder(
+ name='amd64-n-5', processor=self.amd_proc, virtualized=False)
+
+ self.builders = dict()
+ self.x86_proc = getUtility(IProcessorSet).getByName('386')
+ # x86 native
+ self.builders[(self.x86_proc.id, False)] = [
+ self.i6, self.i7, self.i8, self.i9]
+ # x86 virtual
+ self.builders[(self.x86_proc.id, True)] = [
+ self.i1, self.i2, self.i3, self.i4, self.i5]
+
+ # amd64 native
+ self.builders[(self.amd_proc.id, False)] = [self.a4, self.a5]
+ # amd64 virtual
+ self.builders[(self.amd_proc.id, True)] = [self.a1, self.a2, self.a3]
+
+ # hppa native
+ self.builders[(self.hppa_proc.id, False)] = [
+ self.h5,
+ self.h6,
+ self.h7,
+ ]
+ # hppa virtual
+ self.builders[(self.hppa_proc.id, True)] = [
+ self.h1, self.h2, self.h3, self.h4]
+
+ # Ensure all builders are operational.
+ for builders in self.builders.values():
+ for builder in builders:
+ builder.builderok = True
+ builder.manual = False
+
+ # Native builders irrespective of processor.
+ self.builders[(None, False)] = []
+ self.builders[(None, False)].extend(
+ self.builders[(self.x86_proc.id, False)])
+ self.builders[(None, False)].extend(
+ self.builders[(self.amd_proc.id, False)])
+ self.builders[(None, False)].extend(
+ self.builders[(self.hppa_proc.id, False)])
+
+ # Virtual builders irrespective of processor.
+ self.builders[(None, True)] = []
+ self.builders[(None, True)].extend(
+ self.builders[(self.x86_proc.id, True)])
+ self.builders[(None, True)].extend(
+ self.builders[(self.amd_proc.id, True)])
+ self.builders[(None, True)].extend(
+ self.builders[(self.hppa_proc.id, True)])
+
+ # Disable the sample data builders.
+ getUtility(IBuilderSet)['bob'].builderok = False
+ getUtility(IBuilderSet)['frog'].builderok = False
+
+
+class SingleArchBuildsBase(TestBuildQueueBase):
+ """Set up a test environment with builds that target a single
+ processor."""
+
+ def setUp(self):
+ """Set up some native x86 builds for the test archive."""
+ super(SingleArchBuildsBase, self).setUp()
+ # The builds will be set up as follows:
+ #
+ # gedit, p: 386, v:False e:0:01:00 *** s: 1001
+ # firefox, p: 386, v:False e:0:02:00 *** s: 1002
+ # apg, p: 386, v:False e:0:03:00 *** s: 1003
+ # vim, p: 386, v:False e:0:04:00 *** s: 1004
+ # gcc, p: 386, v:False e:0:05:00 *** s: 1005
+ # bison, p: 386, v:False e:0:06:00 *** s: 1006
+ # flex, p: 386, v:False e:0:07:00 *** s: 1007
+ # postgres, p: 386, v:False e:0:08:00 *** s: 1008
+ #
+ # p=processor, v=virtualized, e=estimated_duration, s=score
+
+ # First mark all builds in the sample data as already built.
+ sample_data = IStore(BinaryPackageBuild).find(BinaryPackageBuild)
+ for build in sample_data:
+ build.buildstate = BuildStatus.FULLYBUILT
+ IStore(BinaryPackageBuild).flush()
+
+ # We test builds that target a primary archive.
+ self.non_ppa = self.factory.makeArchive(
+ name="primary", purpose=ArchivePurpose.PRIMARY)
+ self.non_ppa.require_virtualized = False
+
+ self.builds = []
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="firefox",
+ status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="postgres",
+ status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa).createMissingBuilds())
+ # Set up the builds for test.
+ score = 1000
+ duration = 0
+ for build in self.builds:
+ score += 1
+ duration += 60
+ bq = build.buildqueue_record
+ bq.lastscore = score
+ bq.estimated_duration = timedelta(seconds=duration)
+
+
+class TestBuilderData(SingleArchBuildsBase):
+ """Test the retrieval of builder related data. The latter is required
+ for job dispatch time estimations irrespective of job processor
+ architecture and virtualization setting."""
+
+ def test_builder_data(self):
+ # Make sure the builder numbers are correct. The builder data will
+ # be the same for all of our builds.
+ bq = self.builds[0].buildqueue_record
+ self.assertEqual(
+ 21, total_builders(),
+ "The total number of builders is wrong.")
+ self.assertEqual(
+ 4, builders_for_job(bq),
+ "[1] The total number of builders that can build the job in "
+ "question is wrong.")
+ builder_stats = get_builder_data()
+ self.assertEqual(
+ 4, builder_stats[(self.x86_proc.id, False)],
+ "The number of native x86 builders is wrong")
+ self.assertEqual(
+ 5, builder_stats[(self.x86_proc.id, True)],
+ "The number of virtual x86 builders is wrong")
+ self.assertEqual(
+ 2, builder_stats[(self.amd_proc.id, False)],
+ "The number of native amd64 builders is wrong")
+ self.assertEqual(
+ 3, builder_stats[(self.amd_proc.id, True)],
+ "The number of virtual amd64 builders is wrong")
+ self.assertEqual(
+ 3, builder_stats[(self.hppa_proc.id, False)],
+ "The number of native hppa builders is wrong")
+ self.assertEqual(
+ 4, builder_stats[(self.hppa_proc.id, True)],
+ "The number of virtual hppa builders is wrong")
+ self.assertEqual(
+ 9, builder_stats[(None, False)],
+ "The number of *virtual* builders across all processors is wrong")
+ self.assertEqual(
+ 12, builder_stats[(None, True)],
+ "The number of *native* builders across all processors is wrong")
+ # Disable the native x86 builders.
+ for builder in self.builders[(self.x86_proc.id, False)]:
+ builder.builderok = False
+ # Since all native x86 builders were disabled there are none left
+ # to build the job.
+ self.assertEqual(
+ 0, builders_for_job(bq),
+ "[2] The total number of builders that can build the job in "
+ "question is wrong.")
+ # Re-enable one of them.
+ for builder in self.builders[(self.x86_proc.id, False)]:
+ builder.builderok = True
+ break
+ # Now there should be one builder available to build the job.
+ self.assertEqual(
+ 1, builders_for_job(bq),
+ "[3] The total number of builders that can build the job in "
+ "question is wrong.")
+ # Disable the *virtual* x86 builders -- should not make any
+ # difference.
+ for builder in self.builders[(self.x86_proc.id, True)]:
+ builder.builderok = False
+ # There should still be one builder available to build the job.
+ self.assertEqual(
+ 1, builders_for_job(bq),
+ "[4] The total number of builders that can build the job in "
+ "question is wrong.")
+
+ def test_free_builder_counts(self):
+ # Make sure the builder numbers are correct. The builder data will
+ # be the same for all of our builds.
+ build = self.builds[0]
+ # The build in question is an x86/native one.
+ self.assertEqual(self.x86_proc.id, build.processor.id)
+ self.assertEqual(False, build.is_virtualized)
+
+ # To test this non-interface method, we need to remove the
+ # security proxy.
+ bq = removeSecurityProxy(build.buildqueue_record)
+ builder_stats = get_builder_data()
+ # We have 4 x86 native builders.
+ self.assertEqual(
+ 4, builder_stats[(self.x86_proc.id, False)],
+ "The number of native x86 builders is wrong")
+ # Initially all 4 builders are free.
+ free_count = get_free_builders_count(
+ build.processor, build.is_virtualized)
+ self.assertEqual(4, free_count)
+ # Once we assign a build to one of them we should see the free
+ # builders count drop by one.
+ assign_to_builder(self, 'postgres', 1)
+ free_count = get_free_builders_count(
+ build.processor, build.is_virtualized)
+ self.assertEqual(3, free_count)
+ # When we assign another build to one of them we should see the free
+ # builders count drop by one again.
+ assign_to_builder(self, 'gcc', 2)
+ free_count = get_free_builders_count(
+ build.processor, build.is_virtualized)
+ self.assertEqual(2, free_count)
+ # Let's use up another builder.
+ assign_to_builder(self, 'apg', 3)
+ free_count = get_free_builders_count(
+ build.processor, build.is_virtualized)
+ self.assertEqual(1, free_count)
+ # And now for the last one.
+ assign_to_builder(self, 'flex', 4)
+ free_count = get_free_builders_count(
+ build.processor, build.is_virtualized)
+ self.assertEqual(0, free_count)
+ # If we reset the 'flex' build the builder that was assigned to it
+ # will be free again.
+ build, bq = find_job(self, 'flex')
+ bq.reset()
+ free_count = get_free_builders_count(
+ build.processor, build.is_virtualized)
+ self.assertEqual(1, free_count)
+
+
+class TestMinTimeToNextBuilder(SingleArchBuildsBase):
+ """Test estimated time-to-builder with builds targetting a single
+ processor."""
+
+ def test_min_time_to_next_builder(self):
+ """When is the next builder capable of running the job at the head of
+ the queue becoming available?"""
+ # Test the estimation of the minimum time until a builder becomes
+ # available.
+
+ # The builds will be set up as follows:
+ #
+ # gedit, p: 386, v:False e:0:01:00 *** s: 1001
+ # firefox, p: 386, v:False e:0:02:00 *** s: 1002
+ # apg, p: 386, v:False e:0:03:00 *** s: 1003
+ # vim, p: 386, v:False e:0:04:00 *** s: 1004
+ # gcc, p: 386, v:False e:0:05:00 *** s: 1005
+ # bison, p: 386, v:False e:0:06:00 *** s: 1006
+ # flex, p: 386, v:False e:0:07:00 *** s: 1007
+ # postgres, p: 386, v:False e:0:08:00 *** s: 1008
+ #
+ # p=processor, v=virtualized, e=estimated_duration, s=score
+
+ # This will be the job of interest.
+ apg_build, apg_job = find_job(self, 'apg')
+ # One of four builders for the 'apg' build is immediately available.
+ check_mintime_to_builder(self, apg_job, 0)
+
+ # Assign the postgres job to a builder.
+ assign_to_builder(self, 'postgres', 1)
+ # Now one builder is gone. But there should still be a builder
+ # immediately available.
+ check_mintime_to_builder(self, apg_job, 0)
+
+ assign_to_builder(self, 'flex', 2)
+ check_mintime_to_builder(self, apg_job, 0)
+
+ assign_to_builder(self, 'bison', 3)
+ check_mintime_to_builder(self, apg_job, 0)
+
+ assign_to_builder(self, 'gcc', 4)
+ # Now that no builder is immediately available, the shortest
+ # remaing build time (based on the estimated duration) is returned:
+ # 300 seconds
+ # This is equivalent to the 'gcc' job's estimated duration.
+ check_mintime_to_builder(self, apg_job, 300)
+
+ # Now we pretend that the 'postgres' started 6 minutes ago. Its
+ # remaining execution time should be 2 minutes = 120 seconds and
+ # it now becomes the job whose builder becomes available next.
+ build, bq = find_job(self, 'postgres')
+ set_remaining_time_for_running_job(bq, 120)
+ check_mintime_to_builder(self, apg_job, 120)
+
+ # What happens when jobs overdraw the estimated duration? Let's
+ # pretend the 'flex' job started 8 minutes ago.
+ build, bq = find_job(self, 'flex')
+ set_remaining_time_for_running_job(bq, -60)
+ # In such a case we assume that the job will complete within 2
+ # minutes, this is a guess that has worked well so far.
+ check_mintime_to_builder(self, apg_job, 120)
+
+ # If there's a job that will complete within a shorter time then
+ # we expect to be given that time frame.
+ build, bq = find_job(self, 'postgres')
+ set_remaining_time_for_running_job(bq, 30)
+ check_mintime_to_builder(self, apg_job, 30)
+
+ # Disable the native x86 builders.
+ for builder in self.builders[(self.x86_proc.id, False)]:
+ builder.builderok = False
+
+ # No builders capable of running the job at hand are available now.
+ self.assertEquals(0, builders_for_job(apg_job))
+ # The "minimum time to builder" estimation logic is not aware of this
+ # though.
+ check_mintime_to_builder(self, apg_job, 0)
+
+ # The following job can only run on a native builder.
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=111, sourcename=u'xxr-gftp', score=1055,
+ virtualized=False)
+ self.builds.append(job.specific_job.build)
+
+ # Disable all native builders.
+ for builder in self.builders[(None, False)]:
+ builder.builderok = False
+
+ # All native builders are disabled now. No builders capable of
+ # running the job at hand are available.
+ self.assertEquals(0, builders_for_job(job))
+ # The "minimum time to builder" estimation logic is not aware of the
+ # fact that no builders capable of running the job are available.
+ check_mintime_to_builder(self, job, 0)
+
+
+class MultiArchBuildsBase(TestBuildQueueBase):
+ """Set up a test environment with builds and multiple processors."""
+
+ def setUp(self):
+ """Set up some native x86 builds for the test archive."""
+ super(MultiArchBuildsBase, self).setUp()
+ # The builds will be set up as follows:
+ #
+ # gedit, p: hppa, v:False e:0:01:00 *** s: 1001
+ # gedit, p: 386, v:False e:0:02:00 *** s: 1002
+ # firefox, p: hppa, v:False e:0:03:00 *** s: 1003
+ # firefox, p: 386, v:False e:0:04:00 *** s: 1004
+ # apg, p: hppa, v:False e:0:05:00 *** s: 1005
+ # apg, p: 386, v:False e:0:06:00 *** s: 1006
+ # vim, p: hppa, v:False e:0:07:00 *** s: 1007
+ # vim, p: 386, v:False e:0:08:00 *** s: 1008
+ # gcc, p: hppa, v:False e:0:09:00 *** s: 1009
+ # gcc, p: 386, v:False e:0:10:00 *** s: 1010
+ # bison, p: hppa, v:False e:0:11:00 *** s: 1011
+ # bison, p: 386, v:False e:0:12:00 *** s: 1012
+ # flex, p: hppa, v:False e:0:13:00 *** s: 1013
+ # flex, p: 386, v:False e:0:14:00 *** s: 1014
+ # postgres, p: hppa, v:False e:0:15:00 *** s: 1015
+ # postgres, p: 386, v:False e:0:16:00 *** s: 1016
+ #
+ # p=processor, v=virtualized, e=estimated_duration, s=score
+
+ # First mark all builds in the sample data as already built.
+ sample_data = IStore(BinaryPackageBuild).find(BinaryPackageBuild)
+ for build in sample_data:
+ build.buildstate = BuildStatus.FULLYBUILT
+ IStore(BinaryPackageBuild).flush()
+
+ # We test builds that target a primary archive.
+ self.non_ppa = self.factory.makeArchive(
+ name="primary", purpose=ArchivePurpose.PRIMARY)
+ self.non_ppa.require_virtualized = False
+
+ self.builds = []
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="firefox",
+ status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ self.builds.extend(
+ self.publisher.getPubSource(
+ sourcename="postgres",
+ status=PackagePublishingStatus.PUBLISHED,
+ archive=self.non_ppa,
+ architecturehintlist='any').createMissingBuilds())
+ # Set up the builds for test.
+ score = 1000
+ duration = 0
+ for build in self.builds:
+ score += getattr(self, 'score_increment', 1)
+ score += 1
+ duration += 60
+ bq = build.buildqueue_record
+ bq.lastscore = score
+ bq.estimated_duration = timedelta(seconds=duration)
+
+
+class TestMinTimeToNextBuilderMulti(MultiArchBuildsBase):
+ """Test estimated time-to-builder with builds and multiple processors."""
+
+ def disabled_test_min_time_to_next_builder(self):
+ """When is the next builder capable of running the job at the head of
+ the queue becoming available?"""
+ # XXX AaronBentley 2010-03-19 bug=541914: Fails spuriously
+ # One of four builders for the 'apg' build is immediately available.
+ apg_build, apg_job = find_job(self, 'apg', 'hppa')
+ check_mintime_to_builder(self, apg_job, 0)
+
+ # Assign the postgres job to a builder.
+ assign_to_builder(self, 'postgres', 1, 'hppa')
+ # Now one builder is gone. But there should still be a builder
+ # immediately available.
+ check_mintime_to_builder(self, apg_job, 0)
+
+ assign_to_builder(self, 'flex', 2, 'hppa')
+ check_mintime_to_builder(self, apg_job, 0)
+
+ assign_to_builder(self, 'bison', 3, 'hppa')
+ # Now that no builder is immediately available, the shortest
+ # remaing build time (based on the estimated duration) is returned:
+ # 660 seconds
+ # This is equivalent to the 'bison' job's estimated duration.
+ check_mintime_to_builder(self, apg_job, 660)
+
+ # Now we pretend that the 'postgres' started 13 minutes ago. Its
+ # remaining execution time should be 2 minutes = 120 seconds and
+ # it now becomes the job whose builder becomes available next.
+ build, bq = find_job(self, 'postgres', 'hppa')
+ set_remaining_time_for_running_job(bq, 120)
+ check_mintime_to_builder(self, apg_job, 120)
+
+ # What happens when jobs overdraw the estimated duration? Let's
+ # pretend the 'flex' job started 14 minutes ago.
+ build, bq = find_job(self, 'flex', 'hppa')
+ set_remaining_time_for_running_job(bq, -60)
+ # In such a case we assume that the job will complete within 2
+ # minutes, this is a guess that has worked well so far.
+ check_mintime_to_builder(self, apg_job, 120)
+
+ # If there's a job that will complete within a shorter time then
+ # we expect to be given that time frame.
+ build, bq = find_job(self, 'postgres', 'hppa')
+ set_remaining_time_for_running_job(bq, 30)
+ check_mintime_to_builder(self, apg_job, 30)
+
+ # Disable the native hppa builders.
+ for builder in self.builders[(self.hppa_proc.id, False)]:
+ builder.builderok = False
+
+ # No builders capable of running the job at hand are available now.
+ self.assertEquals(0, builders_for_job(apg_job))
+ check_mintime_to_builder(self, apg_job, 0)
+
+ # Let's add a processor-independent job to the mix.
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ virtualized=False, estimated_duration=22,
+ sourcename='my-recipe-digikam', score=9999)
+ # There are still builders available for the processor-independent
+ # job.
+ self.assertEquals(6, builders_for_job(job))
+ # Even free ones.
+ self.assertTrue(
+ bq._getFreeBuildersCount(job.processor, job.virtualized) > 0,
+ "Builders are immediately available for processor-independent "
+ "jobs.")
+ check_mintime_to_builder(self, job, 0)
+
+ # Let's disable all builders.
+ for builders in self.builders.itervalues():
+ for builder in builders:
+ builder.builderok = False
+
+ # There are no builders capable of running even the processor
+ # independent jobs now.
+ self.assertEquals(0, builders_for_job(job))
+ check_mintime_to_builder(self, job, 0)
+
+ # Re-enable the native hppa builders.
+ for builder in self.builders[(self.hppa_proc.id, False)]:
+ builder.builderok = True
+
+ # The builder that's becoming available next is the one that's
+ # running the 'postgres' build.
+ check_mintime_to_builder(self, apg_job, 30)
+
+ # Make sure we'll find an x86 builder as well.
+ builder = self.builders[(self.x86_proc.id, False)][0]
+ builder.builderok = True
+
+ # Now this builder is the one that becomes available next (29 minutes
+ # remaining build time).
+ assign_to_builder(self, 'gcc', 1, '386')
+ build, bq = find_job(self, 'gcc', '386')
+ set_remaining_time_for_running_job(bq, 29)
+
+ check_mintime_to_builder(self, apg_job, 29)
+
+ # Make a second, idle x86 builder available.
+ builder = self.builders[(self.x86_proc.id, False)][1]
+ builder.builderok = True
+
+ # That builder should be available immediately since it's idle.
+ check_mintime_to_builder(self, apg_job, 0)
+
+
+class TestMultiArchJobDelayEstimation(MultiArchBuildsBase):
+ """Test estimated job delays with various processors."""
+ score_increment = 2
+
+ def setUp(self):
+ """Add 2 'build source package from recipe' builds to the mix.
+
+ The two platform-independent jobs will have a score of 1025 and 1053
+ respectively.
+ In case of jobs with equal scores the one with the lesser 'job' value
+ (i.e. the older one wins).
+
+ 3, gedit, p: hppa, v:False e:0:01:00 *** s: 1003
+ 4, gedit, p: 386, v:False e:0:02:00 *** s: 1006
+ 5, firefox, p: hppa, v:False e:0:03:00 *** s: 1009
+ 6, firefox, p: 386, v:False e:0:04:00 *** s: 1012
+ 7, apg, p: hppa, v:False e:0:05:00 *** s: 1015
+ 9, vim, p: hppa, v:False e:0:07:00 *** s: 1021
+ 10, vim, p: 386, v:False e:0:08:00 *** s: 1024
+ 8, apg, p: 386, v:False e:0:06:00 *** s: 1024
+ --> 19, xx-recipe-bash, p: None, v:False e:0:00:22 *** s: 1025
+ 11, gcc, p: hppa, v:False e:0:09:00 *** s: 1027
+ 12, gcc, p: 386, v:False e:0:10:00 *** s: 1030
+ 13, bison, p: hppa, v:False e:0:11:00 *** s: 1033
+ 14, bison, p: 386, v:False e:0:12:00 *** s: 1036
+ 15, flex, p: hppa, v:False e:0:13:00 *** s: 1039
+ 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
+ 17, postgres, p: hppa, v:False e:0:15:00 *** s: 1045
+ 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
+ --> 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
+
+ p=processor, v=virtualized, e=estimated_duration, s=score
+ """
+ super(TestMultiArchJobDelayEstimation, self).setUp()
+
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ virtualized=False, estimated_duration=22,
+ sourcename=u'xx-recipe-bash', score=1025)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ virtualized=False, estimated_duration=222,
+ sourcename=u'xx-recipe-zsh', score=1053)
+ self.builds.append(job.specific_job.build)
+
+ # Assign the same score to the '386' vim and apg build jobs.
+ _apg_build, apg_job = find_job(self, 'apg', '386')
+ apg_job.lastscore = 1024
+
+ def disabled_test_job_delay_for_binary_builds(self):
+ # One of four builders for the 'flex' build is immediately available.
+ flex_build, flex_job = find_job(self, 'flex', 'hppa')
+ check_mintime_to_builder(self, flex_job, 0)
+
+ # The delay will be 900 (= 15*60) + 222 seconds
+ check_delay_for_job(self, flex_job, 1122)
+
+ # Assign the postgres job to a builder.
+ assign_to_builder(self, 'postgres', 1, 'hppa')
+ # The 'postgres' job is not pending any more. Now only the 222
+ # seconds (the estimated duration of the platform-independent job)
+ # should be returned.
+ check_delay_for_job(self, flex_job, 222)
+
+ # How about some estimates for x86 builds?
+ _bison_build, bison_job = find_job(self, 'bison', '386')
+ check_mintime_to_builder(self, bison_job, 0)
+ # The delay will be 900 (= (14+16)*60/2) + 222 seconds.
+ check_delay_for_job(self, bison_job, 1122)
+
+ # The 2 tests that follow exercise the estimation in conjunction with
+ # longer pending job queues. Please note that the sum of estimates for
+ # the '386' jobs is divided by 4 which is the number of native '386'
+ # builders.
+
+ # Also, this tests that jobs with equal score but a lower 'job' value
+ # (i.e. older jobs) are queued ahead of the job of interest (JOI).
+ _vim_build, vim_job = find_job(self, 'vim', '386')
+ check_mintime_to_builder(self, vim_job, 0)
+ # The delay will be 870 (= (6+10+12+14+16)*60/4) + 122 (= (222+22)/2)
+ # seconds.
+ check_delay_for_job(self, vim_job, 992)
+
+ _gedit_build, gedit_job = find_job(self, 'gedit', '386')
+ check_mintime_to_builder(self, gedit_job, 0)
+ # The delay will be
+ # 1080 (= (4+6+8+10+12+14+16)*60/4) + 122 (= (222+22)/2)
+ # seconds.
+ check_delay_for_job(self, gedit_job, 1172)
+
+ def disabled_test_job_delay_for_recipe_builds(self):
+ # One of the 9 builders for the 'bash' build is immediately available.
+ bash_build, bash_job = find_job(self, 'xx-recipe-bash', None)
+ check_mintime_to_builder(self, bash_job, 0)
+
+ # The delay will be 960 + 780 + 222 = 1962, where
+ # hppa job delays: 960 = (9+11+13+15)*60/3
+ # 386 job delays: 780 = (10+12+14+16)*60/4
+ check_delay_for_job(self, bash_job, 1962)
+
+ # One of the 9 builders for the 'zsh' build is immediately available.
+ zsh_build, zsh_job = find_job(self, 'xx-recipe-zsh', None)
+ check_mintime_to_builder(self, zsh_job, 0)
+
+ # The delay will be 0 since this is the head job.
+ check_delay_for_job(self, zsh_job, 0)
+
+ # Assign the zsh job to a builder.
+ self.assertEquals((None, False), bash_job._getHeadJobPlatform())
+ assign_to_builder(self, 'xx-recipe-zsh', 1, None)
+ self.assertEquals((1, False), bash_job._getHeadJobPlatform())
+
+ # Now that the highest-scored job is out of the way, the estimation
+ # for the 'bash' recipe build is 222 seconds shorter.
+
+ # The delay will be 960 + 780 = 1740, where
+ # hppa job delays: 960 = (9+11+13+15)*60/3
+ # 386 job delays: 780 = (10+12+14+16)*60/4
+ check_delay_for_job(self, bash_job, 1740)
+
+ _postgres_build, postgres_job = find_job(self, 'postgres', '386')
+ # The delay will be 0 since this is the head job now.
+ check_delay_for_job(self, postgres_job, 0)
+ # Also, the platform of the postgres job is returned since it *is*
+ # the head job now.
+ pg_platform = (postgres_job.processor.id, postgres_job.virtualized)
+ self.assertEquals(pg_platform, postgres_job._getHeadJobPlatform())
+
+ def test_job_delay_for_unspecified_virtualization(self):
+ # Make sure that jobs with a NULL 'virtualized' flag get the same
+ # treatment as the ones with virtualized=TRUE.
+ # First toggle the 'virtualized' flag for all hppa jobs.
+ for build in self.builds:
+ bq = build.buildqueue_record
+ if bq.processor == self.hppa_proc:
+ removeSecurityProxy(bq).virtualized = True
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ virtualized=True, estimated_duration=332,
+ sourcename=u'xxr-openssh-client', score=1050)
+ self.builds.append(job.specific_job.build)
+ # print_build_setup(self.builds)
+ # ...
+ # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039
+ # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
+ # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045
+ # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
+ # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050
+ # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
+
+ flex_build, flex_job = find_job(self, 'flex', 'hppa')
+ # The head job platform is the one of job #21 (xxr-openssh-client).
+ self.assertEquals(
+ (None, True), get_head_job_platform(removeSecurityProxy(flex_job)))
+ # The delay will be 900 (= 15*60) + 332 seconds
+ check_delay_for_job(self, flex_job, 1232)
+
+ # Now add a job with a NULL 'virtualized' flag. It should be treated
+ # like jobs with virtualized=TRUE.
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=111, sourcename=u'xxr-gwibber', score=1051,
+ virtualized=None)
+ self.builds.append(job.specific_job.build)
+ # print_build_setup(self.builds)
+ self.assertEqual(None, job.virtualized)
+ # ...
+ # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039
+ # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
+ # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045
+ # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
+ # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050
+ # 22, xxr-gwibber, p: None, v: None e:0:01:51 *** s: 1051
+ # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
+
+ # The newly added 'xxr-gwibber' job is the new head job now.
+ self.assertEquals(
+ (None, None), get_head_job_platform(removeSecurityProxy(flex_job)))
+ # The newly added 'xxr-gwibber' job now weighs in as well and the
+ # delay is 900 (= 15*60) + (332+111)/2 seconds
+ check_delay_for_job(self, flex_job, 1121)
+
+ # The '386' flex job does not care about the 'xxr-gwibber' and
+ # 'xxr-openssh-client' jobs since the 'virtualized' values do not
+ # match.
+ flex_build, flex_job = find_job(self, 'flex', '386')
+ self.assertEquals(
+ (None, False),
+ get_head_job_platform(removeSecurityProxy(flex_job)))
+ # delay is 960 (= 16*60) + 222 seconds
+ check_delay_for_job(self, flex_job, 1182)
+
+
+class TestJobDispatchTimeEstimation(MultiArchBuildsBase):
+ """Test estimated job delays with various processors."""
+ score_increment = 2
+
+ def setUp(self):
+ """Add more processor-independent jobs to the mix, make the '386' jobs
+ virtual.
+
+ 3, gedit, p: hppa, v:False e:0:01:00 *** s: 1003
+ 4, gedit, p: 386, v: True e:0:02:00 *** s: 1006
+ 5, firefox, p: hppa, v:False e:0:03:00 *** s: 1009
+ 6, firefox, p: 386, v: True e:0:04:00 *** s: 1012
+ 7, apg, p: hppa, v:False e:0:05:00 *** s: 1015
+ 9, vim, p: hppa, v:False e:0:07:00 *** s: 1021
+ 10, vim, p: 386, v: True e:0:08:00 *** s: 1024
+ 8, apg, p: 386, v: True e:0:06:00 *** s: 1024
+ 19, xxr-aptitude, p: None, v:False e:0:05:32 *** s: 1025
+ 11, gcc, p: hppa, v:False e:0:09:00 *** s: 1027
+ 12, gcc, p: 386, v: True e:0:10:00 *** s: 1030
+ 13, bison, p: hppa, v:False e:0:11:00 *** s: 1033
+ 14, bison, p: 386, v: True e:0:12:00 *** s: 1036
+ 15, flex, p: hppa, v:False e:0:13:00 *** s: 1039
+ 16, flex, p: 386, v: True e:0:14:00 *** s: 1042
+ 23, xxr-apt-build, p: None, v: True e:0:12:56 *** s: 1043
+ 22, xxr-cron-apt, p: None, v: True e:0:11:05 *** s: 1043
+ 26, xxr-cupt, p: None, v: None e:0:18:30 *** s: 1044
+ 25, xxr-apt, p: None, v: None e:0:16:38 *** s: 1044
+ 24, xxr-debdelta, p: None, v: None e:0:14:47 *** s: 1044
+ 17, postgres, p: hppa, v:False e:0:15:00 *** s: 1045
+ 18, postgres, p: 386, v: True e:0:16:00 *** s: 1048
+ 21, xxr-daptup, p: None, v: None e:0:09:14 *** s: 1051
+ 20, xxr-auto-apt, p: None, v:False e:0:07:23 *** s: 1053
+
+ p=processor, v=virtualized, e=estimated_duration, s=score
+ """
+ super(TestJobDispatchTimeEstimation, self).setUp()
+
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ virtualized=False, estimated_duration=332,
+ sourcename=u'xxr-aptitude', score=1025)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ virtualized=False, estimated_duration=443,
+ sourcename=u'xxr-auto-apt', score=1053)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=554, sourcename=u'xxr-daptup', score=1051,
+ virtualized=None)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=665, sourcename=u'xxr-cron-apt', score=1043)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=776, sourcename=u'xxr-apt-build', score=1043)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=887, sourcename=u'xxr-debdelta', score=1044,
+ virtualized=None)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=998, sourcename=u'xxr-apt', score=1044,
+ virtualized=None)
+ self.builds.append(job.specific_job.build)
+ job = self.factory.makeSourcePackageRecipeBuildJob(
+ estimated_duration=1110, sourcename=u'xxr-cupt', score=1044,
+ virtualized=None)
+ self.builds.append(job.specific_job.build)
+
+ # Assign the same score to the '386' vim and apg build jobs.
+ _apg_build, apg_job = find_job(self, 'apg', '386')
+ apg_job.lastscore = 1024
+
+ # Also, toggle the 'virtualized' flag for all '386' jobs.
+ for build in self.builds:
+ bq = build.buildqueue_record
+ if bq.processor == self.x86_proc:
+ removeSecurityProxy(bq).virtualized = True
+
+ def test_pending_jobs_only(self):
+ # Let's see the assertion fail for a job that's not pending any more.
+ assign_to_builder(self, 'gedit', 1, 'hppa')
+ gedit_build, gedit_job = find_job(self, 'gedit', 'hppa')
+ self.assertRaises(AssertionError, gedit_job.getEstimatedJobStartTime)
+
+ def test_estimation_binary_virtual(self):
+ gcc_build, gcc_job = find_job(self, 'gcc', '386')
+ # The delay of 1671 seconds is calculated as follows:
+ # 386 jobs: (12+14+16)*60/3 = 840
+ # processor-independent jobs:
+ # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
+ check_estimate(self, gcc_job, 1671)
+ self.assertEquals(5, builders_for_job(gcc_job))
+
+ def test_proc_indep_virtual_true(self):
+ xxr_build, xxr_job = find_job(self, 'xxr-apt-build', None)
+ # The delay of 1802 seconds is calculated as follows:
+ # 386 jobs: 16*60 = 960
+ # processor-independent jobs:
+ # (11:05 + 18:30 + 16:38 + 14:47 + 9:14)/5 = 842
+ check_estimate(self, xxr_job, 1802)
+
+ def test_estimation_binary_virtual_long_queue(self):
+ gedit_build, gedit_job = find_job(self, 'gedit', '386')
+ # The delay of 1671 seconds is calculated as follows:
+ # 386 jobs:
+ # (4+6+8+10+12+14+16)*60/5 = 840
+ # processor-independent jobs:
+ # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
+ check_estimate(self, gedit_job, 1671)
+
+ def test_proc_indep_virtual_null_headjob(self):
+ xxr_build, xxr_job = find_job(self, 'xxr-daptup', None)
+ # This job is at the head of the queue for virtualized builders and
+ # will get dispatched within the next 5 seconds.
+ check_estimate(self, xxr_job, 5)
+
+ def test_proc_indep_virtual_false(self):
+ xxr_build, xxr_job = find_job(self, 'xxr-aptitude', None)
+ # The delay of 1403 seconds is calculated as follows:
+ # hppa jobs: (9+11+13+15)*60/3 = 960
+ # processor-independent jobs: 7:23 = 443
+ check_estimate(self, xxr_job, 1403)
+
+ def test_proc_indep_virtual_false_headjob(self):
+ xxr_build, xxr_job = find_job(self, 'xxr-auto-apt', None)
+ # This job is at the head of the queue for native builders and
+ # will get dispatched within the next 5 seconds.
+ check_estimate(self, xxr_job, 5)
+
+ def test_estimation_binary_virtual_same_score(self):
+ vim_build, vim_job = find_job(self, 'vim', '386')
+ # The apg job is ahead of the vim job.
+ # The delay of 1527 seconds is calculated as follows:
+ # 386 jobs: (6+10+12+14+16)*60/5 = 696
+ # processor-independent jobs:
+ # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
+ check_estimate(self, vim_job, 1527)
+
+ def test_no_builder_no_estimate(self):
+ # No dispatch estimate is provided in the absence of builders that
+ # can run the job of interest (JOI).
+ disable_builders(self, '386', True)
+ vim_build, vim_job = find_job(self, 'vim', '386')
+ check_estimate(self, vim_job, None)
+
+ def disabled_test_estimates_with_small_builder_pool(self):
+ # Test that a reduced builder pool results in longer dispatch time
+ # estimates.
+ vim_build, vim_job = find_job(self, 'vim', '386')
+ disable_builders(self, '386', True)
+ # Re-enable one builder.
+ builder = self.builders[(self.x86_proc.id, True)][0]
+ builder.builderok = True
+ # Dispatch the firefox job to it.
+ assign_to_builder(self, 'firefox', 1, '386')
+ # Dispatch the head job, making postgres/386 the new head job and
+ # resulting in a 240 seconds head job dispatch delay.
+ assign_to_builder(self, 'xxr-daptup', 1, None)
+ check_mintime_to_builder(self, vim_job, 240)
+ # Re-enable another builder.
+ builder = self.builders[(self.x86_proc.id, True)][1]
+ builder.builderok = True
+ # Assign a job to it.
+ assign_to_builder(self, 'gedit', 2, '386')
+ check_mintime_to_builder(self, vim_job, 120)
+
+ xxr_build, xxr_job = find_job(self, 'xxr-apt', None)
+ # The delay of 2627+120 seconds is calculated as follows:
+ # 386 jobs : (6+10+12+14+16)*60/2 = 1740
+ # processor-independent jobs :
+ # (12:56 + 11:05 + 18:30 + 16:38 + 14:47)/5 = 887
+ # waiting time for next builder: = 120
+ self.assertEquals(2, builders_for_job(vim_job))
+ self.assertEquals(9, builders_for_job(xxr_job))
+ check_estimate(self, vim_job, 2747)
+
+ def test_estimation_binary_virtual_headjob(self):
+ # The head job only waits for the next builder to become available.
+ disable_builders(self, '386', True)
+ # Re-enable one builder.
+ builder = self.builders[(self.x86_proc.id, True)][0]
+ builder.builderok = True
+ # Assign a job to it.
+ assign_to_builder(self, 'gedit', 1, '386')
+ # Dispatch the head job, making postgres/386 the new head job.
+ assign_to_builder(self, 'xxr-daptup', 1, None)
+ postgres_build, postgres_job = find_job(self, 'postgres', '386')
+ check_estimate(self, postgres_job, 120)
Follow ups