1# Copyright 2015 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import collections 6import logging 7import re 8 9from autotest_lib.client.bin import utils 10from autotest_lib.client.common_lib import error 11 12 13def get_histogram_text(tab, histogram_name): 14 """ 15 This returns contents of the given histogram. 16 17 @param tab: object, Chrome tab instance 18 @param histogram_name: string, name of the histogram 19 @returns string: contents of the histogram 20 """ 21 docEle = 'document.documentElement' 22 tab.Navigate('chrome://histograms/%s' % histogram_name) 23 tab.WaitForDocumentReadyStateToBeComplete() 24 raw_text = tab.EvaluateJavaScript( 25 '{0} && {0}.innerText'.format(docEle)) 26 # extract the contents of the histogram 27 histogram = raw_text[raw_text.find('Histogram:'):].strip() 28 if histogram: 29 logging.debug('chrome://histograms/%s:\n%s', histogram_name, 30 histogram) 31 else: 32 logging.debug('No histogram is shown in chrome://histograms/%s', 33 histogram_name) 34 return histogram 35 36 37def loaded(tab, histogram_name, pattern): 38 """ 39 Checks if the histogram page has been fully loaded. 40 41 @param tab: object, Chrome tab instance 42 @param histogram_name: string, name of the histogram 43 @param pattern: string, required text to look for 44 @returns re.MatchObject if the given pattern is found in the text 45 None otherwise 46 47 """ 48 return re.search(pattern, get_histogram_text(tab, histogram_name)) 49 50 51def verify(cr, histogram_name, histogram_bucket_value): 52 """ 53 Verifies histogram string and success rate in a parsed histogram bucket. 54 The histogram buckets are outputted in debug log regardless of the 55 verification result. 56 57 Full histogram URL is used to load histogram. Example Histogram URL is : 58 chrome://histograms/Media.GpuVideoDecoderInitializeStatus 59 60 @param cr: object, the Chrome instance 61 @param histogram_name: string, name of the histogram 62 @param histogram_bucket_value: int, required bucket number to look for 63 @raises error.TestError if histogram is not successful 64 65 """ 66 bucket_pattern = '\n'+ str(histogram_bucket_value) +'.*100\.0%.*' 67 error_msg_format = ('{} not loaded or histogram bucket not found ' 68 'or histogram bucket found at < 100%') 69 tab = cr.browser.tabs.New() 70 msg = error_msg_format.format(histogram_name) 71 utils.poll_for_condition(lambda : loaded(tab, histogram_name, 72 bucket_pattern), 73 exception=error.TestError(msg), 74 sleep_interval=1) 75 76 77def is_bucket_present(cr,histogram_name, histogram_bucket_value): 78 """ 79 This returns histogram succes or fail to called function 80 81 @param cr: object, the Chrome instance 82 @param histogram_name: string, name of the histogram 83 @param histogram_bucket_value: int, required bucket number to look for 84 @returns True if histogram page was loaded and the bucket was found. 85 False otherwise 86 87 """ 88 try: 89 verify(cr,histogram_name, histogram_bucket_value) 90 except error.TestError: 91 return False 92 else: 93 return True 94 95 96def is_histogram_present(cr, histogram_name): 97 """ 98 This checks if the given histogram is present and non-zero. 99 100 @param cr: object, the Chrome instance 101 @param histogram_name: string, name of the histogram 102 @returns True if histogram page was loaded and the histogram is present 103 False otherwise 104 105 """ 106 histogram_pattern = 'Histogram: '+ histogram_name + ' recorded ' + \ 107 r'[1-9][0-9]*' + ' samples' 108 tab = cr.browser.tabs.New() 109 try: 110 utils.poll_for_condition(lambda : loaded(tab, histogram_name, 111 histogram_pattern), 112 timeout=2, 113 sleep_interval=0.1) 114 return True 115 except utils.TimeoutError: 116 # the histogram is not present, and then returns false 117 return False 118 119 120def get_histogram(cr, histogram_name): 121 """ 122 This returns contents of the given histogram. 123 124 @param cr: object, the Chrome instance 125 @param histogram_name: string, name of the histogram 126 @returns string: contents of the histogram 127 128 """ 129 tab = cr.browser.tabs.New() 130 return get_histogram_text(tab, histogram_name) 131 132 133def parse_histogram(histogram_text): 134 """ 135 Parses histogram text into bucket structure. 136 137 @param histogram_text: histogram raw text. 138 @returns dict(bucket_value, bucket_count) 139 """ 140 # Match separator line, e.g. "1 ..." 141 RE_SEPEARTOR = re.compile(r'\d+\s+\.\.\.') 142 # Match bucket line, e.g. "2 --O (46 = 1.5%) {46.1%}" 143 RE_BUCKET = re.compile( 144 r'(\d+)\s+\-*O\s+\((\d+) = (\d+\.\d+)%\).*') 145 result = {} 146 for line in histogram_text.splitlines(): 147 if RE_SEPEARTOR.match(line): 148 continue 149 m = RE_BUCKET.match(line) 150 if m: 151 result[int(m.group(1))] = int(m.group(2)) 152 return result 153 154 155def subtract_histogram(minuend, subtrahend): 156 """ 157 Subtracts histogram: minuend - subtrahend 158 159 @param minuend: histogram bucket dict from which another is to be 160 subtracted. 161 @param subtrahend: histogram bucket dict to be subtracted from another. 162 @result difference of the two histograms in bucket dict. Note that 163 zero-counted buckets are removed. 164 """ 165 result = collections.defaultdict(int, minuend) 166 for k, v in subtrahend.iteritems(): 167 result[k] -= v 168 169 # Remove zero counted buckets. 170 return {k: v for k, v in result.iteritems() if v} 171 172 173def expect_sole_bucket(histogram_differ, bucket, bucket_name, timeout=10, 174 sleep_interval=1): 175 """ 176 Returns true if the given bucket solely exists in histogram differ. 177 178 @param histogram_differ: a HistogramDiffer instance used to get histogram 179 name and histogram diff multiple times. 180 @param bucket: bucket value. 181 @param bucket_name: bucket name to be shown on error message. 182 @param timeout: timeout in seconds. 183 @param sleep_interval: interval in seconds between getting diff. 184 @returns True if the given bucket solely exists in histogram. 185 @raises TestError if bucket doesn't exist or other buckets exist. 186 """ 187 timer = utils.Timer(timeout) 188 histogram = {} 189 histogram_name = histogram_differ.histogram_name 190 while timer.sleep(sleep_interval): 191 histogram = histogram_differ.end() 192 if histogram: 193 break 194 195 if bucket not in histogram: 196 raise error.TestError('Expect %s has %s. Histogram: %r' % 197 (histogram_name, bucket_name, histogram)) 198 if len(histogram) > 1: 199 raise error.TestError('%s has bucket other than %s. Histogram: %r' % 200 (histogram_name, bucket_name, histogram)) 201 return True 202 203 204def poll_histogram_grow(histogram_differ, timeout=2, sleep_interval=0.1): 205 """ 206 Polls histogram to see if it grows within |timeout| seconds. 207 208 @param histogram_differ: a HistogramDiffer instance used to get histogram 209 name and histogram diff multiple times. 210 @param timeout: observation timeout in seconds. 211 @param sleep_interval: interval in seconds between getting diff. 212 @returns (True, histogram_diff) if the histogram grows. 213 (False, {}) if it does not grow in |timeout| seconds. 214 """ 215 timer = utils.Timer(timeout) 216 while timer.sleep(sleep_interval): 217 histogram_diff = histogram_differ.end() 218 if histogram_diff: 219 return (True, histogram_diff) 220 return (False, {}) 221 222 223class HistogramDiffer(object): 224 """ 225 Calculates a histogram's progress between begin() and end(). 226 227 Usage: 228 differ = HistogramDiffer(cr, 'Media.GpuVideoDecoderError') 229 .... 230 diff_gvd_error = differ.end() 231 """ 232 def __init__(self, cr, histogram_name, begin=True): 233 """ 234 Constructor. 235 236 @param: cr: object, the Chrome instance 237 @param: histogram_name: string, name of the histogram 238 @param: begin: if set, calls begin(). 239 """ 240 self.cr = cr 241 self.histogram_name = histogram_name 242 self.begin_histogram_text = '' 243 self.end_histogram_text = '' 244 self.begin_histogram = {} 245 self.end_histogram = {} 246 if begin: 247 self.begin() 248 249 def _get_histogram(self): 250 """ 251 Gets current histogram bucket. 252 253 @returns (dict(bucket_value, bucket_count), histogram_text) 254 """ 255 tab = self.cr.browser.tabs.New() 256 text = get_histogram_text(tab, self.histogram_name) 257 tab.Close() 258 return (parse_histogram(text), text) 259 260 def begin(self): 261 """ 262 Takes a histogram snapshot as begin_histogram. 263 """ 264 (self.begin_histogram, 265 self.begin_histogram_text) = self._get_histogram() 266 logging.debug('begin histograms/%s: %r\nraw_text: %s', 267 self.histogram_name, self.begin_histogram, 268 self.begin_histogram_text) 269 270 def end(self): 271 """ 272 Takes a histogram snapshot as end_histogram. 273 274 @returns self.diff() 275 """ 276 self.end_histogram, self.end_histogram_text = self._get_histogram() 277 logging.debug('end histograms/%s: %r\nraw_text: %s', 278 self.histogram_name, self.end_histogram, 279 self.end_histogram_text) 280 diff = subtract_histogram(self.end_histogram, self.begin_histogram) 281 logging.debug('histogram diff: %r', diff) 282 return diff 283