PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/media/webrtc/trunk/build/android/android_commands.py

https://bitbucket.org/Jiten/mozilla-central
Python | 780 lines | 672 code | 32 blank | 76 comment | 23 complexity | 12649b3744a0a0b32846193340aa77fa MD5 | raw file
Possible License(s): JSON, Apache-2.0, 0BSD, LGPL-3.0, BSD-2-Clause, AGPL-1.0, MPL-2.0, GPL-2.0, LGPL-2.1, MIT, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. #!/usr/bin/env python
  2. # Copyright (c) 2011 The Chromium 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. """Provides an interface to communicate with the device via the adb command.
  6. Assumes adb binary is currently on system path.
  7. Usage:
  8. python android_commands.py wait-for-pm
  9. """
  10. import collections
  11. import datetime
  12. import logging
  13. import optparse
  14. import os
  15. import pexpect
  16. import re
  17. import subprocess
  18. import sys
  19. import tempfile
  20. import time
  21. # adb_interface.py is under ../../third_party/android/testrunner/
  22. sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..',
  23. '..', 'third_party', 'android', 'testrunner'))
  24. import adb_interface
  25. import cmd_helper
  26. import errors # is under ../../third_party/android/testrunner/errors.py
  27. from run_tests_helper import IsRunningAsBuildbot
  28. # Pattern to search for the next whole line of pexpect output and capture it
  29. # into a match group. We can't use ^ and $ for line start end with pexpect,
  30. # see http://www.noah.org/python/pexpect/#doc for explanation why.
  31. PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
  32. # Set the adb shell prompt to be a unique marker that will [hopefully] not
  33. # appear at the start of any line of a command's output.
  34. SHELL_PROMPT = '~+~PQ\x17RS~+~'
  35. # This only works for single core devices.
  36. SCALING_GOVERNOR = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'
  37. DROP_CACHES = '/proc/sys/vm/drop_caches'
  38. # Java properties file
  39. LOCAL_PROPERTIES_PATH = '/data/local.prop'
  40. # Property in /data/local.prop that controls Java assertions.
  41. JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
  42. BOOT_COMPLETE_RE = re.compile(
  43. re.escape('android.intent.action.MEDIA_MOUNTED path: /mnt/sdcard')
  44. + '|' + re.escape('PowerManagerService: bootCompleted'))
  45. # Keycode "enum" suitable for passing to AndroidCommands.SendKey().
  46. KEYCODE_DPAD_RIGHT = 22
  47. KEYCODE_ENTER = 66
  48. KEYCODE_MENU = 82
  49. KEYCODE_BACK = 4
  50. def GetEmulators():
  51. """Returns a list of emulators. Does not filter by status (e.g. offline).
  52. Both devices starting with 'emulator' will be returned in below output:
  53. * daemon not running. starting it now on port 5037 *
  54. * daemon started successfully *
  55. List of devices attached
  56. 027c10494100b4d7 device
  57. emulator-5554 offline
  58. emulator-5558 device
  59. """
  60. re_device = re.compile('^emulator-[0-9]+', re.MULTILINE)
  61. devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices']))
  62. return devices
  63. def GetAttachedDevices():
  64. """Returns a list of attached, online android devices.
  65. If a preferred device has been set with ANDROID_SERIAL, it will be first in
  66. the returned list.
  67. Example output:
  68. * daemon not running. starting it now on port 5037 *
  69. * daemon started successfully *
  70. List of devices attached
  71. 027c10494100b4d7 device
  72. emulator-5554 offline
  73. """
  74. re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
  75. devices = re_device.findall(cmd_helper.GetCmdOutput(['adb', 'devices']))
  76. preferred_device = os.environ.get("ANDROID_SERIAL")
  77. if preferred_device in devices:
  78. devices.remove(preferred_device)
  79. devices.insert(0, preferred_device)
  80. return devices
  81. def _GetHostFileInfo(file_name):
  82. """Returns a tuple containing size and modified UTC time for file_name."""
  83. # The time accuracy on device is only to minute level, remove the second and
  84. # microsecond from host results.
  85. utc_time = datetime.datetime.utcfromtimestamp(os.path.getmtime(file_name))
  86. time_delta = datetime.timedelta(seconds=utc_time.second,
  87. microseconds=utc_time.microsecond)
  88. return os.path.getsize(file_name), utc_time - time_delta
  89. def ListHostPathContents(path):
  90. """Lists files in all subdirectories of |path|.
  91. Args:
  92. path: The path to list.
  93. Returns:
  94. A dict of {"name": (size, lastmod), ...}.
  95. """
  96. if os.path.isfile(path):
  97. return {os.path.basename(path): _GetHostFileInfo(path)}
  98. ret = {}
  99. for root, dirs, files in os.walk(path):
  100. for d in dirs:
  101. if d.startswith('.'):
  102. dirs.remove(d) # Prune the dir for subsequent iterations.
  103. for f in files:
  104. if f.startswith('.'):
  105. continue
  106. full_file_name = os.path.join(root, f)
  107. file_name = os.path.relpath(full_file_name, path)
  108. ret[file_name] = _GetHostFileInfo(full_file_name)
  109. return ret
  110. def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
  111. """Gets a list of files from `ls` command output.
  112. Python's os.walk isn't used because it doesn't work over adb shell.
  113. Args:
  114. path: The path to list.
  115. ls_output: A list of lines returned by an `ls -lR` command.
  116. re_file: A compiled regular expression which parses a line into named groups
  117. consisting of at minimum "filename", "date", "time", "size" and
  118. optionally "timezone".
  119. utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
  120. 2-digit string giving the number of UTC offset hours, and MM is a
  121. 2-digit string giving the number of UTC offset minutes. If the input
  122. utc_offset is None, will try to look for the value of "timezone" if it
  123. is specified in re_file.
  124. Returns:
  125. A dict of {"name": (size, lastmod), ...} where:
  126. name: The file name relative to |path|'s directory.
  127. size: The file size in bytes (0 for directories).
  128. lastmod: The file last modification date in UTC.
  129. """
  130. re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
  131. path_dir = os.path.dirname(path)
  132. current_dir = ''
  133. files = {}
  134. for line in ls_output:
  135. directory_match = re_directory.match(line)
  136. if directory_match:
  137. current_dir = directory_match.group('dir')
  138. continue
  139. file_match = re_file.match(line)
  140. if file_match:
  141. filename = os.path.join(current_dir, file_match.group('filename'))
  142. if filename.startswith(path_dir):
  143. filename = filename[len(path_dir)+1:]
  144. lastmod = datetime.datetime.strptime(
  145. file_match.group('date') + ' ' + file_match.group('time')[:5],
  146. '%Y-%m-%d %H:%M')
  147. if not utc_offset and 'timezone' in re_file.groupindex:
  148. utc_offset = file_match.group('timezone')
  149. if isinstance(utc_offset, str) and len(utc_offset) == 5:
  150. utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
  151. minutes=int(utc_offset[3:5]))
  152. if utc_offset[0:1] == '-':
  153. utc_delta = -utc_delta;
  154. lastmod -= utc_delta
  155. files[filename] = (int(file_match.group('size')), lastmod)
  156. return files
  157. def GetLogTimestamp(log_line):
  158. """Returns the timestamp of the given |log_line|."""
  159. try:
  160. return datetime.datetime.strptime(log_line[:18], '%m-%d %H:%M:%S.%f')
  161. except (ValueError, IndexError):
  162. logging.critical('Error reading timestamp from ' + log_line)
  163. return None
  164. class AndroidCommands(object):
  165. """Helper class for communicating with Android device via adb.
  166. Args:
  167. device: If given, adb commands are only send to the device of this ID.
  168. Otherwise commands are sent to all attached devices.
  169. wait_for_pm: If true, issues an adb wait-for-device command.
  170. """
  171. def __init__(self, device=None, wait_for_pm=False):
  172. self._adb = adb_interface.AdbInterface()
  173. if device:
  174. self._adb.SetTargetSerial(device)
  175. if wait_for_pm:
  176. self.WaitForDevicePm()
  177. self._logcat = None
  178. self._original_governor = None
  179. self._pushed_files = []
  180. def Adb(self):
  181. """Returns our AdbInterface to avoid us wrapping all its methods."""
  182. return self._adb
  183. def WaitForDevicePm(self):
  184. """Blocks until the device's package manager is available.
  185. To workaround http://b/5201039, we restart the shell and retry if the
  186. package manager isn't back after 120 seconds.
  187. Raises:
  188. errors.WaitForResponseTimedOutError after max retries reached.
  189. """
  190. last_err = None
  191. retries = 3
  192. while retries:
  193. try:
  194. self._adb.WaitForDevicePm()
  195. return # Success
  196. except errors.WaitForResponseTimedOutError as e:
  197. last_err = e
  198. logging.warning('Restarting and retrying after timeout: %s' % str(e))
  199. retries -= 1
  200. self.RestartShell()
  201. raise last_err # Only reached after max retries, re-raise the last error.
  202. def SynchronizeDateTime(self):
  203. """Synchronize date/time between host and device."""
  204. self._adb.SendShellCommand('date -u %f' % time.time())
  205. def RestartShell(self):
  206. """Restarts the shell on the device. Does not block for it to return."""
  207. self.RunShellCommand('stop')
  208. self.RunShellCommand('start')
  209. def Reboot(self, full_reboot=True):
  210. """Reboots the device and waits for the package manager to return.
  211. Args:
  212. full_reboot: Whether to fully reboot the device or just restart the shell.
  213. """
  214. # TODO(torne): hive can't reboot the device either way without breaking the
  215. # connection; work out if we can handle this better
  216. if os.environ.get('USING_HIVE'):
  217. logging.warning('Ignoring reboot request as we are on hive')
  218. return
  219. if full_reboot:
  220. self._adb.SendCommand('reboot')
  221. else:
  222. self.RestartShell()
  223. self.WaitForDevicePm()
  224. self.StartMonitoringLogcat(timeout=120)
  225. self.WaitForLogMatch(BOOT_COMPLETE_RE)
  226. self.UnlockDevice()
  227. def Uninstall(self, package):
  228. """Uninstalls the specified package from the device.
  229. Args:
  230. package: Name of the package to remove.
  231. """
  232. uninstall_command = 'uninstall %s' % package
  233. logging.info('>>> $' + uninstall_command)
  234. self._adb.SendCommand(uninstall_command, timeout_time=60)
  235. def Install(self, package_file_path):
  236. """Installs the specified package to the device.
  237. Args:
  238. package_file_path: Path to .apk file to install.
  239. """
  240. assert os.path.isfile(package_file_path)
  241. install_command = 'install %s' % package_file_path
  242. logging.info('>>> $' + install_command)
  243. self._adb.SendCommand(install_command, timeout_time=2*60)
  244. # It is tempting to turn this function into a generator, however this is not
  245. # possible without using a private (local) adb_shell instance (to ensure no
  246. # other command interleaves usage of it), which would defeat the main aim of
  247. # being able to reuse the adb shell instance across commands.
  248. def RunShellCommand(self, command, timeout_time=20, log_result=True):
  249. """Send a command to the adb shell and return the result.
  250. Args:
  251. command: String containing the shell command to send. Must not include
  252. the single quotes as we use them to escape the whole command.
  253. timeout_time: Number of seconds to wait for command to respond before
  254. retrying, used by AdbInterface.SendShellCommand.
  255. log_result: Boolean to indicate whether we should log the result of the
  256. shell command.
  257. Returns:
  258. list containing the lines of output received from running the command
  259. """
  260. logging.info('>>> $' + command)
  261. if "'" in command: logging.warning(command + " contains ' quotes")
  262. result = self._adb.SendShellCommand("'%s'" % command,
  263. timeout_time).splitlines()
  264. if log_result:
  265. logging.info('\n>>> '.join(result))
  266. return result
  267. def KillAll(self, process):
  268. """Android version of killall, connected via adb.
  269. Args:
  270. process: name of the process to kill off
  271. Returns:
  272. the number of processess killed
  273. """
  274. pids = self.ExtractPid(process)
  275. if pids:
  276. self.RunShellCommand('kill ' + ' '.join(pids))
  277. return len(pids)
  278. def StartActivity(self, package, activity,
  279. action='android.intent.action.VIEW', data=None,
  280. extras=None, trace_file_name=None):
  281. """Starts |package|'s activity on the device.
  282. Args:
  283. package: Name of package to start (e.g. 'com.android.chrome').
  284. activity: Name of activity (e.g. '.Main' or 'com.android.chrome.Main').
  285. data: Data string to pass to activity (e.g. 'http://www.example.com/').
  286. extras: Dict of extras to pass to activity.
  287. trace_file_name: If used, turns on and saves the trace to this file name.
  288. """
  289. cmd = 'am start -a %s -n %s/%s' % (action, package, activity)
  290. if data:
  291. cmd += ' -d "%s"' % data
  292. if extras:
  293. cmd += ' -e'
  294. for key in extras:
  295. cmd += ' %s %s' % (key, extras[key])
  296. if trace_file_name:
  297. cmd += ' -S -P ' + trace_file_name
  298. self.RunShellCommand(cmd)
  299. def EnableAdbRoot(self):
  300. """Enable root on the device."""
  301. self._adb.EnableAdbRoot()
  302. def CloseApplication(self, package):
  303. """Attempt to close down the application, using increasing violence.
  304. Args:
  305. package: Name of the process to kill off, e.g. com.android.chrome
  306. """
  307. self.RunShellCommand('am force-stop ' + package)
  308. def ClearApplicationState(self, package):
  309. """Closes and clears all state for the given |package|."""
  310. self.CloseApplication(package)
  311. self.RunShellCommand('rm -r /data/data/%s/cache/*' % package)
  312. self.RunShellCommand('rm -r /data/data/%s/files/*' % package)
  313. self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package)
  314. def SendKeyEvent(self, keycode):
  315. """Sends keycode to the device.
  316. Args:
  317. keycode: Numeric keycode to send (see "enum" at top of file).
  318. """
  319. self.RunShellCommand('input keyevent %d' % keycode)
  320. def PushIfNeeded(self, local_path, device_path):
  321. """Pushes |local_path| to |device_path|.
  322. Works for files and directories. This method skips copying any paths in
  323. |test_data_paths| that already exist on the device with the same timestamp
  324. and size.
  325. All pushed files can be removed by calling RemovePushedFiles().
  326. """
  327. assert os.path.exists(local_path)
  328. self._pushed_files.append(device_path)
  329. # If the path contents are the same, there's nothing to do.
  330. local_contents = ListHostPathContents(local_path)
  331. device_contents = self.ListPathContents(device_path)
  332. # Only compare the size and timestamp if only copying a file because
  333. # the filename on device can be renamed.
  334. if os.path.isfile(local_path):
  335. assert len(local_contents) == 1
  336. is_equal = local_contents.values() == device_contents.values()
  337. else:
  338. is_equal = local_contents == device_contents
  339. if is_equal:
  340. logging.info('%s is up-to-date. Skipping file push.' % device_path)
  341. return
  342. # They don't match, so remove everything first and then create it.
  343. if os.path.isdir(local_path):
  344. self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60)
  345. self.RunShellCommand('mkdir -p %s' % device_path)
  346. # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of
  347. # 60 seconds which isn't sufficient for a lot of users of this method.
  348. push_command = 'push %s %s' % (local_path, device_path)
  349. logging.info('>>> $' + push_command)
  350. output = self._adb.SendCommand(push_command, timeout_time=30*60)
  351. # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
  352. # Errors look like this: "failed to copy ... "
  353. if not re.search('^[0-9]', output):
  354. logging.critical('PUSH FAILED: ' + output)
  355. def GetFileContents(self, filename):
  356. """Gets contents from the file specified by |filename|."""
  357. return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' +
  358. filename + '"; fi')
  359. def SetFileContents(self, filename, contents):
  360. """Writes |contents| to the file specified by |filename|."""
  361. with tempfile.NamedTemporaryFile() as f:
  362. f.write(contents)
  363. f.flush()
  364. self._adb.Push(f.name, filename)
  365. def RemovePushedFiles(self):
  366. """Removes all files pushed with PushIfNeeded() from the device."""
  367. for p in self._pushed_files:
  368. self.RunShellCommand('rm -r %s' % p, timeout_time=2*60)
  369. def ListPathContents(self, path):
  370. """Lists files in all subdirectories of |path|.
  371. Args:
  372. path: The path to list.
  373. Returns:
  374. A dict of {"name": (size, lastmod), ...}.
  375. """
  376. # Example output:
  377. # /foo/bar:
  378. # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt
  379. re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
  380. '(?P<user>[^\s]+)\s+'
  381. '(?P<group>[^\s]+)\s+'
  382. '(?P<size>[^\s]+)\s+'
  383. '(?P<date>[^\s]+)\s+'
  384. '(?P<time>[^\s]+)\s+'
  385. '(?P<filename>[^\s]+)$')
  386. return _GetFilesFromRecursiveLsOutput(
  387. path, self.RunShellCommand('ls -lR %s' % path), re_file,
  388. self.RunShellCommand('date +%z')[0])
  389. def SetupPerformanceTest(self):
  390. """Sets up performance tests."""
  391. # Disable CPU scaling to reduce noise in tests
  392. if not self._original_governor:
  393. self._original_governor = self.RunShellCommand('cat ' + SCALING_GOVERNOR)
  394. self.RunShellCommand('echo performance > ' + SCALING_GOVERNOR)
  395. self.DropRamCaches()
  396. def TearDownPerformanceTest(self):
  397. """Tears down performance tests."""
  398. if self._original_governor:
  399. self.RunShellCommand('echo %s > %s' % (self._original_governor[0],
  400. SCALING_GOVERNOR))
  401. self._original_governor = None
  402. def SetJavaAssertsEnabled(self, enable):
  403. """Sets or removes the device java assertions property.
  404. Args:
  405. enable: If True the property will be set.
  406. Returns:
  407. True if the file was modified (reboot is required for it to take effect).
  408. """
  409. # First ensure the desired property is persisted.
  410. temp_props_file = tempfile.NamedTemporaryFile()
  411. properties = ''
  412. if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
  413. properties = file(temp_props_file.name).read()
  414. re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
  415. r'\s*=\s*all\s*$', re.MULTILINE)
  416. if enable != bool(re.search(re_search, properties)):
  417. re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
  418. r'\s*=\s*\w+\s*$', re.MULTILINE)
  419. properties = re.sub(re_replace, '', properties)
  420. if enable:
  421. properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
  422. file(temp_props_file.name, 'w').write(properties)
  423. self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
  424. # Next, check the current runtime value is what we need, and
  425. # if not, set it and report that a reboot is required.
  426. was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY)
  427. if was_set == enable:
  428. return False
  429. self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY,
  430. enable and 'all' or ''))
  431. return True
  432. def DropRamCaches(self):
  433. """Drops the filesystem ram caches for performance testing."""
  434. self.RunShellCommand('echo 3 > ' + DROP_CACHES)
  435. def StartMonitoringLogcat(self, clear=True, timeout=10, logfile=None,
  436. filters=[]):
  437. """Starts monitoring the output of logcat, for use with WaitForLogMatch.
  438. Args:
  439. clear: If True the existing logcat output will be cleared, to avoiding
  440. matching historical output lurking in the log.
  441. timeout: How long WaitForLogMatch will wait for the given match
  442. filters: A list of logcat filters to be used.
  443. """
  444. if clear:
  445. self.RunShellCommand('logcat -c')
  446. args = ['logcat', '-v', 'threadtime']
  447. if filters:
  448. args.extend(filters)
  449. else:
  450. args.append('*:v')
  451. # Spawn logcat and syncronize with it.
  452. for _ in range(4):
  453. self._logcat = pexpect.spawn('adb', args, timeout=timeout,
  454. logfile=logfile)
  455. self.RunShellCommand('log startup_sync')
  456. if self._logcat.expect(['startup_sync', pexpect.EOF,
  457. pexpect.TIMEOUT]) == 0:
  458. break
  459. self._logcat.close(force=True)
  460. else:
  461. logging.critical('Error reading from logcat: ' + str(self._logcat.match))
  462. sys.exit(1)
  463. def GetMonitoredLogCat(self):
  464. """Returns an "adb logcat" command as created by pexpected.spawn."""
  465. if not self._logcat:
  466. self.StartMonitoringLogcat(clear=False)
  467. return self._logcat
  468. def WaitForLogMatch(self, search_re):
  469. """Blocks until a line containing |line_re| is logged or a timeout occurs.
  470. Args:
  471. search_re: The compiled re to search each line for.
  472. Returns:
  473. The re match object.
  474. """
  475. if not self._logcat:
  476. self.StartMonitoringLogcat(clear=False)
  477. logging.info('<<< Waiting for logcat:' + str(search_re.pattern))
  478. t0 = time.time()
  479. try:
  480. while True:
  481. # Note this will block for upto the timeout _per log line_, so we need
  482. # to calculate the overall timeout remaining since t0.
  483. time_remaining = t0 + self._logcat.timeout - time.time()
  484. if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat)
  485. self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
  486. line = self._logcat.match.group(1)
  487. search_match = search_re.search(line)
  488. if search_match:
  489. return search_match
  490. logging.info('<<< Skipped Logcat Line:' + str(line))
  491. except pexpect.TIMEOUT:
  492. raise pexpect.TIMEOUT(
  493. 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
  494. 'to debug)' %
  495. (self._logcat.timeout, search_re.pattern))
  496. def StartRecordingLogcat(self, clear=True, filters=['*:v']):
  497. """Starts recording logcat output to eventually be saved as a string.
  498. This call should come before some series of tests are run, with either
  499. StopRecordingLogcat or SearchLogcatRecord following the tests.
  500. Args:
  501. clear: True if existing log output should be cleared.
  502. filters: A list of logcat filters to be used.
  503. """
  504. if clear:
  505. self._adb.SendCommand('logcat -c')
  506. logcat_command = 'adb logcat -v threadtime %s' % ' '.join(filters)
  507. self.logcat_process = subprocess.Popen(logcat_command, shell=True,
  508. stdout=subprocess.PIPE)
  509. def StopRecordingLogcat(self):
  510. """Stops an existing logcat recording subprocess and returns output.
  511. Returns:
  512. The logcat output as a string or an empty string if logcat was not
  513. being recorded at the time.
  514. """
  515. if not self.logcat_process:
  516. return ''
  517. # Cannot evaluate directly as 0 is a possible value.
  518. # Better to read the self.logcat_process.stdout before killing it,
  519. # Otherwise the communicate may return incomplete output due to pipe break.
  520. if self.logcat_process.poll() == None:
  521. self.logcat_process.kill()
  522. (output, _) = self.logcat_process.communicate()
  523. self.logcat_process = None
  524. return output
  525. def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None,
  526. log_level=None, component=None):
  527. """Searches the specified logcat output and returns results.
  528. This method searches through the logcat output specified by record for a
  529. certain message, narrowing results by matching them against any other
  530. specified criteria. It returns all matching lines as described below.
  531. Args:
  532. record: A string generated by Start/StopRecordingLogcat to search.
  533. message: An output string to search for.
  534. thread_id: The thread id that is the origin of the message.
  535. proc_id: The process that is the origin of the message.
  536. log_level: The log level of the message.
  537. component: The name of the component that would create the message.
  538. Returns:
  539. A list of dictionaries represeting matching entries, each containing keys
  540. thread_id, proc_id, log_level, component, and message.
  541. """
  542. if thread_id:
  543. thread_id = str(thread_id)
  544. if proc_id:
  545. proc_id = str(proc_id)
  546. results = []
  547. reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
  548. re.MULTILINE)
  549. log_list = reg.findall(record)
  550. for (tid, pid, log_lev, comp, msg) in log_list:
  551. if ((not thread_id or thread_id == tid) and
  552. (not proc_id or proc_id == pid) and
  553. (not log_level or log_level == log_lev) and
  554. (not component or component == comp) and msg.find(message) > -1):
  555. match = dict({'thread_id': tid, 'proc_id': pid,
  556. 'log_level': log_lev, 'component': comp,
  557. 'message': msg})
  558. results.append(match)
  559. return results
  560. def ExtractPid(self, process_name):
  561. """Extracts Process Ids for a given process name from Android Shell.
  562. Args:
  563. process_name: name of the process on the device.
  564. Returns:
  565. List of all the process ids (as strings) that match the given name.
  566. """
  567. pids = []
  568. for line in self.RunShellCommand('ps'):
  569. data = line.split()
  570. try:
  571. if process_name in data[-1]: # name is in the last column
  572. pids.append(data[1]) # PID is in the second column
  573. except IndexError:
  574. pass
  575. return pids
  576. def GetIoStats(self):
  577. """Gets cumulative disk IO stats since boot (for all processes).
  578. Returns:
  579. Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
  580. was an error.
  581. """
  582. # Field definitions.
  583. # http://www.kernel.org/doc/Documentation/iostats.txt
  584. device = 2
  585. num_reads_issued_idx = 3
  586. num_reads_merged_idx = 4
  587. num_sectors_read_idx = 5
  588. ms_spent_reading_idx = 6
  589. num_writes_completed_idx = 7
  590. num_writes_merged_idx = 8
  591. num_sectors_written_idx = 9
  592. ms_spent_writing_idx = 10
  593. num_ios_in_progress_idx = 11
  594. ms_spent_doing_io_idx = 12
  595. ms_spent_doing_io_weighted_idx = 13
  596. for line in self.RunShellCommand('cat /proc/diskstats'):
  597. fields = line.split()
  598. if fields[device] == 'mmcblk0':
  599. return {
  600. 'num_reads': int(fields[num_reads_issued_idx]),
  601. 'num_writes': int(fields[num_writes_completed_idx]),
  602. 'read_ms': int(fields[ms_spent_reading_idx]),
  603. 'write_ms': int(fields[ms_spent_writing_idx]),
  604. }
  605. logging.warning('Could not find disk IO stats.')
  606. return None
  607. def GetMemoryUsage(self, package):
  608. """Returns the memory usage for all processes whose name contains |pacakge|.
  609. Args:
  610. name: A string holding process name to lookup pid list for.
  611. Returns:
  612. Dict of {metric:usage_kb}, summed over all pids associated with |name|.
  613. The metric keys retruned are: Size, Rss, Pss, Shared_Clean, Shared_Dirty,
  614. Private_Clean, Private_Dirty, Referenced, Swap, KernelPageSize,
  615. MMUPageSize.
  616. """
  617. usage_dict = collections.defaultdict(int)
  618. pid_list = self.ExtractPid(package)
  619. # We used to use the showmap command, but it is currently broken on
  620. # stingray so it's easier to just parse /proc/<pid>/smaps directly.
  621. memory_stat_re = re.compile('^(?P<key>\w+):\s+(?P<value>\d+) kB$')
  622. for pid in pid_list:
  623. for line in self.RunShellCommand('cat /proc/%s/smaps' % pid,
  624. log_result=False):
  625. match = re.match(memory_stat_re, line)
  626. if match: usage_dict[match.group('key')] += int(match.group('value'))
  627. if not usage_dict or not any(usage_dict.values()):
  628. # Presumably the process died between ps and showmap.
  629. logging.warning('Could not find memory usage for pid ' + str(pid))
  630. return usage_dict
  631. def UnlockDevice(self):
  632. """Unlocks the screen of the device."""
  633. # Make sure a menu button event will actually unlock the screen.
  634. if IsRunningAsBuildbot():
  635. assert self.RunShellCommand('getprop ro.test_harness')[0].strip() == '1'
  636. # The following keyevent unlocks the screen if locked.
  637. self.SendKeyEvent(KEYCODE_MENU)
  638. # If the screen wasn't locked the previous command will bring up the menu,
  639. # which this will dismiss. Otherwise this shouldn't change anything.
  640. self.SendKeyEvent(KEYCODE_BACK)
  641. def main(argv):
  642. option_parser = optparse.OptionParser()
  643. option_parser.add_option('-w', '--wait_for_pm', action='store_true',
  644. default=False, dest='wait_for_pm',
  645. help='Waits for Device Package Manager to become available')
  646. option_parser.add_option('--enable_asserts', dest='set_asserts',
  647. action='store_true', default=None,
  648. help='Sets the dalvik.vm.enableassertions property to "all"')
  649. option_parser.add_option('--disable_asserts', dest='set_asserts',
  650. action='store_false', default=None,
  651. help='Removes the dalvik.vm.enableassertions property')
  652. options, args = option_parser.parse_args(argv)
  653. commands = AndroidCommands(wait_for_pm=options.wait_for_pm)
  654. if options.set_asserts != None:
  655. if commands.SetJavaAssertsEnabled(options.set_asserts):
  656. commands.Reboot(full_reboot=False)
  657. if __name__ == '__main__':
  658. main(sys.argv)