1# Lint as: python2, python3
2# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import logging, time
7
8from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib.cros import chrome
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.cros import service_stopper
12from autotest_lib.client.cros.graphics import graphics_utils
13from autotest_lib.client.cros.power import power_rapl
14from autotest_lib.client.cros.power import power_status
15from autotest_lib.client.cros.power import power_utils
16
17TEST_NAME_AND_FLAGS = [
18    ['hw_overlays_hw_decode', ['']],
19    ['no_overlays_hw_decode', ['--enable-hardware-overlays=']],
20    ['hw_overlays_sw_decode', ['--disable-accelerated-video-decode']],
21    [
22        'no_overlays_sw_decode',
23        ['--disable-accelerated-video-decode', '--enable-hardware-overlays=']
24    ]
25]
26# Amount of time to wait for the URL to load and the video to start playing.
27PREAMBLE_DURATION_SECONDS = 8
28# Amount of time to let the video play while measuring power consumption.
29MEASUREMENT_DURATION_SECONDS = 12
30
31# Time in seconds to wait for cpu idle until giveup.
32IDLE_CPU_WAIT_TIMEOUT_SECONDS = 60.0
33# Maximum percent of cpu usage considered as idle.
34IDLE_CPU_LOAD_PERCENTAGE = 2.5
35
36GRAPH_NAME = 'power_consumption'
37
38
39class graphics_VideoRenderingPower(graphics_utils.GraphicsTest):
40    """This test renders on screen for a short while a video from a given
41
42    (controlled) URL while measuring the power consumption of the different SoC
43    domains.
44    """
45    version = 1
46    _backlight = None
47    _service_stopper = None
48    _power_status = None
49
50    def initialize(self):
51        super(graphics_VideoRenderingPower, self).initialize()
52
53        self._backlight = power_utils.Backlight()
54        self._backlight.set_default()
55
56        self._service_stopper = service_stopper.ServiceStopper(
57            service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
58        self._service_stopper.stop_services()
59
60        self._power_status = power_status.get_status()
61
62    def cleanup(self):
63        if self._backlight:
64            self._backlight.restore()
65        if self._service_stopper:
66            self._service_stopper.restore_services()
67        super(graphics_VideoRenderingPower, self).cleanup()
68
69    @graphics_utils.GraphicsTest.failure_report_decorator(
70        'graphics_VideoRenderingPower')
71    def run_once(self, video_url, video_short_name):
72        """Runs the graphics_VideoRenderingPower test.
73
74        @param video_url: URL with autoplay video inside. It's assumed that
75                 there's just one <video> in the HTML, and that it fits in the
76                 viewport.
77        @param video_short_name: short string describing the video; itt will be
78                 presented as part of the dashboard entry name.
79        """
80
81        # TODO(mcasas): Extend this test to non-Intel platforms.
82        if not power_utils.has_rapl_support():
83            logging.warning('This board has no RAPL power measurement support, '
84                            'skipping test.')
85            return
86
87        rapl = []
88        if power_utils.has_battery():
89            # Sometimes, the DUT is supposed to have a battery but we may not
90            # detect one. This is a symptom of a bad battery (b/145144707).
91            if self._power_status.battery_path is None:
92                raise error.TestFail('No battery found in this DUT (this is a '
93                                     'symptom of a bad battery).')
94            rapl.append(
95                power_status.SystemPower(self._power_status.battery_path))
96        else:
97            logging.warning('This board has no battery.')
98        rapl += power_rapl.create_rapl()
99
100        for test_name_and_flags in TEST_NAME_AND_FLAGS:
101            logging.info('Test case: %s', test_name_and_flags[0])
102            # Launch Chrome with the appropriate flag combination.
103            with chrome.Chrome(
104                    extra_browser_args=test_name_and_flags[1],
105                    init_network_controller=True) as cr:
106
107                if not utils.wait_for_idle_cpu(IDLE_CPU_WAIT_TIMEOUT_SECONDS,
108                                               IDLE_CPU_LOAD_PERCENTAGE):
109                    raise error.TestFail('Failed: Could not get idle CPU.')
110                if not utils.wait_for_cool_machine():
111                    raise error.TestFail('Failed: Could not get cold machine.')
112
113                tab = cr.browser.tabs[0]
114                tab.Navigate(video_url)
115                tab.WaitForDocumentReadyStateToBeComplete()
116                tab.EvaluateJavaScript(
117                    'document.'
118                    'getElementsByTagName(\'video\')[0].scrollIntoView(true)')
119
120                # Disabling hardware overlays is difficult because the flag is
121                # already in the browser. Instead, scroll a bit down to make the
122                # video bleed out of the viewport.
123                if '--enable-hardware-overlays=' in test_name_and_flags[1]:
124                    tab.EvaluateJavaScript('window.scrollBy(0, 1)')
125
126                power_logger = power_status.PowerLogger(rapl)
127                power_logger.start()
128                time.sleep(PREAMBLE_DURATION_SECONDS)
129
130                start_time = time.time()
131                time.sleep(MEASUREMENT_DURATION_SECONDS)
132                power_logger.checkpoint('result', start_time)
133
134                measurements = power_logger.calc()
135                logging.debug(measurements)
136
137                for category in sorted(measurements):
138                    if category.endswith('_pwr_avg'):
139                        description = '%s_%s_%s' % (
140                            video_short_name, test_name_and_flags[0], category)
141                        self.output_perf_value(
142                            description=description,
143                            value=measurements[category],
144                            units='W',
145                            higher_is_better=False,
146                            graph=GRAPH_NAME)
147
148                    if category.endswith('_pwr_avg'):
149                        # write_perf_keyval() wants units (W) first in lowercase.
150                        description = '%s_%s_%s' % (
151                            video_short_name, test_name_and_flags[0], category)
152                        self.write_perf_keyval({
153                            'w_' + description: measurements[category]
154                        })
155