• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 The Chromium 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 hashlib, logging, os, re, time, tempfile
6
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib import file_utils
10from autotest_lib.client.cros import chrome_binary_test
11from autotest_lib.client.cros import service_stopper
12from autotest_lib.client.cros.audio import cmd_utils
13from autotest_lib.client.cros.power import power_status, power_utils
14from autotest_lib.client.cros.video import device_capability
15from autotest_lib.client.cros.video import helper_logger
16
17# The download base for test assets.
18DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com'
19                 '/chromiumos-test-assets-public/')
20
21# The executable name of the vda unittest
22VDA_BINARY = 'video_decode_accelerator_unittest'
23
24# The executable name of the vea unittest
25VEA_BINARY = 'video_encode_accelerator_unittest'
26
27# The input frame rate for the vea_unittest.
28INPUT_FPS = 30
29
30# The rendering fps in the vda_unittest.
31RENDERING_FPS = 30
32
33UNIT_PERCENT = '%'
34UNIT_WATT = 'W'
35
36# The regex of the versioning file.
37# e.g., crowd720-3cfe7b096f765742b4aa79e55fe7c994.yuv
38RE_VERSIONING_FILE = re.compile(r'(.+)-([0-9a-fA-F]{32})(\..+)?')
39
40# Time in seconds to wait for cpu idle until giveup.
41WAIT_FOR_IDLE_CPU_TIMEOUT = 60
42
43# Maximum percent of cpu usage considered as idle.
44CPU_IDLE_USAGE = 0.1
45
46# Measurement duration in seconds.
47MEASUREMENT_DURATION = 30
48
49# Time to exclude from calculation after playing a video [seconds].
50STABILIZATION_DURATION = 10
51
52# A big number, used to keep the [vda|vea]_unittest running during the
53# measurement.
54MAX_INT = 2 ** 31 - 1
55
56# Minimum battery charge percentage to run the test
57BATTERY_INITIAL_CHARGED_MIN = 10
58
59
60class CpuUsageMeasurer(object):
61    """ Class used to measure the CPU usage."""
62
63    def __init__(self):
64        self._service_stopper = None
65        self._original_governors = None
66
67    def __enter__(self):
68        # Stop the thermal service that may change the cpu frequency.
69        self._service_stopper = service_stopper.get_thermal_service_stopper()
70        self._service_stopper.stop_services()
71
72        if not utils.wait_for_idle_cpu(
73                WAIT_FOR_IDLE_CPU_TIMEOUT, CPU_IDLE_USAGE):
74            raise error.TestError('Could not get idle CPU.')
75        if not utils.wait_for_cool_machine():
76            raise error.TestError('Could not get cold machine.')
77
78        # Set the scaling governor to performance mode to set the cpu to the
79        # highest frequency available.
80        self._original_governors = utils.set_high_performance_mode()
81        return self
82
83    def start(self):
84        self.start_cpu_usage_ = utils.get_cpu_usage()
85
86    def stop(self):
87        return utils.compute_active_cpu_time(
88                self.start_cpu_usage_, utils.get_cpu_usage())
89
90    def __exit__(self, type, value, tb):
91        if self._service_stopper:
92            self._service_stopper.restore_services()
93            self._service_stopper = None
94        if self._original_governors:
95            utils.restore_scaling_governor_states(self._original_governors)
96            self._original_governors = None
97
98
99class PowerMeasurer(object):
100    """ Class used to measure the power consumption."""
101
102    def __init__(self):
103        self._backlight = None
104        self._service_stopper = None
105
106    def __enter__(self):
107        status = power_status.get_status()
108
109        # We expect the DUT is powered by battery now. But this is not always
110        # true due to other bugs. Disable this test temporarily as workaround.
111        # TODO(johnylin): remove this workaround after AC control is stable
112        #                 crbug.com/914211
113        if status.on_ac():
114            logging.warning('Still powered by AC. Skip this test')
115            raise error.TestNAError('Unable to disable AC.')
116        # Verify that we are running on battery and the battery is sufficiently
117        # charged.
118        status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN)
119
120        self._backlight = power_utils.Backlight()
121        self._backlight.set_default()
122
123        self._service_stopper = service_stopper.ServiceStopper(
124                service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
125        self._service_stopper.stop_services()
126
127        self._system_power = power_status.SystemPower(status.battery_path)
128        self._power_logger = power_status.PowerLogger([self._system_power])
129        return self
130
131    def start(self):
132        self._power_logger.start()
133
134    def stop(self):
135        self._power_logger.checkpoint('result')
136        keyval = self._power_logger.calc()
137        logging.info(keyval)
138        return keyval['result_' + self._system_power.domain + '_pwr_avg']
139
140    def __exit__(self, type, value, tb):
141        if self._backlight:
142            self._backlight.restore()
143        if self._service_stopper:
144            self._service_stopper.restore_services()
145
146
147class DownloadManager(object):
148    """Use this class to download and manage the resources for testing."""
149
150    def __init__(self, tmpdir=None):
151        self._download_map = {}
152        self._tmpdir = tmpdir
153
154    def get_path(self, name):
155        return self._download_map[name]
156
157    def clear(self):
158        map(os.unlink, self._download_map.values())
159        self._download_map.clear()
160
161    def _download_single_file(self, remote_path):
162        url = DOWNLOAD_BASE + remote_path
163        tmp = tempfile.NamedTemporaryFile(delete=False, dir=self._tmpdir)
164        logging.info('download "%s" to "%s"', url, tmp.name)
165
166        file_utils.download_file(url, tmp.name)
167        md5 = hashlib.md5()
168        with open(tmp.name, 'r') as r:
169            while True:
170                block = r.read(128 * 1024)
171                if not block:
172                    break
173                md5.update(block)
174
175        filename = os.path.basename(remote_path)
176        m = RE_VERSIONING_FILE.match(filename)
177        if m:
178            prefix, md5sum, suffix = m.groups()
179            if md5.hexdigest() != md5sum:
180                raise error.TestError(
181                        'unmatched md5 sum: %s' % md5.hexdigest())
182            filename = prefix + (suffix or '')
183        self._download_map[filename] = tmp.name
184
185    def download_all(self, resources):
186        for r in resources:
187            self._download_single_file(r)
188
189
190class video_HangoutHardwarePerf(chrome_binary_test.ChromeBinaryTest):
191    """
192    The test outputs the cpu usage when doing video encoding and video
193    decoding concurrently.
194    """
195
196    version = 1
197
198    def get_vda_unittest_cmd_line(self, decode_videos):
199        test_video_data = []
200        for v in decode_videos:
201            assert len(v) == 6
202            # Convert to strings, also make a copy of the list.
203            v = map(str, v)
204            v[0] = self._downloads.get_path(v[0])
205            v[-1:-1] = ['0', '0'] # no fps requirements
206            test_video_data.append(':'.join(v))
207        cmd_line = [
208            self.get_chrome_binary_path(VDA_BINARY),
209            '--gtest_filter=DecodeVariations/*/0',
210            '--test_video_data=%s' % ';'.join(test_video_data),
211            '--rendering_fps=%f' % RENDERING_FPS,
212            '--num_play_throughs=%d' % MAX_INT,
213            helper_logger.chrome_vmodule_flag(),
214        ]
215        cmd_line.append('--ozone-platform=gbm')
216        return cmd_line
217
218
219    def get_vea_unittest_cmd_line(self, encode_videos):
220        test_stream_data = []
221        for v in encode_videos:
222            assert len(v) == 5
223            # Convert to strings, also make a copy of the list.
224            v = map(str, v)
225            v[0] = self._downloads.get_path(v[0])
226            # The output destination, ignore the output.
227            v.insert(4, '/dev/null')
228            # Insert the FPS requirement
229            v.append(str(INPUT_FPS))
230            test_stream_data.append(':'.join(v))
231        cmd_line = [
232            self.get_chrome_binary_path(VEA_BINARY),
233            '--gtest_filter=SimpleEncode/*/0',
234            '--test_stream_data=%s' % ';'.join(test_stream_data),
235            '--run_at_fps',
236            '--num_frames_to_encode=%d' % MAX_INT,
237            helper_logger.chrome_vmodule_flag(),
238        ]
239        cmd_line.append('--ozone-platform=gbm')
240        return cmd_line
241
242    def run_in_parallel(self, *commands):
243        env = os.environ.copy()
244
245        # To clear the temparory files created by vea_unittest.
246        env['TMPDIR'] = self.tmpdir
247        return map(lambda c: cmd_utils.popen(c, env=env), commands)
248
249    def simulate_hangout(self, decode_videos, encode_videos, measurer,
250                         decode_threads, encode_threads):
251        decode_cmds = [self.get_vda_unittest_cmd_line(decode_videos)
252                       for i in range(decode_threads)]
253        encode_cmds = [self.get_vea_unittest_cmd_line(encode_videos)
254                       for i in range(encode_threads)]
255        popens = self.run_in_parallel(*(decode_cmds + encode_cmds))
256        try:
257            time.sleep(STABILIZATION_DURATION)
258            measurer.start()
259            time.sleep(MEASUREMENT_DURATION)
260            measurement = measurer.stop()
261
262            # Ensure both encoding and decoding are still alive
263            if any(p.poll() is not None for p in popens):
264                raise error.TestError('vea/vda_unittest failed')
265
266            return measurement
267        finally:
268            cmd_utils.kill_or_log_returncode(*popens)
269
270    @helper_logger.video_log_wrapper
271    @chrome_binary_test.nuke_chrome
272    def run_once(self, resources, decode_videos, encode_videos, measurement,
273                 capabilities, decode_threads=1, encode_threads=1):
274        dc = device_capability.DeviceCapability()
275        for cap in capabilities:
276            dc.ensure_capability(cap)
277
278        self._downloads = DownloadManager(tmpdir = self.tmpdir)
279        try:
280            self._downloads.download_all(resources)
281            if measurement == 'cpu':
282                with CpuUsageMeasurer() as measurer:
283                    value = self.simulate_hangout(
284                            decode_videos, encode_videos, measurer,
285                            decode_threads, encode_threads)
286                    self.output_perf_value(
287                            description='cpu_usage', value=value * 100,
288                            units=UNIT_PERCENT, higher_is_better=False)
289            elif measurement == 'power':
290                with PowerMeasurer() as measurer:
291                    value = self.simulate_hangout(
292                            decode_videos, encode_videos, measurer,
293                            decode_threads, encode_threads)
294                    self.output_perf_value(
295                            description='power_usage', value=value,
296                            units=UNIT_WATT, higher_is_better=False)
297            else:
298                raise error.TestError('Unknown measurement: ' + measurement)
299        finally:
300            self._downloads.clear()
301