1# Copyright (c) 2014 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 logging
6import os
7import time
8
9from autotest_lib.client.bin import site_utils, test, utils
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib import file_utils
12from autotest_lib.client.common_lib.cros import chrome
13from autotest_lib.client.cros import power_status, power_utils
14from autotest_lib.client.cros import service_stopper
15from autotest_lib.client.cros.video import histogram_verifier
16from autotest_lib.client.cros.video import constants
17
18
19DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS = [
20        '--disable-accelerated-video-decode']
21DOWNLOAD_BASE = 'http://commondatastorage.googleapis.com/chromiumos-test-assets-public/'
22
23PLAYBACK_WITH_HW_ACCELERATION = 'playback_with_hw_acceleration'
24PLAYBACK_WITHOUT_HW_ACCELERATION = 'playback_without_hw_acceleration'
25
26# Measurement duration in seconds.
27MEASUREMENT_DURATION = 30
28# Time to exclude from calculation after playing a video [seconds].
29STABILIZATION_DURATION = 10
30
31# List of thermal throttling services that should be disabled.
32# - temp_metrics for link.
33# - thermal for daisy, snow, pit etc.
34THERMAL_SERVICES = ['temp_metrics', 'thermal']
35
36# Time in seconds to wait for cpu idle until giveup.
37WAIT_FOR_IDLE_CPU_TIMEOUT = 60.0
38# Maximum percent of cpu usage considered as idle.
39CPU_IDLE_USAGE = 0.1
40
41CPU_USAGE_DESCRIPTION = 'video_cpu_usage_'
42DROPPED_FRAMES_DESCRIPTION = 'video_dropped_frames_'
43DROPPED_FRAMES_PERCENT_DESCRIPTION = 'video_dropped_frames_percent_'
44POWER_DESCRIPTION = 'video_mean_energy_rate_'
45
46# Minimum battery charge percentage to run the test
47BATTERY_INITIAL_CHARGED_MIN = 10
48
49
50class video_PlaybackPerf(test.test):
51    """
52    The test outputs the cpu usage, the dropped frame count and the power
53    consumption for video playback to performance dashboard.
54    """
55    version = 1
56    arc_mode = None
57
58
59    def initialize(self):
60        self._service_stopper = None
61        self._original_governors = None
62        self._backlight = None
63
64
65    def start_playback(self, cr, local_path):
66        """
67        Opens the video and plays it.
68
69        @param cr: Autotest Chrome instance.
70        @param local_path: path to the local video file to play.
71        """
72        cr.browser.platform.SetHTTPServerDirectories(self.bindir)
73
74        tab = cr.browser.tabs[0]
75        tab.Navigate(cr.browser.platform.http_server.UrlOf(local_path))
76        tab.WaitForDocumentReadyStateToBeComplete()
77        tab.EvaluateJavaScript("document.getElementsByTagName('video')[0]."
78                               "loop=true")
79
80
81    def run_once(self, video_name, video_description, power_test=False,
82                 arc_mode=None):
83        """
84        Runs the video_PlaybackPerf test.
85
86        @param video_name: the name of video to play in the DOWNLOAD_BASE
87        @param video_description: a string describes the video to play which
88                will be part of entry name in dashboard.
89        @param power_test: True if this is a power test and it would only run
90                the power test. If False, it would run the cpu usage test and
91                the dropped frame count test.
92        @param arc_mode: if 'enabled', run the test with Android enabled.
93        """
94        # Download test video.
95        url = DOWNLOAD_BASE + video_name
96        local_path = os.path.join(self.bindir, os.path.basename(video_name))
97        logging.info("Downloading %s to %s", url, local_path);
98        file_utils.download_file(url, local_path)
99        self.arc_mode = arc_mode
100
101        if not power_test:
102            # Run the video playback dropped frame tests.
103            keyvals = self.test_dropped_frames(local_path)
104
105            # Every dictionary value is a tuple. The first element of the tuple
106            # is dropped frames. The second is dropped frames percent.
107            keyvals_dropped_frames = {k: v[0] for k, v in keyvals.iteritems()}
108            keyvals_dropped_frames_percent = {
109                    k: v[1] for k, v in keyvals.iteritems()}
110
111            self.log_result(keyvals_dropped_frames, DROPPED_FRAMES_DESCRIPTION +
112                                video_description, 'frames')
113            self.log_result(keyvals_dropped_frames_percent,
114                            DROPPED_FRAMES_PERCENT_DESCRIPTION +
115                                video_description, 'percent')
116
117            # Run the video playback cpu usage tests.
118            keyvals = self.test_cpu_usage(local_path)
119            self.log_result(keyvals, CPU_USAGE_DESCRIPTION + video_description,
120                            'percent')
121        else:
122            keyvals = self.test_power(local_path)
123            self.log_result(keyvals, POWER_DESCRIPTION + video_description, 'W')
124
125
126    def test_dropped_frames(self, local_path):
127        """
128        Runs the video dropped frame test.
129
130        @param local_path: the path to the video file.
131
132        @return a dictionary that contains the test result.
133        """
134        def get_dropped_frames(cr):
135            time.sleep(MEASUREMENT_DURATION)
136            tab = cr.browser.tabs[0]
137            decoded_frame_count = tab.EvaluateJavaScript(
138                    "document.getElementsByTagName"
139                    "('video')[0].webkitDecodedFrameCount")
140            dropped_frame_count = tab.EvaluateJavaScript(
141                    "document.getElementsByTagName"
142                    "('video')[0].webkitDroppedFrameCount")
143            if decoded_frame_count != 0:
144                dropped_frame_percent = \
145                        100.0 * dropped_frame_count / decoded_frame_count
146            else:
147                logging.error("No frame is decoded. Set drop percent to 100.")
148                dropped_frame_percent = 100.0
149            logging.info("Decoded frames=%d, dropped frames=%d, percent=%f",
150                              decoded_frame_count,
151                              dropped_frame_count,
152                              dropped_frame_percent)
153            return (dropped_frame_count, dropped_frame_percent)
154        return self.test_playback(local_path, get_dropped_frames)
155
156
157    def test_cpu_usage(self, local_path):
158        """
159        Runs the video cpu usage test.
160
161        @param local_path: the path to the video file.
162
163        @return a dictionary that contains the test result.
164        """
165        def get_cpu_usage(cr):
166            time.sleep(STABILIZATION_DURATION)
167            cpu_usage_start = site_utils.get_cpu_usage()
168            time.sleep(MEASUREMENT_DURATION)
169            cpu_usage_end = site_utils.get_cpu_usage()
170            return site_utils.compute_active_cpu_time(cpu_usage_start,
171                                                      cpu_usage_end) * 100
172        if not utils.wait_for_idle_cpu(WAIT_FOR_IDLE_CPU_TIMEOUT,
173                                       CPU_IDLE_USAGE):
174            raise error.TestError('Could not get idle CPU.')
175        if not utils.wait_for_cool_machine():
176            raise error.TestError('Could not get cold machine.')
177        # Stop the thermal service that may change the cpu frequency.
178        self._service_stopper = service_stopper.ServiceStopper(THERMAL_SERVICES)
179        self._service_stopper.stop_services()
180        # Set the scaling governor to performance mode to set the cpu to the
181        # highest frequency available.
182        self._original_governors = utils.set_high_performance_mode()
183        return self.test_playback(local_path, get_cpu_usage)
184
185
186    def test_power(self, local_path):
187        """
188        Runs the video power consumption test.
189
190        @param local_path: the path to the video file.
191
192        @return a dictionary that contains the test result.
193        """
194
195        self._backlight = power_utils.Backlight()
196        self._backlight.set_default()
197
198        self._service_stopper = service_stopper.ServiceStopper(
199                service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
200        self._service_stopper.stop_services()
201
202        self._power_status = power_status.get_status()
203        # Verify that we are running on battery and the battery is sufficiently
204        # charged.
205        self._power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN)
206
207        measurements = [power_status.SystemPower(
208                self._power_status.battery_path)]
209
210        def get_power(cr):
211            power_logger = power_status.PowerLogger(measurements)
212            power_logger.start()
213            time.sleep(STABILIZATION_DURATION)
214            start_time = time.time()
215            time.sleep(MEASUREMENT_DURATION)
216            power_logger.checkpoint('result', start_time)
217            keyval = power_logger.calc()
218            return keyval['result_' + measurements[0].domain + '_pwr']
219
220        return self.test_playback(local_path, get_power)
221
222
223    def test_playback(self, local_path, gather_result):
224        """
225        Runs the video playback test with and without hardware acceleration.
226
227        @param local_path: the path to the video file.
228        @param gather_result: a function to run and return the test result
229                after chrome opens. The input parameter of the funciton is
230                Autotest chrome instance.
231
232        @return a dictionary that contains test the result.
233        """
234        keyvals = {}
235
236        with chrome.Chrome(arc_mode=self.arc_mode,
237                           init_network_controller=True) as cr:
238            # Open the video playback page and start playing.
239            self.start_playback(cr, local_path)
240            result = gather_result(cr)
241
242            # Check if decode is hardware accelerated.
243            if histogram_verifier.is_bucket_present(
244                    cr,
245                    constants.MEDIA_GVD_INIT_STATUS,
246                    constants.MEDIA_GVD_BUCKET):
247                keyvals[PLAYBACK_WITH_HW_ACCELERATION] = result
248            else:
249                logging.info("Can not use hardware decoding.")
250                keyvals[PLAYBACK_WITHOUT_HW_ACCELERATION] = result
251                return keyvals
252
253        # Start chrome with disabled video hardware decode flag.
254        with chrome.Chrome(extra_browser_args=
255                DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS,
256                arc_mode=self.arc_mode) as cr:
257            # Open the video playback page and start playing.
258            self.start_playback(cr, local_path)
259            result = gather_result(cr)
260
261            # Make sure decode is not hardware accelerated.
262            if histogram_verifier.is_bucket_present(
263                    cr,
264                    constants.MEDIA_GVD_INIT_STATUS,
265                    constants.MEDIA_GVD_BUCKET):
266                raise error.TestError(
267                        'Video decode acceleration should not be working.')
268            keyvals[PLAYBACK_WITHOUT_HW_ACCELERATION] = result
269
270        return keyvals
271
272
273    def log_result(self, keyvals, description, units):
274        """
275        Logs the test result output to the performance dashboard.
276
277        @param keyvals: a dictionary that contains results returned by
278                test_playback.
279        @param description: a string that describes the video and test result
280                and it will be part of the entry name in the dashboard.
281        @param units: the units of test result.
282        """
283        result_with_hw = keyvals.get(PLAYBACK_WITH_HW_ACCELERATION)
284        if result_with_hw is not None:
285            self.output_perf_value(
286                    description= 'hw_' + description, value=result_with_hw,
287                    units=units, higher_is_better=False)
288
289        result_without_hw = keyvals[PLAYBACK_WITHOUT_HW_ACCELERATION]
290        self.output_perf_value(
291                description= 'sw_' + description, value=result_without_hw,
292                units=units, higher_is_better=False)
293
294
295    def cleanup(self):
296        # cleanup() is run by common_lib/test.py.
297        if self._backlight:
298            self._backlight.restore()
299        if self._service_stopper:
300            self._service_stopper.restore_services()
301        if self._original_governors:
302            utils.restore_scaling_governor_states(self._original_governors)
303
304        super(video_PlaybackPerf, self).cleanup()
305