blob: cfbf56c8bc474c44c41ce09938ad30cd523b70b7 [file] [log] [blame]
# Copyright (c) 2013 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 re
from collections import namedtuple
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
BucketStats = namedtuple('BucketStats', 'value percent')
class HistogramParser(object):
"""
Parses a chrome histogram page and provide access to its values.
Example usage:
parser = histogram_parser.HistogramParser('some_histogram_name')
# Later access amazing magical values:
buckets = parser.buckets
if buckets and buckets[1] == ??:
# do cool stuff
"""
def __init__(self, chrome, histogram, time_out_s=10):
"""
@param chrome: Chrome instance representing the browser in current test.
@param histogram: string, name of the histogram of interest.
@param time_out_s: int, max duration in secs to wait for specified
histogram to be loaded.
"""
# This pattern was built by observing the chrome://histogram output
self._histogram_pattern = ('Histogram.*([0-9]+)'
'samples.*average.*([0-9]+\.[0-9]+)')
self._bucket_pattern = '(^[0-9]+).*\(([0-9]+)'
"""
Match counts are based on the text that needs to be parsed.
E.g: "0 ---------------------------O (9 = 16.4%)" is a typical entry
in the list of buckets. In this case we want to match 0 and 9,
therefore the match count is 2.
"""
self._histogram_match_count = 2
self._bucket_match_count = 2
self._histogram = histogram
self._time_out_s = time_out_s
self._raw_text = None
self._sample_count = None
self._average = None
self._buckets = {}
self.tab = chrome.browser.tabs.New()
self.wait_for_histogram_loaded()
self.parse()
@property
def buckets(self):
"""
@returns the dictionary containing buckets and their values.
"""
return self._buckets
@property
def sample_count(self):
"""
@returns the count of all samples in histogram as int.
"""
return self._sample_count
@property
def average(self):
"""
@returns the average of bucket values as float.
"""
return self._average
def wait_for_histogram_loaded(self):
"""
Uses js to poll doc content until valid content is retrieved.
"""
def loaded():
"""
Checks if the histogram page has been fully loaded.
"""
self.tab.Navigate('chrome://histograms/%s' % self._histogram)
self.tab.WaitForDocumentReadyStateToBeComplete()
docEle = 'document.documentElement'
self._raw_text = self.tab.EvaluateJavaScript(
"{0} && {0}.innerText".format(docEle))
return self._histogram in self._raw_text
msg = "%s not loaded. Waited %ss" % (self._histogram, self._time_out_s)
utils.poll_for_condition(condition=loaded,
exception=error.TestError(msg),
sleep_interval=1)
def parse(self):
"""
Parses histogram text to retrieve useful properties.
@raises whatever _check_match() raises.
"""
histogram_entries = self._raw_text.split('\n')
found_hist_title = False
for entry in histogram_entries:
matches = self._check_match(self._histogram_pattern,
entry,
self._histogram_match_count)
if matches:
if not found_hist_title:
self._sample_count = int(matches[0])
self._average = matches[1]
found_hist_title = True
else: # this is another histogram, bail out
return
else:
matches = self._check_match(self._bucket_pattern,
entry,
self._bucket_match_count)
if matches:
self._buckets[int(matches[0])] = int(matches[1])
bucket_sum = sum(self._buckets.values())
for key, value in self._buckets.items():
percent = (float(value) / bucket_sum) * 100
percent = round(number=percent, ndigits=2)
self._buckets[key] = BucketStats(value, percent)
def _check_match(self, pattern, text, expected_match_count):
"""
Checks if provided text contains a pattern and if so expected number of
matches is found.
@param pattern: string, regex pattern to search for.
@param text: string, text to search for patterns.
@param expected_match_count: int, number of matches expected.
@returns: tuple, match groups, none if no match was found.
@raises TestError if a match was found but number of matches is not
equal to expected count.
"""
m = re.match(pattern, text)
if not m:
return m
ln = len(m.groups())
if ln != expected_match_count:
msg = ('Expected %d matches. Got %d. Pattern: %s. Text: %s'
% (expected_match_count, ln, pattern, text))
raise error.TestError(msg)
return m.groups()
def __str__(self):
return ("Histogram name: %s. Buckets: %s"
% (self._histogram, str(self._buckets)))