Merge lp://staging/~al-maisan/launchpad/ejdt-n-1 into lp://staging/launchpad
- ejdt-n-1
- Merge into devel
Proposed by
Muharem Hrnjadovic
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp://staging/~al-maisan/launchpad/ejdt-n-1 |
Merge into: | lp://staging/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://staging/~al-maisan/launchpad/ejdt-n-1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | code | Approve | |
Review via email: mp+18391@code.staging.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote : | # |
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
1 | === modified file 'lib/lp/soyuz/interfaces/buildqueue.py' | |||
2 | --- lib/lp/soyuz/interfaces/buildqueue.py 2010-01-20 22:09:26 +0000 | |||
3 | +++ lib/lp/soyuz/interfaces/buildqueue.py 2010-02-02 15:26:16 +0000 | |||
4 | @@ -101,6 +101,15 @@ | |||
5 | 101 | title=_('Start time'), | 101 | title=_('Start time'), |
6 | 102 | description=_('Time when the job started.')) | 102 | description=_('Time when the job started.')) |
7 | 103 | 103 | ||
8 | 104 | def getEstimatedJobStartTime(): | ||
9 | 105 | """Get the estimated start time for a pending build farm job. | ||
10 | 106 | |||
11 | 107 | :return: a timestamp upon success or None on failure. None | ||
12 | 108 | indicates that an estimated start time is not available. | ||
13 | 109 | :raise: AssertionError when the build job is not in the | ||
14 | 110 | `JobStatus.WAITING` state. | ||
15 | 111 | """ | ||
16 | 112 | |||
17 | 104 | 113 | ||
18 | 105 | class IBuildQueueSet(Interface): | 114 | class IBuildQueueSet(Interface): |
19 | 106 | """Launchpad Auto Build queue set handler and auxiliary methods.""" | 115 | """Launchpad Auto Build queue set handler and auxiliary methods.""" |
20 | 107 | 116 | ||
21 | === modified file 'lib/lp/soyuz/model/buildqueue.py' | |||
22 | --- lib/lp/soyuz/model/buildqueue.py 2010-01-30 05:27:48 +0000 | |||
23 | +++ lib/lp/soyuz/model/buildqueue.py 2010-02-02 15:26:16 +0000 | |||
24 | @@ -11,6 +11,8 @@ | |||
25 | 11 | 'specific_job_classes', | 11 | 'specific_job_classes', |
26 | 12 | ] | 12 | ] |
27 | 13 | 13 | ||
28 | 14 | from collections import defaultdict | ||
29 | 15 | from datetime import datetime, timedelta | ||
30 | 14 | import logging | 16 | import logging |
31 | 15 | 17 | ||
32 | 16 | from zope.component import getSiteManager, getUtility | 18 | from zope.component import getSiteManager, getUtility |
33 | @@ -38,6 +40,12 @@ | |||
34 | 38 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) | 40 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) |
35 | 39 | 41 | ||
36 | 40 | 42 | ||
37 | 43 | def normalize_virtualization(virtualized): | ||
38 | 44 | """Jobs with NULL virtualization settings should be treated the | ||
39 | 45 | same way as virtualized jobs.""" | ||
40 | 46 | return virtualized is None or virtualized | ||
41 | 47 | |||
42 | 48 | |||
43 | 41 | def specific_job_classes(): | 49 | def specific_job_classes(): |
44 | 42 | """Job classes that may run on the build farm.""" | 50 | """Job classes that may run on the build farm.""" |
45 | 43 | job_classes = dict() | 51 | job_classes = dict() |
46 | @@ -54,6 +62,32 @@ | |||
47 | 54 | return job_classes | 62 | return job_classes |
48 | 55 | 63 | ||
49 | 56 | 64 | ||
50 | 65 | def get_builder_data(): | ||
51 | 66 | """How many working builders are there, how are they configured?""" | ||
52 | 67 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
53 | 68 | builder_data = """ | ||
54 | 69 | SELECT processor, virtualized, COUNT(id) FROM builder | ||
55 | 70 | WHERE builderok = TRUE AND manual = FALSE | ||
56 | 71 | GROUP BY processor, virtualized; | ||
57 | 72 | """ | ||
58 | 73 | results = store.execute(builder_data).get_all() | ||
59 | 74 | builders_in_total = builders_for_job = virtualized_total = 0 | ||
60 | 75 | |||
61 | 76 | builder_stats = defaultdict(int) | ||
62 | 77 | for processor, virtualized, count in results: | ||
63 | 78 | builders_in_total += count | ||
64 | 79 | if virtualized: | ||
65 | 80 | virtualized_total += count | ||
66 | 81 | builder_stats[(processor, virtualized)] = count | ||
67 | 82 | |||
68 | 83 | builder_stats[(None, True)] = virtualized_total | ||
69 | 84 | # Jobs with a NULL virtualized flag should be treated the same as | ||
70 | 85 | # jobs where virtualized=TRUE. | ||
71 | 86 | builder_stats[(None, None)] = virtualized_total | ||
72 | 87 | builder_stats[(None, False)] = builders_in_total - virtualized_total | ||
73 | 88 | return builder_stats | ||
74 | 89 | |||
75 | 90 | |||
76 | 57 | class BuildQueue(SQLBase): | 91 | class BuildQueue(SQLBase): |
77 | 58 | implements(IBuildQueue) | 92 | implements(IBuildQueue) |
78 | 59 | _table = "BuildQueue" | 93 | _table = "BuildQueue" |
79 | @@ -142,54 +176,7 @@ | |||
80 | 142 | """See `IBuildQueue`.""" | 176 | """See `IBuildQueue`.""" |
81 | 143 | self.job.date_started = timestamp | 177 | self.job.date_started = timestamp |
82 | 144 | 178 | ||
131 | 145 | def _getBuilderData(self): | 179 | def _getFreeBuildersCount(self, processor, virtualized): |
84 | 146 | """How many working builders are there, how are they configured?""" | ||
85 | 147 | # Please note: this method will send only one request to the database. | ||
86 | 148 | |||
87 | 149 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
88 | 150 | my_processor = self.specific_job.processor | ||
89 | 151 | my_virtualized = self.specific_job.virtualized | ||
90 | 152 | |||
91 | 153 | # We need to know the total number of builders as well as the | ||
92 | 154 | # number of builders that can run the job of interest (JOI). | ||
93 | 155 | # If the JOI is processor independent these builder counts will | ||
94 | 156 | # have the same value. | ||
95 | 157 | builder_data = """ | ||
96 | 158 | SELECT processor, virtualized, COUNT(id) FROM builder | ||
97 | 159 | WHERE builderok = TRUE AND manual = FALSE | ||
98 | 160 | GROUP BY processor, virtualized; | ||
99 | 161 | """ | ||
100 | 162 | results = store.execute(builder_data).get_all() | ||
101 | 163 | builders_in_total = builders_for_job = 0 | ||
102 | 164 | virtualized_total = 0 | ||
103 | 165 | native_total = 0 | ||
104 | 166 | |||
105 | 167 | builder_stats = dict() | ||
106 | 168 | for processor, virtualized, count in results: | ||
107 | 169 | builders_in_total += count | ||
108 | 170 | if virtualized: | ||
109 | 171 | virtualized_total += count | ||
110 | 172 | else: | ||
111 | 173 | native_total += count | ||
112 | 174 | if my_processor is not None: | ||
113 | 175 | if (my_processor.id == processor and | ||
114 | 176 | my_virtualized == virtualized): | ||
115 | 177 | # The job on hand can only run on builders with a | ||
116 | 178 | # particular processor/virtualization combination and | ||
117 | 179 | # this is how many of these we have. | ||
118 | 180 | builders_for_job = count | ||
119 | 181 | builder_stats[(processor, virtualized)] = count | ||
120 | 182 | if my_processor is None: | ||
121 | 183 | # The job of interest (JOI) is processor independent. | ||
122 | 184 | builders_for_job = builders_in_total | ||
123 | 185 | |||
124 | 186 | builder_stats[(None, None)] = builders_in_total | ||
125 | 187 | builder_stats[(None, True)] = virtualized_total | ||
126 | 188 | builder_stats[(None, False)] = native_total | ||
127 | 189 | |||
128 | 190 | return (builders_in_total, builders_for_job, builder_stats) | ||
129 | 191 | |||
130 | 192 | def _freeBuildersCount(self, processor, virtualized): | ||
132 | 193 | """How many builders capable of running jobs for the given processor | 180 | """How many builders capable of running jobs for the given processor |
133 | 194 | and virtualization combination are idle/free at present?""" | 181 | and virtualization combination are idle/free at present?""" |
134 | 195 | query = """ | 182 | query = """ |
135 | @@ -198,18 +185,18 @@ | |||
136 | 198 | builderok = TRUE AND manual = FALSE | 185 | builderok = TRUE AND manual = FALSE |
137 | 199 | AND id NOT IN ( | 186 | AND id NOT IN ( |
138 | 200 | SELECT builder FROM BuildQueue WHERE builder IS NOT NULL) | 187 | SELECT builder FROM BuildQueue WHERE builder IS NOT NULL) |
140 | 201 | """ | 188 | AND virtualized = %s |
141 | 189 | """ % sqlvalues(normalize_virtualization(virtualized)) | ||
142 | 202 | if processor is not None: | 190 | if processor is not None: |
143 | 203 | query += """ | 191 | query += """ |
146 | 204 | AND processor = %s AND virtualized = %s | 192 | AND processor = %s |
147 | 205 | """ % sqlvalues(processor, virtualized) | 193 | """ % sqlvalues(processor) |
148 | 206 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | 194 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
149 | 207 | result_set = store.execute(query) | 195 | result_set = store.execute(query) |
150 | 208 | free_builders = result_set.get_one()[0] | 196 | free_builders = result_set.get_one()[0] |
151 | 209 | return free_builders | 197 | return free_builders |
152 | 210 | 198 | ||
155 | 211 | def _estimateTimeToNextBuilder( | 199 | def _estimateTimeToNextBuilder(self): |
154 | 212 | self, head_job_processor, head_job_virtualized): | ||
156 | 213 | """Estimate time until next builder becomes available. | 200 | """Estimate time until next builder becomes available. |
157 | 214 | 201 | ||
158 | 215 | For the purpose of estimating the dispatch time of the job of interest | 202 | For the purpose of estimating the dispatch time of the job of interest |
159 | @@ -220,36 +207,21 @@ | |||
160 | 220 | 207 | ||
161 | 221 | - processor dependent: only builders with the matching | 208 | - processor dependent: only builders with the matching |
162 | 222 | processor/virtualization combination should be considered. | 209 | processor/virtualization combination should be considered. |
164 | 223 | - *not* processor dependent: all builders should be considered. | 210 | - *not* processor dependent: all builders with the matching |
165 | 211 | virtualization setting should be considered. | ||
166 | 224 | 212 | ||
167 | 225 | :param head_job_processor: The processor required by the job at the | ||
168 | 226 | head of the queue. | ||
169 | 227 | :param head_job_virtualized: The virtualization setting required by | ||
170 | 228 | the job at the head of the queue. | ||
171 | 229 | :return: The estimated number of seconds untils a builder capable of | 213 | :return: The estimated number of seconds untils a builder capable of |
174 | 230 | running the head job becomes available or None if no such builder | 214 | running the head job becomes available. |
173 | 231 | exists. | ||
175 | 232 | """ | 215 | """ |
182 | 233 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | 216 | head_job_platform = self._getHeadJobPlatform() |
183 | 234 | 217 | ||
184 | 235 | # First check whether we have free builders. | 218 | # Return a zero delay if we still have free builders available for the |
185 | 236 | free_builders = self._freeBuildersCount( | 219 | # given platform/virtualization combination. |
186 | 237 | head_job_processor, head_job_virtualized) | 220 | free_builders = self._getFreeBuildersCount(*head_job_platform) |
181 | 238 | |||
187 | 239 | if free_builders > 0: | 221 | if free_builders > 0: |
188 | 240 | # We have free builders for the given processor/virtualization | ||
189 | 241 | # combination -> zero delay | ||
190 | 242 | return 0 | 222 | return 0 |
191 | 243 | 223 | ||
201 | 244 | extra_clauses = '' | 224 | head_job_processor, head_job_virtualized = head_job_platform |
193 | 245 | if head_job_processor is not None: | ||
194 | 246 | # Only look at builders with specific processor types. | ||
195 | 247 | extra_clauses += """ | ||
196 | 248 | AND Builder.processor = %s | ||
197 | 249 | AND Builder.virtualized = %s | ||
198 | 250 | """ % sqlvalues(head_job_processor, head_job_virtualized) | ||
199 | 251 | |||
200 | 252 | params = sqlvalues(JobStatus.RUNNING) + (extra_clauses,) | ||
202 | 253 | 225 | ||
203 | 254 | delay_query = """ | 226 | delay_query = """ |
204 | 255 | SELECT MIN( | 227 | SELECT MIN( |
205 | @@ -279,15 +251,85 @@ | |||
206 | 279 | AND Builder.manual = False | 251 | AND Builder.manual = False |
207 | 280 | AND Builder.builderok = True | 252 | AND Builder.builderok = True |
208 | 281 | AND Job.status = %s | 253 | AND Job.status = %s |
212 | 282 | %s | 254 | AND Builder.virtualized = %s |
213 | 283 | """ % params | 255 | """ % sqlvalues( |
214 | 284 | 256 | JobStatus.RUNNING, | |
215 | 257 | normalize_virtualization(head_job_virtualized)) | ||
216 | 258 | |||
217 | 259 | if head_job_processor is not None: | ||
218 | 260 | # Only look at builders with specific processor types. | ||
219 | 261 | delay_query += """ | ||
220 | 262 | AND Builder.processor = %s | ||
221 | 263 | """ % sqlvalues(head_job_processor) | ||
222 | 264 | |||
223 | 265 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
224 | 285 | result_set = store.execute(delay_query) | 266 | result_set = store.execute(delay_query) |
225 | 286 | head_job_delay = result_set.get_one()[0] | 267 | head_job_delay = result_set.get_one()[0] |
230 | 287 | if head_job_delay is None: | 268 | return (0 if head_job_delay is None else int(head_job_delay)) |
231 | 288 | return None | 269 | |
232 | 289 | else: | 270 | def _getPendingJobsClauses(self): |
233 | 290 | return int(head_job_delay) | 271 | """WHERE clauses for pending job queries, used for dipatch time |
234 | 272 | estimation.""" | ||
235 | 273 | virtualized = normalize_virtualization(self.virtualized) | ||
236 | 274 | clauses = """ | ||
237 | 275 | BuildQueue.job = Job.id | ||
238 | 276 | AND Job.status = %s | ||
239 | 277 | AND ( | ||
240 | 278 | -- The score must be either above my score or the | ||
241 | 279 | -- job must be older than me in cases where the | ||
242 | 280 | -- score is equal. | ||
243 | 281 | BuildQueue.lastscore > %s OR | ||
244 | 282 | (BuildQueue.lastscore = %s AND Job.id < %s)) | ||
245 | 283 | -- The virtualized values either match or the job | ||
246 | 284 | -- does not care about virtualization and the job | ||
247 | 285 | -- of interest (JOI) is to be run on a virtual builder | ||
248 | 286 | -- (we want to prevent the execution of untrusted code | ||
249 | 287 | -- on native builders). | ||
250 | 288 | AND COALESCE(buildqueue.virtualized, TRUE) = %s | ||
251 | 289 | """ % sqlvalues( | ||
252 | 290 | JobStatus.WAITING, self.lastscore, self.lastscore, self.job, | ||
253 | 291 | virtualized) | ||
254 | 292 | processor_clause = """ | ||
255 | 293 | AND ( | ||
256 | 294 | -- The processor values either match or the candidate | ||
257 | 295 | -- job is processor-independent. | ||
258 | 296 | buildqueue.processor = %s OR | ||
259 | 297 | buildqueue.processor IS NULL) | ||
260 | 298 | """ % sqlvalues(self.processor) | ||
261 | 299 | # We don't care about processors if the estimation is for a | ||
262 | 300 | # processor-independent job. | ||
263 | 301 | if self.processor is not None: | ||
264 | 302 | clauses += processor_clause | ||
265 | 303 | return clauses | ||
266 | 304 | |||
267 | 305 | def _getHeadJobPlatform(self): | ||
268 | 306 | """Find the processor and virtualization setting for the head job. | ||
269 | 307 | |||
270 | 308 | Among the jobs that compete with the job of interest (JOI) for | ||
271 | 309 | builders and are queued ahead of it the head job is the one in pole | ||
272 | 310 | position i.e. the one to be dispatched to a builder next. | ||
273 | 311 | |||
274 | 312 | :return: A (processor, virtualized) tuple which is the head job's | ||
275 | 313 | platform or None if the JOI is the head job. | ||
276 | 314 | """ | ||
277 | 315 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) | ||
278 | 316 | my_platform = ( | ||
279 | 317 | getattr(self.processor, 'id', None), | ||
280 | 318 | normalize_virtualization(self.virtualized)) | ||
281 | 319 | query = """ | ||
282 | 320 | SELECT | ||
283 | 321 | processor, | ||
284 | 322 | virtualized | ||
285 | 323 | FROM | ||
286 | 324 | BuildQueue, Job | ||
287 | 325 | WHERE | ||
288 | 326 | """ | ||
289 | 327 | query += self._getPendingJobsClauses() | ||
290 | 328 | query += """ | ||
291 | 329 | ORDER BY lastscore DESC, job LIMIT 1 | ||
292 | 330 | """ | ||
293 | 331 | result = store.execute(query).get_one() | ||
294 | 332 | return (my_platform if result is None else result) | ||
295 | 291 | 333 | ||
296 | 292 | def _estimateJobDelay(self, builder_stats): | 334 | def _estimateJobDelay(self, builder_stats): |
297 | 293 | """Sum of estimated durations for *pending* jobs ahead in queue. | 335 | """Sum of estimated durations for *pending* jobs ahead in queue. |
298 | @@ -304,10 +346,6 @@ | |||
299 | 304 | :return: An integer value holding the sum of delays (in seconds) | 346 | :return: An integer value holding the sum of delays (in seconds) |
300 | 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. |
301 | 306 | """ | 348 | """ |
302 | 307 | def normalize_virtualization(virtualized): | ||
303 | 308 | """Jobs with NULL virtualization settings should be treated the | ||
304 | 309 | same way as virtualized jobs.""" | ||
305 | 310 | return virtualized is None or virtualized | ||
306 | 311 | def jobs_compete_for_builders(a, b): | 349 | def jobs_compete_for_builders(a, b): |
307 | 312 | """True if the two jobs compete for builders.""" | 350 | """True if the two jobs compete for builders.""" |
308 | 313 | a_processor, a_virtualized = a | 351 | a_processor, a_virtualized = a |
309 | @@ -338,37 +376,8 @@ | |||
310 | 338 | FROM | 376 | FROM |
311 | 339 | BuildQueue, Job | 377 | BuildQueue, Job |
312 | 340 | WHERE | 378 | WHERE |
344 | 341 | BuildQueue.job = Job.id | 379 | """ |
345 | 342 | AND Job.status = %s | 380 | query += self._getPendingJobsClauses() |
315 | 343 | AND ( | ||
316 | 344 | -- The score must be either above my score or the | ||
317 | 345 | -- job must be older than me in cases where the | ||
318 | 346 | -- score is equal. | ||
319 | 347 | BuildQueue.lastscore > %s OR | ||
320 | 348 | (BuildQueue.lastscore = %s AND Job.id < %s)) | ||
321 | 349 | AND ( | ||
322 | 350 | -- The virtualized values either match or the job | ||
323 | 351 | -- does not care about virtualization and the job | ||
324 | 352 | -- of interest (JOI) is to be run on a virtual builder | ||
325 | 353 | -- (we want to prevent the execution of untrusted code | ||
326 | 354 | -- on native builders). | ||
327 | 355 | buildqueue.virtualized = %s OR | ||
328 | 356 | (buildqueue.virtualized IS NULL AND %s = TRUE)) | ||
329 | 357 | """ % sqlvalues( | ||
330 | 358 | JobStatus.WAITING, self.lastscore, self.lastscore, self.job, | ||
331 | 359 | self.virtualized, self.virtualized) | ||
332 | 360 | processor_clause = """ | ||
333 | 361 | AND ( | ||
334 | 362 | -- The processor values either match or the candidate | ||
335 | 363 | -- job is processor-independent. | ||
336 | 364 | buildqueue.processor = %s OR | ||
337 | 365 | buildqueue.processor IS NULL) | ||
338 | 366 | """ % sqlvalues(self.processor) | ||
339 | 367 | # We don't care about processors if the estimation is for a | ||
340 | 368 | # processor-independent job. | ||
341 | 369 | if self.processor is not None: | ||
342 | 370 | query += processor_clause | ||
343 | 371 | |||
346 | 372 | query += """ | 381 | query += """ |
347 | 373 | GROUP BY BuildQueue.processor, BuildQueue.virtualized | 382 | GROUP BY BuildQueue.processor, BuildQueue.virtualized |
348 | 374 | """ | 383 | """ |
349 | @@ -376,11 +385,11 @@ | |||
350 | 376 | delays_by_platform = store.execute(query).get_all() | 385 | delays_by_platform = store.execute(query).get_all() |
351 | 377 | 386 | ||
352 | 378 | # This will be used to capture per-platform delay totals. | 387 | # This will be used to capture per-platform delay totals. |
354 | 379 | delays = dict() | 388 | delays = defaultdict(int) |
355 | 380 | # This will be used to capture per-platform job counts. | 389 | # This will be used to capture per-platform job counts. |
357 | 381 | job_counts = dict() | 390 | job_counts = defaultdict(int) |
358 | 382 | 391 | ||
360 | 383 | # Apply weights to the estimated duration of the jobs as follows: | 392 | # Divide the estimated duration of the jobs as follows: |
361 | 384 | # - if a job is tied to a processor TP then divide the estimated | 393 | # - if a job is tied to a processor TP then divide the estimated |
362 | 385 | # duration of that job by the number of builders that target TP | 394 | # duration of that job by the number of builders that target TP |
363 | 386 | # since only these can build the job. | 395 | # since only these can build the job. |
364 | @@ -390,7 +399,7 @@ | |||
365 | 390 | for processor, virtualized, job_count, delay in delays_by_platform: | 399 | for processor, virtualized, job_count, delay in delays_by_platform: |
366 | 391 | virtualized = normalize_virtualization(virtualized) | 400 | virtualized = normalize_virtualization(virtualized) |
367 | 392 | platform = (processor, virtualized) | 401 | platform = (processor, virtualized) |
369 | 393 | builder_count = builder_stats.get((processor, virtualized), 0) | 402 | builder_count = builder_stats.get(platform, 0) |
370 | 394 | if builder_count == 0: | 403 | if builder_count == 0: |
371 | 395 | # There is no builder that can run this job, ignore it | 404 | # There is no builder that can run this job, ignore it |
372 | 396 | # for the purpose of dispatch time estimation. | 405 | # for the purpose of dispatch time estimation. |
373 | @@ -399,11 +408,11 @@ | |||
374 | 399 | if jobs_compete_for_builders(my_platform, platform): | 408 | if jobs_compete_for_builders(my_platform, platform): |
375 | 400 | # The jobs that target the platform at hand compete with | 409 | # The jobs that target the platform at hand compete with |
376 | 401 | # the JOI for builders, add their delays. | 410 | # the JOI for builders, add their delays. |
379 | 402 | delays[platform] = delay | 411 | delays[platform] += delay |
380 | 403 | job_counts[platform] = job_count | 412 | job_counts[platform] += job_count |
381 | 404 | 413 | ||
382 | 405 | sum_of_delays = 0 | 414 | sum_of_delays = 0 |
384 | 406 | # Now weight/average the delays based on a jobs/builders comparison. | 415 | # Now devide the delays based on a jobs/builders comparison. |
385 | 407 | for platform, duration in delays.iteritems(): | 416 | for platform, duration in delays.iteritems(): |
386 | 408 | jobs = job_counts[platform] | 417 | jobs = job_counts[platform] |
387 | 409 | builders = builder_stats[platform] | 418 | builders = builder_stats[platform] |
388 | @@ -417,6 +426,44 @@ | |||
389 | 417 | 426 | ||
390 | 418 | return sum_of_delays | 427 | return sum_of_delays |
391 | 419 | 428 | ||
392 | 429 | def getEstimatedJobStartTime(self): | ||
393 | 430 | """See `IBuildQueue`. | ||
394 | 431 | |||
395 | 432 | The estimated dispatch time for the build farm job at hand is | ||
396 | 433 | calculated from the following ingredients: | ||
397 | 434 | * the start time for the head job (job at the | ||
398 | 435 | head of the respective build queue) | ||
399 | 436 | * the estimated build durations of all jobs that | ||
400 | 437 | precede the job of interest (JOI) in the build queue | ||
401 | 438 | (divided by the number of machines in the respective | ||
402 | 439 | build pool) | ||
403 | 440 | """ | ||
404 | 441 | # This method may only be invoked for pending jobs. | ||
405 | 442 | if self.job.status != JobStatus.WAITING: | ||
406 | 443 | raise AssertionError( | ||
407 | 444 | "The start time is only estimated for pending jobs.") | ||
408 | 445 | |||
409 | 446 | builder_stats = get_builder_data() | ||
410 | 447 | platform = (getattr(self.processor, 'id', None), self.virtualized) | ||
411 | 448 | if builder_stats[platform] == 0: | ||
412 | 449 | # No builders that can run the job at hand | ||
413 | 450 | # -> no dispatch time estimation available. | ||
414 | 451 | return None | ||
415 | 452 | |||
416 | 453 | # Get the sum of the estimated run times for *pending* jobs that are | ||
417 | 454 | # ahead of us in the queue. | ||
418 | 455 | sum_of_delays = self._estimateJobDelay(builder_stats) | ||
419 | 456 | |||
420 | 457 | # Get the minimum time duration until the next builder becomes | ||
421 | 458 | # available. | ||
422 | 459 | min_wait_time = self._estimateTimeToNextBuilder() | ||
423 | 460 | |||
424 | 461 | # A job will not get dispatched in less than 5 seconds no matter what. | ||
425 | 462 | start_time = max(5, min_wait_time + sum_of_delays) | ||
426 | 463 | result = datetime.utcnow() + timedelta(seconds=start_time) | ||
427 | 464 | |||
428 | 465 | return result | ||
429 | 466 | |||
430 | 420 | 467 | ||
431 | 421 | class BuildQueueSet(object): | 468 | class BuildQueueSet(object): |
432 | 422 | """Utility to deal with BuildQueue content class.""" | 469 | """Utility to deal with BuildQueue content class.""" |
433 | 423 | 470 | ||
434 | === modified file 'lib/lp/soyuz/tests/test_buildqueue.py' | |||
435 | --- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-30 05:27:48 +0000 | |||
436 | +++ lib/lp/soyuz/tests/test_buildqueue.py 2010-02-02 15:26:16 +0000 | |||
437 | @@ -15,15 +15,14 @@ | |||
438 | 15 | from canonical.testing import LaunchpadZopelessLayer | 15 | from canonical.testing import LaunchpadZopelessLayer |
439 | 16 | 16 | ||
440 | 17 | from lp.buildmaster.interfaces.builder import IBuilderSet | 17 | from lp.buildmaster.interfaces.builder import IBuilderSet |
443 | 18 | from lp.buildmaster.interfaces.buildfarmjob import ( | 18 | from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType |
442 | 19 | BuildFarmJobType) | ||
444 | 20 | from lp.buildmaster.model.builder import specific_job_classes | 19 | from lp.buildmaster.model.builder import specific_job_classes |
445 | 21 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob | 20 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
446 | 22 | from lp.services.job.model.job import Job | 21 | from lp.services.job.model.job import Job |
447 | 23 | from lp.soyuz.interfaces.archive import ArchivePurpose | 22 | from lp.soyuz.interfaces.archive import ArchivePurpose |
448 | 24 | from lp.soyuz.interfaces.build import BuildStatus | 23 | from lp.soyuz.interfaces.build import BuildStatus |
449 | 25 | from lp.soyuz.interfaces.buildqueue import IBuildQueueSet | 24 | from lp.soyuz.interfaces.buildqueue import IBuildQueueSet |
451 | 26 | from lp.soyuz.model.buildqueue import BuildQueue | 25 | from lp.soyuz.model.buildqueue import BuildQueue, get_builder_data |
452 | 27 | from lp.soyuz.model.processor import ProcessorFamilySet | 26 | from lp.soyuz.model.processor import ProcessorFamilySet |
453 | 28 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus | 27 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
454 | 29 | from lp.soyuz.model.build import Build | 28 | from lp.soyuz.model.build import Build |
455 | @@ -60,7 +59,9 @@ | |||
456 | 60 | builder = None | 59 | builder = None |
457 | 61 | builders = test.builders.get(builder_key(bq), []) | 60 | builders = test.builders.get(builder_key(bq), []) |
458 | 62 | try: | 61 | try: |
460 | 63 | builder = builders[n-1] | 62 | for builder in builders[n-1:]: |
461 | 63 | if builder.builderok: | ||
462 | 64 | break | ||
463 | 64 | except IndexError: | 65 | except IndexError: |
464 | 65 | pass | 66 | pass |
465 | 66 | return builder | 67 | return builder |
466 | @@ -95,18 +96,13 @@ | |||
467 | 95 | queue_entry.lastscore) | 96 | queue_entry.lastscore) |
468 | 96 | 97 | ||
469 | 97 | 98 | ||
472 | 98 | def check_mintime_to_builder( | 99 | def check_mintime_to_builder(test, bq, min_time): |
471 | 99 | test, bq, head_job_processor, head_job_virtualized, min_time): | ||
473 | 100 | """Test the estimated time until a builder becomes available.""" | 100 | """Test the estimated time until a builder becomes available.""" |
483 | 101 | delay = bq._estimateTimeToNextBuilder( | 101 | delay = bq._estimateTimeToNextBuilder() |
484 | 102 | head_job_processor, head_job_virtualized) | 102 | test.assertTrue( |
485 | 103 | if min_time is not None: | 103 | almost_equal(delay, min_time), |
486 | 104 | test.assertTrue( | 104 | "Wrong min time to next available builder (%s != %s)" |
487 | 105 | almost_equal(delay, min_time), | 105 | % (delay, min_time)) |
479 | 106 | "Wrong min time to next available builder (%s != %s)" | ||
480 | 107 | % (delay, min_time)) | ||
481 | 108 | else: | ||
482 | 109 | test.assertTrue(delay is None, "No delay to next builder available") | ||
488 | 110 | 106 | ||
489 | 111 | 107 | ||
490 | 112 | def almost_equal(a, b, deviation=1): | 108 | def almost_equal(a, b, deviation=1): |
491 | @@ -126,10 +122,46 @@ | |||
492 | 126 | 122 | ||
493 | 127 | def check_delay_for_job(test, the_job, delay): | 123 | def check_delay_for_job(test, the_job, delay): |
494 | 128 | # Obtain the builder statistics pertaining to this job. | 124 | # Obtain the builder statistics pertaining to this job. |
499 | 129 | builder_data = the_job._getBuilderData() | 125 | builder_data = get_builder_data() |
500 | 130 | builders_in_total, builders_for_job, builder_stats = builder_data | 126 | estimated_delay = the_job._estimateJobDelay(builder_data) |
501 | 131 | estimated_delay = the_job._estimateJobDelay(builder_stats) | 127 | test.assertEqual(delay, estimated_delay) |
502 | 132 | test.assertEqual(estimated_delay, delay) | 128 | |
503 | 129 | |||
504 | 130 | def total_builders(): | ||
505 | 131 | """How many available builders do we have in total?""" | ||
506 | 132 | builder_data = get_builder_data() | ||
507 | 133 | return builder_data[(None, False)] + builder_data[(None, True)] | ||
508 | 134 | |||
509 | 135 | |||
510 | 136 | def builders_for_job(job): | ||
511 | 137 | """How many available builders can run the given job?""" | ||
512 | 138 | builder_data = get_builder_data() | ||
513 | 139 | return builder_data[(getattr(job.processor, 'id', None), job.virtualized)] | ||
514 | 140 | |||
515 | 141 | |||
516 | 142 | def check_estimate(test, job, delay_in_seconds): | ||
517 | 143 | """Does the dispatch time estimate match the expectation?""" | ||
518 | 144 | estimate = job.getEstimatedJobStartTime() | ||
519 | 145 | if delay_in_seconds is None: | ||
520 | 146 | test.assertEquals( | ||
521 | 147 | delay_in_seconds, estimate, | ||
522 | 148 | "An estimate should not be possible at present but one was " | ||
523 | 149 | "returned (%s) nevertheless." % estimate) | ||
524 | 150 | else: | ||
525 | 151 | estimate -= datetime.utcnow() | ||
526 | 152 | test.assertTrue( | ||
527 | 153 | almost_equal(estimate.seconds, delay_in_seconds), | ||
528 | 154 | "The estimated delay (%s) deviates from the expected one (%s)" % | ||
529 | 155 | (estimate.seconds, delay_in_seconds)) | ||
530 | 156 | |||
531 | 157 | |||
532 | 158 | def disable_builders(test, processor, virtualized): | ||
533 | 159 | """Disable bulders with the given processor and virtualization setting.""" | ||
534 | 160 | if processor is not None: | ||
535 | 161 | processor_fam = ProcessorFamilySet().getByName(processor) | ||
536 | 162 | processor = processor_fam.processors[0].id | ||
537 | 163 | for builder in test.builders[(processor, virtualized)]: | ||
538 | 164 | builder.builderok = False | ||
539 | 133 | 165 | ||
540 | 134 | 166 | ||
541 | 135 | class TestBuildQueueSet(TestCaseWithFactory): | 167 | class TestBuildQueueSet(TestCaseWithFactory): |
542 | @@ -177,55 +209,55 @@ | |||
543 | 177 | 209 | ||
544 | 178 | # Next make seven 'hppa' builders. | 210 | # Next make seven 'hppa' builders. |
545 | 179 | processor_fam = ProcessorFamilySet().getByName('hppa') | 211 | processor_fam = ProcessorFamilySet().getByName('hppa') |
547 | 180 | hppa_proc = processor_fam.processors[0] | 212 | self.hppa_proc = processor_fam.processors[0] |
548 | 181 | self.h1 = self.factory.makeBuilder( | 213 | self.h1 = self.factory.makeBuilder( |
550 | 182 | name='hppa-v-1', processor=hppa_proc) | 214 | name='hppa-v-1', processor=self.hppa_proc) |
551 | 183 | self.h2 = self.factory.makeBuilder( | 215 | self.h2 = self.factory.makeBuilder( |
553 | 184 | name='hppa-v-2', processor=hppa_proc) | 216 | name='hppa-v-2', processor=self.hppa_proc) |
554 | 185 | self.h3 = self.factory.makeBuilder( | 217 | self.h3 = self.factory.makeBuilder( |
556 | 186 | name='hppa-v-3', processor=hppa_proc) | 218 | name='hppa-v-3', processor=self.hppa_proc) |
557 | 187 | self.h4 = self.factory.makeBuilder( | 219 | self.h4 = self.factory.makeBuilder( |
559 | 188 | name='hppa-v-4', processor=hppa_proc) | 220 | name='hppa-v-4', processor=self.hppa_proc) |
560 | 189 | self.h5 = self.factory.makeBuilder( | 221 | self.h5 = self.factory.makeBuilder( |
562 | 190 | name='hppa-n-5', processor=hppa_proc, virtualized=False) | 222 | name='hppa-n-5', processor=self.hppa_proc, virtualized=False) |
563 | 191 | self.h6 = self.factory.makeBuilder( | 223 | self.h6 = self.factory.makeBuilder( |
565 | 192 | name='hppa-n-6', processor=hppa_proc, virtualized=False) | 224 | name='hppa-n-6', processor=self.hppa_proc, virtualized=False) |
566 | 193 | self.h7 = self.factory.makeBuilder( | 225 | self.h7 = self.factory.makeBuilder( |
568 | 194 | name='hppa-n-7', processor=hppa_proc, virtualized=False) | 226 | name='hppa-n-7', processor=self.hppa_proc, virtualized=False) |
569 | 195 | 227 | ||
570 | 196 | # Finally make five 'amd64' builders. | 228 | # Finally make five 'amd64' builders. |
571 | 197 | processor_fam = ProcessorFamilySet().getByName('amd64') | 229 | processor_fam = ProcessorFamilySet().getByName('amd64') |
573 | 198 | amd_proc = processor_fam.processors[0] | 230 | self.amd_proc = processor_fam.processors[0] |
574 | 199 | self.a1 = self.factory.makeBuilder( | 231 | self.a1 = self.factory.makeBuilder( |
576 | 200 | name='amd64-v-1', processor=amd_proc) | 232 | name='amd64-v-1', processor=self.amd_proc) |
577 | 201 | self.a2 = self.factory.makeBuilder( | 233 | self.a2 = self.factory.makeBuilder( |
579 | 202 | name='amd64-v-2', processor=amd_proc) | 234 | name='amd64-v-2', processor=self.amd_proc) |
580 | 203 | self.a3 = self.factory.makeBuilder( | 235 | self.a3 = self.factory.makeBuilder( |
582 | 204 | name='amd64-v-3', processor=amd_proc) | 236 | name='amd64-v-3', processor=self.amd_proc) |
583 | 205 | self.a4 = self.factory.makeBuilder( | 237 | self.a4 = self.factory.makeBuilder( |
585 | 206 | name='amd64-n-4', processor=amd_proc, virtualized=False) | 238 | name='amd64-n-4', processor=self.amd_proc, virtualized=False) |
586 | 207 | self.a5 = self.factory.makeBuilder( | 239 | self.a5 = self.factory.makeBuilder( |
588 | 208 | name='amd64-n-5', processor=amd_proc, virtualized=False) | 240 | name='amd64-n-5', processor=self.amd_proc, virtualized=False) |
589 | 209 | 241 | ||
590 | 210 | self.builders = dict() | 242 | self.builders = dict() |
591 | 211 | processor_fam = ProcessorFamilySet().getByName('x86') | 243 | processor_fam = ProcessorFamilySet().getByName('x86') |
593 | 212 | x86_proc = processor_fam.processors[0] | 244 | self.x86_proc = processor_fam.processors[0] |
594 | 213 | # x86 native | 245 | # x86 native |
596 | 214 | self.builders[(x86_proc.id, False)] = [ | 246 | self.builders[(self.x86_proc.id, False)] = [ |
597 | 215 | self.i6, self.i7, self.i8, self.i9] | 247 | self.i6, self.i7, self.i8, self.i9] |
598 | 216 | # x86 virtual | 248 | # x86 virtual |
600 | 217 | self.builders[(x86_proc.id, True)] = [ | 249 | self.builders[(self.x86_proc.id, True)] = [ |
601 | 218 | self.i1, self.i2, self.i3, self.i4, self.i5] | 250 | self.i1, self.i2, self.i3, self.i4, self.i5] |
602 | 219 | 251 | ||
603 | 220 | # amd64 native | 252 | # amd64 native |
605 | 221 | self.builders[(amd_proc.id, False)] = [self.a4, self.a5] | 253 | self.builders[(self.amd_proc.id, False)] = [self.a4, self.a5] |
606 | 222 | # amd64 virtual | 254 | # amd64 virtual |
608 | 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] |
609 | 224 | 256 | ||
610 | 225 | # hppa native | 257 | # hppa native |
612 | 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] |
613 | 227 | # hppa virtual | 259 | # hppa virtual |
615 | 228 | self.builders[(hppa_proc.id, True)] = [ | 260 | self.builders[(self.hppa_proc.id, True)] = [ |
616 | 229 | self.h1, self.h2, self.h3, self.h4] | 261 | self.h1, self.h2, self.h3, self.h4] |
617 | 230 | 262 | ||
618 | 231 | # Ensure all builders are operational. | 263 | # Ensure all builders are operational. |
619 | @@ -237,20 +269,20 @@ | |||
620 | 237 | # Native builders irrespective of processor. | 269 | # Native builders irrespective of processor. |
621 | 238 | self.builders[(None, False)] = [] | 270 | self.builders[(None, False)] = [] |
622 | 239 | self.builders[(None, False)].extend( | 271 | self.builders[(None, False)].extend( |
628 | 240 | self.builders[(x86_proc.id, False)]) | 272 | self.builders[(self.x86_proc.id, False)]) |
629 | 241 | self.builders[(None, False)].extend( | 273 | self.builders[(None, False)].extend( |
630 | 242 | self.builders[(amd_proc.id, False)]) | 274 | self.builders[(self.amd_proc.id, False)]) |
631 | 243 | self.builders[(None, False)].extend( | 275 | self.builders[(None, False)].extend( |
632 | 244 | self.builders[(hppa_proc.id, False)]) | 276 | self.builders[(self.hppa_proc.id, False)]) |
633 | 245 | 277 | ||
634 | 246 | # Virtual builders irrespective of processor. | 278 | # Virtual builders irrespective of processor. |
635 | 247 | self.builders[(None, True)] = [] | 279 | self.builders[(None, True)] = [] |
636 | 248 | self.builders[(None, True)].extend( | 280 | self.builders[(None, True)].extend( |
642 | 249 | self.builders[(x86_proc.id, True)]) | 281 | self.builders[(self.x86_proc.id, True)]) |
643 | 250 | self.builders[(None, True)].extend( | 282 | self.builders[(None, True)].extend( |
644 | 251 | self.builders[(amd_proc.id, True)]) | 283 | self.builders[(self.amd_proc.id, True)]) |
645 | 252 | self.builders[(None, True)].extend( | 284 | self.builders[(None, True)].extend( |
646 | 253 | self.builders[(hppa_proc.id, True)]) | 285 | self.builders[(self.hppa_proc.id, True)]) |
647 | 254 | 286 | ||
648 | 255 | # Disable the sample data builders. | 287 | # Disable the sample data builders. |
649 | 256 | getUtility(IBuilderSet)['bob'].builderok = False | 288 | getUtility(IBuilderSet)['bob'].builderok = False |
650 | @@ -343,130 +375,112 @@ | |||
651 | 343 | # Make sure the builder numbers are correct. The builder data will | 375 | # Make sure the builder numbers are correct. The builder data will |
652 | 344 | # be the same for all of our builds. | 376 | # be the same for all of our builds. |
653 | 345 | bq = self.builds[0].buildqueue_record | 377 | bq = self.builds[0].buildqueue_record |
660 | 346 | builder_data = bq._getBuilderData() | 378 | self.assertEqual( |
661 | 347 | builders_in_total, builders_for_job, builder_stats = builder_data | 379 | 21, total_builders(), |
662 | 348 | self.assertEqual( | 380 | "The total number of builders is wrong.") |
663 | 349 | builders_in_total, 21, "The total number of builders is wrong.") | 381 | self.assertEqual( |
664 | 350 | self.assertEqual( | 382 | 4, builders_for_job(bq), |
659 | 351 | builders_for_job, 4, | ||
665 | 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 " |
666 | 353 | "question is wrong.") | 384 | "question is wrong.") |
669 | 354 | processor_fam = ProcessorFamilySet().getByName('x86') | 385 | builder_stats = get_builder_data() |
668 | 355 | x86_proc = processor_fam.processors[0] | ||
670 | 356 | self.assertEqual( | 386 | self.assertEqual( |
672 | 357 | builder_stats[(x86_proc.id, False)], 4, | 387 | 4, builder_stats[(self.x86_proc.id, False)], |
673 | 358 | "The number of native x86 builders is wrong") | 388 | "The number of native x86 builders is wrong") |
674 | 359 | self.assertEqual( | 389 | self.assertEqual( |
676 | 360 | builder_stats[(x86_proc.id, True)], 5, | 390 | 5, builder_stats[(self.x86_proc.id, True)], |
677 | 361 | "The number of virtual x86 builders is wrong") | 391 | "The number of virtual x86 builders is wrong") |
678 | 362 | processor_fam = ProcessorFamilySet().getByName('amd64') | ||
679 | 363 | amd_proc = processor_fam.processors[0] | ||
680 | 364 | self.assertEqual( | 392 | self.assertEqual( |
682 | 365 | builder_stats[(amd_proc.id, False)], 2, | 393 | 2, builder_stats[(self.amd_proc.id, False)], |
683 | 366 | "The number of native amd64 builders is wrong") | 394 | "The number of native amd64 builders is wrong") |
684 | 367 | self.assertEqual( | 395 | self.assertEqual( |
686 | 368 | builder_stats[(amd_proc.id, True)], 3, | 396 | 3, builder_stats[(self.amd_proc.id, True)], |
687 | 369 | "The number of virtual amd64 builders is wrong") | 397 | "The number of virtual amd64 builders is wrong") |
688 | 370 | processor_fam = ProcessorFamilySet().getByName('hppa') | ||
689 | 371 | hppa_proc = processor_fam.processors[0] | ||
690 | 372 | self.assertEqual( | 398 | self.assertEqual( |
692 | 373 | builder_stats[(hppa_proc.id, False)], 3, | 399 | 3, builder_stats[(self.hppa_proc.id, False)], |
693 | 374 | "The number of native hppa builders is wrong") | 400 | "The number of native hppa builders is wrong") |
694 | 375 | self.assertEqual( | 401 | self.assertEqual( |
696 | 376 | builder_stats[(hppa_proc.id, True)], 4, | 402 | 4, builder_stats[(self.hppa_proc.id, True)], |
697 | 377 | "The number of virtual hppa builders is wrong") | 403 | "The number of virtual hppa builders is wrong") |
698 | 378 | self.assertEqual( | 404 | self.assertEqual( |
700 | 379 | builder_stats[(None, False)], 9, | 405 | 9, builder_stats[(None, False)], |
701 | 380 | "The number of *virtual* builders across all processors is wrong") | 406 | "The number of *virtual* builders across all processors is wrong") |
702 | 381 | self.assertEqual( | 407 | self.assertEqual( |
704 | 382 | builder_stats[(None, True)], 12, | 408 | 12, builder_stats[(None, True)], |
705 | 383 | "The number of *native* builders across all processors is wrong") | 409 | "The number of *native* builders across all processors is wrong") |
706 | 384 | # Disable the native x86 builders. | 410 | # Disable the native x86 builders. |
708 | 385 | for builder in self.builders[(x86_proc.id, False)]: | 411 | for builder in self.builders[(self.x86_proc.id, False)]: |
709 | 386 | builder.builderok = False | 412 | builder.builderok = False |
710 | 387 | # Get the builder statistics again. | ||
711 | 388 | builder_data = bq._getBuilderData() | ||
712 | 389 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
713 | 390 | # Since all native x86 builders were disabled there are none left | 413 | # Since all native x86 builders were disabled there are none left |
714 | 391 | # to build the job. | 414 | # to build the job. |
715 | 392 | self.assertEqual( | 415 | self.assertEqual( |
717 | 393 | builders_for_job, 0, | 416 | 0, builders_for_job(bq), |
718 | 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 " |
719 | 395 | "question is wrong.") | 418 | "question is wrong.") |
720 | 396 | # Re-enable one of them. | 419 | # Re-enable one of them. |
722 | 397 | for builder in self.builders[(x86_proc.id, False)]: | 420 | for builder in self.builders[(self.x86_proc.id, False)]: |
723 | 398 | builder.builderok = True | 421 | builder.builderok = True |
724 | 399 | break | 422 | break |
725 | 400 | # Get the builder statistics again. | ||
726 | 401 | builder_data = bq._getBuilderData() | ||
727 | 402 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
728 | 403 | # Now there should be one builder available to build the job. | 423 | # Now there should be one builder available to build the job. |
729 | 404 | self.assertEqual( | 424 | self.assertEqual( |
731 | 405 | builders_for_job, 1, | 425 | 1, builders_for_job(bq), |
732 | 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 " |
733 | 407 | "question is wrong.") | 427 | "question is wrong.") |
734 | 408 | # Disable the *virtual* x86 builders -- should not make any | 428 | # Disable the *virtual* x86 builders -- should not make any |
735 | 409 | # difference. | 429 | # difference. |
737 | 410 | for builder in self.builders[(x86_proc.id, True)]: | 430 | for builder in self.builders[(self.x86_proc.id, True)]: |
738 | 411 | builder.builderok = False | 431 | builder.builderok = False |
739 | 412 | # Get the builder statistics again. | ||
740 | 413 | builder_data = bq._getBuilderData() | ||
741 | 414 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
742 | 415 | # There should still be one builder available to build the job. | 432 | # There should still be one builder available to build the job. |
743 | 416 | self.assertEqual( | 433 | self.assertEqual( |
745 | 417 | builders_for_job, 1, | 434 | 1, builders_for_job(bq), |
746 | 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 " |
747 | 419 | "question is wrong.") | 436 | "question is wrong.") |
748 | 420 | 437 | ||
749 | 421 | def test_free_builder_counts(self): | 438 | def test_free_builder_counts(self): |
750 | 422 | # Make sure the builder numbers are correct. The builder data will | 439 | # Make sure the builder numbers are correct. The builder data will |
751 | 423 | # be the same for all of our builds. | 440 | # be the same for all of our builds. |
752 | 424 | processor_fam = ProcessorFamilySet().getByName('x86') | ||
753 | 425 | proc_386 = processor_fam.processors[0] | ||
754 | 426 | build = self.builds[0] | 441 | build = self.builds[0] |
755 | 427 | # The build in question is an x86/native one. | 442 | # The build in question is an x86/native one. |
758 | 428 | self.assertEqual(build.processor.id, proc_386.id) | 443 | self.assertEqual(self.x86_proc.id, build.processor.id) |
759 | 429 | self.assertEqual(build.is_virtualized, False) | 444 | self.assertEqual(False, build.is_virtualized) |
760 | 430 | bq = build.buildqueue_record | 445 | bq = build.buildqueue_record |
763 | 431 | builder_data = bq._getBuilderData() | 446 | builder_stats = get_builder_data() |
762 | 432 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
764 | 433 | # We have 4 x86 native builders. | 447 | # We have 4 x86 native builders. |
765 | 434 | self.assertEqual( | 448 | self.assertEqual( |
767 | 435 | builder_stats[(proc_386.id, False)], 4, | 449 | 4, builder_stats[(self.x86_proc.id, False)], |
768 | 436 | "The number of native x86 builders is wrong") | 450 | "The number of native x86 builders is wrong") |
769 | 437 | # Initially all 4 builders are free. | 451 | # Initially all 4 builders are free. |
771 | 438 | free_count = bq._freeBuildersCount( | 452 | free_count = bq._getFreeBuildersCount( |
772 | 439 | build.processor, build.is_virtualized) | 453 | build.processor, build.is_virtualized) |
774 | 440 | self.assertEqual(free_count, 4) | 454 | self.assertEqual(4, free_count) |
775 | 441 | # Once we assign a build to one of them we should see the free | 455 | # Once we assign a build to one of them we should see the free |
776 | 442 | # builders count drop by one. | 456 | # builders count drop by one. |
777 | 443 | assign_to_builder(self, 'postgres', 1) | 457 | assign_to_builder(self, 'postgres', 1) |
779 | 444 | free_count = bq._freeBuildersCount( | 458 | free_count = bq._getFreeBuildersCount( |
780 | 445 | build.processor, build.is_virtualized) | 459 | build.processor, build.is_virtualized) |
782 | 446 | self.assertEqual(free_count, 3) | 460 | self.assertEqual(3, free_count) |
783 | 447 | # When we assign another build to one of them we should see the free | 461 | # When we assign another build to one of them we should see the free |
784 | 448 | # builders count drop by one again. | 462 | # builders count drop by one again. |
785 | 449 | assign_to_builder(self, 'gcc', 2) | 463 | assign_to_builder(self, 'gcc', 2) |
787 | 450 | free_count = bq._freeBuildersCount( | 464 | free_count = bq._getFreeBuildersCount( |
788 | 451 | build.processor, build.is_virtualized) | 465 | build.processor, build.is_virtualized) |
790 | 452 | self.assertEqual(free_count, 2) | 466 | self.assertEqual(2, free_count) |
791 | 453 | # Let's use up another builder. | 467 | # Let's use up another builder. |
792 | 454 | assign_to_builder(self, 'apg', 3) | 468 | assign_to_builder(self, 'apg', 3) |
794 | 455 | free_count = bq._freeBuildersCount( | 469 | free_count = bq._getFreeBuildersCount( |
795 | 456 | build.processor, build.is_virtualized) | 470 | build.processor, build.is_virtualized) |
797 | 457 | self.assertEqual(free_count, 1) | 471 | self.assertEqual(1, free_count) |
798 | 458 | # And now for the last one. | 472 | # And now for the last one. |
799 | 459 | assign_to_builder(self, 'flex', 4) | 473 | assign_to_builder(self, 'flex', 4) |
801 | 460 | free_count = bq._freeBuildersCount( | 474 | free_count = bq._getFreeBuildersCount( |
802 | 461 | build.processor, build.is_virtualized) | 475 | build.processor, build.is_virtualized) |
804 | 462 | self.assertEqual(free_count, 0) | 476 | self.assertEqual(0, free_count) |
805 | 463 | # If we reset the 'flex' build the builder that was assigned to it | 477 | # If we reset the 'flex' build the builder that was assigned to it |
806 | 464 | # will be free again. | 478 | # will be free again. |
807 | 465 | build, bq = find_job(self, 'flex') | 479 | build, bq = find_job(self, 'flex') |
808 | 466 | bq.reset() | 480 | bq.reset() |
810 | 467 | free_count = bq._freeBuildersCount( | 481 | free_count = bq._getFreeBuildersCount( |
811 | 468 | build.processor, build.is_virtualized) | 482 | build.processor, build.is_virtualized) |
813 | 469 | self.assertEqual(free_count, 1) | 483 | self.assertEqual(1, free_count) |
814 | 470 | 484 | ||
815 | 471 | 485 | ||
816 | 472 | class TestMinTimeToNextBuilder(SingleArchBuildsBase): | 486 | class TestMinTimeToNextBuilder(SingleArchBuildsBase): |
817 | @@ -491,38 +505,36 @@ | |||
818 | 491 | # | 505 | # |
819 | 492 | # p=processor, v=virtualized, e=estimated_duration, s=score | 506 | # p=processor, v=virtualized, e=estimated_duration, s=score |
820 | 493 | 507 | ||
821 | 494 | processor_fam = ProcessorFamilySet().getByName('x86') | ||
822 | 495 | x86_proc = processor_fam.processors[0] | ||
823 | 496 | # This will be the job of interest. | 508 | # This will be the job of interest. |
824 | 497 | apg_build, apg_job = find_job(self, 'apg') | 509 | apg_build, apg_job = find_job(self, 'apg') |
825 | 498 | # One of four builders for the 'apg' build is immediately available. | 510 | # One of four builders for the 'apg' build is immediately available. |
827 | 499 | check_mintime_to_builder(self, apg_job, x86_proc, False, 0) | 511 | check_mintime_to_builder(self, apg_job, 0) |
828 | 500 | 512 | ||
829 | 501 | # Assign the postgres job to a builder. | 513 | # Assign the postgres job to a builder. |
830 | 502 | assign_to_builder(self, 'postgres', 1) | 514 | assign_to_builder(self, 'postgres', 1) |
831 | 503 | # Now one builder is gone. But there should still be a builder | 515 | # Now one builder is gone. But there should still be a builder |
832 | 504 | # immediately available. | 516 | # immediately available. |
834 | 505 | check_mintime_to_builder(self, apg_job, x86_proc, False, 0) | 517 | check_mintime_to_builder(self, apg_job, 0) |
835 | 506 | 518 | ||
836 | 507 | assign_to_builder(self, 'flex', 2) | 519 | assign_to_builder(self, 'flex', 2) |
838 | 508 | check_mintime_to_builder(self, apg_job, x86_proc, False, 0) | 520 | check_mintime_to_builder(self, apg_job, 0) |
839 | 509 | 521 | ||
840 | 510 | assign_to_builder(self, 'bison', 3) | 522 | assign_to_builder(self, 'bison', 3) |
842 | 511 | check_mintime_to_builder(self, apg_job, x86_proc, False, 0) | 523 | check_mintime_to_builder(self, apg_job, 0) |
843 | 512 | 524 | ||
844 | 513 | assign_to_builder(self, 'gcc', 4) | 525 | assign_to_builder(self, 'gcc', 4) |
845 | 514 | # Now that no builder is immediately available, the shortest | 526 | # Now that no builder is immediately available, the shortest |
846 | 515 | # remaing build time (based on the estimated duration) is returned: | 527 | # remaing build time (based on the estimated duration) is returned: |
847 | 516 | # 300 seconds | 528 | # 300 seconds |
848 | 517 | # This is equivalent to the 'gcc' job's estimated duration. | 529 | # This is equivalent to the 'gcc' job's estimated duration. |
850 | 518 | check_mintime_to_builder(self, apg_job, x86_proc, False, 300) | 530 | check_mintime_to_builder(self, apg_job, 300) |
851 | 519 | 531 | ||
852 | 520 | # Now we pretend that the 'postgres' started 6 minutes ago. Its | 532 | # Now we pretend that the 'postgres' started 6 minutes ago. Its |
853 | 521 | # remaining execution time should be 2 minutes = 120 seconds and | 533 | # remaining execution time should be 2 minutes = 120 seconds and |
854 | 522 | # it now becomes the job whose builder becomes available next. | 534 | # it now becomes the job whose builder becomes available next. |
855 | 523 | build, bq = find_job(self, 'postgres') | 535 | build, bq = find_job(self, 'postgres') |
856 | 524 | set_remaining_time_for_running_job(bq, 120) | 536 | set_remaining_time_for_running_job(bq, 120) |
858 | 525 | check_mintime_to_builder(self, apg_job, x86_proc, False, 120) | 537 | check_mintime_to_builder(self, apg_job, 120) |
859 | 526 | 538 | ||
860 | 527 | # What happens when jobs overdraw the estimated duration? Let's | 539 | # What happens when jobs overdraw the estimated duration? Let's |
861 | 528 | # pretend the 'flex' job started 8 minutes ago. | 540 | # pretend the 'flex' job started 8 minutes ago. |
862 | @@ -530,22 +542,40 @@ | |||
863 | 530 | set_remaining_time_for_running_job(bq, -60) | 542 | set_remaining_time_for_running_job(bq, -60) |
864 | 531 | # In such a case we assume that the job will complete within 2 | 543 | # In such a case we assume that the job will complete within 2 |
865 | 532 | # minutes, this is a guess that has worked well so far. | 544 | # minutes, this is a guess that has worked well so far. |
867 | 533 | check_mintime_to_builder(self, apg_job, x86_proc, False, 120) | 545 | check_mintime_to_builder(self, apg_job, 120) |
868 | 534 | 546 | ||
869 | 535 | # If there's a job that will complete within a shorter time then | 547 | # If there's a job that will complete within a shorter time then |
870 | 536 | # we expect to be given that time frame. | 548 | # we expect to be given that time frame. |
871 | 537 | build, bq = find_job(self, 'postgres') | 549 | build, bq = find_job(self, 'postgres') |
872 | 538 | set_remaining_time_for_running_job(bq, 30) | 550 | set_remaining_time_for_running_job(bq, 30) |
874 | 539 | check_mintime_to_builder(self, apg_job, x86_proc, False, 30) | 551 | check_mintime_to_builder(self, apg_job, 30) |
875 | 540 | 552 | ||
876 | 541 | # Disable the native x86 builders. | 553 | # Disable the native x86 builders. |
884 | 542 | for builder in self.builders[(x86_proc.id, False)]: | 554 | for builder in self.builders[(self.x86_proc.id, False)]: |
885 | 543 | builder.builderok = False | 555 | builder.builderok = False |
886 | 544 | 556 | ||
887 | 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. |
888 | 546 | # this is indicated by a None value. | 558 | self.assertEquals(0, builders_for_job(apg_job)) |
889 | 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 |
890 | 548 | 560 | # though. | |
891 | 561 | check_mintime_to_builder(self, apg_job, 0) | ||
892 | 562 | |||
893 | 563 | # The following job can only run on a native builder. | ||
894 | 564 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
895 | 565 | estimated_duration=111, sourcename='xxr-gftp', score=1055, | ||
896 | 566 | virtualized=False) | ||
897 | 567 | self.builds.append(job.specific_job.build) | ||
898 | 568 | |||
899 | 569 | # Disable all native builders. | ||
900 | 570 | for builder in self.builders[(None, False)]: | ||
901 | 571 | builder.builderok = False | ||
902 | 572 | |||
903 | 573 | # All native builders are disabled now. No builders capable of | ||
904 | 574 | # running the job at hand are available. | ||
905 | 575 | self.assertEquals(0, builders_for_job(job)) | ||
906 | 576 | # The "minimum time to builder" estimation logic is not aware of the | ||
907 | 577 | # fact that no builders capable of running the job are available. | ||
908 | 578 | check_mintime_to_builder(self, job, 0) | ||
909 | 549 | 579 | ||
910 | 550 | class MultiArchBuildsBase(TestBuildQueueBase): | 580 | class MultiArchBuildsBase(TestBuildQueueBase): |
911 | 551 | """Set up a test environment with builds and multiple processors.""" | 581 | """Set up a test environment with builds and multiple processors.""" |
912 | @@ -646,35 +676,32 @@ | |||
913 | 646 | def test_min_time_to_next_builder(self): | 676 | def test_min_time_to_next_builder(self): |
914 | 647 | """When is the next builder capable of running the job at the head of | 677 | """When is the next builder capable of running the job at the head of |
915 | 648 | the queue becoming available?""" | 678 | the queue becoming available?""" |
916 | 649 | processor_fam = ProcessorFamilySet().getByName('hppa') | ||
917 | 650 | hppa_proc = processor_fam.processors[0] | ||
918 | 651 | |||
919 | 652 | # One of four builders for the 'apg' build is immediately available. | 679 | # One of four builders for the 'apg' build is immediately available. |
920 | 653 | apg_build, apg_job = find_job(self, 'apg', 'hppa') | 680 | apg_build, apg_job = find_job(self, 'apg', 'hppa') |
922 | 654 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 0) | 681 | check_mintime_to_builder(self, apg_job, 0) |
923 | 655 | 682 | ||
924 | 656 | # Assign the postgres job to a builder. | 683 | # Assign the postgres job to a builder. |
925 | 657 | assign_to_builder(self, 'postgres', 1, 'hppa') | 684 | assign_to_builder(self, 'postgres', 1, 'hppa') |
926 | 658 | # Now one builder is gone. But there should still be a builder | 685 | # Now one builder is gone. But there should still be a builder |
927 | 659 | # immediately available. | 686 | # immediately available. |
929 | 660 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 0) | 687 | check_mintime_to_builder(self, apg_job, 0) |
930 | 661 | 688 | ||
931 | 662 | assign_to_builder(self, 'flex', 2, 'hppa') | 689 | assign_to_builder(self, 'flex', 2, 'hppa') |
933 | 663 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 0) | 690 | check_mintime_to_builder(self, apg_job, 0) |
934 | 664 | 691 | ||
935 | 665 | assign_to_builder(self, 'bison', 3, 'hppa') | 692 | assign_to_builder(self, 'bison', 3, 'hppa') |
936 | 666 | # Now that no builder is immediately available, the shortest | 693 | # Now that no builder is immediately available, the shortest |
937 | 667 | # remaing build time (based on the estimated duration) is returned: | 694 | # remaing build time (based on the estimated duration) is returned: |
938 | 668 | # 660 seconds | 695 | # 660 seconds |
939 | 669 | # This is equivalent to the 'bison' job's estimated duration. | 696 | # This is equivalent to the 'bison' job's estimated duration. |
941 | 670 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 660) | 697 | check_mintime_to_builder(self, apg_job, 660) |
942 | 671 | 698 | ||
943 | 672 | # Now we pretend that the 'postgres' started 13 minutes ago. Its | 699 | # Now we pretend that the 'postgres' started 13 minutes ago. Its |
944 | 673 | # remaining execution time should be 2 minutes = 120 seconds and | 700 | # remaining execution time should be 2 minutes = 120 seconds and |
945 | 674 | # it now becomes the job whose builder becomes available next. | 701 | # it now becomes the job whose builder becomes available next. |
946 | 675 | build, bq = find_job(self, 'postgres', 'hppa') | 702 | build, bq = find_job(self, 'postgres', 'hppa') |
947 | 676 | set_remaining_time_for_running_job(bq, 120) | 703 | set_remaining_time_for_running_job(bq, 120) |
949 | 677 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 120) | 704 | check_mintime_to_builder(self, apg_job, 120) |
950 | 678 | 705 | ||
951 | 679 | # What happens when jobs overdraw the estimated duration? Let's | 706 | # What happens when jobs overdraw the estimated duration? Let's |
952 | 680 | # pretend the 'flex' job started 14 minutes ago. | 707 | # pretend the 'flex' job started 14 minutes ago. |
953 | @@ -682,30 +709,35 @@ | |||
954 | 682 | set_remaining_time_for_running_job(bq, -60) | 709 | set_remaining_time_for_running_job(bq, -60) |
955 | 683 | # In such a case we assume that the job will complete within 2 | 710 | # In such a case we assume that the job will complete within 2 |
956 | 684 | # minutes, this is a guess that has worked well so far. | 711 | # minutes, this is a guess that has worked well so far. |
958 | 685 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 120) | 712 | check_mintime_to_builder(self, apg_job, 120) |
959 | 686 | 713 | ||
960 | 687 | # If there's a job that will complete within a shorter time then | 714 | # If there's a job that will complete within a shorter time then |
961 | 688 | # we expect to be given that time frame. | 715 | # we expect to be given that time frame. |
962 | 689 | build, bq = find_job(self, 'postgres', 'hppa') | 716 | build, bq = find_job(self, 'postgres', 'hppa') |
963 | 690 | set_remaining_time_for_running_job(bq, 30) | 717 | set_remaining_time_for_running_job(bq, 30) |
965 | 691 | check_mintime_to_builder(self, apg_job, hppa_proc, False, 30) | 718 | check_mintime_to_builder(self, apg_job, 30) |
966 | 692 | 719 | ||
967 | 693 | # Disable the native hppa builders. | 720 | # Disable the native hppa builders. |
969 | 694 | for builder in self.builders[(hppa_proc.id, False)]: | 721 | for builder in self.builders[(self.hppa_proc.id, False)]: |
970 | 695 | builder.builderok = False | 722 | builder.builderok = False |
971 | 696 | 723 | ||
975 | 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. |
976 | 698 | # this is indicated by a None value. | 725 | self.assertEquals(0, builders_for_job(apg_job)) |
977 | 699 | check_mintime_to_builder(self, apg_job, hppa_proc, False, None) | 726 | check_mintime_to_builder(self, apg_job, 0) |
978 | 700 | 727 | ||
982 | 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. |
983 | 702 | # build queue is processor independent. In that case we'd ask for | 729 | job = self.factory.makeSourcePackageRecipeBuildJob( |
984 | 703 | # *any* next available builder. | 730 | virtualized=False, estimated_duration=22, |
985 | 731 | sourcename='my-recipe-digikam', score=9999) | ||
986 | 732 | # There are still builders available for the processor-independent | ||
987 | 733 | # job. | ||
988 | 734 | self.assertEquals(6, builders_for_job(job)) | ||
989 | 735 | # Even free ones. | ||
990 | 704 | self.assertTrue( | 736 | self.assertTrue( |
995 | 705 | bq._freeBuildersCount(None, None) > 0, | 737 | bq._getFreeBuildersCount(job.processor, job.virtualized) > 0, |
996 | 706 | "Builders are immediately available for jobs that don't care " | 738 | "Builders are immediately available for processor-independent " |
997 | 707 | "about processor architectures or virtualization") | 739 | "jobs.") |
998 | 708 | check_mintime_to_builder(self, apg_job, None, None, 0) | 740 | check_mintime_to_builder(self, job, 0) |
999 | 709 | 741 | ||
1000 | 710 | # Let's disable all builders. | 742 | # Let's disable all builders. |
1001 | 711 | for builders in self.builders.itervalues(): | 743 | for builders in self.builders.itervalues(): |
1002 | @@ -713,21 +745,20 @@ | |||
1003 | 713 | builder.builderok = False | 745 | builder.builderok = False |
1004 | 714 | 746 | ||
1005 | 715 | # There are no builders capable of running even the processor | 747 | # There are no builders capable of running even the processor |
1008 | 716 | # independent jobs now and that this is indicated by a None value. | 748 | # independent jobs now. |
1009 | 717 | check_mintime_to_builder(self, apg_job, None, None, None) | 749 | self.assertEquals(0, builders_for_job(job)) |
1010 | 750 | check_mintime_to_builder(self, job, 0) | ||
1011 | 718 | 751 | ||
1012 | 719 | # Re-enable the native hppa builders. | 752 | # Re-enable the native hppa builders. |
1014 | 720 | for builder in self.builders[(hppa_proc.id, False)]: | 753 | for builder in self.builders[(self.hppa_proc.id, False)]: |
1015 | 721 | builder.builderok = True | 754 | builder.builderok = True |
1016 | 722 | 755 | ||
1017 | 723 | # The builder that's becoming available next is the one that's | 756 | # The builder that's becoming available next is the one that's |
1018 | 724 | # running the 'postgres' build. | 757 | # running the 'postgres' build. |
1020 | 725 | check_mintime_to_builder(self, apg_job, None, None, 30) | 758 | check_mintime_to_builder(self, apg_job, 30) |
1021 | 726 | 759 | ||
1022 | 727 | # Make sure we'll find an x86 builder as well. | 760 | # Make sure we'll find an x86 builder as well. |
1026 | 728 | processor_fam = ProcessorFamilySet().getByName('x86') | 761 | builder = self.builders[(self.x86_proc.id, False)][0] |
1024 | 729 | x86_proc = processor_fam.processors[0] | ||
1025 | 730 | builder = self.builders[(x86_proc.id, False)][0] | ||
1027 | 731 | builder.builderok = True | 762 | builder.builderok = True |
1028 | 732 | 763 | ||
1029 | 733 | # Now this builder is the one that becomes available next (29 minutes | 764 | # Now this builder is the one that becomes available next (29 minutes |
1030 | @@ -736,14 +767,14 @@ | |||
1031 | 736 | build, bq = find_job(self, 'gcc', '386') | 767 | build, bq = find_job(self, 'gcc', '386') |
1032 | 737 | set_remaining_time_for_running_job(bq, 29) | 768 | set_remaining_time_for_running_job(bq, 29) |
1033 | 738 | 769 | ||
1035 | 739 | check_mintime_to_builder(self, apg_job, None, None, 29) | 770 | check_mintime_to_builder(self, apg_job, 29) |
1036 | 740 | 771 | ||
1037 | 741 | # Make a second, idle x86 builder available. | 772 | # Make a second, idle x86 builder available. |
1039 | 742 | builder = self.builders[(x86_proc.id, False)][1] | 773 | builder = self.builders[(self.x86_proc.id, False)][1] |
1040 | 743 | builder.builderok = True | 774 | builder.builderok = True |
1041 | 744 | 775 | ||
1042 | 745 | # That builder should be available immediately since it's idle. | 776 | # That builder should be available immediately since it's idle. |
1044 | 746 | check_mintime_to_builder(self, apg_job, None, None, 0) | 777 | check_mintime_to_builder(self, apg_job, 0) |
1045 | 747 | 778 | ||
1046 | 748 | 779 | ||
1047 | 749 | class TestJobClasses(TestCaseWithFactory): | 780 | class TestJobClasses(TestCaseWithFactory): |
1048 | @@ -781,20 +812,20 @@ | |||
1049 | 781 | 812 | ||
1050 | 782 | # This is a binary package build. | 813 | # This is a binary package build. |
1051 | 783 | self.assertEqual( | 814 | self.assertEqual( |
1053 | 784 | bq.job_type, BuildFarmJobType.PACKAGEBUILD, | 815 | BuildFarmJobType.PACKAGEBUILD, bq.job_type, |
1054 | 785 | "This is a binary package build") | 816 | "This is a binary package build") |
1055 | 786 | 817 | ||
1056 | 787 | # The class registered for 'PACKAGEBUILD' is `BuildPackageJob`. | 818 | # The class registered for 'PACKAGEBUILD' is `BuildPackageJob`. |
1057 | 788 | self.assertEqual( | 819 | self.assertEqual( |
1058 | 820 | BuildPackageJob, | ||
1059 | 789 | specific_job_classes()[BuildFarmJobType.PACKAGEBUILD], | 821 | specific_job_classes()[BuildFarmJobType.PACKAGEBUILD], |
1060 | 790 | BuildPackageJob, | ||
1061 | 791 | "The class registered for 'PACKAGEBUILD' is `BuildPackageJob`") | 822 | "The class registered for 'PACKAGEBUILD' is `BuildPackageJob`") |
1062 | 792 | 823 | ||
1063 | 793 | # The 'specific_job' object associated with this `BuildQueue` | 824 | # The 'specific_job' object associated with this `BuildQueue` |
1064 | 794 | # instance is of type `BuildPackageJob`. | 825 | # instance is of type `BuildPackageJob`. |
1065 | 795 | self.assertTrue(bq.specific_job is not None) | 826 | self.assertTrue(bq.specific_job is not None) |
1066 | 796 | self.assertEqual( | 827 | self.assertEqual( |
1068 | 797 | bq.specific_job.__class__, BuildPackageJob, | 828 | BuildPackageJob, bq.specific_job.__class__, |
1069 | 798 | "The 'specific_job' object associated with this `BuildQueue` " | 829 | "The 'specific_job' object associated with this `BuildQueue` " |
1070 | 799 | "instance is of type `BuildPackageJob`") | 830 | "instance is of type `BuildPackageJob`") |
1071 | 800 | 831 | ||
1072 | @@ -913,23 +944,14 @@ | |||
1073 | 913 | self.builds.append(job.specific_job.build) | 944 | self.builds.append(job.specific_job.build) |
1074 | 914 | 945 | ||
1075 | 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. |
1076 | 916 | processor_fam = ProcessorFamilySet().getByName('x86') | ||
1077 | 917 | x86_proc = processor_fam.processors[0] | ||
1078 | 918 | _apg_build, apg_job = find_job(self, 'apg', '386') | 947 | _apg_build, apg_job = find_job(self, 'apg', '386') |
1079 | 919 | apg_job.lastscore = 1024 | 948 | apg_job.lastscore = 1024 |
1080 | 920 | # print_build_setup(self.builds) | 949 | # print_build_setup(self.builds) |
1081 | 921 | 950 | ||
1082 | 922 | def test_job_delay_for_binary_builds(self): | 951 | def test_job_delay_for_binary_builds(self): |
1083 | 923 | processor_fam = ProcessorFamilySet().getByName('hppa') | ||
1084 | 924 | hppa_proc = processor_fam.processors[0] | ||
1085 | 925 | |||
1086 | 926 | # One of four builders for the 'flex' build is immediately available. | 952 | # One of four builders for the 'flex' build is immediately available. |
1087 | 927 | flex_build, flex_job = find_job(self, 'flex', 'hppa') | 953 | flex_build, flex_job = find_job(self, 'flex', 'hppa') |
1093 | 928 | check_mintime_to_builder(self, flex_job, hppa_proc, False, 0) | 954 | check_mintime_to_builder(self, flex_job, 0) |
1089 | 929 | |||
1090 | 930 | # Obtain the builder statistics pertaining to this job. | ||
1091 | 931 | builder_data = flex_job._getBuilderData() | ||
1092 | 932 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
1094 | 933 | 955 | ||
1095 | 934 | # The delay will be 900 (= 15*60) + 222 seconds | 956 | # The delay will be 900 (= 15*60) + 222 seconds |
1096 | 935 | check_delay_for_job(self, flex_job, 1122) | 957 | check_delay_for_job(self, flex_job, 1122) |
1097 | @@ -942,11 +964,8 @@ | |||
1098 | 942 | check_delay_for_job(self, flex_job, 222) | 964 | check_delay_for_job(self, flex_job, 222) |
1099 | 943 | 965 | ||
1100 | 944 | # How about some estimates for x86 builds? | 966 | # How about some estimates for x86 builds? |
1101 | 945 | processor_fam = ProcessorFamilySet().getByName('x86') | ||
1102 | 946 | x86_proc = processor_fam.processors[0] | ||
1103 | 947 | |||
1104 | 948 | _bison_build, bison_job = find_job(self, 'bison', '386') | 967 | _bison_build, bison_job = find_job(self, 'bison', '386') |
1106 | 949 | check_mintime_to_builder(self, bison_job, x86_proc, False, 0) | 968 | check_mintime_to_builder(self, bison_job, 0) |
1107 | 950 | # The delay will be 900 (= (14+16)*60/2) + 222 seconds. | 969 | # The delay will be 900 (= (14+16)*60/2) + 222 seconds. |
1108 | 951 | check_delay_for_job(self, bison_job, 1122) | 970 | check_delay_for_job(self, bison_job, 1122) |
1109 | 952 | 971 | ||
1110 | @@ -958,13 +977,13 @@ | |||
1111 | 958 | # Also, this tests that jobs with equal score but a lower 'job' value | 977 | # Also, this tests that jobs with equal score but a lower 'job' value |
1112 | 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). |
1113 | 960 | _vim_build, vim_job = find_job(self, 'vim', '386') | 979 | _vim_build, vim_job = find_job(self, 'vim', '386') |
1115 | 961 | check_mintime_to_builder(self, vim_job, x86_proc, False, 0) | 980 | check_mintime_to_builder(self, vim_job, 0) |
1116 | 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) |
1117 | 963 | # seconds. | 982 | # seconds. |
1118 | 964 | check_delay_for_job(self, vim_job, 992) | 983 | check_delay_for_job(self, vim_job, 992) |
1119 | 965 | 984 | ||
1120 | 966 | _gedit_build, gedit_job = find_job(self, 'gedit', '386') | 985 | _gedit_build, gedit_job = find_job(self, 'gedit', '386') |
1122 | 967 | check_mintime_to_builder(self, gedit_job, x86_proc, False, 0) | 986 | check_mintime_to_builder(self, gedit_job, 0) |
1123 | 968 | # The delay will be | 987 | # The delay will be |
1124 | 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) |
1125 | 970 | # seconds. | 989 | # seconds. |
1126 | @@ -973,11 +992,7 @@ | |||
1127 | 973 | def test_job_delay_for_recipe_builds(self): | 992 | def test_job_delay_for_recipe_builds(self): |
1128 | 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. |
1129 | 975 | bash_build, bash_job = find_job(self, 'xx-recipe-bash', None) | 994 | bash_build, bash_job = find_job(self, 'xx-recipe-bash', None) |
1135 | 976 | check_mintime_to_builder(self, bash_job, None, False, 0) | 995 | check_mintime_to_builder(self, bash_job, 0) |
1131 | 977 | |||
1132 | 978 | # Obtain the builder statistics pertaining to this job. | ||
1133 | 979 | builder_data = bash_job._getBuilderData() | ||
1134 | 980 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
1136 | 981 | 996 | ||
1137 | 982 | # The delay will be 960 + 780 + 222 = 1962, where | 997 | # The delay will be 960 + 780 + 222 = 1962, where |
1138 | 983 | # hppa job delays: 960 = (9+11+13+15)*60/3 | 998 | # hppa job delays: 960 = (9+11+13+15)*60/3 |
1139 | @@ -986,17 +1001,15 @@ | |||
1140 | 986 | 1001 | ||
1141 | 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. |
1142 | 988 | zsh_build, zsh_job = find_job(self, 'xx-recipe-zsh', None) | 1003 | zsh_build, zsh_job = find_job(self, 'xx-recipe-zsh', None) |
1148 | 989 | check_mintime_to_builder(self, zsh_job, None, False, 0) | 1004 | check_mintime_to_builder(self, zsh_job, 0) |
1144 | 990 | |||
1145 | 991 | # Obtain the builder statistics pertaining to this job. | ||
1146 | 992 | builder_data = zsh_job._getBuilderData() | ||
1147 | 993 | builders_in_total, builders_for_job, builder_stats = builder_data | ||
1149 | 994 | 1005 | ||
1150 | 995 | # The delay will be 0 since this is the head job. | 1006 | # The delay will be 0 since this is the head job. |
1151 | 996 | check_delay_for_job(self, zsh_job, 0) | 1007 | check_delay_for_job(self, zsh_job, 0) |
1152 | 997 | 1008 | ||
1153 | 998 | # Assign the zsh job to a builder. | 1009 | # Assign the zsh job to a builder. |
1154 | 1010 | self.assertEquals((None, False), bash_job._getHeadJobPlatform()) | ||
1155 | 999 | assign_to_builder(self, 'xx-recipe-zsh', 1, None) | 1011 | assign_to_builder(self, 'xx-recipe-zsh', 1, None) |
1156 | 1012 | self.assertEquals((1, False), bash_job._getHeadJobPlatform()) | ||
1157 | 1000 | 1013 | ||
1158 | 1001 | # Now that the highest-scored job is out of the way, the estimation | 1014 | # Now that the highest-scored job is out of the way, the estimation |
1159 | 1002 | # for the 'bash' recipe build is 222 seconds shorter. | 1015 | # for the 'bash' recipe build is 222 seconds shorter. |
1160 | @@ -1006,9 +1019,257 @@ | |||
1161 | 1006 | # 386 job delays: 780 = (10+12+14+16)*60/4 | 1019 | # 386 job delays: 780 = (10+12+14+16)*60/4 |
1162 | 1007 | check_delay_for_job(self, bash_job, 1740) | 1020 | check_delay_for_job(self, bash_job, 1740) |
1163 | 1008 | 1021 | ||
1164 | 1009 | processor_fam = ProcessorFamilySet().getByName('x86') | ||
1165 | 1010 | x86_proc = processor_fam.processors[0] | ||
1166 | 1011 | |||
1167 | 1012 | _postgres_build, postgres_job = find_job(self, 'postgres', '386') | 1022 | _postgres_build, postgres_job = find_job(self, 'postgres', '386') |
1168 | 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. |
1169 | 1014 | check_delay_for_job(self, postgres_job, 0) | 1024 | check_delay_for_job(self, postgres_job, 0) |
1170 | 1025 | # Also, the platform of the postgres job is returned since it *is* | ||
1171 | 1026 | # the head job now. | ||
1172 | 1027 | pg_platform = (postgres_job.processor.id, postgres_job.virtualized) | ||
1173 | 1028 | self.assertEquals(pg_platform, postgres_job._getHeadJobPlatform()) | ||
1174 | 1029 | |||
1175 | 1030 | def test_job_delay_for_unspecified_virtualization(self): | ||
1176 | 1031 | # Make sure that jobs with a NULL 'virtualized' flag get the same | ||
1177 | 1032 | # treatment as the ones with virtualized=TRUE. | ||
1178 | 1033 | # First toggle the 'virtualized' flag for all hppa jobs. | ||
1179 | 1034 | for build in self.builds: | ||
1180 | 1035 | bq = build.buildqueue_record | ||
1181 | 1036 | if bq.processor == self.hppa_proc: | ||
1182 | 1037 | bq.virtualized = True | ||
1183 | 1038 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1184 | 1039 | virtualized=True, estimated_duration=332, | ||
1185 | 1040 | sourcename='xxr-openssh-client', score=1050) | ||
1186 | 1041 | self.builds.append(job.specific_job.build) | ||
1187 | 1042 | # print_build_setup(self.builds) | ||
1188 | 1043 | # ... | ||
1189 | 1044 | # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039 | ||
1190 | 1045 | # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042 | ||
1191 | 1046 | # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045 | ||
1192 | 1047 | # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048 | ||
1193 | 1048 | # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050 | ||
1194 | 1049 | # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053 | ||
1195 | 1050 | |||
1196 | 1051 | flex_build, flex_job = find_job(self, 'flex', 'hppa') | ||
1197 | 1052 | # The head job platform is the one of job #21 (xxr-openssh-client). | ||
1198 | 1053 | self.assertEquals((None, True), flex_job._getHeadJobPlatform()) | ||
1199 | 1054 | # The delay will be 900 (= 15*60) + 332 seconds | ||
1200 | 1055 | check_delay_for_job(self, flex_job, 1232) | ||
1201 | 1056 | |||
1202 | 1057 | # Now add a job with a NULL 'virtualized' flag. It should be treated | ||
1203 | 1058 | # like jobs with virtualized=TRUE. | ||
1204 | 1059 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1205 | 1060 | estimated_duration=111, sourcename='xxr-gwibber', score=1051, | ||
1206 | 1061 | virtualized=None) | ||
1207 | 1062 | self.builds.append(job.specific_job.build) | ||
1208 | 1063 | # print_build_setup(self.builds) | ||
1209 | 1064 | self.assertEqual(None, job.virtualized) | ||
1210 | 1065 | # ... | ||
1211 | 1066 | # 15, flex, p: hppa, v: True e:0:13:00 *** s: 1039 | ||
1212 | 1067 | # 16, flex, p: 386, v:False e:0:14:00 *** s: 1042 | ||
1213 | 1068 | # 17, postgres, p: hppa, v: True e:0:15:00 *** s: 1045 | ||
1214 | 1069 | # 18, postgres, p: 386, v:False e:0:16:00 *** s: 1048 | ||
1215 | 1070 | # 21, xxr-openssh-client, p: None, v: True e:0:05:32 *** s: 1050 | ||
1216 | 1071 | # 22, xxr-gwibber, p: None, v: None e:0:01:51 *** s: 1051 | ||
1217 | 1072 | # 20, xx-recipe-zsh, p: None, v:False e:0:03:42 *** s: 1053 | ||
1218 | 1073 | |||
1219 | 1074 | # The newly added 'xxr-gwibber' job is the new head job now. | ||
1220 | 1075 | self.assertEquals((None, None), flex_job._getHeadJobPlatform()) | ||
1221 | 1076 | # The newly added 'xxr-gwibber' job now weighs in as well and the | ||
1222 | 1077 | # delay is 900 (= 15*60) + (332+111)/2 seconds | ||
1223 | 1078 | check_delay_for_job(self, flex_job, 1121) | ||
1224 | 1079 | |||
1225 | 1080 | # The '386' flex job does not care about the 'xxr-gwibber' and | ||
1226 | 1081 | # 'xxr-openssh-client' jobs since the 'virtualized' values do not | ||
1227 | 1082 | # match. | ||
1228 | 1083 | flex_build, flex_job = find_job(self, 'flex', '386') | ||
1229 | 1084 | self.assertEquals((None, False), flex_job._getHeadJobPlatform()) | ||
1230 | 1085 | # delay is 960 (= 16*60) + 222 seconds | ||
1231 | 1086 | check_delay_for_job(self, flex_job, 1182) | ||
1232 | 1087 | |||
1233 | 1088 | |||
1234 | 1089 | class TestJobDispatchTimeEstimation(MultiArchBuildsBase): | ||
1235 | 1090 | """Test estimated job delays with various processors.""" | ||
1236 | 1091 | score_increment = 2 | ||
1237 | 1092 | def setUp(self): | ||
1238 | 1093 | """Add more processor-independent jobs to the mix, make the '386' jobs | ||
1239 | 1094 | virtual. | ||
1240 | 1095 | |||
1241 | 1096 | 3, gedit, p: hppa, v:False e:0:01:00 *** s: 1003 | ||
1242 | 1097 | 4, gedit, p: 386, v: True e:0:02:00 *** s: 1006 | ||
1243 | 1098 | 5, firefox, p: hppa, v:False e:0:03:00 *** s: 1009 | ||
1244 | 1099 | 6, firefox, p: 386, v: True e:0:04:00 *** s: 1012 | ||
1245 | 1100 | 7, apg, p: hppa, v:False e:0:05:00 *** s: 1015 | ||
1246 | 1101 | 9, vim, p: hppa, v:False e:0:07:00 *** s: 1021 | ||
1247 | 1102 | 10, vim, p: 386, v: True e:0:08:00 *** s: 1024 | ||
1248 | 1103 | 8, apg, p: 386, v: True e:0:06:00 *** s: 1024 | ||
1249 | 1104 | 19, xxr-aptitude, p: None, v:False e:0:05:32 *** s: 1025 | ||
1250 | 1105 | 11, gcc, p: hppa, v:False e:0:09:00 *** s: 1027 | ||
1251 | 1106 | 12, gcc, p: 386, v: True e:0:10:00 *** s: 1030 | ||
1252 | 1107 | 13, bison, p: hppa, v:False e:0:11:00 *** s: 1033 | ||
1253 | 1108 | 14, bison, p: 386, v: True e:0:12:00 *** s: 1036 | ||
1254 | 1109 | 15, flex, p: hppa, v:False e:0:13:00 *** s: 1039 | ||
1255 | 1110 | 16, flex, p: 386, v: True e:0:14:00 *** s: 1042 | ||
1256 | 1111 | 23, xxr-apt-build, p: None, v: True e:0:12:56 *** s: 1043 | ||
1257 | 1112 | 22, xxr-cron-apt, p: None, v: True e:0:11:05 *** s: 1043 | ||
1258 | 1113 | 26, xxr-cupt, p: None, v: None e:0:18:30 *** s: 1044 | ||
1259 | 1114 | 25, xxr-apt, p: None, v: None e:0:16:38 *** s: 1044 | ||
1260 | 1115 | 24, xxr-debdelta, p: None, v: None e:0:14:47 *** s: 1044 | ||
1261 | 1116 | 17, postgres, p: hppa, v:False e:0:15:00 *** s: 1045 | ||
1262 | 1117 | 18, postgres, p: 386, v: True e:0:16:00 *** s: 1048 | ||
1263 | 1118 | 21, xxr-daptup, p: None, v: None e:0:09:14 *** s: 1051 | ||
1264 | 1119 | 20, xxr-auto-apt, p: None, v:False e:0:07:23 *** s: 1053 | ||
1265 | 1120 | |||
1266 | 1121 | p=processor, v=virtualized, e=estimated_duration, s=score | ||
1267 | 1122 | """ | ||
1268 | 1123 | super(TestJobDispatchTimeEstimation, self).setUp() | ||
1269 | 1124 | |||
1270 | 1125 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1271 | 1126 | virtualized=False, estimated_duration=332, | ||
1272 | 1127 | sourcename='xxr-aptitude', score=1025) | ||
1273 | 1128 | self.builds.append(job.specific_job.build) | ||
1274 | 1129 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1275 | 1130 | virtualized=False, estimated_duration=443, | ||
1276 | 1131 | sourcename='xxr-auto-apt', score=1053) | ||
1277 | 1132 | self.builds.append(job.specific_job.build) | ||
1278 | 1133 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1279 | 1134 | estimated_duration=554, sourcename='xxr-daptup', score=1051, | ||
1280 | 1135 | virtualized=None) | ||
1281 | 1136 | self.builds.append(job.specific_job.build) | ||
1282 | 1137 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1283 | 1138 | estimated_duration=665, sourcename='xxr-cron-apt', score=1043) | ||
1284 | 1139 | self.builds.append(job.specific_job.build) | ||
1285 | 1140 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1286 | 1141 | estimated_duration=776, sourcename='xxr-apt-build', score=1043) | ||
1287 | 1142 | self.builds.append(job.specific_job.build) | ||
1288 | 1143 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1289 | 1144 | estimated_duration=887, sourcename='xxr-debdelta', score=1044, | ||
1290 | 1145 | virtualized=None) | ||
1291 | 1146 | self.builds.append(job.specific_job.build) | ||
1292 | 1147 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1293 | 1148 | estimated_duration=998, sourcename='xxr-apt', score=1044, | ||
1294 | 1149 | virtualized=None) | ||
1295 | 1150 | self.builds.append(job.specific_job.build) | ||
1296 | 1151 | job = self.factory.makeSourcePackageRecipeBuildJob( | ||
1297 | 1152 | estimated_duration=1110, sourcename='xxr-cupt', score=1044, | ||
1298 | 1153 | virtualized=None) | ||
1299 | 1154 | self.builds.append(job.specific_job.build) | ||
1300 | 1155 | |||
1301 | 1156 | # Assign the same score to the '386' vim and apg build jobs. | ||
1302 | 1157 | _apg_build, apg_job = find_job(self, 'apg', '386') | ||
1303 | 1158 | apg_job.lastscore = 1024 | ||
1304 | 1159 | |||
1305 | 1160 | # Also, toggle the 'virtualized' flag for all '386' jobs. | ||
1306 | 1161 | for build in self.builds: | ||
1307 | 1162 | bq = build.buildqueue_record | ||
1308 | 1163 | if bq.processor == self.x86_proc: | ||
1309 | 1164 | bq.virtualized = True | ||
1310 | 1165 | |||
1311 | 1166 | def test_pending_jobs_only(self): | ||
1312 | 1167 | # Let's see the assertion fail for a job that's not pending any more. | ||
1313 | 1168 | assign_to_builder(self, 'gedit', 1, 'hppa') | ||
1314 | 1169 | gedit_build, gedit_job = find_job(self, 'gedit', 'hppa') | ||
1315 | 1170 | self.assertRaises(AssertionError, gedit_job.getEstimatedJobStartTime) | ||
1316 | 1171 | |||
1317 | 1172 | def test_estimation_binary_virtual(self): | ||
1318 | 1173 | gcc_build, gcc_job = find_job(self, 'gcc', '386') | ||
1319 | 1174 | # The delay of 1671 seconds is calculated as follows: | ||
1320 | 1175 | # 386 jobs: (12+14+16)*60/3 = 840 | ||
1321 | 1176 | # processor-independent jobs: | ||
1322 | 1177 | # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831 | ||
1323 | 1178 | check_estimate(self, gcc_job, 1671) | ||
1324 | 1179 | self.assertEquals(5, builders_for_job(gcc_job)) | ||
1325 | 1180 | |||
1326 | 1181 | def test_proc_indep_virtual_true(self): | ||
1327 | 1182 | xxr_build, xxr_job = find_job(self, 'xxr-apt-build', None) | ||
1328 | 1183 | # The delay of 1802 seconds is calculated as follows: | ||
1329 | 1184 | # 386 jobs: 16*60 = 960 | ||
1330 | 1185 | # processor-independent jobs: | ||
1331 | 1186 | # (11:05 + 18:30 + 16:38 + 14:47 + 9:14)/5 = 842 | ||
1332 | 1187 | check_estimate(self, xxr_job, 1802) | ||
1333 | 1188 | |||
1334 | 1189 | def test_estimation_binary_virtual_long_queue(self): | ||
1335 | 1190 | gedit_build, gedit_job = find_job(self, 'gedit', '386') | ||
1336 | 1191 | # The delay of 1671 seconds is calculated as follows: | ||
1337 | 1192 | # 386 jobs: | ||
1338 | 1193 | # (4+6+8+10+12+14+16)*60/5 = 840 | ||
1339 | 1194 | # processor-independent jobs: | ||
1340 | 1195 | # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831 | ||
1341 | 1196 | check_estimate(self, gedit_job, 1671) | ||
1342 | 1197 | |||
1343 | 1198 | def test_proc_indep_virtual_null_headjob(self): | ||
1344 | 1199 | xxr_build, xxr_job = find_job(self, 'xxr-daptup', None) | ||
1345 | 1200 | # This job is at the head of the queue for virtualized builders and | ||
1346 | 1201 | # will get dispatched within the next 5 seconds. | ||
1347 | 1202 | check_estimate(self, xxr_job, 5) | ||
1348 | 1203 | |||
1349 | 1204 | def test_proc_indep_virtual_false(self): | ||
1350 | 1205 | xxr_build, xxr_job = find_job(self, 'xxr-aptitude', None) | ||
1351 | 1206 | # The delay of 1403 seconds is calculated as follows: | ||
1352 | 1207 | # hppa jobs: (9+11+13+15)*60/3 = 960 | ||
1353 | 1208 | # processor-independent jobs: 7:23 = 443 | ||
1354 | 1209 | check_estimate(self, xxr_job, 1403) | ||
1355 | 1210 | |||
1356 | 1211 | def test_proc_indep_virtual_false_headjob(self): | ||
1357 | 1212 | xxr_build, xxr_job = find_job(self, 'xxr-auto-apt', None) | ||
1358 | 1213 | # This job is at the head of the queue for native builders and | ||
1359 | 1214 | # will get dispatched within the next 5 seconds. | ||
1360 | 1215 | check_estimate(self, xxr_job, 5) | ||
1361 | 1216 | |||
1362 | 1217 | def test_estimation_binary_virtual_same_score(self): | ||
1363 | 1218 | vim_build, vim_job = find_job(self, 'vim', '386') | ||
1364 | 1219 | # The apg job is ahead of the vim job. | ||
1365 | 1220 | # The delay of 1527 seconds is calculated as follows: | ||
1366 | 1221 | # 386 jobs: (6+10+12+14+16)*60/5 = 696 | ||
1367 | 1222 | # processor-independent jobs: | ||
1368 | 1223 | # (12:56 + 11:05 + 18:30 + 16:38 + 14:47 + 9:14)/6 = 831 | ||
1369 | 1224 | check_estimate(self, vim_job, 1527) | ||
1370 | 1225 | |||
1371 | 1226 | def test_no_builder_no_estimate(self): | ||
1372 | 1227 | # No dispatch estimate is provided in the absence of builders that | ||
1373 | 1228 | # can run the job of interest (JOI). | ||
1374 | 1229 | disable_builders(self, 'x86', True) | ||
1375 | 1230 | vim_build, vim_job = find_job(self, 'vim', '386') | ||
1376 | 1231 | check_estimate(self, vim_job, None) | ||
1377 | 1232 | |||
1378 | 1233 | def test_estimates_with_small_builder_pool(self): | ||
1379 | 1234 | # Test that a reduced builder pool results in longer dispatch time | ||
1380 | 1235 | # estimates. | ||
1381 | 1236 | vim_build, vim_job = find_job(self, 'vim', '386') | ||
1382 | 1237 | disable_builders(self, 'x86', True) | ||
1383 | 1238 | # Re-enable one builder. | ||
1384 | 1239 | builder = self.builders[(self.x86_proc.id, True)][0] | ||
1385 | 1240 | builder.builderok = True | ||
1386 | 1241 | # Dispatch the firefox job to it. | ||
1387 | 1242 | assign_to_builder(self, 'firefox', 1, '386') | ||
1388 | 1243 | # Dispatch the head job, making postgres/386 the new head job and | ||
1389 | 1244 | # resulting in a 240 seconds head job dispatch delay. | ||
1390 | 1245 | assign_to_builder(self, 'xxr-daptup', 1, None) | ||
1391 | 1246 | check_mintime_to_builder(self, vim_job, 240) | ||
1392 | 1247 | # Re-enable another builder. | ||
1393 | 1248 | builder = self.builders[(self.x86_proc.id, True)][1] | ||
1394 | 1249 | builder.builderok = True | ||
1395 | 1250 | # Assign a job to it. | ||
1396 | 1251 | assign_to_builder(self, 'gedit', 2, '386') | ||
1397 | 1252 | check_mintime_to_builder(self, vim_job, 120) | ||
1398 | 1253 | |||
1399 | 1254 | xxr_build, xxr_job = find_job(self, 'xxr-apt', None) | ||
1400 | 1255 | # The delay of 2627+120 seconds is calculated as follows: | ||
1401 | 1256 | # 386 jobs : (6+10+12+14+16)*60/2 = 1740 | ||
1402 | 1257 | # processor-independent jobs : | ||
1403 | 1258 | # (12:56 + 11:05 + 18:30 + 16:38 + 14:47)/5 = 887 | ||
1404 | 1259 | # waiting time for next builder: = 120 | ||
1405 | 1260 | self.assertEquals(2, builders_for_job(vim_job)) | ||
1406 | 1261 | self.assertEquals(9, builders_for_job(xxr_job)) | ||
1407 | 1262 | check_estimate(self, vim_job, 2747) | ||
1408 | 1263 | |||
1409 | 1264 | def test_estimation_binary_virtual_headjob(self): | ||
1410 | 1265 | # The head job only waits for the next builder to become available. | ||
1411 | 1266 | disable_builders(self, 'x86', True) | ||
1412 | 1267 | # Re-enable one builder. | ||
1413 | 1268 | builder = self.builders[(self.x86_proc.id, True)][0] | ||
1414 | 1269 | builder.builderok = True | ||
1415 | 1270 | # Assign a job to it. | ||
1416 | 1271 | assign_to_builder(self, 'gedit', 1, '386') | ||
1417 | 1272 | # Dispatch the head job, making postgres/386 the new head job. | ||
1418 | 1273 | assign_to_builder(self, 'xxr-daptup', 1, None) | ||
1419 | 1274 | postgres_build, postgres_job = find_job(self, 'postgres', '386') | ||
1420 | 1275 | check_estimate(self, postgres_job, 120) |
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 _estimateTimeTo NextBuilder( ) 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.