Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / platform / android_platform_backend.py
1 # Copyright 2013 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
5 import logging
6 import os
7 import re
8 import shutil
9 import subprocess
10 import tempfile
11 import time
12
13 from telemetry import decorators
14 from telemetry.core import exceptions
15 from telemetry.core import platform
16 from telemetry.core import util
17 from telemetry.core import video
18 from telemetry.core.backends import adb_commands
19 from telemetry.core.platform import android_device
20 from telemetry.core.platform import linux_based_platform_backend
21 from telemetry.core.platform.power_monitor import android_ds2784_power_monitor
22 from telemetry.core.platform.power_monitor import android_dumpsys_power_monitor
23 from telemetry.core.platform.power_monitor import android_temperature_monitor
24 from telemetry.core.platform.power_monitor import monsoon_power_monitor
25 from telemetry.core.platform.power_monitor import power_monitor_controller
26 from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
27 from telemetry.util import exception_formatter
28
29 util.AddDirToPythonPath(util.GetChromiumSrcDir(),
30                         'third_party', 'webpagereplay')
31 import adb_install_cert  # pylint: disable=F0401
32 import certutils  # pylint: disable=F0401
33
34 # Get build/android scripts into our path.
35 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
36 from pylib import screenshot  # pylint: disable=F0401
37 from pylib.device import device_errors  # pylint: disable=F0401
38 from pylib.perf import cache_control  # pylint: disable=F0401
39 from pylib.perf import perf_control  # pylint: disable=F0401
40 from pylib.perf import thermal_throttle  # pylint: disable=F0401
41
42 try:
43   from pylib.perf import surface_stats_collector  # pylint: disable=F0401
44 except Exception:
45   surface_stats_collector = None
46
47
48 class AndroidPlatformBackend(
49     linux_based_platform_backend.LinuxBasedPlatformBackend):
50   def __init__(self, device):
51     assert device, (
52         'AndroidPlatformBackend can only be initialized from remote device')
53     super(AndroidPlatformBackend, self).__init__(device)
54     self._adb = adb_commands.AdbCommands(device=device.device_id)
55     installed_prebuilt_tools = adb_commands.SetupPrebuiltTools(self._adb)
56     if not installed_prebuilt_tools:
57       logging.error(
58           '%s detected, however prebuilt android tools could not '
59           'be used. To run on Android you must build them first:\n'
60           '  $ ninja -C out/Release android_tools' % device.name)
61       raise exceptions.PlatformError()
62     # Trying to root the device, if possible.
63     if not self._adb.IsRootEnabled():
64       # Ignore result.
65       self._adb.EnableAdbRoot()
66     self._device = self._adb.device()
67     self._enable_performance_mode = device.enable_performance_mode
68     self._surface_stats_collector = None
69     self._perf_tests_setup = perf_control.PerfControl(self._device)
70     self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device)
71     self._raw_display_frame_rate_measurements = []
72     self._can_access_protected_file_contents = \
73         self._device.old_interface.CanAccessProtectedFileContents()
74     power_controller = power_monitor_controller.PowerMonitorController([
75         monsoon_power_monitor.MonsoonPowerMonitor(self._device, self),
76         android_ds2784_power_monitor.DS2784PowerMonitor(self._device, self),
77         android_dumpsys_power_monitor.DumpsysPowerMonitor(self._device, self),
78     ])
79     self._power_monitor = android_temperature_monitor.AndroidTemperatureMonitor(
80         power_controller, self._device)
81     self._video_recorder = None
82     self._installed_applications = None
83
84     self._wpr_ca_cert_path = None
85     self._device_cert_util = None
86
87   @classmethod
88   def SupportsDevice(cls, device):
89     return isinstance(device, android_device.AndroidDevice)
90
91   @property
92   def adb(self):
93     return self._adb
94
95   def IsRawDisplayFrameRateSupported(self):
96     return True
97
98   def StartRawDisplayFrameRateMeasurement(self):
99     assert not self._surface_stats_collector
100     # Clear any leftover data from previous timed out tests
101     self._raw_display_frame_rate_measurements = []
102     self._surface_stats_collector = \
103         surface_stats_collector.SurfaceStatsCollector(self._device)
104     self._surface_stats_collector.Start()
105
106   def StopRawDisplayFrameRateMeasurement(self):
107     if not self._surface_stats_collector:
108       return
109
110     self._surface_stats_collector.Stop()
111     for r in self._surface_stats_collector.GetResults():
112       self._raw_display_frame_rate_measurements.append(
113           platform.Platform.RawDisplayFrameRateMeasurement(
114               r.name, r.value, r.unit))
115
116     self._surface_stats_collector = None
117
118   def GetRawDisplayFrameRateMeasurements(self):
119     ret = self._raw_display_frame_rate_measurements
120     self._raw_display_frame_rate_measurements = []
121     return ret
122
123   def SetFullPerformanceModeEnabled(self, enabled):
124     if not self._enable_performance_mode:
125       logging.warning('CPU governor will not be set!')
126       return
127     if enabled:
128       self._perf_tests_setup.SetHighPerfMode()
129     else:
130       self._perf_tests_setup.SetDefaultPerfMode()
131
132   def CanMonitorThermalThrottling(self):
133     return True
134
135   def IsThermallyThrottled(self):
136     return self._thermal_throttle.IsThrottled()
137
138   def HasBeenThermallyThrottled(self):
139     return self._thermal_throttle.HasBeenThrottled()
140
141   def GetCpuStats(self, pid):
142     if not self._can_access_protected_file_contents:
143       logging.warning('CPU stats cannot be retrieved on non-rooted device.')
144       return {}
145     return super(AndroidPlatformBackend, self).GetCpuStats(pid)
146
147   def GetCpuTimestamp(self):
148     if not self._can_access_protected_file_contents:
149       logging.warning('CPU timestamp cannot be retrieved on non-rooted device.')
150       return {}
151     return super(AndroidPlatformBackend, self).GetCpuTimestamp()
152
153   def PurgeUnpinnedMemory(self):
154     """Purges the unpinned ashmem memory for the whole system.
155
156     This can be used to make memory measurements more stable. Requires root.
157     """
158     if not self._can_access_protected_file_contents:
159       logging.warning('Cannot run purge_ashmem. Requires a rooted device.')
160       return
161
162     if not android_prebuilt_profiler_helper.InstallOnDevice(
163         self._device, 'purge_ashmem'):
164       raise Exception('Error installing purge_ashmem.')
165     (status, output) = self._device.old_interface.GetAndroidToolStatusAndOutput(
166         android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'),
167         log_result=True)
168     if status != 0:
169       raise Exception('Error while purging ashmem: ' + '\n'.join(output))
170
171   def GetMemoryStats(self, pid):
172     memory_usage = self._device.GetMemoryUsageForPid(pid)
173     if not memory_usage:
174       return {}
175     return {'ProportionalSetSize': memory_usage['Pss'] * 1024,
176             'SharedDirty': memory_usage['Shared_Dirty'] * 1024,
177             'PrivateDirty': memory_usage['Private_Dirty'] * 1024,
178             'VMPeak': memory_usage['VmHWM'] * 1024}
179
180   def GetIOStats(self, pid):
181     return {}
182
183   def GetChildPids(self, pid):
184     child_pids = []
185     ps = self.GetPsOutput(['pid', 'name'])
186     for curr_pid, curr_name in ps:
187       if int(curr_pid) == pid:
188         name = curr_name
189         for curr_pid, curr_name in ps:
190           if curr_name.startswith(name) and curr_name != name:
191             child_pids.append(int(curr_pid))
192         break
193     return child_pids
194
195   @decorators.Cache
196   def GetCommandLine(self, pid):
197     ps = self.GetPsOutput(['pid', 'name'], pid)
198     if not ps:
199       raise exceptions.ProcessGoneException()
200     return ps[0][1]
201
202   def GetOSName(self):
203     return 'android'
204
205   @decorators.Cache
206   def GetOSVersionName(self):
207     return self._device.GetProp('ro.build.id')[0]
208
209   def CanFlushIndividualFilesFromSystemCache(self):
210     return False
211
212   def FlushEntireSystemCache(self):
213     cache = cache_control.CacheControl(self._device)
214     cache.DropRamCaches()
215
216   def FlushSystemCacheForDirectory(self, directory, ignoring=None):
217     raise NotImplementedError()
218
219   def FlushDnsCache(self):
220     self._device.RunShellCommand('ndc resolver flushdefaultif', as_root=True)
221
222   def StopApplication(self, application):
223     """Stop the given |application|.
224
225     Args:
226        application: The full package name string of the application to stop.
227     """
228     self._device.ForceStop(application)
229
230   def KillApplication(self, application):
231     """Kill the given application.
232
233     Args:
234       application: The full package name string of the application to kill.
235     """
236     # We use KillAll rather than ForceStop for efficiency reasons.
237     try:
238       self._adb.device().KillAll(application, retries=0)
239       time.sleep(3)
240     except device_errors.CommandFailedError:
241       pass
242
243   def LaunchApplication(
244       self, application, parameters=None, elevate_privilege=False):
245     """Launches the given |application| with a list of |parameters| on the OS.
246
247     Args:
248       application: The full package name string of the application to launch.
249       parameters: A list of parameters to be passed to the ActivityManager.
250       elevate_privilege: Currently unimplemented on Android.
251     """
252     if elevate_privilege:
253       raise NotImplementedError("elevate_privilege isn't supported on android.")
254     if not parameters:
255       parameters = ''
256     result_lines = self._device.RunShellCommand('am start %s %s' %
257                                                 (parameters, application))
258     for line in result_lines:
259       if line.startswith('Error: '):
260         raise ValueError('Failed to start "%s" with error\n  %s' %
261                          (application, line))
262
263   def IsApplicationRunning(self, application):
264     return len(self._device.GetPids(application)) > 0
265
266   def CanLaunchApplication(self, application):
267     if not self._installed_applications:
268       self._installed_applications = self._device.RunShellCommand(
269           'pm list packages')
270     return 'package:' + application in self._installed_applications
271
272   def InstallApplication(self, application):
273     self._installed_applications = None
274     self._device.Install(application)
275
276   @decorators.Cache
277   def CanCaptureVideo(self):
278     return self.GetOSVersionName() >= 'K'
279
280   def StartVideoCapture(self, min_bitrate_mbps):
281     """Starts the video capture at specified bitrate."""
282     min_bitrate_mbps = max(min_bitrate_mbps, 0.1)
283     if min_bitrate_mbps > 100:
284       raise ValueError('Android video capture cannot capture at %dmbps. '
285                        'Max capture rate is 100mbps.' % min_bitrate_mbps)
286     if self.is_video_capture_running:
287       self._video_recorder.Stop()
288     self._video_recorder = screenshot.VideoRecorder(
289         self._device, megabits_per_second=min_bitrate_mbps)
290     self._video_recorder.Start()
291     util.WaitFor(self._video_recorder.IsStarted, 5)
292
293   @property
294   def is_video_capture_running(self):
295     return self._video_recorder is not None
296
297   def StopVideoCapture(self):
298     assert self.is_video_capture_running, 'Must start video capture first'
299     self._video_recorder.Stop()
300     video_file_obj = tempfile.NamedTemporaryFile()
301     self._video_recorder.Pull(video_file_obj.name)
302     self._video_recorder = None
303
304     return video.Video(video_file_obj)
305
306   def CanMonitorPower(self):
307     return self._power_monitor.CanMonitorPower()
308
309   def StartMonitoringPower(self, browser):
310     self._power_monitor.StartMonitoringPower(browser)
311
312   def StopMonitoringPower(self):
313     return self._power_monitor.StopMonitoringPower()
314
315   def GetFileContents(self, fname):
316     if not self._can_access_protected_file_contents:
317       logging.warning('%s cannot be retrieved on non-rooted device.' % fname)
318       return ''
319     return '\n'.join(self._device.ReadFile(fname, as_root=True))
320
321   def GetPsOutput(self, columns, pid=None):
322     assert columns == ['pid', 'name'] or columns == ['pid'], \
323         'Only know how to return pid and name. Requested: ' + columns
324     command = 'ps'
325     if pid:
326       command += ' -p %d' % pid
327     ps = self._device.RunShellCommand(command)[1:]
328     output = []
329     for line in ps:
330       data = line.split()
331       curr_pid = data[1]
332       curr_name = data[-1]
333       if columns == ['pid', 'name']:
334         output.append([curr_pid, curr_name])
335       else:
336         output.append([curr_pid])
337     return output
338
339   def RunCommand(self, command):
340     return '\n'.join(self._device.RunShellCommand(command))
341
342   @staticmethod
343   def ParseCStateSample(sample):
344     sample_stats = {}
345     for cpu in sample:
346       values = sample[cpu].splitlines()
347       # Each state has three values after excluding the time value.
348       num_states = (len(values) - 1) / 3
349       names = values[:num_states]
350       times = values[num_states:2 * num_states]
351       cstates = {'C0': int(values[-1]) * 10 ** 6}
352       for i, state in enumerate(names):
353         if state == 'C0':
354           # The Exynos cpuidle driver for the Nexus 10 uses the name 'C0' for
355           # its WFI state.
356           # TODO(tmandel): We should verify that no other Android device
357           # actually reports time in C0 causing this to report active time as
358           # idle time.
359           state = 'WFI'
360         cstates[state] = int(times[i])
361         cstates['C0'] -= int(times[i])
362       sample_stats[cpu] = cstates
363     return sample_stats
364
365   def SetRelaxSslCheck(self, value):
366     old_flag = self._device.GetProp('socket.relaxsslcheck')
367     self._device.SetProp('socket.relaxsslcheck', value)
368     return old_flag
369
370   def ForwardHostToDevice(self, host_port, device_port):
371     self._adb.Forward('tcp:%d' % host_port, device_port)
372
373   def DismissCrashDialogIfNeeded(self):
374     """Dismiss any error dialogs.
375
376     Limit the number in case we have an error loop or we are failing to dismiss.
377     """
378     for _ in xrange(10):
379       if not self._device.old_interface.DismissCrashDialogIfNeeded():
380         break
381
382   def IsAppRunning(self, process_name):
383     """Determine if the given process is running.
384
385     Args:
386       process_name: The full package name string of the process.
387     """
388     pids = self._adb.ExtractPid(process_name)
389     return len(pids) != 0
390
391   @property
392   def wpr_ca_cert_path(self):
393     """Path to root certificate installed on browser (or None).
394
395     If this is set, web page replay will use it to sign HTTPS responses.
396     """
397     if self._wpr_ca_cert_path:
398       assert os.path.isfile(self._wpr_ca_cert_path)
399     return self._wpr_ca_cert_path
400
401   def InstallTestCa(self):
402     """Install a randomly generated root CA on the android device.
403
404     This allows transparent HTTPS testing with WPR server without need
405     to tweak application network stack.
406
407     Returns:
408       True if the certificate installation succeeded.
409     """
410     if certutils.openssl_import_error:
411       logging.warn(
412           'The OpenSSL module is unavailable. '
413           'Will fallback to ignoring certificate errors.')
414       return False
415
416     try:
417       self._wpr_ca_cert_path = os.path.join(tempfile.mkdtemp(), 'testca.pem')
418       certutils.write_dummy_ca_cert(*certutils.generate_dummy_ca_cert(),
419                                     cert_path=self._wpr_ca_cert_path)
420       self._device_cert_util = adb_install_cert.AndroidCertInstaller(
421           self._adb.device_serial(), None, self._wpr_ca_cert_path)
422       logging.info('Installing test certificate authority on device: %s',
423                    self._adb.device_serial())
424       self._device_cert_util.install_cert(overwrite_cert=True)
425     except Exception:
426       # Fallback to ignoring certificate errors.
427       self.RemoveTestCa()
428       exception_formatter.PrintFormattedException(
429           msg=('Unable to install test certificate authority on device: %s. '
430                'Will fallback to ignoring certificate errors.'
431                % self._adb.device_serial()))
432       return False
433     return True
434
435   def RemoveTestCa(self):
436     """Remove root CA generated by previous call to InstallTestCa().
437
438     Removes the test root certificate from both the device and host machine.
439     """
440     if not self._wpr_ca_cert_path:
441       return
442
443     if self._device_cert_util:
444       self._device_cert_util.remove_cert()
445
446     shutil.rmtree(os.path.dirname(self._wpr_ca_cert_path), ignore_errors = True)
447     self._wpr_ca_cert_path = None
448     self._device_cert_util = None
449
450   def PushProfile(self, package, new_profile_dir):
451     """Replace application profile with files found on host machine.
452
453     Pushing the profile is slow, so we don't want to do it every time.
454     Avoid this by pushing to a safe location using PushChangedFiles, and
455     then copying into the correct location on each test run.
456
457     Args:
458       package: The full package name string of the application for which the
459         profile is to be updated.
460       new_profile_dir: Location where profile to be pushed is stored on the
461         host machine.
462     """
463     (profile_parent, profile_base) = os.path.split(new_profile_dir)
464     # If the path ends with a '/' python split will return an empty string for
465     # the base name; so we now need to get the base name from the directory.
466     if not profile_base:
467       profile_base = os.path.basename(profile_parent)
468
469     saved_profile_location = '/sdcard/profile/%s' % profile_base
470     self._device.PushChangedFiles([(new_profile_dir, saved_profile_location)])
471
472     profile_dir = self._GetProfileDir(package)
473     self._device.old_interface.EfficientDeviceDirectoryCopy(
474         saved_profile_location, profile_dir)
475     dumpsys = self._device.RunShellCommand('dumpsys package %s' % package)
476     id_line = next(line for line in dumpsys if 'userId=' in line)
477     uid = re.search(r'\d+', id_line).group()
478     files = self._device.RunShellCommand(
479         'ls "%s"' % profile_dir, as_root=True)
480     files.remove('lib')
481     paths = ['%s%s' % (profile_dir, f) for f in files]
482     for path in paths:
483       extended_path = '%s %s/* %s/*/* %s/*/*/*' % (path, path, path, path)
484       self._device.RunShellCommand(
485           'chown %s.%s %s' % (uid, uid, extended_path))
486
487   def RemoveProfile(self, package, ignore_list):
488     """Delete application profile on device.
489
490     Args:
491       package: The full package name string of the application for which the
492         profile is to be deleted.
493       ignore_list: List of files to keep.
494     """
495     profile_dir = self._GetProfileDir(package)
496     files = self._device.RunShellCommand(
497         'ls "%s"' % profile_dir, as_root=True)
498     paths = ['"%s%s"' % (profile_dir, f) for f in files
499              if f not in ignore_list]
500     self._device.RunShellCommand('rm -r %s' % ' '.join(paths), as_root=True)
501
502   def PullProfile(self, package, output_profile_path):
503     """Copy application profile from device to host machine.
504
505     Args:
506       package: The full package name string of the application for which the
507         profile is to be copied.
508       output_profile_dir: Location where profile to be stored on host machine.
509     """
510     profile_dir = self._GetProfileDir(package)
511     logging.info("Pulling profile directory from device: '%s'->'%s'.",
512                  profile_dir, output_profile_path)
513     # To minimize bandwidth it might be good to look at whether all the data
514     # pulled down is really needed e.g. .pak files.
515     if not os.path.exists(output_profile_path):
516       os.makedirs(output_profile_path)
517     files = self._device.RunShellCommand('ls "%s"' % profile_dir)
518     for f in files:
519       # Don't pull lib, since it is created by the installer.
520       if f != 'lib':
521         source = '%s%s' % (profile_dir, f)
522         dest = os.path.join(output_profile_path, f)
523         # self._adb.Pull(source, dest) doesn't work because its timeout
524         # is fixed in android's adb_interface at 60 seconds, which may
525         # be too short to pull the cache.
526         cmd = 'pull %s %s' % (source, dest)
527         self._device.old_interface.Adb().SendCommand(cmd, timeout_time=240)
528
529   def _GetProfileDir(self, package):
530     """Returns the on-device location where the application profile is stored
531     based on Android convention.
532
533     Args:
534       package: The full package name string of the application.
535     """
536     return '/data/data/%s/' % package
537
538   def SetDebugApp(self, package):
539     """Set application to debugging.
540
541     Args:
542       package: The full package name string of the application.
543     """
544     if self._adb.IsUserBuild():
545       logging.debug('User build device, setting debug app')
546       self._device.RunShellCommand('am set-debug-app --persistent %s' % package)
547
548   def GetStandardOutput(self, number_of_lines=500):
549     """Returns most recent lines of logcat dump.
550
551     Args:
552       number_of_lines: Number of lines of log to return.
553     """
554     return '\n'.join(self.adb.device().RunShellCommand(
555         'logcat -d -t %d' % number_of_lines))
556
557   def GetStackTrace(self, target_arch):
558     """Returns stack trace.
559
560     The stack trace consists of raw logcat dump, logcat dump with symbols,
561     and stack info from tomstone files.
562
563     Args:
564       target_arch: String specifying device architecture (eg. arm, arm64, mips,
565         x86, x86_64)
566     """
567     def Decorate(title, content):
568       return "%s\n%s\n%s\n" % (title, content, '*' * 80)
569     # Get the last lines of logcat (large enough to contain stacktrace)
570     logcat = self.GetStandardOutput()
571     ret = Decorate('Logcat', logcat)
572     stack = os.path.join(util.GetChromiumSrcDir(), 'third_party',
573                          'android_platform', 'development', 'scripts', 'stack')
574     # Try to symbolize logcat.
575     if os.path.exists(stack):
576       cmd = [stack]
577       if target_arch:
578         cmd.append('--arch=%s' % target_arch)
579       p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
580       ret += Decorate('Stack from Logcat', p.communicate(input=logcat)[0])
581
582     # Try to get tombstones.
583     tombstones = os.path.join(util.GetChromiumSrcDir(), 'build', 'android',
584                               'tombstones.py')
585     if os.path.exists(tombstones):
586       ret += Decorate('Tombstones',
587                       subprocess.Popen([tombstones, '-w', '--device',
588                                         self._adb.device_serial()],
589                                        stdout=subprocess.PIPE).communicate()[0])
590     return ret