| ... |
... |
@@ -84,8 +84,11 @@ PerfStat = NamedTuple('PerfStat', [('test_env', TestEnv), |
|
84
|
84
|
('value', float)])
|
|
85
|
85
|
|
|
86
|
86
|
# A baseline recovered form stored metrics.
|
|
87
|
|
-Baseline = NamedTuple('Baseline', [('perfStat', PerfStat),
|
|
88
|
|
- ('commit', GitHash)])
|
|
|
87
|
+class Baseline(NamedTuple):
|
|
|
88
|
+ perfStat: PerfStat
|
|
|
89
|
+ commit: GitHash
|
|
|
90
|
+ # The raw samples the baseline value was averaged over.
|
|
|
91
|
+ samples: List[float] = []
|
|
89
|
92
|
|
|
90
|
93
|
# The type of exceptions which are thrown when computing the current stat value
|
|
91
|
94
|
# fails.
|
| ... |
... |
@@ -465,6 +468,10 @@ def get_allowed_changes(baseline_ref: Optional[GitRef]) -> Dict[TestName, List[A |
|
465
|
468
|
# (bool , str ) -> (str , str , str , str) -> float
|
|
466
|
469
|
_commit_metric_cache = {} # type: ignore
|
|
467
|
470
|
|
|
|
471
|
+# Like _commit_metric_cache, but mapping to the list of raw sample values the
|
|
|
472
|
+# baseline was averaged over. Filled by get_commit_metric.
|
|
|
473
|
+_commit_samples_cache = {} # type: ignore
|
|
|
474
|
+
|
|
468
|
475
|
# Get the baseline of a test at a given commit. This is the expected value
|
|
469
|
476
|
# *before* the commit is applied (i.e. on the parent commit).
|
|
470
|
477
|
# This searches git notes from older commits for recorded metrics (locally and
|
| ... |
... |
@@ -506,7 +513,8 @@ def baseline_metric(commit: GitHash, |
|
506
|
513
|
if baseline_commit is not None:
|
|
507
|
514
|
current_metric = get_commit_metric(namespace, baseline_commit, test_env, name, metric, way)
|
|
508
|
515
|
if current_metric is not None:
|
|
509
|
|
- return Baseline(current_metric, baseline_commit)
|
|
|
516
|
+ return Baseline(current_metric, baseline_commit,
|
|
|
517
|
+ get_commit_samples(namespace, baseline_commit, test_env, name, metric, way))
|
|
510
|
518
|
else:
|
|
511
|
519
|
return None
|
|
512
|
520
|
|
| ... |
... |
@@ -515,7 +523,8 @@ def baseline_metric(commit: GitHash, |
|
515
|
523
|
# Check for a metric on this commit.
|
|
516
|
524
|
current_metric = get_commit_metric(namespace, current_commit, test_env, name, metric, way)
|
|
517
|
525
|
if current_metric is not None:
|
|
518
|
|
- return Baseline(current_metric, current_commit)
|
|
|
526
|
+ return Baseline(current_metric, current_commit,
|
|
|
527
|
+ get_commit_samples(namespace, current_commit, test_env, name, metric, way))
|
|
519
|
528
|
|
|
520
|
529
|
# Stop if there is an expected change at this commit. In that case
|
|
521
|
530
|
# metrics on ancestor commits will not be a valid baseline.
|
| ... |
... |
@@ -598,8 +607,26 @@ def get_commit_metric(gitNoteRef, |
|
598
|
607
|
|
|
599
|
608
|
# Save baselines to the cache.
|
|
600
|
609
|
_commit_metric_cache[cacheKeyA] = baseline_by_cache_key_b
|
|
|
610
|
+ _commit_samples_cache[cacheKeyA] = values_by_cache_key_b
|
|
601
|
611
|
return baseline_by_cache_key_b.get(cacheKeyB)
|
|
602
|
612
|
|
|
|
613
|
+# Get the raw sample values that get_commit_metric averages over. Uses the
|
|
|
614
|
+# cache filled by get_commit_metric, so no extra git calls after it has run.
|
|
|
615
|
+def get_commit_samples(gitNoteRef,
|
|
|
616
|
+ ref: Union[GitRef, GitHash],
|
|
|
617
|
+ test_env: TestEnv,
|
|
|
618
|
+ name: TestName,
|
|
|
619
|
+ metric: MetricName,
|
|
|
620
|
+ way: WayName
|
|
|
621
|
+ ) -> List[float]:
|
|
|
622
|
+ get_commit_metric(gitNoteRef, ref, test_env, name, metric, way)
|
|
|
623
|
+ cacheKeyA = (gitNoteRef, commit_hash(ref))
|
|
|
624
|
+ cacheKeyB = (test_env, name, metric, way)
|
|
|
625
|
+ return _commit_samples_cache.get(cacheKeyA, {}).get(cacheKeyB, [])
|
|
|
626
|
+
|
|
|
627
|
+def format_samples(samples: List[float]) -> str:
|
|
|
628
|
+ return ', '.join(str(int(s)) if s == int(s) else str(s) for s in samples)
|
|
|
629
|
+
|
|
603
|
630
|
def check_stats_change(actual: PerfStat,
|
|
604
|
631
|
baseline: Baseline,
|
|
605
|
632
|
acceptance_window: MetricAcceptanceWindow,
|
| ... |
... |
@@ -654,9 +681,16 @@ def check_stats_change(actual: PerfStat, |
|
654
|
681
|
' baseline @ %s' % baseline.commit
|
|
655
|
682
|
print(actual.metric, error + ':')
|
|
656
|
683
|
dev = 100.0 if expected_val == 0 else round(((float(actual.value) * 100) / int(expected_val)) - 100, 1)
|
|
|
684
|
+ # Show the samples so outliers become visible (#27602).
|
|
|
685
|
+ if len(baseline.samples) > 1:
|
|
|
686
|
+ samples_note = ('; baseline is mean of %d samples: %s'
|
|
|
687
|
+ % (len(baseline.samples),
|
|
|
688
|
+ format_samples(baseline.samples)))
|
|
|
689
|
+ else:
|
|
|
690
|
+ samples_note = ''
|
|
657
|
691
|
change_line = (f'{actual.metric} {change.value} from {baseline.perfStat.test_env} '
|
|
658
|
692
|
f'baseline @ {baseline.commit[:7]}: {expected_val} -> {actual.value} '
|
|
659
|
|
- f'({dev:+g}%, allowed {acceptance_window.describe()})')
|
|
|
693
|
+ f'({dev:+g}%, allowed {acceptance_window.describe()}{samples_note})')
|
|
660
|
694
|
result = failBecause('stat ' + change_line, tag='stat')
|
|
661
|
695
|
|
|
662
|
696
|
if not change_allowed or force_print:
|
| ... |
... |
@@ -666,6 +700,10 @@ def check_stats_change(actual: PerfStat, |
|
666
|
700
|
print(descr, str(val).rjust(length), extra)
|
|
667
|
701
|
|
|
668
|
702
|
display(' Expected ' + full_name + ' ' + actual.metric + ':', expected_val, acceptance_window.describe())
|
|
|
703
|
+ if len(baseline.samples) > 1:
|
|
|
704
|
+ display(' Samples ' + full_name + ' ' + actual.metric + ':',
|
|
|
705
|
+ len(baseline.samples),
|
|
|
706
|
+ '(' + format_samples(baseline.samples) + ')')
|
|
669
|
707
|
display(' Lower bound ' + full_name + ' ' + actual.metric + ':', lowerBound, '')
|
|
670
|
708
|
display(' Upper bound ' + full_name + ' ' + actual.metric + ':', upperBound, '')
|
|
671
|
709
|
display(' Actual ' + full_name + ' ' + actual.metric + ':', actual.value, '')
|