Merge lp:~al-maisan/launchpad/ejdt-n-1 into lp:launchpad

Proposed by Muharem Hrnjadovic
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~al-maisan/launchpad/ejdt-n-1
Merge into: lp:launchpad
Diff against target: 1420 lines (+627/-310)
3 files modified
lib/lp/soyuz/interfaces/buildqueue.py (+9/-0)
lib/lp/soyuz/model/buildqueue.py (+173/-126)
lib/lp/soyuz/tests/test_buildqueue.py (+445/-184)
To merge this branch: bzr merge lp:~al-maisan/launchpad/ejdt-n-1
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+18391@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Hello there!

This branch integrates all the logic that was put in place beforehand to
facilitate the estimation of build farm job dispatch times.

The main functions _estimateTimeToNextBuilder() and _estimateJobDelay()
were streamlined so that they do only thing namely estimate the
time/delay in seconds

  - until the next builder capable of running the job of interest (JOI)
    becomes available and
  - until all the jobs ahead of the JOI have been dispatched

The bulk of this branch (roughly two thirds) is concerned with cleaning
up tests and although it has gotten a bit bigger than I wanted it's
still fairly "review-able" :)

Tests to run:

   bin/test -vv -t test_buildqueue

No "make lint" errors or warnings.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

We went through a lot of improvements together: polishing up names, using COALESCE() to make the SQL more succinct, breaking up unit tests more finely, moving bits of code about. The last change I saw is that you replaced the endless repeated processor lookups with attributes on the test case. The result looks good to me.

