blob: cfbf56c8bc474c44c41ce09938ad30cd523b70b7 [file] [log] [blame]
Mussa422993a2014-09-29 15:03:54 -07001# Copyright (c) 2013 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 re
6from collections import namedtuple
7
8from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib import error
10
11
12BucketStats = namedtuple('BucketStats', 'value percent')
13
14
15class HistogramParser(object):
16 """
17 Parses a chrome histogram page and provide access to its values.
18
19 Example usage:
20 parser = histogram_parser.HistogramParser('some_histogram_name')
21
22 # Later access amazing magical values:
23 buckets = parser.buckets
24
25 if buckets and buckets[1] == ??:
26 # do cool stuff
27
28 """
29
30 def __init__(self, chrome, histogram, time_out_s=10):
31 """
32 @param chrome: Chrome instance representing the browser in current test.
33 @param histogram: string, name of the histogram of interest.
34 @param time_out_s: int, max duration in secs to wait for specified
35 histogram to be loaded.
36
37 """
38 # This pattern was built by observing the chrome://histogram output
39 self._histogram_pattern = ('Histogram.*([0-9]+)'
40 'samples.*average.*([0-9]+\.[0-9]+)')
41
42 self._bucket_pattern = '(^[0-9]+).*\(([0-9]+)'
43
44 """
45 Match counts are based on the text that needs to be parsed.
46 E.g: "0 ---------------------------O (9 = 16.4%)" is a typical entry
47 in the list of buckets. In this case we want to match 0 and 9,
48 therefore the match count is 2.
49
50 """
51
52 self._histogram_match_count = 2
53 self._bucket_match_count = 2
54
55 self._histogram = histogram
56 self._time_out_s = time_out_s
57 self._raw_text = None
58 self._sample_count = None
59 self._average = None
60 self._buckets = {}
61 self.tab = chrome.browser.tabs.New()
62 self.wait_for_histogram_loaded()
63 self.parse()
64
65
66 @property
67 def buckets(self):
68 """
69 @returns the dictionary containing buckets and their values.
70
71 """
72 return self._buckets
73
74
75 @property
76 def sample_count(self):
77 """
78 @returns the count of all samples in histogram as int.
79
80 """
81 return self._sample_count
82
83
84 @property
85 def average(self):
86 """
87 @returns the average of bucket values as float.
88
89 """
90 return self._average
91
92
93 def wait_for_histogram_loaded(self):
94 """
95 Uses js to poll doc content until valid content is retrieved.
96
97 """
98 def loaded():
99 """
100 Checks if the histogram page has been fully loaded.
101
102 """
103
104 self.tab.Navigate('chrome://histograms/%s' % self._histogram)
105 self.tab.WaitForDocumentReadyStateToBeComplete()
106 docEle = 'document.documentElement'
107 self._raw_text = self.tab.EvaluateJavaScript(
108 "{0} && {0}.innerText".format(docEle))
109 return self._histogram in self._raw_text
110
111 msg = "%s not loaded. Waited %ss" % (self._histogram, self._time_out_s)
112
113 utils.poll_for_condition(condition=loaded,
114 exception=error.TestError(msg),
115 sleep_interval=1)
116
117 def parse(self):
118 """
119 Parses histogram text to retrieve useful properties.
120
121 @raises whatever _check_match() raises.
122
123 """
124
125 histogram_entries = self._raw_text.split('\n')
126 found_hist_title = False
127
128 for entry in histogram_entries:
129 matches = self._check_match(self._histogram_pattern,
130 entry,
131 self._histogram_match_count)
132
133 if matches:
134 if not found_hist_title:
135 self._sample_count = int(matches[0])
136 self._average = matches[1]
137 found_hist_title = True
138
139 else: # this is another histogram, bail out
140 return
141
142 else:
143 matches = self._check_match(self._bucket_pattern,
144 entry,
145 self._bucket_match_count)
146 if matches:
147 self._buckets[int(matches[0])] = int(matches[1])
148
149 bucket_sum = sum(self._buckets.values())
150
151 for key, value in self._buckets.items():
152 percent = (float(value) / bucket_sum) * 100
153 percent = round(number=percent, ndigits=2)
154 self._buckets[key] = BucketStats(value, percent)
155
156
157 def _check_match(self, pattern, text, expected_match_count):
158 """
159 Checks if provided text contains a pattern and if so expected number of
160 matches is found.
161
162 @param pattern: string, regex pattern to search for.
163 @param text: string, text to search for patterns.
164 @param expected_match_count: int, number of matches expected.
165
166 @returns: tuple, match groups, none if no match was found.
167 @raises TestError if a match was found but number of matches is not
168 equal to expected count.
169
170 """
171 m = re.match(pattern, text)
172
173 if not m:
174 return m
175
176 ln = len(m.groups())
177 if ln != expected_match_count:
178 msg = ('Expected %d matches. Got %d. Pattern: %s. Text: %s'
179 % (expected_match_count, ln, pattern, text))
180 raise error.TestError(msg)
181
182 return m.groups()
183
184
185 def __str__(self):
186 return ("Histogram name: %s. Buckets: %s"
187 % (self._histogram, str(self._buckets)))