1*3ac0a46fSAndroid Build Coastguard Worker# Copyright 2017 The PDFium Authors 2*3ac0a46fSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*3ac0a46fSAndroid Build Coastguard Worker# found in the LICENSE file. 4*3ac0a46fSAndroid Build Coastguard Worker"""Classes that draw conclusions out of a comparison and represent them.""" 5*3ac0a46fSAndroid Build Coastguard Worker 6*3ac0a46fSAndroid Build Coastguard Workerfrom collections import Counter 7*3ac0a46fSAndroid Build Coastguard Worker 8*3ac0a46fSAndroid Build Coastguard WorkerFORMAT_RED = '\033[01;31m{0}\033[00m' 9*3ac0a46fSAndroid Build Coastguard WorkerFORMAT_GREEN = '\033[01;32m{0}\033[00m' 10*3ac0a46fSAndroid Build Coastguard WorkerFORMAT_MAGENTA = '\033[01;35m{0}\033[00m' 11*3ac0a46fSAndroid Build Coastguard WorkerFORMAT_CYAN = '\033[01;36m{0}\033[00m' 12*3ac0a46fSAndroid Build Coastguard WorkerFORMAT_NORMAL = '{0}' 13*3ac0a46fSAndroid Build Coastguard Worker 14*3ac0a46fSAndroid Build Coastguard WorkerRATING_FAILURE = 'failure' 15*3ac0a46fSAndroid Build Coastguard WorkerRATING_REGRESSION = 'regression' 16*3ac0a46fSAndroid Build Coastguard WorkerRATING_IMPROVEMENT = 'improvement' 17*3ac0a46fSAndroid Build Coastguard WorkerRATING_NO_CHANGE = 'no_change' 18*3ac0a46fSAndroid Build Coastguard WorkerRATING_SMALL_CHANGE = 'small_change' 19*3ac0a46fSAndroid Build Coastguard Worker 20*3ac0a46fSAndroid Build Coastguard WorkerRATINGS = [ 21*3ac0a46fSAndroid Build Coastguard Worker RATING_FAILURE, RATING_REGRESSION, RATING_IMPROVEMENT, RATING_NO_CHANGE, 22*3ac0a46fSAndroid Build Coastguard Worker RATING_SMALL_CHANGE 23*3ac0a46fSAndroid Build Coastguard Worker] 24*3ac0a46fSAndroid Build Coastguard Worker 25*3ac0a46fSAndroid Build Coastguard WorkerRATING_TO_COLOR = { 26*3ac0a46fSAndroid Build Coastguard Worker RATING_FAILURE: FORMAT_MAGENTA, 27*3ac0a46fSAndroid Build Coastguard Worker RATING_REGRESSION: FORMAT_RED, 28*3ac0a46fSAndroid Build Coastguard Worker RATING_IMPROVEMENT: FORMAT_CYAN, 29*3ac0a46fSAndroid Build Coastguard Worker RATING_NO_CHANGE: FORMAT_GREEN, 30*3ac0a46fSAndroid Build Coastguard Worker RATING_SMALL_CHANGE: FORMAT_NORMAL, 31*3ac0a46fSAndroid Build Coastguard Worker} 32*3ac0a46fSAndroid Build Coastguard Worker 33*3ac0a46fSAndroid Build Coastguard Worker 34*3ac0a46fSAndroid Build Coastguard Workerclass ComparisonConclusions: 35*3ac0a46fSAndroid Build Coastguard Worker """All conclusions drawn from a comparison. 36*3ac0a46fSAndroid Build Coastguard Worker 37*3ac0a46fSAndroid Build Coastguard Worker This is initialized empty and then processes pairs of results for each test 38*3ac0a46fSAndroid Build Coastguard Worker case, determining the rating for that case, which can be: 39*3ac0a46fSAndroid Build Coastguard Worker "failure" if either or both runs for the case failed. 40*3ac0a46fSAndroid Build Coastguard Worker "regression" if there is a significant increase in time for the test case. 41*3ac0a46fSAndroid Build Coastguard Worker "improvement" if there is a significant decrease in time for the test case. 42*3ac0a46fSAndroid Build Coastguard Worker "no_change" if the time for the test case did not change at all. 43*3ac0a46fSAndroid Build Coastguard Worker "small_change" if the time for the test case changed but within the threshold. 44*3ac0a46fSAndroid Build Coastguard Worker """ 45*3ac0a46fSAndroid Build Coastguard Worker 46*3ac0a46fSAndroid Build Coastguard Worker def __init__(self, threshold_significant): 47*3ac0a46fSAndroid Build Coastguard Worker """Initializes an empty ComparisonConclusions. 48*3ac0a46fSAndroid Build Coastguard Worker 49*3ac0a46fSAndroid Build Coastguard Worker Args: 50*3ac0a46fSAndroid Build Coastguard Worker threshold_significant: Float with the tolerance beyond which changes in 51*3ac0a46fSAndroid Build Coastguard Worker measurements are considered significant. 52*3ac0a46fSAndroid Build Coastguard Worker 53*3ac0a46fSAndroid Build Coastguard Worker The change is considered as a multiplication rather than an addition 54*3ac0a46fSAndroid Build Coastguard Worker of a fraction of the previous measurement, that is, a 55*3ac0a46fSAndroid Build Coastguard Worker threshold_significant of 1.0 will flag test cases that became over 56*3ac0a46fSAndroid Build Coastguard Worker 100% slower (> 200% of the previous time measured) or over 100% faster 57*3ac0a46fSAndroid Build Coastguard Worker (< 50% of the previous time measured). 58*3ac0a46fSAndroid Build Coastguard Worker 59*3ac0a46fSAndroid Build Coastguard Worker threshold_significant 0.02 -> 98.04% to 102% is not significant 60*3ac0a46fSAndroid Build Coastguard Worker threshold_significant 0.1 -> 90.9% to 110% is not significant 61*3ac0a46fSAndroid Build Coastguard Worker threshold_significant 0.25 -> 80% to 125% is not significant 62*3ac0a46fSAndroid Build Coastguard Worker threshold_significant 1 -> 50% to 200% is not significant 63*3ac0a46fSAndroid Build Coastguard Worker threshold_significant 4 -> 20% to 500% is not significant 64*3ac0a46fSAndroid Build Coastguard Worker 65*3ac0a46fSAndroid Build Coastguard Worker """ 66*3ac0a46fSAndroid Build Coastguard Worker self.threshold_significant = threshold_significant 67*3ac0a46fSAndroid Build Coastguard Worker self.threshold_significant_negative = (1 / (1 + threshold_significant)) - 1 68*3ac0a46fSAndroid Build Coastguard Worker 69*3ac0a46fSAndroid Build Coastguard Worker self.params = {'threshold': threshold_significant} 70*3ac0a46fSAndroid Build Coastguard Worker self.summary = ComparisonSummary() 71*3ac0a46fSAndroid Build Coastguard Worker self.case_results = {} 72*3ac0a46fSAndroid Build Coastguard Worker 73*3ac0a46fSAndroid Build Coastguard Worker def ProcessCase(self, case_name, before, after): 74*3ac0a46fSAndroid Build Coastguard Worker """Feeds a test case results to the ComparisonConclusions. 75*3ac0a46fSAndroid Build Coastguard Worker 76*3ac0a46fSAndroid Build Coastguard Worker Args: 77*3ac0a46fSAndroid Build Coastguard Worker case_name: String identifying the case. 78*3ac0a46fSAndroid Build Coastguard Worker before: Measurement for the "before" version of the code. 79*3ac0a46fSAndroid Build Coastguard Worker after: Measurement for the "after" version of the code. 80*3ac0a46fSAndroid Build Coastguard Worker """ 81*3ac0a46fSAndroid Build Coastguard Worker 82*3ac0a46fSAndroid Build Coastguard Worker # Switch 0 to None to simplify the json dict output. All zeros are 83*3ac0a46fSAndroid Build Coastguard Worker # considered failed runs, so they will be represented by "null". 84*3ac0a46fSAndroid Build Coastguard Worker if not before: 85*3ac0a46fSAndroid Build Coastguard Worker before = None 86*3ac0a46fSAndroid Build Coastguard Worker if not after: 87*3ac0a46fSAndroid Build Coastguard Worker after = None 88*3ac0a46fSAndroid Build Coastguard Worker 89*3ac0a46fSAndroid Build Coastguard Worker if not before or not after: 90*3ac0a46fSAndroid Build Coastguard Worker ratio = None 91*3ac0a46fSAndroid Build Coastguard Worker rating = RATING_FAILURE 92*3ac0a46fSAndroid Build Coastguard Worker else: 93*3ac0a46fSAndroid Build Coastguard Worker ratio = (float(after) / before) - 1.0 94*3ac0a46fSAndroid Build Coastguard Worker if ratio > self.threshold_significant: 95*3ac0a46fSAndroid Build Coastguard Worker rating = RATING_REGRESSION 96*3ac0a46fSAndroid Build Coastguard Worker elif ratio < self.threshold_significant_negative: 97*3ac0a46fSAndroid Build Coastguard Worker rating = RATING_IMPROVEMENT 98*3ac0a46fSAndroid Build Coastguard Worker elif ratio == 0: 99*3ac0a46fSAndroid Build Coastguard Worker rating = RATING_NO_CHANGE 100*3ac0a46fSAndroid Build Coastguard Worker else: 101*3ac0a46fSAndroid Build Coastguard Worker rating = RATING_SMALL_CHANGE 102*3ac0a46fSAndroid Build Coastguard Worker 103*3ac0a46fSAndroid Build Coastguard Worker case_result = CaseResult(case_name, before, after, ratio, rating) 104*3ac0a46fSAndroid Build Coastguard Worker 105*3ac0a46fSAndroid Build Coastguard Worker self.summary.ProcessCaseResult(case_result) 106*3ac0a46fSAndroid Build Coastguard Worker self.case_results[case_name] = case_result 107*3ac0a46fSAndroid Build Coastguard Worker 108*3ac0a46fSAndroid Build Coastguard Worker def GetSummary(self): 109*3ac0a46fSAndroid Build Coastguard Worker """Gets the ComparisonSummary with consolidated totals.""" 110*3ac0a46fSAndroid Build Coastguard Worker return self.summary 111*3ac0a46fSAndroid Build Coastguard Worker 112*3ac0a46fSAndroid Build Coastguard Worker def GetCaseResults(self): 113*3ac0a46fSAndroid Build Coastguard Worker """Gets a dict mapping each test case identifier to its CaseResult.""" 114*3ac0a46fSAndroid Build Coastguard Worker return self.case_results 115*3ac0a46fSAndroid Build Coastguard Worker 116*3ac0a46fSAndroid Build Coastguard Worker def GetOutputDict(self): 117*3ac0a46fSAndroid Build Coastguard Worker """Returns a conclusions dict with all the conclusions drawn. 118*3ac0a46fSAndroid Build Coastguard Worker 119*3ac0a46fSAndroid Build Coastguard Worker Returns: 120*3ac0a46fSAndroid Build Coastguard Worker A serializable dict with the format illustrated below: 121*3ac0a46fSAndroid Build Coastguard Worker { 122*3ac0a46fSAndroid Build Coastguard Worker "version": 1, 123*3ac0a46fSAndroid Build Coastguard Worker "params": { 124*3ac0a46fSAndroid Build Coastguard Worker "threshold": 0.02 125*3ac0a46fSAndroid Build Coastguard Worker }, 126*3ac0a46fSAndroid Build Coastguard Worker "summary": { 127*3ac0a46fSAndroid Build Coastguard Worker "total": 123, 128*3ac0a46fSAndroid Build Coastguard Worker "failure": 1, 129*3ac0a46fSAndroid Build Coastguard Worker "regression": 2, 130*3ac0a46fSAndroid Build Coastguard Worker "improvement": 1, 131*3ac0a46fSAndroid Build Coastguard Worker "no_change": 100, 132*3ac0a46fSAndroid Build Coastguard Worker "small_change": 19 133*3ac0a46fSAndroid Build Coastguard Worker }, 134*3ac0a46fSAndroid Build Coastguard Worker "comparison_by_case": { 135*3ac0a46fSAndroid Build Coastguard Worker "testing/resources/new_test.pdf": { 136*3ac0a46fSAndroid Build Coastguard Worker "before": None, 137*3ac0a46fSAndroid Build Coastguard Worker "after": 1000, 138*3ac0a46fSAndroid Build Coastguard Worker "ratio": None, 139*3ac0a46fSAndroid Build Coastguard Worker "rating": "failure" 140*3ac0a46fSAndroid Build Coastguard Worker }, 141*3ac0a46fSAndroid Build Coastguard Worker "testing/resources/test1.pdf": { 142*3ac0a46fSAndroid Build Coastguard Worker "before": 100, 143*3ac0a46fSAndroid Build Coastguard Worker "after": 120, 144*3ac0a46fSAndroid Build Coastguard Worker "ratio": 0.2, 145*3ac0a46fSAndroid Build Coastguard Worker "rating": "regression" 146*3ac0a46fSAndroid Build Coastguard Worker }, 147*3ac0a46fSAndroid Build Coastguard Worker "testing/resources/test2.pdf": { 148*3ac0a46fSAndroid Build Coastguard Worker "before": 100, 149*3ac0a46fSAndroid Build Coastguard Worker "after": 2000, 150*3ac0a46fSAndroid Build Coastguard Worker "ratio": 19.0, 151*3ac0a46fSAndroid Build Coastguard Worker "rating": "regression" 152*3ac0a46fSAndroid Build Coastguard Worker }, 153*3ac0a46fSAndroid Build Coastguard Worker "testing/resources/test3.pdf": { 154*3ac0a46fSAndroid Build Coastguard Worker "before": 1000, 155*3ac0a46fSAndroid Build Coastguard Worker "after": 1005, 156*3ac0a46fSAndroid Build Coastguard Worker "ratio": 0.005, 157*3ac0a46fSAndroid Build Coastguard Worker "rating": "small_change" 158*3ac0a46fSAndroid Build Coastguard Worker }, 159*3ac0a46fSAndroid Build Coastguard Worker "testing/resources/test4.pdf": { 160*3ac0a46fSAndroid Build Coastguard Worker "before": 1000, 161*3ac0a46fSAndroid Build Coastguard Worker "after": 1000, 162*3ac0a46fSAndroid Build Coastguard Worker "ratio": 0.0, 163*3ac0a46fSAndroid Build Coastguard Worker "rating": "no_change" 164*3ac0a46fSAndroid Build Coastguard Worker }, 165*3ac0a46fSAndroid Build Coastguard Worker "testing/resources/test5.pdf": { 166*3ac0a46fSAndroid Build Coastguard Worker "before": 1000, 167*3ac0a46fSAndroid Build Coastguard Worker "after": 600, 168*3ac0a46fSAndroid Build Coastguard Worker "ratio": -0.4, 169*3ac0a46fSAndroid Build Coastguard Worker "rating": "improvement" 170*3ac0a46fSAndroid Build Coastguard Worker } 171*3ac0a46fSAndroid Build Coastguard Worker } 172*3ac0a46fSAndroid Build Coastguard Worker } 173*3ac0a46fSAndroid Build Coastguard Worker """ 174*3ac0a46fSAndroid Build Coastguard Worker output_dict = {} 175*3ac0a46fSAndroid Build Coastguard Worker output_dict['version'] = 1 176*3ac0a46fSAndroid Build Coastguard Worker output_dict['params'] = {'threshold': self.threshold_significant} 177*3ac0a46fSAndroid Build Coastguard Worker output_dict['summary'] = self.summary.GetOutputDict() 178*3ac0a46fSAndroid Build Coastguard Worker output_dict['comparison_by_case'] = { 179*3ac0a46fSAndroid Build Coastguard Worker cr.case_name.decode('utf-8'): cr.GetOutputDict() 180*3ac0a46fSAndroid Build Coastguard Worker for cr in self.GetCaseResults().values() 181*3ac0a46fSAndroid Build Coastguard Worker } 182*3ac0a46fSAndroid Build Coastguard Worker return output_dict 183*3ac0a46fSAndroid Build Coastguard Worker 184*3ac0a46fSAndroid Build Coastguard Worker 185*3ac0a46fSAndroid Build Coastguard Workerclass ComparisonSummary: 186*3ac0a46fSAndroid Build Coastguard Worker """Totals computed for a comparison.""" 187*3ac0a46fSAndroid Build Coastguard Worker 188*3ac0a46fSAndroid Build Coastguard Worker def __init__(self): 189*3ac0a46fSAndroid Build Coastguard Worker self.rating_counter = Counter() 190*3ac0a46fSAndroid Build Coastguard Worker 191*3ac0a46fSAndroid Build Coastguard Worker def ProcessCaseResult(self, case_result): 192*3ac0a46fSAndroid Build Coastguard Worker self.rating_counter[case_result.rating] += 1 193*3ac0a46fSAndroid Build Coastguard Worker 194*3ac0a46fSAndroid Build Coastguard Worker def GetTotal(self): 195*3ac0a46fSAndroid Build Coastguard Worker """Gets the number of test cases processed.""" 196*3ac0a46fSAndroid Build Coastguard Worker return sum(self.rating_counter.values()) 197*3ac0a46fSAndroid Build Coastguard Worker 198*3ac0a46fSAndroid Build Coastguard Worker def GetCount(self, rating): 199*3ac0a46fSAndroid Build Coastguard Worker """Gets the number of test cases processed with a given rating.""" 200*3ac0a46fSAndroid Build Coastguard Worker return self.rating_counter[rating] 201*3ac0a46fSAndroid Build Coastguard Worker 202*3ac0a46fSAndroid Build Coastguard Worker def GetOutputDict(self): 203*3ac0a46fSAndroid Build Coastguard Worker """Returns a dict that can be serialized with all the totals.""" 204*3ac0a46fSAndroid Build Coastguard Worker result = {'total': self.GetTotal()} 205*3ac0a46fSAndroid Build Coastguard Worker for rating in RATINGS: 206*3ac0a46fSAndroid Build Coastguard Worker result[rating] = self.GetCount(rating) 207*3ac0a46fSAndroid Build Coastguard Worker return result 208*3ac0a46fSAndroid Build Coastguard Worker 209*3ac0a46fSAndroid Build Coastguard Worker 210*3ac0a46fSAndroid Build Coastguard Workerclass CaseResult: 211*3ac0a46fSAndroid Build Coastguard Worker """The conclusion for the comparison of a single test case.""" 212*3ac0a46fSAndroid Build Coastguard Worker 213*3ac0a46fSAndroid Build Coastguard Worker def __init__(self, case_name, before, after, ratio, rating): 214*3ac0a46fSAndroid Build Coastguard Worker """Initializes an empty ComparisonConclusions. 215*3ac0a46fSAndroid Build Coastguard Worker 216*3ac0a46fSAndroid Build Coastguard Worker Args: 217*3ac0a46fSAndroid Build Coastguard Worker case_name: String identifying the case. 218*3ac0a46fSAndroid Build Coastguard Worker before: Measurement for the "before" version of the code. 219*3ac0a46fSAndroid Build Coastguard Worker after: Measurement for the "after" version of the code. 220*3ac0a46fSAndroid Build Coastguard Worker ratio: Difference between |after| and |before| as a fraction of |before|. 221*3ac0a46fSAndroid Build Coastguard Worker rating: Rating for this test case. 222*3ac0a46fSAndroid Build Coastguard Worker """ 223*3ac0a46fSAndroid Build Coastguard Worker self.case_name = case_name 224*3ac0a46fSAndroid Build Coastguard Worker self.before = before 225*3ac0a46fSAndroid Build Coastguard Worker self.after = after 226*3ac0a46fSAndroid Build Coastguard Worker self.ratio = ratio 227*3ac0a46fSAndroid Build Coastguard Worker self.rating = rating 228*3ac0a46fSAndroid Build Coastguard Worker 229*3ac0a46fSAndroid Build Coastguard Worker def GetOutputDict(self): 230*3ac0a46fSAndroid Build Coastguard Worker """Returns a dict with the test case's conclusions.""" 231*3ac0a46fSAndroid Build Coastguard Worker return { 232*3ac0a46fSAndroid Build Coastguard Worker 'before': self.before, 233*3ac0a46fSAndroid Build Coastguard Worker 'after': self.after, 234*3ac0a46fSAndroid Build Coastguard Worker 'ratio': self.ratio, 235*3ac0a46fSAndroid Build Coastguard Worker 'rating': self.rating 236*3ac0a46fSAndroid Build Coastguard Worker } 237*3ac0a46fSAndroid Build Coastguard Worker 238*3ac0a46fSAndroid Build Coastguard Worker 239*3ac0a46fSAndroid Build Coastguard Workerdef PrintConclusionsDictHumanReadable(conclusions_dict, colored, key=None): 240*3ac0a46fSAndroid Build Coastguard Worker """Prints a conclusions dict in a human-readable way. 241*3ac0a46fSAndroid Build Coastguard Worker 242*3ac0a46fSAndroid Build Coastguard Worker Args: 243*3ac0a46fSAndroid Build Coastguard Worker conclusions_dict: Dict to print. 244*3ac0a46fSAndroid Build Coastguard Worker colored: Whether to color the output to highlight significant changes. 245*3ac0a46fSAndroid Build Coastguard Worker key: String with the CaseResult dictionary key to sort the cases. 246*3ac0a46fSAndroid Build Coastguard Worker """ 247*3ac0a46fSAndroid Build Coastguard Worker # Print header 248*3ac0a46fSAndroid Build Coastguard Worker print('=' * 80) 249*3ac0a46fSAndroid Build Coastguard Worker print('{0:>11s} {1:>15s} {2}'.format('% Change', 'Time after', 'Test case')) 250*3ac0a46fSAndroid Build Coastguard Worker print('-' * 80) 251*3ac0a46fSAndroid Build Coastguard Worker 252*3ac0a46fSAndroid Build Coastguard Worker color = FORMAT_NORMAL 253*3ac0a46fSAndroid Build Coastguard Worker 254*3ac0a46fSAndroid Build Coastguard Worker # Print cases 255*3ac0a46fSAndroid Build Coastguard Worker if key is not None: 256*3ac0a46fSAndroid Build Coastguard Worker case_pairs = sorted( 257*3ac0a46fSAndroid Build Coastguard Worker conclusions_dict['comparison_by_case'].iteritems(), 258*3ac0a46fSAndroid Build Coastguard Worker key=lambda kv: kv[1][key]) 259*3ac0a46fSAndroid Build Coastguard Worker else: 260*3ac0a46fSAndroid Build Coastguard Worker case_pairs = sorted(conclusions_dict['comparison_by_case'].iteritems()) 261*3ac0a46fSAndroid Build Coastguard Worker 262*3ac0a46fSAndroid Build Coastguard Worker for case_name, case_dict in case_pairs: 263*3ac0a46fSAndroid Build Coastguard Worker if colored: 264*3ac0a46fSAndroid Build Coastguard Worker color = RATING_TO_COLOR[case_dict['rating']] 265*3ac0a46fSAndroid Build Coastguard Worker 266*3ac0a46fSAndroid Build Coastguard Worker if case_dict['rating'] == RATING_FAILURE: 267*3ac0a46fSAndroid Build Coastguard Worker print(u'{} to measure time for {}'.format( 268*3ac0a46fSAndroid Build Coastguard Worker color.format('Failed'), case_name).encode('utf-8')) 269*3ac0a46fSAndroid Build Coastguard Worker continue 270*3ac0a46fSAndroid Build Coastguard Worker 271*3ac0a46fSAndroid Build Coastguard Worker print(u'{0} {1:15,d} {2}'.format( 272*3ac0a46fSAndroid Build Coastguard Worker color.format('{:+11.4%}'.format(case_dict['ratio'])), 273*3ac0a46fSAndroid Build Coastguard Worker case_dict['after'], case_name).encode('utf-8')) 274*3ac0a46fSAndroid Build Coastguard Worker 275*3ac0a46fSAndroid Build Coastguard Worker # Print totals 276*3ac0a46fSAndroid Build Coastguard Worker totals = conclusions_dict['summary'] 277*3ac0a46fSAndroid Build Coastguard Worker print('=' * 80) 278*3ac0a46fSAndroid Build Coastguard Worker print('Test cases run: %d' % totals['total']) 279*3ac0a46fSAndroid Build Coastguard Worker 280*3ac0a46fSAndroid Build Coastguard Worker if colored: 281*3ac0a46fSAndroid Build Coastguard Worker color = FORMAT_MAGENTA if totals[RATING_FAILURE] else FORMAT_GREEN 282*3ac0a46fSAndroid Build Coastguard Worker print('Failed to measure: %s' % color.format(totals[RATING_FAILURE])) 283*3ac0a46fSAndroid Build Coastguard Worker 284*3ac0a46fSAndroid Build Coastguard Worker if colored: 285*3ac0a46fSAndroid Build Coastguard Worker color = FORMAT_RED if totals[RATING_REGRESSION] else FORMAT_GREEN 286*3ac0a46fSAndroid Build Coastguard Worker print('Regressions: %s' % color.format(totals[RATING_REGRESSION])) 287*3ac0a46fSAndroid Build Coastguard Worker 288*3ac0a46fSAndroid Build Coastguard Worker if colored: 289*3ac0a46fSAndroid Build Coastguard Worker color = FORMAT_CYAN if totals[RATING_IMPROVEMENT] else FORMAT_GREEN 290*3ac0a46fSAndroid Build Coastguard Worker print('Improvements: %s' % color.format(totals[RATING_IMPROVEMENT])) 291