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