Thanks for doing this work!

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/interfaces/buildqueue.py'
--- lib/lp/soyuz/interfaces/buildqueue.py 2010-01-20 22:09:26 +0000
+++ lib/lp/soyuz/interfaces/buildqueue.py 2010-02-02 15:26:16 +0000
@@ -101,6 +101,15 @@
101 title=_('Start time'),101 title=_('Start time'),
102 description=_('Time when the job started.'))102 description=_('Time when the job started.'))
103103
104 def getEstimatedJobStartTime():
105 """Get the estimated start time for a pending build farm job.
106
107 :return: a timestamp upon success or None on failure. None
108 indicates that an estimated start time is not available.
109 :raise: AssertionError when the build job is not in the
110 `JobStatus.WAITING` state.
111 """
112
104113
105class IBuildQueueSet(Interface):114class IBuildQueueSet(Interface):
106 """Launchpad Auto Build queue set handler and auxiliary methods."""115 """Launchpad Auto Build queue set handler and auxiliary methods."""
107116
=== modified file 'lib/lp/soyuz/model/buildqueue.py'
--- lib/lp/soyuz/model/buildqueue.py 2010-01-30 05:27:48 +0000
+++ lib/lp/soyuz/model/buildqueue.py 2010-02-02 15:26:16 +0000
@@ -11,6 +11,8 @@
11 'specific_job_classes',11 'specific_job_classes',
12 ]12 ]
1313
14from collections import defaultdict
15from datetime import datetime, timedelta
14import logging16import logging
1517
16from zope.component import getSiteManager, getUtility18from zope.component import getSiteManager, getUtility
@@ -38,6 +40,12 @@
38 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)40 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
3941
4042
43def normalize_virtualization(virtualized):
44 """Jobs with NULL virtualization settings should be treated the
45 same way as virtualized jobs."""
46 return virtualized is None or virtualized
47
48
41def specific_job_classes():49def specific_job_classes():
42 """Job classes that may run on the build farm."""50 """Job classes that may run on the build farm."""
43 job_classes = dict()51 job_classes = dict()
@@ -54,6 +62,32 @@
54 return job_classes62 return job_classes
5563
5664
65def get_builder_data():
66 """How many working builders are there, how are they configured?"""
67 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
68 builder_data = """
69 SELECT processor, virtualized, COUNT(id) FROM builder
70 WHERE builderok = TRUE AND manual = FALSE
71 GROUP BY processor, virtualized;
72 """
73 results = store.execute(builder_data).get_all()
74 builders_in_total = builders_for_job = virtualized_total = 0
75
76 builder_stats = defaultdict(int)
77 for processor, virtualized, count in results:
78 builders_in_total += count
79 if virtualized:
80 virtualized_total += count
81 builder_stats[(processor, virtualized)] = count
82
83 builder_stats[(None, True)] = virtualized_total
84 # Jobs with a NULL virtualized flag should be treated the same as
85 # jobs where virtualized=TRUE.
86 builder_stats[(None, None)] = virtualized_total
87 builder_stats[(None, False)] = builders_in_total - virtualized_total
88 return builder_stats
89
90
57class BuildQueue(SQLBase):91class BuildQueue(SQLBase):
58 implements(IBuildQueue)92 implements(IBuildQueue)
59 _table = "BuildQueue"93 _table = "BuildQueue"
@@ -142,54 +176,7 @@
142 """See `IBuildQueue`."""176 """See `IBuildQueue`."""
143 self.job.date_started = timestamp177 self.job.date_started = timestamp
144178
145 def _getBuilderData(self):179 def _getFreeBuildersCount(self, processor, virtualized):
146 """How many working builders are there, how are they configured?"""
147 # Please note: this method will send only one request to the database.
148
149 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
150 my_processor = self.specific_job.processor
151 my_virtualized = self.specific_job.virtualized
152
153 # We need to know the total number of builders as well as the
154 # number of builders that can run the job of interest (JOI).
155 # If the JOI is processor independent these builder counts will
156 # have the same value.
157 builder_data = """
158 SELECT processor, virtualized, COUNT(id) FROM builder
159 WHERE builderok = TRUE AND manual = FALSE
160 GROUP BY processor, virtualized;
161 """
162 results = store.execute(builder_data).get_all()
163 builders_in_total = builders_for_job = 0
164 virtualized_total = 0
165 native_total = 0
166
167 builder_stats = dict()
168 for processor, virtualized, count in results:
169 builders_in_total += count
170 if virtualized:
171 virtualized_total += count
172 else:
173 native_total += count
174 if my_processor is not None:
175 if (my_processor.id == processor and
176 my_virtualized == virtualized):
177 # The job on hand can only run on builders with a
178 # particular processor/virtualization combination and
179 # this is how many of these we have.
180 builders_for_job = count
181 builder_stats[(processor, virtualized)] = count
182 if my_processor is None:
183 # The job of interest (JOI) is processor independent.
184 builders_for_job = builders_in_total
185
186 builder_stats[(None, None)] = builders_in_total
187 builder_stats[(None, True)] = virtualized_total
188 builder_stats[(None, False)] = native_total
189
190 return (builders_in_total, builders_for_job, builder_stats)
191
192 def _freeBuildersCount(self, processor, virtualized):
193 """How many builders capable of running jobs for the given processor180 """How many builders capable of running jobs for the given processor
194 and virtualization combination are idle/free at present?"""181 and virtualization combination are idle/free at present?"""
195 query = """182 query = """
@@ -198,18 +185,18 @@
198 builderok = TRUE AND manual = FALSE185 builderok = TRUE AND manual = FALSE
199 AND id NOT IN (186 AND id NOT IN (
200 SELECT builder FROM BuildQueue WHERE builder IS NOT NULL)187 SELECT builder FROM BuildQueue WHERE builder IS NOT NULL)
201 """188 AND virtualized = %s
189 """ % sqlvalues(normalize_virtualization(virtualized))
202 if processor is not None:190 if processor is not None:
203 query += """191 query += """
204 AND processor = %s AND virtualized = %s192 AND processor = %s
205 """ % sqlvalues(processor, virtualized)193 """ % sqlvalues(processor)
206 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)194 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
207 result_set = store.execute(query)195 result_set = store.execute(query)
208 free_builders = result_set.get_one()[0]196 free_builders = result_set.get_one()[0]
209 return free_builders197 return free_builders
210198
211 def _estimateTimeToNextBuilder(199 def _estimateTimeToNextBuilder(self):
212 self, head_job_processor, head_job_virtualized):
213 """Estimate time until next builder becomes available.200 """Estimate time until next builder becomes available.
214201
215 For the purpose of estimating the dispatch time of the job of interest202 For the purpose of estimating the dispatch time of the job of interest
@@ -220,36 +207,21 @@
220207
221 - processor dependent: only builders with the matching208 - processor dependent: only builders with the matching
222 processor/virtualization combination should be considered.209 processor/virtualization combination should be considered.
223 - *not* processor dependent: all builders should be considered.210 - *not* processor dependent: all builders with the matching
211 virtualization setting should be considered.
224212
225 :param head_job_processor: The processor required by the job at the
226 head of the queue.
227 :param head_job_virtualized: The virtualization setting required by
228 the job at the head of the queue.
229 :return: The estimated number of seconds untils a builder capable of213 :return: The estimated number of seconds untils a builder capable of
230 running the head job becomes available or None if no such builder214 running the head job becomes available.
231 exists.
232 """215 """
233 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)216 head_job_platform = self._getHeadJobPlatform()
234217
235 # First check whether we have free builders.218 # Return a zero delay if we still have free builders available for the
236 free_builders = self._freeBuildersCount(219 # given platform/virtualization combination.
237 head_job_processor, head_job_virtualized)220 free_builders = self._getFreeBuildersCount(*head_job_platform)
238
239 if free_builders > 0:221 if free_builders > 0:
240 # We have free builders for the given processor/virtualization
241 # combination -> zero delay
242 return 0222 return 0
243223
244 extra_clauses = ''224 head_job_processor, head_job_virtualized = head_job_platform
245 if head_job_processor is not None:
246 # Only look at builders with specific processor types.
247 extra_clauses += """
248 AND Builder.processor = %s
249 AND Builder.virtualized = %s
250 """ % sqlvalues(head_job_processor, head_job_virtualized)
251
252 params = sqlvalues(JobStatus.RUNNING) + (extra_clauses,)
253225
254 delay_query = """226 delay_query = """
255 SELECT MIN(227 SELECT MIN(
@@ -279,15 +251,85 @@
279 AND Builder.manual = False251 AND Builder.manual = False
280 AND Builder.builderok = True252 AND Builder.builderok = True
281 AND Job.status = %s253 AND Job.status = %s
282 %s254 AND Builder.virtualized = %s
283 """ % params255 """ % sqlvalues(
284256 JobStatus.RUNNING,
257 normalize_virtualization(head_job_virtualized))
258
259 if head_job_processor is not None:
260 # Only look at builders with specific processor types.
261 delay_query += """
262 AND Builder.processor = %s
263 """ % sqlvalues(head_job_processor)
264
265 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
285 result_set = store.execute(delay_query)266 result_set = store.execute(delay_query)
286 head_job_delay = result_set.get_one()[0]267 head_job_delay = result_set.get_one()[0]
287 if head_job_delay is None:268 return (0 if head_job_delay is None else int(head_job_delay))
288 return None269
289 else:270 def _getPendingJobsClauses(self):
290 return int(head_job_delay)271 """WHERE clauses for pending job queries, used for dipatch time
272 estimation."""
273 virtualized = normalize_virtualization(self.virtualized)
274 clauses = """
275 BuildQueue.job = Job.id
276 AND Job.status = %s
277 AND (
278 -- The score must be either above my score or the
279 -- job must be older than me in cases where the
280 -- score is equal.
281 BuildQueue.lastscore > %s OR
282 (BuildQueue.lastscore = %s AND Job.id < %s))
283 -- The virtualized values either match or the job
284 -- does not care about virtualization and the job
285 -- of interest (JOI) is to be run on a virtual builder
286 -- (we want to prevent the execution of untrusted code
287 -- on native builders).
288 AND COALESCE(buildqueue.virtualized, TRUE) = %s
289 """ % sqlvalues(
290 JobStatus.WAITING, self.lastscore, self.lastscore, self.job,
291 virtualized)
292 processor_clause = """
293 AND (
294 -- The processor values either match or the candidate
295 -- job is processor-independent.
296 buildqueue.processor = %s OR
297 buildqueue.processor IS NULL)
298 """ % sqlvalues(self.processor)
299 # We don't care about processors if the estimation is for a
300 # processor-independent job.
301 if self.processor is not None:
302 clauses += processor_clause
303 return clauses
304
305 def _getHeadJobPlatform(self):
306 """Find the processor and virtualization setting for the head job.
307
308 Among the jobs that compete with the job of interest (JOI) for
309 builders and are queued ahead of it the head job is the one in pole
310 position i.e. the one to be dispatched to a builder next.
311
312 :return: A (processor, virtualized) tuple which is the head job's
313 platform or None if the JOI is the head job.
314 """
315 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
316 my_platform = (
317 getattr(self.processor, 'id', None),
318 normalize_virtualization(self.virtualized))
319 query = """
320 SELECT
321 processor,
322 virtualized
323 FROM
324 BuildQueue, Job
325 WHERE
326 """
327 query += self._getPendingJobsClauses()
328 query += """
329 ORDER BY lastscore DESC, job LIMIT 1
330 """
331 result = store.execute(query).get_one()
332 return (my_platform if result is None else result)
291333
292 def _estimateJobDelay(self, builder_stats):334 def _estimateJobDelay(self, builder_stats):
293 """Sum of estimated durations for *pending* jobs ahead in queue.335 """Sum of estimated durations for *pending* jobs ahead in queue.
@@ -304,10 +346,6 @@
304 :return: An integer value holding the sum of delays (in seconds)346 :return: An integer value holding the sum of delays (in seconds)
305 caused by the jobs that are ahead of and competing with the JOI.347 caused by the jobs that are ahead of and competing with the JOI.
306 """348 """
307 def normalize_virtualization(virtualized):
308 """Jobs with NULL virtualization settings should be treated the
309 same way as virtualized jobs."""
310 return virtualized is None or virtualized
311 def jobs_compete_for_builders(a, b):349 def jobs_compete_for_builders(a, b):
312 """True if the two jobs compete for builders."""350 """True if the two jobs compete for builders."""
313 a_processor, a_virtualized = a351 a_processor, a_virtualized = a
@@ -338,37 +376,8 @@
338 FROM376 FROM
339 BuildQueue, Job377 BuildQueue, Job
340 WHERE378 WHERE
341 BuildQueue.job = Job.id379 """
342 AND Job.status = %s380 query += self._getPendingJobsClauses()
343 AND (
344 -- The score must be either above my score or the
345 -- job must be older than me in cases where the
346 -- score is equal.
347 BuildQueue.lastscore > %s OR
348 (BuildQueue.lastscore = %s AND Job.id < %s))
349 AND (
350 -- The virtualized values either match or the job
351 -- does not care about virtualization and the job
352 -- of interest (JOI) is to be run on a virtual builder
353 -- (we want to prevent the execution of untrusted code
354 -- on native builders).
355 buildqueue.virtualized = %s OR
356 (buildqueue.virtualized IS NULL AND %s = TRUE))
357 """ % sqlvalues(
358 JobStatus.WAITING, self.lastscore, self.lastscore, self.job,
359 self.virtualized, self.virtualized)
360 processor_clause = """
361 AND (
362 -- The processor values either match or the candidate
363 -- job is processor-independent.
364 buildqueue.processor = %s OR
365 buildqueue.processor IS NULL)
366 """ % sqlvalues(self.processor)
367 # We don't care about processors if the estimation is for a
368 # processor-independent job.
369 if self.processor is not None:
370 query += processor_clause
371
372 query += """381 query += """
373 GROUP BY BuildQueue.processor, BuildQueue.virtualized382 GROUP BY BuildQueue.processor, BuildQueue.virtualized
374 """383 """
@@ -376,11 +385,11 @@
376 delays_by_platform = store.execute(query).get_all()385 delays_by_platform = store.execute(query).get_all()
377386
378 # This will be used to capture per-platform delay totals.387 # This will be used to capture per-platform delay totals.
379 delays = dict()388 delays = defaultdict(int)
380 # This will be used to capture per-platform job counts.389 # This will be used to capture per-platform job counts.
381 job_counts = dict()390 job_counts = defaultdict(int)
382391
383 # Apply weights to the estimated duration of the jobs as follows:392 # Divide the estimated duration of the jobs as follows:
384 # - if a job is tied to a processor TP then divide the estimated393 # - if a job is tied to a processor TP then divide the estimated
385 # duration of that job by the number of builders that target TP394 # duration of that job by the number of builders that target TP
386 # since only these can build the job.395 # since only these can build the job.
@@ -390,7 +399,7 @@
390 for processor, virtualized, job_count, delay in delays_by_platform:399 for processor, virtualized, job_count, delay in delays_by_platform:
391 virtualized = normalize_virtualization(virtualized)400 virtualized = normalize_virtualization(virtualized)
392 platform = (processor, virtualized)401 platform = (processor, virtualized)
393 builder_count = builder_stats.get((processor, virtualized), 0)402 builder_count = builder_stats.get(platform, 0)
394 if builder_count == 0:403 if builder_count == 0:
395 # There is no builder that can run this job, ignore it404 # There is no builder that can run this job, ignore it
396 # for the purpose of dispatch time estimation.405 # for the purpose of dispatch time estimation.
@@ -399,11 +408,11 @@
399 if jobs_compete_for_builders(my_platform, platform):408 if jobs_compete_for_builders(my_platform, platform):
400 # The jobs that target the platform at hand compete with409 # The jobs that target the platform at hand compete with
401 # the JOI for builders, add their delays.410 # the JOI for builders, add their delays.
402 delays[platform] = delay411 delays[platform] += delay
403 job_counts[platform] = job_count412 job_counts[platform] += job_count
404413
405 sum_of_delays = 0414 sum_of_delays = 0
406 # Now weight/average the delays based on a jobs/builders comparison.415 # Now devide the delays based on a jobs/builders comparison.
407 for platform, duration in delays.iteritems():416 for platform, duration in delays.iteritems():
408 jobs = job_counts[platform]417 jobs = job_counts[platform]
409 builders = builder_stats[platform]418 builders = builder_stats[platform]
@@ -417,6 +426,44 @@
417426
418 return sum_of_delays427 return sum_of_delays
419428
429 def getEstimatedJobStartTime(self):
430 """See `IBuildQueue`.
431
432 The estimated dispatch time for the build farm job at hand is
433 calculated from the following ingredients:
434 * the start time for the head job (job at the
435 head of the respective build queue)
436 * the estimated build durations of all jobs that
437 precede the job of interest (JOI) in the build queue
438 (divided by the number of machines in the respective
439 build pool)
440 """
441 # This method may only be invoked for pending jobs.
442 if self.job.status != JobStatus.WAITING:
443 raise AssertionError(
444 "The start time is only estimated for pending jobs.")
445
446 builder_stats = get_builder_data()
447 platform = (getattr(self.processor, 'id', None), self.virtualized)
448 if builder_stats[platform] == 0:
449 # No builders that can run the job at hand
450 # -> no dispatch time estimation available.
451 return None
452
453 # Get the sum of the estimated run times for *pending* jobs that are
454 # ahead of us in the queue.
455 sum_of_delays = self._estimateJobDelay(builder_stats)
456
457 # Get the minimum time duration until the next builder becomes
458 # available.
459 min_wait_time = self._estimateTimeToNextBuilder()
460
461 # A job will not get dispatched in less than 5 seconds no matter what.
462 start_time = max(5, min_wait_time + sum_of_delays)
463 result = datetime.utcnow() + timedelta(seconds=start_time)
464
465 return result
466
420467
421class BuildQueueSet(object):468class BuildQueueSet(object):
422 """Utility to deal with BuildQueue content class."""469 """Utility to deal with BuildQueue content class."""
423470
=== modified file 'lib/lp/soyuz/tests/test_buildqueue.py'
--- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-30 05:27:48 +0000
+++ lib/lp/soyuz/tests/test_buildqueue.py 2010-02-02 15:26:16 +0000
@@ -15,15 +15,14 @@
15from canonical.testing import LaunchpadZopelessLayer15from canonical.testing import LaunchpadZopelessLayer
1616
17from lp.buildmaster.interfaces.builder import IBuilderSet17from lp.buildmaster.interfaces.builder import IBuilderSet
18from lp.buildmaster.interfaces.buildfarmjob import (18from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
19 BuildFarmJobType)
20from lp.buildmaster.model.builder import specific_job_classes19from lp.buildmaster.model.builder import specific_job_classes
21from lp.buildmaster.model.buildfarmjob import BuildFarmJob20from lp.buildmaster.model.buildfarmjob import BuildFarmJob
22from lp.services.job.model.job import Job21from lp.services.job.model.job import Job
23from lp.soyuz.interfaces.archive import ArchivePurpose22from lp.soyuz.interfaces.archive import ArchivePurpose
24from lp.soyuz.interfaces.build import BuildStatus23from lp.soyuz.interfaces.build import BuildStatus
25from lp.soyuz.interfaces.buildqueue import IBuildQueueSet24from lp.soyuz.interfaces.buildqueue import IBuildQueueSet
26from lp.soyuz.model.buildqueue import BuildQueue25from lp.soyuz.model.buildqueue import BuildQueue, get_builder_data
27from lp.soyuz.model.processor import ProcessorFamilySet26from lp.soyuz.model.processor import ProcessorFamilySet
28from lp.soyuz.interfaces.publishing import PackagePublishingStatus27from lp.soyuz.interfaces.publishing import PackagePublishingStatus
29from lp.soyuz.model.build import Build28from lp.soyuz.model.build import Build
@@ -60,7 +59,9 @@
60 builder = None59 builder = None
61 builders = test.builders.get(builder_key(bq), [])60 builders = test.builders.get(builder_key(bq), [])
62 try:61 try:
63 builder = builders[n-1]62 for builder in builders[n-1:]:
63 if builder.builderok:
64 break
64 except IndexError:65 except IndexError:
65 pass66 pass
66 return builder67 return builder
@@ -95,18 +96,13 @@
95 queue_entry.lastscore)96 queue_entry.lastscore)
9697
9798
98def check_mintime_to_builder(99def check_mintime_to_builder(test, bq, min_time):
99 test, bq, head_job_processor, head_job_virtualized, min_time):
100 """Test the estimated time until a builder becomes available."""100 """Test the estimated time until a builder becomes available."""
101 delay = bq._estimateTimeToNextBuilder(101 delay = bq._estimateTimeToNextBuilder()
102 head_job_processor, head_job_virtualized)102 test.assertTrue(
103 if min_time is not None:103 almost_equal(delay, min_time),
104 test.assertTrue(104 "Wrong min time to next available builder (%s != %s)"
105 almost_equal(delay, min_time),105 % (delay, min_time))
106 "Wrong min time to next available builder (%s != %s)"
107 % (delay, min_time))
108 else:
109 test.assertTrue(delay is None, "No delay to next builder available")
110106
111107
112def almost_equal(a, b, deviation=1):108def almost_equal(a, b, deviation=1):
@@ -126,10 +122,46 @@
126122
127def check_delay_for_job(test, the_job, delay):123def check_delay_for_job(test, the_job, delay):
128 # Obtain the builder statistics pertaining to this job.124 # Obtain the builder statistics pertaining to this job.
129 builder_data = the_job._getBuilderData()125 builder_data = get_builder_data()
130 builders_in_total, builders_for_job, builder_stats = builder_data126 estimated_delay = the_job._estimateJobDelay(builder_data)
131 estimated_delay = the_job._estimateJobDelay(builder_stats)127 test.assertEqual(delay, estimated_delay)
132 test.assertEqual(estimated_delay, delay)128
129
130def total_builders():
131 """How many available builders do we have in total?"""
132 builder_data = get_builder_data()
133 return builder_data[(None, False)] + builder_data[(None, True)]
134
135
136def builders_for_job(job):
137 """How many available builders can run the given job?"""
138 builder_data = get_builder_data()
139 return builder_data[(getattr(job.processor, 'id', None), job.virtualized)]
140
141
142def check_estimate(test, job, delay_in_seconds):
143 """Does the dispatch time estimate match the expectation?"""
144 estimate = job.getEstimatedJobStartTime()
145 if delay_in_seconds is None:
146 test.assertEquals(
147 delay_in_seconds, estimate,
148 "An estimate should not be possible at present but one was "
149 "returned (%s) nevertheless." % estimate)
150 else:
151 estimate -= datetime.utcnow()
152 test.assertTrue(
153 almost_equal(estimate.seconds, delay_in_seconds),
154 "The estimated delay (%s) deviates from the expected one (%s)" %
155 (estimate.seconds, delay_in_seconds))
156
157
158def disable_builders(test, processor, virtualized):
159 """Disable bulders with the given processor and virtualization setting."""
160 if processor is not None:
161 processor_fam = ProcessorFamilySet().getByName(processor)
162 processor = processor_fam.processors[0].id
163 for builder in test.builders[(processor, virtualized)]:
164 builder.builderok = False
133165
134166
135class TestBuildQueueSet(TestCaseWithFactory):167class TestBuildQueueSet(TestCaseWithFactory):
@@ -177,55 +209,55 @@
177209
178 # Next make seven 'hppa' builders.210 # Next make seven 'hppa' builders.
179 processor_fam = ProcessorFamilySet().getByName('hppa')211 processor_fam = ProcessorFamilySet().getByName('hppa')
180 hppa_proc = processor_fam.processors[0]212 self.hppa_proc = processor_fam.processors[0]
181 self.h1 = self.factory.makeBuilder(213 self.h1 = self.factory.makeBuilder(
182 name='hppa-v-1', processor=hppa_proc)214 name='hppa-v-1', processor=self.hppa_proc)
183 self.h2 = self.factory.makeBuilder(215 self.h2 = self.factory.makeBuilder(
184 name='hppa-v-2', processor=hppa_proc)216 name='hppa-v-2', processor=self.hppa_proc)
185 self.h3 = self.factory.makeBuilder(217 self.h3 = self.factory.makeBuilder(
186 name='hppa-v-3', processor=hppa_proc)218 name='hppa-v-3', processor=self.hppa_proc)
187 self.h4 = self.factory.makeBuilder(219 self.h4 = self.factory.makeBuilder(
188 name='hppa-v-4', processor=hppa_proc)220 name='hppa-v-4', processor=self.hppa_proc)
189 self.h5 = self.factory.makeBuilder(221 self.h5 = self.factory.makeBuilder(
190 name='hppa-n-5', processor=hppa_proc, virtualized=False)222 name='hppa-n-5', processor=self.hppa_proc, virtualized=False)
191 self.h6 = self.factory.makeBuilder(223 self.h6 = self.factory.makeBuilder(
192 name='hppa-n-6', processor=hppa_proc, virtualized=False)224 name='hppa-n-6', processor=self.hppa_proc, virtualized=False)
193 self.h7 = self.factory.makeBuilder(225 self.h7 = self.factory.makeBuilder(
194 name='hppa-n-7', processor=hppa_proc, virtualized=False)226 name='hppa-n-7', processor=self.hppa_proc, virtualized=False)
195227
196 # Finally make five 'amd64' builders.228 # Finally make five 'amd64' builders.
197 processor_fam = ProcessorFamilySet().getByName('amd64')229 processor_fam = ProcessorFamilySet().getByName('amd64')
198 amd_proc = processor_fam.processors[0]230 self.amd_proc = processor_fam.processors[0]
199 self.a1 = self.factory.makeBuilder(231 self.a1 = self.factory.makeBuilder(
200 name='amd64-v-1', processor=amd_proc)232 name='amd64-v-1', processor=self.amd_proc)
201 self.a2 = self.factory.makeBuilder(233 self.a2 = self.factory.makeBuilder(
202 name='amd64-v-2', processor=amd_proc)234 name='amd64-v-2', processor=self.amd_proc)
203 self.a3 = self.factory.makeBuilder(235 self.a3 = self.factory.makeBuilder(
204 name='amd64-v-3', processor=amd_proc)236 name='amd64-v-3', processor=self.amd_proc)
205 self.a4 = self.factory.makeBuilder(237 self.a4 = self.factory.makeBuilder(
206 name='amd64-n-4', processor=amd_proc, virtualized=False)238 name='amd64-n-4', processor=self.amd_proc, virtualized=False)
207 self.a5 = self.factory.makeBuilder(239 self.a5 = self.factory.makeBuilder(
208 name='amd64-n-5', processor=amd_proc, virtualized=False)240 name='amd64-n-5', processor=self.amd_proc, virtualized=False)
209241
210 self.builders = dict()242 self.builders = dict()
211 processor_fam = ProcessorFamilySet().getByName('x86')243 processor_fam = ProcessorFamilySet().getByName('x86')
212 x86_proc = processor_fam.processors[0]244 self.x86_proc = processor_fam.processors[0]
213 # x86 native245 # x86 native
214 self.builders[(x86_proc.id, False)] = [246 self.builders[(self.x86_proc.id, False)] = [
215 self.i6, self.i7, self.i8, self.i9]247 self.i6, self.i7, self.i8, self.i9]
216 # x86 virtual248 # x86 virtual
217 self.builders[(x86_proc.id, True)] = [249 self.builders[(self.x86_proc.id, True)] = [
218 self.i1, self.i2, self.i3, self.i4, self.i5]250 self.i1, self.i2, self.i3, self.i4, self.i5]
219251
220 # amd64 native252 # amd64 native
221 self.builders[(amd_proc.id, False)] = [self.a4, self.a5]253 self.builders[(self.amd_proc.id, False)] = [self.a4, self.a5]
222 # amd64 virtual254 # amd64 virtual
223 self.builders[(amd_proc.id, True)] = [self.a1, self.a2, self.a3]255 self.builders[(self.amd_proc.id, True)] = [self.a1, self.a2, self.a3]
224256
225 # hppa native257 # hppa native
226 self.builders[(hppa_proc.id, False)] = [self.h5, self.h6, self.h7]258 self.builders[(self.hppa_proc.id, False)] = [self.h5, self.h6, self.h7]
227 # hppa virtual259 # hppa virtual
228 self.builders[(hppa_proc.id, True)] = [260 self.builders[(self.hppa_proc.id, True)] = [
229 self.h1, self.h2, self.h3, self.h4]261 self.h1, self.h2, self.h3, self.h4]
230262
231 # Ensure all builders are operational.263 # Ensure all builders are operational.
@@ -237,20 +269,20 @@
237 # Native builders irrespective of processor.269 # Native builders irrespective of processor.
238 self.builders[(None, False)] = []270 self.builders[(None, False)] = []
239 self.builders[(None, False)].extend(271 self.builders[(None, False)].extend(
240 self.builders[(x86_proc.id, False)])272 self.builders[(self.x86_proc.id, False)])
241 self.builders[(None, False)].extend(273 self.builders[(None, False)].extend(
242 self.builders[(amd_proc.id, False)])274 self.builders[(self.amd_proc.id, False)])
243 self.builders[(None, False)].extend(275 self.builders[(None, False)].extend(
244 self.builders[(hppa_proc.id, False)])276 self.builders[(self.hppa_proc.id, False)])
245277
246 # Virtual builders irrespective of processor.278 # Virtual builders irrespective of processor.
247 self.builders[(None, True)] = []279 self.builders[(None, True)] = []
248 self.builders[(None, True)].extend(280 self.builders[(None, True)].extend(
249 self.builders[(x86_proc.id, True)])281 self.builders[(self.x86_proc.id, True)])
250 self.builders[(None, True)].extend(282 self.builders[(None, True)].extend(
251 self.builders[(amd_proc.id, True)])283 self.builders[(self.amd_proc.id, True)])
252 self.builders[(None, True)].extend(284 self.builders[(None, True)].extend(
253 self.builders[(hppa_proc.id, True)])285 self.builders[(self.hppa_proc.id, True)])
254286
255 # Disable the sample data builders.287 # Disable the sample data builders.
256 getUtility(IBuilderSet)['bob'].builderok = False288 getUtility(IBuilderSet)['bob'].builderok = False
@@ -343,130 +375,112 @@
343 # Make sure the builder numbers are correct. The builder data will375 # Make sure the builder numbers are correct. The builder data will
344 # be the same for all of our builds.376 # be the same for all of our builds.
345 bq = self.builds[0].buildqueue_record377 bq = self.builds[0].buildqueue_record
346 builder_data = bq._getBuilderData()378 self.assertEqual(
347 builders_in_total, builders_for_job, builder_stats = builder_data379 21, total_builders(),
348 self.assertEqual(380 "The total number of builders is wrong.")
349 builders_in_total, 21, "The total number of builders is wrong.")381 self.assertEqual(
350 self.assertEqual(382 4, builders_for_job(bq),
351 builders_for_job, 4,
352 "[1] The total number of builders that can build the job in "383 "[1] The total number of builders that can build the job in "
353 "question is wrong.")384 "question is wrong.")
354 processor_fam = ProcessorFamilySet().getByName('x86')385 builder_stats = get_builder_data()
355 x86_proc = processor_fam.processors[0]
356 self.assertEqual(386 self.assertEqual(
357 builder_stats[(x86_proc.id, False)], 4,387 4, builder_stats[(self.x86_proc.id, False)],
358 "The number of native x86 builders is wrong")388 "The number of native x86 builders is wrong")
359 self.assertEqual(389 self.assertEqual(
360 builder_stats[(x86_proc.id, True)], 5,390 5, builder_stats[(self.x86_proc.id, True)],
361 "The number of virtual x86 builders is wrong")391 "The number of virtual x86 builders is wrong")
362 processor_fam = ProcessorFamilySet().getByName('amd64')
363 amd_proc = processor_fam.processors[0]
364 self.assertEqual(392 self.assertEqual(
365 builder_stats[(amd_proc.id, False)], 2,393 2, builder_stats[(self.amd_proc.id, False)],
366 "The number of native amd64 builders is wrong")394 "The number of native amd64 builders is wrong")
367 self.assertEqual(395 self.assertEqual(
368 builder_stats[(amd_proc.id, True)], 3,396 3, builder_stats[(self.amd_proc.id, True)],
369 "The number of virtual amd64 builders is wrong")397 "The number of virtual amd64 builders is wrong")
370 processor_fam = ProcessorFamilySet().getByName('hppa')
371 hppa_proc = processor_fam.processors[0]
372 self.assertEqual(398 self.assertEqual(
373 builder_stats[(hppa_proc.id, False)], 3,399 3, builder_stats[(self.hppa_proc.id, False)],
374 "The number of native hppa builders is wrong")400 "The number of native hppa builders is wrong")
375 self.assertEqual(401 self.assertEqual(
376 builder_stats[(hppa_proc.id, True)], 4,402 4, builder_stats[(self.hppa_proc.id, True)],
377 "The number of virtual hppa builders is wrong")403 "The number of virtual hppa builders is wrong")
378 self.assertEqual(404 self.assertEqual(
379 builder_stats[(None, False)], 9,405 9, builder_stats[(None, False)],
380 "The number of *virtual* builders across all processors is wrong")406 "The number of *virtual* builders across all processors is wrong")
381 self.assertEqual(407 self.assertEqual(
382 builder_stats[(None, True)], 12,408 12, builder_stats[(None, True)],
383 "The number of *native* builders across all processors is wrong")409 "The number of *native* builders across all processors is wrong")
384 # Disable the native x86 builders.410 # Disable the native x86 builders.
385 for builder in self.builders[(x86_proc.id, False)]:411 for builder in self.builders[(self.x86_proc.id, False)]:
386 builder.builderok = False412 builder.builderok = False
387 # Get the builder statistics again.
388 builder_data = bq._getBuilderData()
389 builders_in_total, builders_for_job, builder_stats = builder_data
390 # Since all native x86 builders were disabled there are none left413 # Since all native x86 builders were disabled there are none left
391 # to build the job.414 # to build the job.
392 self.assertEqual(415 self.assertEqual(
393 builders_for_job, 0,416 0, builders_for_job(bq),
394 "[2] The total number of builders that can build the job in "417 "[2] The total number of builders that can build the job in "
395 "question is wrong.")418 "question is wrong.")
396 # Re-enable one of them.419 # Re-enable one of them.
397 for builder in self.builders[(x86_proc.id, False)]:420 for builder in self.builders[(self.x86_proc.id, False)]:
398 builder.builderok = True421 builder.builderok = True
399 break422 break
400 # Get the builder statistics again.
401 builder_data = bq._getBuilderData()
402 builders_in_total, builders_for_job, builder_stats = builder_data
403 # Now there should be one builder available to build the job.423 # Now there should be one builder available to build the job.
404 self.assertEqual(424 self.assertEqual(
405 builders_for_job, 1,425 1, builders_for_job(bq),
406 "[3] The total number of builders that can build the job in "426 "[3] The total number of builders that can build the job in "
407 "question is wrong.")427 "question is wrong.")
408 # Disable the *virtual* x86 builders -- should not make any428 # Disable the *virtual* x86 builders -- should not make any
409 # difference.429 # difference.
410 for builder in self.builders[(x86_proc.id, True)]:430 for builder in self.builders[(self.x86_proc.id, True)]:
411 builder.builderok = False431 builder.builderok = False
412 # Get the builder statistics again.
413 builder_data = bq._getBuilderData()
414 builders_in_total, builders_for_job, builder_stats = builder_data
415 # There should still be one builder available to build the job.432 # There should still be one builder available to build the job.
416 self.assertEqual(433 self.assertEqual(
417 builders_for_job, 1,434 1, builders_for_job(bq),
418 "[4] The total number of builders that can build the job in "435 "[4] The total number of builders that can build the job in "
419 "question is wrong.")436 "question is wrong.")
420437
421 def test_free_builder_counts(self):438 def test_free_builder_counts(self):
422 # Make sure the builder numbers are correct. The builder data will439 # Make sure the builder numbers are correct. The builder data will
423 # be the same for all of our builds.440 # be the same for all of our builds.
424 processor_fam = ProcessorFamilySet().getByName('x86')
425 proc_386 = processor_fam.processors[0]
426 build = self.builds[0]441 build = self.builds[0]
427 # The build in question is an x86/native one.442 # The build in question is an x86/native one.
428 self.assertEqual(build.processor.id, proc_386.id)443 self.assertEqual(self.x86_proc.id, build.processor.id)
429 self.assertEqual(build.is_virtualized, False)444 self.assertEqual(False, build.is_virtualized)
430 bq = build.buildqueue_record445 bq = build.buildqueue_record
431 builder_data = bq._getBuilderData()446 builder_stats = get_builder_data()
432 builders_in_total, builders_for_job, builder_stats = builder_data
433 # We have 4 x86 native builders.447 # We have 4 x86 native builders.
434 self.assertEqual(448 self.assertEqual(
435 builder_stats[(proc_386.id, False)], 4,449 4, builder_stats[(self.x86_proc.id, False)],
436 "The number of native x86 builders is wrong")450 "The number of native x86 builders is wrong")
437 # Initially all 4 builders are free.451 # Initially all 4 builders are free.
438 free_count = bq._freeBuildersCount(452 free_count = bq._getFreeBuildersCount(
439 build.processor, build.is_virtualized)453 build.processor, build.is_virtualized)
440 self.assertEqual(free_count, 4)454 self.assertEqual(4, free_count)
441 # Once we assign a build to one of them we should see the free455 # Once we assign a build to one of them we should see the free
442 # builders count drop by one.456 # builders count drop by one.
443 assign_to_builder(self, 'postgres', 1)457 assign_to_builder(self, 'postgres', 1)
444 free_count = bq._freeBuildersCount(458 free_count = bq._getFreeBuildersCount(
445 build.processor, build.is_virtualized)459 build.processor, build.is_virtualized)
446 self.assertEqual(free_count, 3)460 self.assertEqual(3, free_count)
447 # When we assign another build to one of them we should see the free461 # When we assign another build to one of them we should see the free
448 # builders count drop by one again.462 # builders count drop by one again.
449 assign_to_builder(self, 'gcc', 2)463 assign_to_builder(self, 'gcc', 2)
450 free_count = bq._freeBuildersCount(464 free_count = bq._getFreeBuildersCount(
451 build.processor, build.is_virtualized)465 build.processor, build.is_virtualized)
452 self.assertEqual(free_count, 2)466 self.assertEqual(2, free_count)
453 # Let's use up another builder.467 # Let's use up another builder.
454 assign_to_builder(self, 'apg', 3)468 assign_to_builder(self, 'apg', 3)
455 free_count = bq._freeBuildersCount(469 free_count = bq._getFreeBuildersCount(
456 build.processor, build.is_virtualized)470 build.processor, build.is_virtualized)
457 self.assertEqual(free_count, 1)471 self.assertEqual(1, free_count)
458 # And now for the last one.472 # And now for the last one.
459 assign_to_builder(self, 'flex', 4)473 assign_to_builder(self, 'flex', 4)
460 free_count = bq._freeBuildersCount(474 free_count = bq._getFreeBuildersCount(
461 build.processor, build.is_virtualized)475 build.processor, build.is_virtualized)
462 self.assertEqual(free_count, 0)476 self.assertEqual(0, free_count)
463 # If we reset the 'flex' build the builder that was assigned to it477 # If we reset the 'flex' build the builder that was assigned to it
464 # will be free again.478 # will be free again.
465 build, bq = find_job(self, 'flex')479 build, bq = find_job(self, 'flex')
466 bq.reset()480 bq.reset()
467 free_count = bq._freeBuildersCount(481 free_count = bq._getFreeBuildersCount(
468 build.processor, build.is_virtualized)482 build.processor, build.is_virtualized)
469 self.assertEqual(free_count, 1)483 self.assertEqual(1, free_count)
470484
471485
472class TestMinTimeToNextBuilder(SingleArchBuildsBase):486class TestMinTimeToNextBuilder(SingleArchBuildsBase):
@@ -491,38 +505,36 @@
491 #505 #
492 # p=processor, v=virtualized, e=estimated_duration, s=score506 # p=processor, v=virtualized, e=estimated_duration, s=score
493507
494 processor_fam = ProcessorFamilySet().getByName('x86')
495 x86_proc = processor_fam.processors[0]
496 # This will be the job of interest.508 # This will be the job of interest.
497 apg_build, apg_job = find_job(self, 'apg')509 apg_build, apg_job = find_job(self, 'apg')
498 # One of four builders for the 'apg' build is immediately available.510 # One of four builders for the 'apg' build is immediately available.
499 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)511 check_mintime_to_builder(self, apg_job, 0)
500512
501 # Assign the postgres job to a builder.513 # Assign the postgres job to a builder.
502 assign_to_builder(self, 'postgres', 1)514 assign_to_builder(self, 'postgres', 1)
503 # Now one builder is gone. But there should still be a builder515 # Now one builder is gone. But there should still be a builder
504 # immediately available.516 # immediately available.
505 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)517 check_mintime_to_builder(self, apg_job, 0)
506518
507 assign_to_builder(self, 'flex', 2)519 assign_to_builder(self, 'flex', 2)
508 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)520 check_mintime_to_builder(self, apg_job, 0)
509521
510 assign_to_builder(self, 'bison', 3)522 assign_to_builder(self, 'bison', 3)
511 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)523 check_mintime_to_builder(self, apg_job, 0)
512524
513 assign_to_builder(self, 'gcc', 4)525 assign_to_builder(self, 'gcc', 4)
514 # Now that no builder is immediately available, the shortest526 # Now that no builder is immediately available, the shortest
515 # remaing build time (based on the estimated duration) is returned:527 # remaing build time (based on the estimated duration) is returned:
516 # 300 seconds528 # 300 seconds
517 # This is equivalent to the 'gcc' job's estimated duration.529 # This is equivalent to the 'gcc' job's estimated duration.
518 check_mintime_to_builder(self, apg_job, x86_proc, False, 300)530 check_mintime_to_builder(self, apg_job, 300)
519531
520 # Now we pretend that the 'postgres' started 6 minutes ago. Its532 # Now we pretend that the 'postgres' started 6 minutes ago. Its
521 # remaining execution time should be 2 minutes = 120 seconds and533 # remaining execution time should be 2 minutes = 120 seconds and
522 # it now becomes the job whose builder becomes available next.534 # it now becomes the job whose builder becomes available next.
523 build, bq = find_job(self, 'postgres')535 build, bq = find_job(self, 'postgres')
524 set_remaining_time_for_running_job(bq, 120)536 set_remaining_time_for_running_job(bq, 120)
525 check_mintime_to_builder(self, apg_job, x86_proc, False, 120)537 check_mintime_to_builder(self, apg_job, 120)
526538
527 # What happens when jobs overdraw the estimated duration? Let's539 # What happens when jobs overdraw the estimated duration? Let's
528 # pretend the 'flex' job started 8 minutes ago.540 # pretend the 'flex' job started 8 minutes ago.
@@ -530,22 +542,40 @@
530 set_remaining_time_for_running_job(bq, -60)542 set_remaining_time_for_running_job(bq, -60)
531 # In such a case we assume that the job will complete within 2543 # In such a case we assume that the job will complete within 2
532 # minutes, this is a guess that has worked well so far.544 # minutes, this is a guess that has worked well so far.
533 check_mintime_to_builder(self, apg_job, x86_proc, False, 120)545 check_mintime_to_builder(self, apg_job, 120)
534546
535 # If there's a job that will complete within a shorter time then547 # If there's a job that will complete within a shorter time then
536 # we expect to be given that time frame.548 # we expect to be given that time frame.
537 build, bq = find_job(self, 'postgres')549 build, bq = find_job(self, 'postgres')
538 set_remaining_time_for_running_job(bq, 30)550 set_remaining_time_for_running_job(bq, 30)
539 check_mintime_to_builder(self, apg_job, x86_proc, False, 30)551 check_mintime_to_builder(self, apg_job, 30)
540552
541 # Disable the native x86 builders.553 # Disable the native x86 builders.
542 for builder in self.builders[(x86_proc.id, False)]:554 for builder in self.builders[(self.x86_proc.id, False)]:
543 builder.builderok = False555 builder.builderok = False
544556
545 # No builders capable of running the job at hand are available now,557 # No builders capable of running the job at hand are available now.
546 # this is indicated by a None value.558 self.assertEquals(0, builders_for_job(apg_job))
547 check_mintime_to_builder(self, apg_job, x86_proc, False, None)559 # The "minimum time to builder" estimation logic is not aware of this
548560 # though.
561 check_mintime_to_builder(self, apg_job, 0)
562
563 # The following job can only run on a native builder.
564 job = self.factory.makeSourcePackageRecipeBuildJob(
565 estimated_duration=111, sourcename='xxr-gftp', score=1055,
566 virtualized=False)
567 self.builds.append(job.specific_job.build)
568
569 # Disable all native builders.
570 for builder in self.builders[(None, False)]:
571 builder.builderok = False
572
573 # All native builders are disabled now. No builders capable of
574 # running the job at hand are available.
575 self.assertEquals(0, builders_for_job(job))
576 # The "minimum time to builder" estimation logic is not aware of the
577 # fact that no builders capable of running the job are available.
578 check_mintime_to_builder(self, job, 0)
549579
550class MultiArchBuildsBase(TestBuildQueueBase):580class MultiArchBuildsBase(TestBuildQueueBase):
551 """Set up a test environment with builds and multiple processors."""581 """Set up a test environment with builds and multiple processors."""
@@ -646,35 +676,32 @@
646 def test_min_time_to_next_builder(self):676 def test_min_time_to_next_builder(self):
647 """When is the next builder capable of running the job at the head of677 """When is the next builder capable of running the job at the head of
648 the queue becoming available?"""678 the queue becoming available?"""
649 processor_fam = ProcessorFamilySet().getByName('hppa')
650 hppa_proc = processor_fam.processors[0]
651
652 # One of four builders for the 'apg' build is immediately available.679 # One of four builders for the 'apg' build is immediately available.
653 apg_build, apg_job = find_job(self, 'apg', 'hppa')680 apg_build, apg_job = find_job(self, 'apg', 'hppa')
654 check_mintime_to_builder(self, apg_job, hppa_proc, False, 0)681 check_mintime_to_builder(self, apg_job, 0)
655682
656 # Assign the postgres job to a builder.683 # Assign the postgres job to a builder.
657 assign_to_builder(self, 'postgres', 1, 'hppa')684 assign_to_builder(self, 'postgres', 1, 'hppa')
658 # Now one builder is gone. But there should still be a builder685 # Now one builder is gone. But there should still be a builder
659 # immediately available.686 # immediately available.
660 check_mintime_to_builder(self, apg_job, hppa_proc, False, 0)687 check_mintime_to_builder(self, apg_job, 0)
661688
662 assign_to_builder(self, 'flex', 2, 'hppa')689 assign_to_builder(self, 'flex', 2, 'hppa')
663 check_mintime_to_builder(self, apg_job, hppa_proc, False, 0)690 check_mintime_to_builder(self, apg_job, 0)
664691
665 assign_to_builder(self, 'bison', 3, 'hppa')692 assign_to_builder(self, 'bison', 3, 'hppa')
666 # Now that no builder is immediately available, the shortest693 # Now that no builder is immediately available, the shortest
667 # remaing build time (based on the estimated duration) is returned:694 # remaing build time (based on the estimated duration) is returned:
668 # 660 seconds695 # 660 seconds
669 # This is equivalent to the 'bison' job's estimated duration.696 # This is equivalent to the 'bison' job's estimated duration.
670 check_mintime_to_builder(self, apg_job, hppa_proc, False, 660)697 check_mintime_to_builder(self, apg_job, 660)
671698
672 # Now we pretend that the 'postgres' started 13 minutes ago. Its699 # Now we pretend that the 'postgres' started 13 minutes ago. Its
673 # remaining execution time should be 2 minutes = 120 seconds and700 # remaining execution time should be 2 minutes = 120 seconds and
674 # it now becomes the job whose builder becomes available next.701 # it now becomes the job whose builder becomes available next.
675 build, bq = find_job(self, 'postgres', 'hppa')702 build, bq = find_job(self, 'postgres', 'hppa')
676 set_remaining_time_for_running_job(bq, 120)703 set_remaining_time_for_running_job(bq, 120)
677 check_mintime_to_builder(self, apg_job, hppa_proc, False, 120)704 check_mintime_to_builder(self, apg_job, 120)
678705
679 # What happens when jobs overdraw the estimated duration? Let's706 # What happens when jobs overdraw the estimated duration? Let's
680 # pretend the 'flex' job started 14 minutes ago.707 # pretend the 'flex' job started 14 minutes ago.
@@ -682,30 +709,35 @@
682 set_remaining_time_for_running_job(bq, -60)709 set_remaining_time_for_running_job(bq, -60)
683 # In such a case we assume that the job will complete within 2710 # In such a case we assume that the job will complete within 2
684 # minutes, this is a guess that has worked well so far.711 # minutes, this is a guess that has worked well so far.
685 check_mintime_to_builder(self, apg_job, hppa_proc, False, 120)712 check_mintime_to_builder(self, apg_job, 120)
686713
687 # If there's a job that will complete within a shorter time then714 # If there's a job that will complete within a shorter time then
688 # we expect to be given that time frame.715 # we expect to be given that time frame.
689 build, bq = find_job(self, 'postgres', 'hppa')716 build, bq = find_job(self, 'postgres', 'hppa')
690 set_remaining_time_for_running_job(bq, 30)717 set_remaining_time_for_running_job(bq, 30)
691 check_mintime_to_builder(self, apg_job, hppa_proc, False, 30)718 check_mintime_to_builder(self, apg_job, 30)
692719
693 # Disable the native hppa builders.720 # Disable the native hppa builders.
694 for builder in self.builders[(hppa_proc.id, False)]:721 for builder in self.builders[(self.hppa_proc.id, False)]:
695 builder.builderok = False722 builder.builderok = False
696723
697 # No builders capable of running the job at hand are available now,724 # No builders capable of running the job at hand are available now.
698 # this is indicated by a None value.725 self.assertEquals(0, builders_for_job(apg_job))
699 check_mintime_to_builder(self, apg_job, hppa_proc, False, None)726 check_mintime_to_builder(self, apg_job, 0)
700727
701 # Let's assume for the moment that the job at the head of the 'apg'728 # Let's add a processor-independent job to the mix.
702 # build queue is processor independent. In that case we'd ask for729 job = self.factory.makeSourcePackageRecipeBuildJob(
703 # *any* next available builder.730 virtualized=False, estimated_duration=22,
731 sourcename='my-recipe-digikam', score=9999)
732 # There are still builders available for the processor-independent
733 # job.
734 self.assertEquals(6, builders_for_job(job))
735 # Even free ones.
704 self.assertTrue(736 self.assertTrue(
705 bq._freeBuildersCount(None, None) > 0,737 bq._getFreeBuildersCount(job.processor, job.virtualized) > 0,
706 "Builders are immediately available for jobs that don't care "738 "Builders are immediately available for processor-independent "
707 "about processor architectures or virtualization")739 "jobs.")
708 check_mintime_to_builder(self, apg_job, None, None, 0)740 check_mintime_to_builder(self, job, 0)
709741
710 # Let's disable all builders.742 # Let's disable all builders.
711 for builders in self.builders.itervalues():743 for builders in self.builders.itervalues():
@@ -713,21 +745,20 @@
713 builder.builderok = False745 builder.builderok = False
714746
715 # There are no builders capable of running even the processor747 # There are no builders capable of running even the processor
716 # independent jobs now and that this is indicated by a None value.748 # independent jobs now.
717 check_mintime_to_builder(self, apg_job, None, None, None)749 self.assertEquals(0, builders_for_job(job))
750 check_mintime_to_builder(self, job, 0)
718751
719 # Re-enable the native hppa builders.752 # Re-enable the native hppa builders.
720 for builder in self.builders[(hppa_proc.id, False)]:753 for builder in self.builders[(self.hppa_proc.id, False)]:
721 builder.builderok = True754 builder.builderok = True
722755
723 # The builder that's becoming available next is the one that's756 # The builder that's becoming available next is the one that's
724 # running the 'postgres' build.757 # running the 'postgres' build.
725 check_mintime_to_builder(self, apg_job, None, None, 30)758 check_mintime_to_builder(self, apg_job, 30)
726759
727 # Make sure we'll find an x86 builder as well.760 # Make sure we'll find an x86 builder as well.
728 processor_fam = ProcessorFamilySet().getByName('x86')761 builder = self.builders[(self.x86_proc.id, False)][0]
729 x86_proc = processor_fam.processors[0]
730 builder = self.builders[(x86_proc.id, False)][0]
731 builder.builderok = True762 builder.builderok = True
732763
733 # Now this builder is the one that becomes available next (29 minutes764 # Now this builder is the one that becomes available next (29 minutes
@@ -736,14 +767,14 @@
736 build, bq = find_job(self, 'gcc', '386')767 build, bq = find_job(self, 'gcc', '386')
737 set_remaining_time_for_running_job(bq, 29)768 set_remaining_time_for_running_job(bq, 29)
738769
739 check_mintime_to_builder(self, apg_job, None, None, 29)770 check_mintime_to_builder(self, apg_job, 29)
740771
741 # Make a second, idle x86 builder available.772 # Make a second, idle x86 builder available.
742 builder = self.builders[(x86_proc.id, False)][1]773 builder = self.builders[(self.x86_proc.id, False)][1]
743 builder.builderok = True774 builder.builderok = True
744775
745 # That builder should be available immediately since it's idle.776 # That builder should be available immediately since it's idle.
746 check_mintime_to_builder(self, apg_job, None, None, 0)777 check_mintime_to_builder(self, apg_job, 0)
747778
748779
749class TestJobClasses(TestCaseWithFactory):780class TestJobClasses(TestCaseWithFactory):
@@ -781,20 +812,20 @@
781812
782 # This is a binary package build.813 # This is a binary package build.
783 self.assertEqual(814 self.assertEqual(
784 bq.job_type, BuildFarmJobType.PACKAGEBUILD,815 BuildFarmJobType.PACKAGEBUILD, bq.job_type,
785 "This is a binary package build")816 "This is a binary package build")
786817
787 # The class registered for 'PACKAGEBUILD' is `BuildPackageJob`.818 # The class registered for 'PACKAGEBUILD' is `BuildPackageJob`.
788 self.assertEqual(819 self.assertEqual(
820 BuildPackageJob,
789 specific_job_classes()[BuildFarmJobType.PACKAGEBUILD],821 specific_job_classes()[BuildFarmJobType.PACKAGEBUILD],
790 BuildPackageJob,
791 "The class registered for 'PACKAGEBUILD' is `BuildPackageJob`")822 "The class registered for 'PACKAGEBUILD' is `BuildPackageJob`")
792823
793 # The 'specific_job' object associated with this `BuildQueue`824 # The 'specific_job' object associated with this `BuildQueue`
794 # instance is of type `BuildPackageJob`.825 # instance is of type `BuildPackageJob`.
795 self.assertTrue(bq.specific_job is not None)826 self.assertTrue(bq.specific_job is not None)
796 self.assertEqual(827 self.assertEqual(
797 bq.specific_job.__class__, BuildPackageJob,828 BuildPackageJob, bq.specific_job.__class__,
798 "The 'specific_job' object associated with this `BuildQueue` "829 "The 'specific_job' object associated with this `BuildQueue` "
799 "instance is of type `BuildPackageJob`")830 "instance is of type `BuildPackageJob`")
800831
@@ -913,23 +944,14 @@
913 self.builds.append(job.specific_job.build)944 self.builds.append(job.specific_job.build)
914945
915 # Assign the same score to the '386' vim and apg build jobs.946 # Assign the same score to the '386' vim and apg build jobs.
916 processor_fam = ProcessorFamilySet().getByName('x86')
917 x86_proc = processor_fam.processors[0]
918 _apg_build, apg_job = find_job(self, 'apg', '386')947 _apg_build, apg_job = find_job(self, 'apg', '386')
919 apg_job.lastscore = 1024948 apg_job.lastscore = 1024
920 # print_build_setup(self.builds)949 # print_build_setup(self.builds)
921950
922 def test_job_delay_for_binary_builds(self):951 def test_job_delay_for_binary_builds(self):
923 processor_fam = ProcessorFamilySet().getByName('hppa')
924 hppa_proc = processor_fam.processors[0]
925
926 # One of four builders for the 'flex' build is immediately available.952 # One of four builders for the 'flex' build is immediately available.
927 flex_build, flex_job = find_job(self, 'flex', 'hppa')953 flex_build, flex_job = find_job(self, 'flex', 'hppa')
928 check_mintime_to_builder(self, flex_job, hppa_proc, False, 0)954 check_mintime_to_builder(self, flex_job, 0)
929
930 # Obtain the builder statistics pertaining to this job.
931 builder_data = flex_job._getBuilderData()
932 builders_in_total, builders_for_job, builder_stats = builder_data
933955
934 # The delay will be 900 (= 15*60) + 222 seconds956 # The delay will be 900 (= 15*60) + 222 seconds
935 check_delay_for_job(self, flex_job, 1122)957 check_delay_for_job(self, flex_job, 1122)
@@ -942,11 +964,8 @@
942 check_delay_for_job(self, flex_job, 222)964 check_delay_for_job(self, flex_job, 222)
943965
944 # How about some estimates for x86 builds?966 # How about some estimates for x86 builds?
945 processor_fam = ProcessorFamilySet().getByName('x86')
946 x86_proc = processor_fam.processors[0]
947
948 _bison_build, bison_job = find_job(self, 'bison', '386')967 _bison_build, bison_job = find_job(self, 'bison', '386')
949 check_mintime_to_builder(self, bison_job, x86_proc, False, 0)968 check_mintime_to_builder(self, bison_job, 0)
950 # The delay will be 900 (= (14+16)*60/2) + 222 seconds.969 # The delay will be 900 (= (14+16)*60/2) + 222 seconds.
951 check_delay_for_job(self, bison_job, 1122)970 check_delay_for_job(self, bison_job, 1122)
952971
@@ -958,13 +977,13 @@
958 # Also, this tests that jobs with equal score but a lower 'job' value977 # Also, this tests that jobs with equal score but a lower 'job' value
959 # (i.e. older jobs) are queued ahead of the job of interest (JOI).978 # (i.e. older jobs) are queued ahead of the job of interest (JOI).
960 _vim_build, vim_job = find_job(self, 'vim', '386')979 _vim_build, vim_job = find_job(self, 'vim', '386')
961 check_mintime_to_builder(self, vim_job, x86_proc, False, 0)980 check_mintime_to_builder(self, vim_job, 0)
962 # The delay will be 870 (= (6+10+12+14+16)*60/4) + 122 (= (222+22)/2)981 # The delay will be 870 (= (6+10+12+14+16)*60/4) + 122 (= (222+22)/2)
963 # seconds.982 # seconds.
964 check_delay_for_job(self, vim_job, 992)983 check_delay_for_job(self, vim_job, 992)
965984
966 _gedit_build, gedit_job = find_job(self, 'gedit', '386')985 _gedit_build, gedit_job = find_job(self, 'gedit', '386')
967 check_mintime_to_builder(self, gedit_job, x86_proc, False, 0)986 check_mintime_to_builder(self, gedit_job, 0)
968 # The delay will be987 # The delay will be
969 # 1080 (= (4+6+8+10+12+14+16)*60/4) + 122 (= (222+22)/2)988 # 1080 (= (4+6+8+10+12+14+16)*60/4) + 122 (= (222+22)/2)
970 # seconds.989 # seconds.
@@ -973,11 +992,7 @@
973 def test_job_delay_for_recipe_builds(self):992 def test_job_delay_for_recipe_builds(self):
974 # One of the 9 builders for the 'bash' build is immediately available.993 # One of the 9 builders for the 'bash' build is immediately available.
975 bash_build, bash_job = find_job(self, 'xx-recipe-bash', None)994 bash_build, bash_job = find_job(self, 'xx-recipe-bash', None)
976 check_mintime_to_builder(self, bash_job, None, False, 0)995 check_mintime_to_builder(self, bash_job, 0)
977
978 # Obtain the builder statistics pertaining to this job.
979 builder_data = bash_job._getBuilderData()
980 builders_in_total, builders_for_job, builder_stats = builder_data
981996
982 # The delay will be 960 + 780 + 222 = 1962, where997 # The delay will be 960 + 780 + 222 = 1962, where
983 # hppa job delays: 960 = (9+11+13+15)*60/3998 # hppa job delays: 960 = (9+11+13+15)*60/3
@@ -986,17 +1001,15 @@
9861001
987 # One of the 9 builders for the 'zsh' build is immediately available.1002 # One of the 9 builders for the 'zsh' build is immediately available.
988 zsh_build, zsh_job = find_job(self, 'xx-recipe-zsh', None)1003 zsh_build, zsh_job = find_job(self, 'xx-recipe-zsh', None)
989 check_mintime_to_builder(self, zsh_job, None, False, 0)1004 check_mintime_to_builder(self, zsh_job, 0)
990
991 # Obtain the builder statistics pertaining to this job.
992 builder_data = zsh_job._getBuilderData()
993 builders_in_total, builders_for_job, builder_stats = builder_data
9941005
995 # The delay will be 0 since this is the head job.1006 # The delay will be 0 since this is the head job.
996 check_delay_for_job(self, zsh_job, 0)1007 check_delay_for_job(self, zsh_job, 0)
9971008
998 # Assign the zsh job to a builder.1009 # Assign the zsh job to a builder.
1010 self.assertEquals((None, False), bash_job._getHeadJobPlatform())
999 assign_to_builder(self, 'xx-recipe-zsh', 1, None)1011 assign_to_builder(self, 'xx-recipe-zsh', 1, None)
1012 self.assertEquals((1, False), bash_job._getHeadJobPlatform())
10001013
1001 # Now that the highest-scored job is out of the way, the estimation1014 # Now that the highest-scored job is out of the way, the estimation
1002 # for the 'bash' recipe build is 222 seconds shorter.1015 # for the 'bash' recipe build is 222 seconds shorter.
@@ -1006,9 +1019,257 @@
1006 # 386 job delays: 780 = (10+12+14+16)*60/41019 # 386 job delays: 780 = (10+12+14+16)*60/4
1007 check_delay_for_job(self, bash_job, 1740)1020 check_delay_for_job(self, bash_job, 1740)
10081021
1009 processor_fam = ProcessorFamilySet().getByName('x86')
1010 x86_proc = processor_fam.processors[0]
1011
1012 _postgres_build, postgres_job = find_job(self, 'postgres', '386')1022 _postgres_build, postgres_job = find_job(self, 'postgres', '386')
1013 # The delay will be 0 since this is the head job now.1023 # The delay will be 0 since this is the head job now.
1014 check_delay_for_job(self, postgres_job, 0)1024 check_delay_for_job(self, postgres_job, 0)
1025 # Also, the platform of the postgres job is returned since it *is*
1026 # the head job now.
1027 pg_platform = (postgres_job.processor.id, postgres_job.virtualized)
1028 self.assertEquals(pg_platform, postgres_job._getHeadJobPlatform())
1029
1030 def test_job_delay_for_unspecified_virtualization(self):
1031 # Make sure that jobs with a NULL 'virtualized' flag get the same
1032 # treatment as the ones with virtualized=TRUE.
1033 # First toggle the 'virtualized' flag for all hppa jobs.
1034 for build in self.builds:
1035 bq = build.buildqueue_record
1036 if bq.processor == self.hppa_proc:
1037 bq.virtualized = True
1038 job = self.factory.makeSourcePackageRecipeBuildJob(
1039 virtualized=True, estimated_duration=332,
1040 sourcename='xxr-openssh-client', score=1050)
1041 self.builds.append(job.specific_job.build)
1042 # print_build_setup(self.builds)
1043 # ...
1044 # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039
1045 # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
1046 # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045
1047 # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
1048 # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050
1049 # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
1050
1051 flex_build, flex_job = find_job(self, 'flex', 'hppa')
1052 # The head job platform is the one of job #21 (xxr-openssh-client).
1053 self.assertEquals((None, True), flex_job._getHeadJobPlatform())
1054 # The delay will be 900 (= 15*60) + 332 seconds
1055 check_delay_for_job(self, flex_job, 1232)
1056
1057 # Now add a job with a NULL 'virtualized' flag. It should be treated
1058 # like jobs with virtualized=TRUE.
1059 job = self.factory.makeSourcePackageRecipeBuildJob(
1060 estimated_duration=111, sourcename='xxr-gwibber', score=1051,
1061 virtualized=None)
1062 self.builds.append(job.specific_job.build)
1063 # print_build_setup(self.builds)
1064 self.assertEqual(None, job.virtualized)
1065 # ...
1066 # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039
1067 # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042
1068 # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045
1069 # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048
1070 # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050
1071 # 22, xxr-gwibber, p: None, v: None e:0:01:51 *** s: 1051
1072 # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053
1073
1074 # The newly added 'xxr-gwibber' job is the new head job now.
1075 self.assertEquals((None, None), flex_job._getHeadJobPlatform())
1076 # The newly added 'xxr-gwibber' job now weighs in as well and the
1077 # delay is 900 (= 15*60) + (332+111)/2 seconds
1078 check_delay_for_job(self, flex_job, 1121)
1079
1080 # The '386' flex job does not care about the 'xxr-gwibber' and
1081 # 'xxr-openssh-client' jobs since the 'virtualized' values do not
1082 # match.
1083 flex_build, flex_job = find_job(self, 'flex', '386')
1084 self.assertEquals((None, False), flex_job._getHeadJobPlatform())
1085 # delay is 960 (= 16*60) + 222 seconds
1086 check_delay_for_job(self, flex_job, 1182)
1087
1088
1089class TestJobDispatchTimeEstimation(MultiArchBuildsBase):
1090 """Test estimated job delays with various processors."""
1091 score_increment = 2
1092 def setUp(self):
1093 """Add more processor-independent jobs to the mix, make the '386' jobs
1094 virtual.
1095
1096 3, gedit, p: hppa, v:False e:0:01:00 *** s: 1003
1097 4, gedit, p: 386, v: True e:0:02:00 *** s: 1006
1098 5, firefox, p: hppa, v:False e:0:03:00 *** s: 1009
1099 6, firefox, p: 386, v: True e:0:04:00 *** s: 1012
1100 7, apg, p: hppa, v:False e:0:05:00 *** s: 1015
1101 9, vim, p: hppa, v:False e:0:07:00 *** s: 1021
1102 10, vim, p: 386, v: True e:0:08:00 *** s: 1024
1103 8, apg, p: 386, v: True e:0:06:00 *** s: 1024
1104 19, xxr-aptitude, p: None, v:False e:0:05:32 *** s: 1025
1105 11, gcc, p: hppa, v:False e:0:09:00 *** s: 1027
1106 12, gcc, p: 386, v: True e:0:10:00 *** s: 1030
1107 13, bison, p: hppa, v:False e:0:11:00 *** s: 1033
1108 14, bison, p: 386, v: True e:0:12:00 *** s: 1036
1109 15, flex, p: hppa, v:False e:0:13:00 *** s: 1039
1110 16, flex, p: 386, v: True e:0:14:00 *** s: 1042
1111 23, xxr-apt-build, p: None, v: True e:0:12:56 *** s: 1043
1112 22, xxr-cron-apt, p: None, v: True e:0:11:05 *** s: 1043
1113 26, xxr-cupt, p: None, v: None e:0:18:30 *** s: 1044
1114 25, xxr-apt, p: None, v: None e:0:16:38 *** s: 1044
1115 24, xxr-debdelta, p: None, v: None e:0:14:47 *** s: 1044
1116 17, postgres, p: hppa, v:False e:0:15:00 *** s: 1045
1117 18, postgres, p: 386, v: True e:0:16:00 *** s: 1048
1118 21, xxr-daptup, p: None, v: None e:0:09:14 *** s: 1051
1119 20, xxr-auto-apt, p: None, v:False e:0:07:23 *** s: 1053
1120
1121 p=processor, v=virtualized, e=estimated_duration, s=score
1122 """
1123 super(TestJobDispatchTimeEstimation, self).setUp()
1124
1125 job = self.factory.makeSourcePackageRecipeBuildJob(
1126 virtualized=False, estimated_duration=332,
1127 sourcename='xxr-aptitude', score=1025)
1128 self.builds.append(job.specific_job.build)
1129 job = self.factory.makeSourcePackageRecipeBuildJob(
1130 virtualized=False, estimated_duration=443,
1131 sourcename='xxr-auto-apt', score=1053)
1132 self.builds.append(job.specific_job.build)
1133 job = self.factory.makeSourcePackageRecipeBuildJob(
1134 estimated_duration=554, sourcename='xxr-daptup', score=1051,
1135 virtualized=None)
1136 self.builds.append(job.specific_job.build)
1137 job = self.factory.makeSourcePackageRecipeBuildJob(
1138 estimated_duration=665, sourcename='xxr-cron-apt', score=1043)
1139 self.builds.append(job.specific_job.build)
1140 job = self.factory.makeSourcePackageRecipeBuildJob(
1141 estimated_duration=776, sourcename='xxr-apt-build', score=1043)
1142 self.builds.append(job.specific_job.build)
1143 job = self.factory.makeSourcePackageRecipeBuildJob(
1144 estimated_duration=887, sourcename='xxr-debdelta', score=1044,
1145 virtualized=None)
1146 self.builds.append(job.specific_job.build)
1147 job = self.factory.makeSourcePackageRecipeBuildJob(
1148 estimated_duration=998, sourcename='xxr-apt', score=1044,
1149 virtualized=None)
1150 self.builds.append(job.specific_job.build)
1151 job = self.factory.makeSourcePackageRecipeBuildJob(
1152 estimated_duration=1110, sourcename='xxr-cupt', score=1044,
1153 virtualized=None)
1154 self.builds.append(job.specific_job.build)
1155
1156 # Assign the same score to the '386' vim and apg build jobs.
1157 _apg_build, apg_job = find_job(self, 'apg', '386')
1158 apg_job.lastscore = 1024
1159
1160 # Also, toggle the 'virtualized' flag for all '386' jobs.
1161 for build in self.builds:
1162 bq = build.buildqueue_record
1163 if bq.processor == self.x86_proc:
1164 bq.virtualized = True
1165
1166 def test_pending_jobs_only(self):
1167 # Let's see the assertion fail for a job that's not pending any more.
1168 assign_to_builder(self, 'gedit', 1, 'hppa')
1169 gedit_build, gedit_job = find_job(self, 'gedit', 'hppa')
1170 self.assertRaises(AssertionError, gedit_job.getEstimatedJobStartTime)
1171
1172 def test_estimation_binary_virtual(self):
1173 gcc_build, gcc_job = find_job(self, 'gcc', '386')
1174 # The delay of 1671 seconds is calculated as follows:
1175 # 386 jobs: (12+14+16)*60/3 = 840
1176 # processor-independent jobs:
1177 # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
1178 check_estimate(self, gcc_job, 1671)
1179 self.assertEquals(5, builders_for_job(gcc_job))
1180
1181 def test_proc_indep_virtual_true(self):
1182 xxr_build, xxr_job = find_job(self, 'xxr-apt-build', None)
1183 # The delay of 1802 seconds is calculated as follows:
1184 # 386 jobs: 16*60 = 960
1185 # processor-independent jobs:
1186 # (11:05 + 18:30 + 16:38 + 14:47 + 9:14)/5 = 842
1187 check_estimate(self, xxr_job, 1802)
1188
1189 def test_estimation_binary_virtual_long_queue(self):
1190 gedit_build, gedit_job = find_job(self, 'gedit', '386')
1191 # The delay of 1671 seconds is calculated as follows:
1192 # 386 jobs:
1193 # (4+6+8+10+12+14+16)*60/5 = 840
1194 # processor-independent jobs:
1195 # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
1196 check_estimate(self, gedit_job, 1671)
1197
1198 def test_proc_indep_virtual_null_headjob(self):
1199 xxr_build, xxr_job = find_job(self, 'xxr-daptup', None)
1200 # This job is at the head of the queue for virtualized builders and
1201 # will get dispatched within the next 5 seconds.
1202 check_estimate(self, xxr_job, 5)
1203
1204 def test_proc_indep_virtual_false(self):
1205 xxr_build, xxr_job = find_job(self, 'xxr-aptitude', None)
1206 # The delay of 1403 seconds is calculated as follows:
1207 # hppa jobs: (9+11+13+15)*60/3 = 960
1208 # processor-independent jobs: 7:23 = 443
1209 check_estimate(self, xxr_job, 1403)
1210
1211 def test_proc_indep_virtual_false_headjob(self):
1212 xxr_build, xxr_job = find_job(self, 'xxr-auto-apt', None)
1213 # This job is at the head of the queue for native builders and
1214 # will get dispatched within the next 5 seconds.
1215 check_estimate(self, xxr_job, 5)
1216
1217 def test_estimation_binary_virtual_same_score(self):
1218 vim_build, vim_job = find_job(self, 'vim', '386')
1219 # The apg job is ahead of the vim job.
1220 # The delay of 1527 seconds is calculated as follows:
1221 # 386 jobs: (6+10+12+14+16)*60/5 = 696
1222 # processor-independent jobs:
1223 # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831
1224 check_estimate(self, vim_job, 1527)
1225
1226 def test_no_builder_no_estimate(self):
1227 # No dispatch estimate is provided in the absence of builders that
1228 # can run the job of interest (JOI).
1229 disable_builders(self, 'x86', True)
1230 vim_build, vim_job = find_job(self, 'vim', '386')
1231 check_estimate(self, vim_job, None)
1232
1233 def test_estimates_with_small_builder_pool(self):
1234 # Test that a reduced builder pool results in longer dispatch time
1235 # estimates.
1236 vim_build, vim_job = find_job(self, 'vim', '386')
1237 disable_builders(self, 'x86', True)
1238 # Re-enable one builder.
1239 builder = self.builders[(self.x86_proc.id, True)][0]
1240 builder.builderok = True
1241 # Dispatch the firefox job to it.
1242 assign_to_builder(self, 'firefox', 1, '386')
1243 # Dispatch the head job, making postgres/386 the new head job and
1244 # resulting in a 240 seconds head job dispatch delay.
1245 assign_to_builder(self, 'xxr-daptup', 1, None)
1246 check_mintime_to_builder(self, vim_job, 240)
1247 # Re-enable another builder.
1248 builder = self.builders[(self.x86_proc.id, True)][1]
1249 builder.builderok = True
1250 # Assign a job to it.
1251 assign_to_builder(self, 'gedit', 2, '386')
1252 check_mintime_to_builder(self, vim_job, 120)
1253
1254 xxr_build, xxr_job = find_job(self, 'xxr-apt', None)
1255 # The delay of 2627+120 seconds is calculated as follows:
1256 # 386 jobs : (6+10+12+14+16)*60/2 = 1740
1257 # processor-independent jobs :
1258 # (12:56 + 11:05 + 18:30 + 16:38 + 14:47)/5 = 887
1259 # waiting time for next builder: = 120
1260 self.assertEquals(2, builders_for_job(vim_job))
1261 self.assertEquals(9, builders_for_job(xxr_job))
1262 check_estimate(self, vim_job, 2747)
1263
1264 def test_estimation_binary_virtual_headjob(self):
1265 # The head job only waits for the next builder to become available.
1266 disable_builders(self, 'x86', True)
1267 # Re-enable one builder.
1268 builder = self.builders[(self.x86_proc.id, True)][0]
1269 builder.builderok = True
1270 # Assign a job to it.
1271 assign_to_builder(self, 'gedit', 1, '386')
1272 # Dispatch the head job, making postgres/386 the new head job.
1273 assign_to_builder(self, 'xxr-daptup', 1, None)
1274 postgres_build, postgres_job = find_job(self, 'postgres', '386')
1275 check_estimate(self, postgres_job, 120)