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