# Copyright 2015 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import collections import logging import re from autotest_lib.client.bin import utils from autotest_lib.client.common_lib import error def get_histogram_text(tab, histogram_name): """ This returns contents of the given histogram. @param tab: object, Chrome tab instance @param histogram_name: string, name of the histogram @returns string: contents of the histogram """ docEle = 'document.documentElement' tab.Navigate('chrome://histograms/%s' % histogram_name) tab.WaitForDocumentReadyStateToBeComplete() raw_text = tab.EvaluateJavaScript( '{0} && {0}.innerText'.format(docEle)) # extract the contents of the histogram histogram = raw_text[raw_text.find('Histogram:'):].strip() if histogram: logging.debug('chrome://histograms/%s:\n%s', histogram_name, histogram) else: logging.debug('No histogram is shown in chrome://histograms/%s', histogram_name) return histogram def loaded(tab, histogram_name, pattern): """ Checks if the histogram page has been fully loaded. @param tab: object, Chrome tab instance @param histogram_name: string, name of the histogram @param pattern: string, required text to look for @returns re.MatchObject if the given pattern is found in the text None otherwise """ return re.search(pattern, get_histogram_text(tab, histogram_name)) def verify(cr, histogram_name, histogram_bucket_value): """ Verifies histogram string and success rate in a parsed histogram bucket. The histogram buckets are outputted in debug log regardless of the verification result. Full histogram URL is used to load histogram. Example Histogram URL is : chrome://histograms/Media.GpuVideoDecoderInitializeStatus @param cr: object, the Chrome instance @param histogram_name: string, name of the histogram @param histogram_bucket_value: int, required bucket number to look for @raises error.TestError if histogram is not successful """ bucket_pattern = '\n'+ str(histogram_bucket_value) +'.*100\.0%.*' error_msg_format = ('{} not loaded or histogram bucket not found ' 'or histogram bucket found at < 100%') tab = cr.browser.tabs.New() msg = error_msg_format.format(histogram_name) utils.poll_for_condition(lambda : loaded(tab, histogram_name, bucket_pattern), exception=error.TestError(msg), sleep_interval=1) def is_bucket_present(cr,histogram_name, histogram_bucket_value): """ This returns histogram succes or fail to called function @param cr: object, the Chrome instance @param histogram_name: string, name of the histogram @param histogram_bucket_value: int, required bucket number to look for @returns True if histogram page was loaded and the bucket was found. False otherwise """ try: verify(cr,histogram_name, histogram_bucket_value) except error.TestError: return False else: return True def is_histogram_present(cr, histogram_name): """ This checks if the given histogram is present and non-zero. @param cr: object, the Chrome instance @param histogram_name: string, name of the histogram @returns True if histogram page was loaded and the histogram is present False otherwise """ histogram_pattern = 'Histogram: '+ histogram_name + ' recorded ' + \ r'[1-9][0-9]*' + ' samples' tab = cr.browser.tabs.New() try: utils.poll_for_condition(lambda : loaded(tab, histogram_name, histogram_pattern), timeout=2, sleep_interval=0.1) return True except utils.TimeoutError: # the histogram is not present, and then returns false return False def get_histogram(cr, histogram_name): """ This returns contents of the given histogram. @param cr: object, the Chrome instance @param histogram_name: string, name of the histogram @returns string: contents of the histogram """ tab = cr.browser.tabs.New() return get_histogram_text(tab, histogram_name) def parse_histogram(histogram_text): """ Parses histogram text into bucket structure. @param histogram_text: histogram raw text. @returns dict(bucket_value, bucket_count) """ # Match separator line, e.g. "1 ..." RE_SEPEARTOR = re.compile(r'\d+\s+\.\.\.') # Match bucket line, e.g. "2 --O (46 = 1.5%) {46.1%}" RE_BUCKET = re.compile( r'(\d+)\s+\-*O\s+\((\d+) = (\d+\.\d+)%\).*') result = {} for line in histogram_text.splitlines(): if RE_SEPEARTOR.match(line): continue m = RE_BUCKET.match(line) if m: result[int(m.group(1))] = int(m.group(2)) return result def subtract_histogram(minuend, subtrahend): """ Subtracts histogram: minuend - subtrahend @param minuend: histogram bucket dict from which another is to be subtracted. @param subtrahend: histogram bucket dict to be subtracted from another. @result difference of the two histograms in bucket dict. Note that zero-counted buckets are removed. """ result = collections.defaultdict(int, minuend) for k, v in subtrahend.iteritems(): result[k] -= v # Remove zero counted buckets. return {k: v for k, v in result.iteritems() if v} def expect_sole_bucket(histogram_differ, bucket, bucket_name, timeout=10, sleep_interval=1): """ Returns true if the given bucket solely exists in histogram differ. @param histogram_differ: a HistogramDiffer instance used to get histogram name and histogram diff multiple times. @param bucket: bucket value. @param bucket_name: bucket name to be shown on error message. @param timeout: timeout in seconds. @param sleep_interval: interval in seconds between getting diff. @returns True if the given bucket solely exists in histogram. @raises TestError if bucket doesn't exist or other buckets exist. """ timer = utils.Timer(timeout) histogram = {} histogram_name = histogram_differ.histogram_name while timer.sleep(sleep_interval): histogram = histogram_differ.end() if histogram: break if bucket not in histogram: raise error.TestError('Expect %s has %s. Histogram: %r' % (histogram_name, bucket_name, histogram)) if len(histogram) > 1: raise error.TestError('%s has bucket other than %s. Histogram: %r' % (histogram_name, bucket_name, histogram)) return True def poll_histogram_grow(histogram_differ, timeout=2, sleep_interval=0.1): """ Polls histogram to see if it grows within |timeout| seconds. @param histogram_differ: a HistogramDiffer instance used to get histogram name and histogram diff multiple times. @param timeout: observation timeout in seconds. @param sleep_interval: interval in seconds between getting diff. @returns (True, histogram_diff) if the histogram grows. (False, {}) if it does not grow in |timeout| seconds. """ timer = utils.Timer(timeout) while timer.sleep(sleep_interval): histogram_diff = histogram_differ.end() if histogram_diff: return (True, histogram_diff) return (False, {}) class HistogramDiffer(object): """ Calculates a histogram's progress between begin() and end(). Usage: differ = HistogramDiffer(cr, 'Media.GpuVideoDecoderError') .... diff_gvd_error = differ.end() """ def __init__(self, cr, histogram_name, begin=True): """ Constructor. @param: cr: object, the Chrome instance @param: histogram_name: string, name of the histogram @param: begin: if set, calls begin(). """ self.cr = cr self.histogram_name = histogram_name self.begin_histogram_text = '' self.end_histogram_text = '' self.begin_histogram = {} self.end_histogram = {} if begin: self.begin() def _get_histogram(self): """ Gets current histogram bucket. @returns (dict(bucket_value, bucket_count), histogram_text) """ tab = self.cr.browser.tabs.New() text = get_histogram_text(tab, self.histogram_name) tab.Close() return (parse_histogram(text), text) def begin(self): """ Takes a histogram snapshot as begin_histogram. """ (self.begin_histogram, self.begin_histogram_text) = self._get_histogram() logging.debug('begin histograms/%s: %r\nraw_text: %s', self.histogram_name, self.begin_histogram, self.begin_histogram_text) def end(self): """ Takes a histogram snapshot as end_histogram. @returns self.diff() """ self.end_histogram, self.end_histogram_text = self._get_histogram() logging.debug('end histograms/%s: %r\nraw_text: %s', self.histogram_name, self.end_histogram, self.end_histogram_text) diff = subtract_histogram(self.end_histogram, self.begin_histogram) logging.debug('histogram diff: %r', diff) return diff