@@ -28,13 +28,39 @@ pub enum MissingReason {
28
28
} ,
29
29
Try {
30
30
pr : u32 ,
31
+ parent_sha : String ,
31
32
include : Option < String > ,
32
33
exclude : Option < String > ,
33
34
runs : Option < i32 > ,
34
35
} ,
35
36
InProgress ( Option < Box < MissingReason > > ) ,
36
37
}
37
38
39
+ impl MissingReason {
40
+ fn pr ( & self ) -> Option < u32 > {
41
+ let mut this = self ;
42
+ loop {
43
+ match this {
44
+ MissingReason :: Master { pr, .. } => return Some ( * pr) ,
45
+ MissingReason :: Try { pr, .. } => return Some ( * pr) ,
46
+ MissingReason :: InProgress ( Some ( s) ) => this = s,
47
+ MissingReason :: InProgress ( None ) => return None ,
48
+ }
49
+ }
50
+ }
51
+ fn parent_sha ( & self ) -> Option < & str > {
52
+ let mut this = self ;
53
+ loop {
54
+ match this {
55
+ MissingReason :: Master { parent_sha, .. } => return Some ( parent_sha. as_str ( ) ) ,
56
+ MissingReason :: Try { parent_sha, .. } => return Some ( parent_sha. as_str ( ) ) ,
57
+ MissingReason :: InProgress ( Some ( s) ) => this = s,
58
+ MissingReason :: InProgress ( None ) => return None ,
59
+ }
60
+ }
61
+ }
62
+ }
63
+
38
64
#[ derive( Clone , Deserialize , Serialize , Debug , PartialEq , Eq ) ]
39
65
pub struct TryCommit {
40
66
pub sha : String ,
@@ -201,7 +227,7 @@ fn calculate_missing_from(
201
227
mut all_commits : HashSet < String > ,
202
228
time : chrono:: DateTime < chrono:: Utc > ,
203
229
) -> Vec < ( Commit , MissingReason ) > {
204
- let mut master_commits = master_commits
230
+ let mut queue = master_commits
205
231
. into_iter ( )
206
232
. filter ( |c| time. signed_duration_since ( c. time ) < Duration :: days ( 29 ) )
207
233
. map ( |c| {
@@ -219,8 +245,10 @@ fn calculate_missing_from(
219
245
)
220
246
} )
221
247
. collect :: < Vec < _ > > ( ) ;
222
- master_commits. reverse ( ) ;
223
- let mut missing = Vec :: with_capacity ( queued_pr_commits. len ( ) * 2 + master_commits. len ( ) ) ;
248
+ let master_commits = queue
249
+ . iter ( )
250
+ . map ( |( mc, _) | mc. sha . clone ( ) )
251
+ . collect :: < HashSet < _ > > ( ) ;
224
252
for database:: QueuedCommit {
225
253
sha,
226
254
parent_sha,
@@ -231,77 +259,135 @@ fn calculate_missing_from(
231
259
} in queued_pr_commits
232
260
. into_iter ( )
233
261
// filter out any queued PR master commits (leaving only try commits)
234
- . filter ( |c| !master_commits. iter ( ) . any ( | ( mc , _ ) | mc . sha == c. sha ) )
262
+ . filter ( |c| !master_commits. contains ( & c. sha ) )
235
263
{
236
- // Enqueue the `TryParent` commit before the `TryCommit` itself, so that
237
- // all of the `try` run's data is complete when the benchmark results
238
- // of that commit are available.
239
- if let Some ( ( try_parent, metadata) ) = master_commits
240
- . iter ( )
241
- . find ( |( m, _) | m. sha == parent_sha. as_str ( ) )
242
- {
243
- let ( pr, parent_sha) = if let MissingReason :: Master {
244
- pr,
245
- parent_sha,
246
- is_try_parent : _,
247
- } = & metadata
248
- {
249
- ( * pr, parent_sha. clone ( ) )
264
+ // Mark the parent commit as a try_parent.
265
+ if let Some ( ( _, metadata) ) = queue. iter_mut ( ) . find ( |( m, _) | m. sha == parent_sha. as_str ( ) ) {
266
+ if let MissingReason :: Master { is_try_parent, .. } = metadata {
267
+ * is_try_parent = true ;
250
268
} else {
251
- unreachable ! (
252
- "non-master missing reason in master_commits: {:?}" ,
253
- metadata
254
- ) ;
269
+ unreachable ! ( "try commit has non-master parent {:?}" , metadata) ;
255
270
} ;
256
- missing. push ( (
257
- try_parent. clone ( ) ,
258
- MissingReason :: Master {
259
- pr,
260
- parent_sha,
261
- is_try_parent : true ,
262
- } ,
263
- ) ) ;
264
271
}
265
- missing . push ( (
272
+ queue . push ( (
266
273
Commit {
267
274
sha : sha. to_string ( ) ,
268
275
date : Date :: ymd_hms ( 2001 , 01 , 01 , 0 , 0 , 0 ) ,
269
276
} ,
270
277
MissingReason :: Try {
271
278
pr,
279
+ parent_sha,
272
280
include,
273
281
exclude,
274
282
runs,
275
283
} ,
276
284
) ) ;
277
285
}
278
- missing. extend ( master_commits) ;
279
286
for aid in in_progress_artifacts {
280
287
match aid {
281
288
ArtifactId :: Commit ( c) => {
282
- let previous = missing
289
+ let previous = queue
283
290
. iter ( )
284
291
. find ( |( i, _) | i. sha == c. sha )
285
292
. map ( |v| Box :: new ( v. 1 . clone ( ) ) ) ;
286
293
all_commits. remove ( & c. sha ) ;
287
- missing . insert ( 0 , ( c, MissingReason :: InProgress ( previous) ) ) ;
294
+ queue . insert ( 0 , ( c, MissingReason :: InProgress ( previous) ) ) ;
288
295
}
289
296
ArtifactId :: Tag ( _) => {
290
297
// do nothing, for now, though eventually we'll want an artifact queue
291
298
}
292
299
}
293
300
}
294
- let mut already_tested = HashSet :: with_capacity ( all_commits. len ( ) ) ;
295
- already_tested. extend ( all_commits) ;
301
+ let mut already_tested = all_commits. clone ( ) ;
296
302
let mut i = 0 ;
297
- while i != missing . len ( ) {
298
- if !already_tested. insert ( missing [ i] . 0 . sha . clone ( ) ) {
299
- missing . remove ( i) ;
303
+ while i != queue . len ( ) {
304
+ if !already_tested. insert ( queue [ i] . 0 . sha . clone ( ) ) {
305
+ queue . remove ( i) ;
300
306
} else {
301
307
i += 1 ;
302
308
}
303
309
}
304
- missing
310
+ sort_queue ( all_commits. clone ( ) , queue)
311
+ }
312
+
313
+ fn sort_queue (
314
+ mut done : HashSet < String > ,
315
+ mut unordered_queue : Vec < ( Commit , MissingReason ) > ,
316
+ ) -> Vec < ( Commit , MissingReason ) > {
317
+ // A topological sort, where each "level" is additionally altered such that
318
+ // try commits come first, and then sorted by PR # (as a rough heuristic for
319
+ // earlier requests).
320
+
321
+ let mut finished = 0 ;
322
+ while finished < unordered_queue. len ( ) {
323
+ // The next level is those elements in the unordered queue which
324
+ // are ready to be benchmarked (i.e., those with parent in done or no
325
+ // parent).
326
+ let level_len = partition_in_place ( unordered_queue[ finished..] . iter_mut ( ) , |( _, mr) | {
327
+ mr. parent_sha ( ) . map_or ( true , |parent| done. contains ( parent) )
328
+ } ) ;
329
+ assert ! (
330
+ level_len != 0 ,
331
+ "at least one commit is ready done={:#?}, {:?}" ,
332
+ done,
333
+ & unordered_queue[ finished..]
334
+ ) ;
335
+ let level = & mut unordered_queue[ finished..] [ ..level_len] ;
336
+ level. sort_unstable_by_key ( |( c, mr) | {
337
+ (
338
+ // InProgress MR go first (false < true)
339
+ mr. parent_sha ( ) . is_some ( ) ,
340
+ mr. pr ( ) . unwrap_or ( 0 ) ,
341
+ c. sha . clone ( ) ,
342
+ )
343
+ } ) ;
344
+ for ( c, _) in level {
345
+ done. insert ( c. sha . clone ( ) ) ;
346
+ }
347
+ finished += level_len;
348
+ }
349
+ unordered_queue
350
+ }
351
+
352
+ // Copy of Iterator::partition_in_place, which is currently unstable.
353
+ fn partition_in_place < ' a , I , T : ' a , P > ( mut iter : I , ref mut predicate: P ) -> usize
354
+ where
355
+ I : Sized + DoubleEndedIterator < Item = & ' a mut T > ,
356
+ P : FnMut ( & T ) -> bool ,
357
+ {
358
+ // FIXME: should we worry about the count overflowing? The only way to have more than
359
+ // `usize::MAX` mutable references is with ZSTs, which aren't useful to partition...
360
+
361
+ // These closure "factory" functions exist to avoid genericity in `Self`.
362
+
363
+ #[ inline]
364
+ fn is_false < ' a , T > (
365
+ predicate : & ' a mut impl FnMut ( & T ) -> bool ,
366
+ true_count : & ' a mut usize ,
367
+ ) -> impl FnMut ( & & mut T ) -> bool + ' a {
368
+ move |x| {
369
+ let p = predicate ( & * * x) ;
370
+ * true_count += p as usize ;
371
+ !p
372
+ }
373
+ }
374
+
375
+ #[ inline]
376
+ fn is_true < T > ( predicate : & mut impl FnMut ( & T ) -> bool ) -> impl FnMut ( & & mut T ) -> bool + ' _ {
377
+ move |x| predicate ( & * * x)
378
+ }
379
+
380
+ // Repeatedly find the first `false` and swap it with the last `true`.
381
+ let mut true_count = 0 ;
382
+ while let Some ( head) = iter. find ( is_false ( predicate, & mut true_count) ) {
383
+ if let Some ( tail) = iter. rfind ( is_true ( predicate) ) {
384
+ std:: mem:: swap ( head, tail) ;
385
+ true_count += 1 ;
386
+ } else {
387
+ break ;
388
+ }
389
+ }
390
+ true_count
305
391
}
306
392
307
393
/// One decimal place rounded percent
@@ -381,8 +467,8 @@ mod tests {
381
467
} ,
382
468
] ;
383
469
let in_progress_artifacts = vec ! [ ] ;
384
- // Have not benchmarked anything yet.
385
- let all_commits = HashSet :: new ( ) ;
470
+ let mut all_commits = HashSet :: new ( ) ;
471
+ all_commits. insert ( "c" . into ( ) ) ;
386
472
387
473
let expected = vec ! [
388
474
(
@@ -414,6 +500,7 @@ mod tests {
414
500
} ,
415
501
MissingReason :: Try {
416
502
pr: 3 ,
503
+ parent_sha: "a" . into( ) ,
417
504
include: None ,
418
505
exclude: None ,
419
506
runs: None ,
@@ -479,8 +566,23 @@ mod tests {
479
566
let in_progress_artifacts = vec ! [ ] ;
480
567
let mut all_commits = HashSet :: new ( ) ;
481
568
all_commits. insert ( master_commits[ 1 ] . sha . clone ( ) ) ;
569
+ // Parent trailers
570
+ all_commits. insert ( master_commits[ 0 ] . parent_sha . clone ( ) ) ;
571
+ all_commits. insert ( master_commits[ 1 ] . parent_sha . clone ( ) ) ;
572
+ all_commits. insert ( master_commits[ 2 ] . parent_sha . clone ( ) ) ;
482
573
483
574
let expected = vec ! [
575
+ (
576
+ Commit {
577
+ sha: "123" . into( ) ,
578
+ date: database:: Date ( time) ,
579
+ } ,
580
+ MissingReason :: Master {
581
+ pr: 11 ,
582
+ parent_sha: "345" . into( ) ,
583
+ is_try_parent: false ,
584
+ } ,
585
+ ) ,
484
586
(
485
587
Commit {
486
588
sha: "foo" . into( ) ,
@@ -499,22 +601,12 @@ mod tests {
499
601
} ,
500
602
MissingReason :: Try {
501
603
pr: 101 ,
604
+ parent_sha: "foo" . into( ) ,
502
605
include: None ,
503
606
exclude: None ,
504
607
runs: None ,
505
608
} ,
506
609
) ,
507
- (
508
- Commit {
509
- sha: "123" . into( ) ,
510
- date: database:: Date ( time) ,
511
- } ,
512
- MissingReason :: Master {
513
- pr: 11 ,
514
- parent_sha: "345" . into( ) ,
515
- is_try_parent: false ,
516
- } ,
517
- ) ,
518
610
] ;
519
611
assert_eq ! (
520
612
expected,
0 commit comments