OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Provides an interface to communicate with the device via the adb command. | 5 """Provides an interface to communicate with the device via the adb command. |
6 | 6 |
7 Assumes adb binary is currently on system path. | 7 Assumes adb binary is currently on system path. |
8 | |
9 Usage: | |
10 python android_commands.py wait-for-pm | |
11 """ | 8 """ |
12 | 9 |
13 import collections | 10 import collections |
14 import datetime | 11 import datetime |
| 12 import io_stats_parser |
15 import logging | 13 import logging |
16 import optparse | 14 import optparse |
17 import os | 15 import os |
18 import pexpect | 16 import pexpect |
19 import re | 17 import re |
| 18 import shlex |
20 import subprocess | 19 import subprocess |
21 import sys | 20 import sys |
22 import tempfile | 21 import tempfile |
23 import time | 22 import time |
24 | 23 |
| 24 |
25 # adb_interface.py is under ../../../third_party/android_testrunner/ | 25 # adb_interface.py is under ../../../third_party/android_testrunner/ |
26 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', | 26 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', |
27 '..', '..', 'third_party', 'android_testrunner')) | 27 '..', '..', 'third_party', 'android_testrunner')) |
28 import adb_interface | 28 import adb_interface |
29 import cmd_helper | 29 import cmd_helper |
30 import errors # is under ../../third_party/android_testrunner/errors.py | 30 import errors # is under ../../../third_party/android_testrunner/errors.py |
31 from run_tests_helper import IsRunningAsBuildbot | |
32 | 31 |
33 | 32 |
34 # Pattern to search for the next whole line of pexpect output and capture it | 33 # Pattern to search for the next whole line of pexpect output and capture it |
35 # into a match group. We can't use ^ and $ for line start end with pexpect, | 34 # into a match group. We can't use ^ and $ for line start end with pexpect, |
36 # see http://www.noah.org/python/pexpect/#doc for explanation why. | 35 # see http://www.noah.org/python/pexpect/#doc for explanation why. |
37 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') | 36 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') |
38 | 37 |
39 # Set the adb shell prompt to be a unique marker that will [hopefully] not | 38 # Set the adb shell prompt to be a unique marker that will [hopefully] not |
40 # appear at the start of any line of a command's output. | 39 # appear at the start of any line of a command's output. |
41 SHELL_PROMPT = '~+~PQ\x17RS~+~' | 40 SHELL_PROMPT = '~+~PQ\x17RS~+~' |
42 | 41 |
43 # This only works for single core devices. | 42 # This only works for single core devices. |
44 SCALING_GOVERNOR = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' | 43 SCALING_GOVERNOR = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' |
45 DROP_CACHES = '/proc/sys/vm/drop_caches' | 44 DROP_CACHES = '/proc/sys/vm/drop_caches' |
46 | 45 |
47 # Java properties file | 46 # Java properties file |
48 LOCAL_PROPERTIES_PATH = '/data/local.prop' | 47 LOCAL_PROPERTIES_PATH = '/data/local.prop' |
49 | 48 |
50 # Property in /data/local.prop that controls Java assertions. | 49 # Property in /data/local.prop that controls Java assertions. |
51 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' | 50 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
52 | 51 |
53 BOOT_COMPLETE_RE = re.compile( | 52 BOOT_COMPLETE_RE = re.compile( |
54 re.escape('android.intent.action.MEDIA_MOUNTED path: /mnt/sdcard') | 53 'android.intent.action.MEDIA_MOUNTED path: /\w+/sdcard\d?' |
55 + '|' + re.escape('PowerManagerService: bootCompleted')) | 54 + '|' + 'PowerManagerService(\(\s+\d+\))?: bootCompleted') |
| 55 |
| 56 MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$') |
| 57 NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*' |
| 58 '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$') |
56 | 59 |
57 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). | 60 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). |
| 61 KEYCODE_HOME = 3 |
| 62 KEYCODE_BACK = 4 |
| 63 KEYCODE_DPAD_UP = 19 |
| 64 KEYCODE_DPAD_DOWN = 20 |
58 KEYCODE_DPAD_RIGHT = 22 | 65 KEYCODE_DPAD_RIGHT = 22 |
59 KEYCODE_ENTER = 66 | 66 KEYCODE_ENTER = 66 |
60 KEYCODE_MENU = 82 | 67 KEYCODE_MENU = 82 |
61 KEYCODE_BACK = 4 | |
62 | 68 |
63 | 69 |
64 def GetEmulators(): | 70 def GetEmulators(): |
65 """Returns a list of emulators. Does not filter by status (e.g. offline). | 71 """Returns a list of emulators. Does not filter by status (e.g. offline). |
66 | 72 |
67 Both devices starting with 'emulator' will be returned in below output: | 73 Both devices starting with 'emulator' will be returned in below output: |
68 | 74 |
69 * daemon not running. starting it now on port 5037 * | 75 * daemon not running. starting it now on port 5037 * |
70 * daemon started successfully * | 76 * daemon started successfully * |
71 List of devices attached | 77 List of devices attached |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
182 if isinstance(utc_offset, str) and len(utc_offset) == 5: | 188 if isinstance(utc_offset, str) and len(utc_offset) == 5: |
183 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), | 189 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), |
184 minutes=int(utc_offset[3:5])) | 190 minutes=int(utc_offset[3:5])) |
185 if utc_offset[0:1] == '-': | 191 if utc_offset[0:1] == '-': |
186 utc_delta = -utc_delta; | 192 utc_delta = -utc_delta; |
187 lastmod -= utc_delta | 193 lastmod -= utc_delta |
188 files[filename] = (int(file_match.group('size')), lastmod) | 194 files[filename] = (int(file_match.group('size')), lastmod) |
189 return files | 195 return files |
190 | 196 |
191 | 197 |
192 def GetLogTimestamp(log_line): | 198 def GetLogTimestamp(log_line, year): |
193 """Returns the timestamp of the given |log_line|.""" | 199 """Returns the timestamp of the given |log_line| in the given year.""" |
194 try: | 200 try: |
195 return datetime.datetime.strptime(log_line[:18], '%m-%d %H:%M:%S.%f') | 201 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), |
| 202 '%Y-%m-%d %H:%M:%S.%f') |
196 except (ValueError, IndexError): | 203 except (ValueError, IndexError): |
197 logging.critical('Error reading timestamp from ' + log_line) | 204 logging.critical('Error reading timestamp from ' + log_line) |
198 return None | 205 return None |
199 | 206 |
200 | 207 |
201 class AndroidCommands(object): | 208 class AndroidCommands(object): |
202 """Helper class for communicating with Android device via adb. | 209 """Helper class for communicating with Android device via adb. |
203 | 210 |
204 Args: | 211 Args: |
205 device: If given, adb commands are only send to the device of this ID. | 212 device: If given, adb commands are only send to the device of this ID. |
206 Otherwise commands are sent to all attached devices. | 213 Otherwise commands are sent to all attached devices. |
207 wait_for_pm: If true, issues an adb wait-for-device command. | |
208 """ | 214 """ |
209 | 215 |
210 def __init__(self, device=None, wait_for_pm=False): | 216 def __init__(self, device=None): |
211 self._adb = adb_interface.AdbInterface() | 217 self._adb = adb_interface.AdbInterface() |
212 if device: | 218 if device: |
213 self._adb.SetTargetSerial(device) | 219 self._adb.SetTargetSerial(device) |
214 if wait_for_pm: | 220 # So many users require root that we just always do it. This could |
215 self.WaitForDevicePm() | 221 # be made more fine grain if necessary. |
| 222 self._adb.EnableAdbRoot() |
216 self._logcat = None | 223 self._logcat = None |
217 self._original_governor = None | 224 self._original_governor = None |
218 self._pushed_files = [] | 225 self._pushed_files = [] |
| 226 self._device_utc_offset = self.RunShellCommand('date +%z')[0] |
219 | 227 |
220 def Adb(self): | 228 def Adb(self): |
221 """Returns our AdbInterface to avoid us wrapping all its methods.""" | 229 """Returns our AdbInterface to avoid us wrapping all its methods.""" |
222 return self._adb | 230 return self._adb |
223 | 231 |
| 232 def GetDeviceYear(self): |
| 233 """Returns the year information of the date on device""" |
| 234 return self.RunShellCommand('date +%Y')[0] |
| 235 |
224 def WaitForDevicePm(self): | 236 def WaitForDevicePm(self): |
225 """Blocks until the device's package manager is available. | 237 """Blocks until the device's package manager is available. |
226 | 238 |
227 To workaround http://b/5201039, we restart the shell and retry if the | 239 To workaround http://b/5201039, we restart the shell and retry if the |
228 package manager isn't back after 120 seconds. | 240 package manager isn't back after 120 seconds. |
229 | 241 |
230 Raises: | 242 Raises: |
231 errors.WaitForResponseTimedOutError after max retries reached. | 243 errors.WaitForResponseTimedOutError after max retries reached. |
232 """ | 244 """ |
233 last_err = None | 245 last_err = None |
(...skipping 28 matching lines...) Expand all Loading... |
262 # connection; work out if we can handle this better | 274 # connection; work out if we can handle this better |
263 if os.environ.get('USING_HIVE'): | 275 if os.environ.get('USING_HIVE'): |
264 logging.warning('Ignoring reboot request as we are on hive') | 276 logging.warning('Ignoring reboot request as we are on hive') |
265 return | 277 return |
266 if full_reboot: | 278 if full_reboot: |
267 self._adb.SendCommand('reboot') | 279 self._adb.SendCommand('reboot') |
268 else: | 280 else: |
269 self.RestartShell() | 281 self.RestartShell() |
270 self.WaitForDevicePm() | 282 self.WaitForDevicePm() |
271 self.StartMonitoringLogcat(timeout=120) | 283 self.StartMonitoringLogcat(timeout=120) |
272 self.WaitForLogMatch(BOOT_COMPLETE_RE) | 284 self.WaitForLogMatch(BOOT_COMPLETE_RE, None) |
273 self.UnlockDevice() | |
274 | 285 |
275 def Uninstall(self, package): | 286 def Uninstall(self, package): |
276 """Uninstalls the specified package from the device. | 287 """Uninstalls the specified package from the device. |
277 | 288 |
278 Args: | 289 Args: |
279 package: Name of the package to remove. | 290 package: Name of the package to remove. |
| 291 |
| 292 Returns: |
| 293 A status string returned by adb uninstall |
280 """ | 294 """ |
281 uninstall_command = 'uninstall %s' % package | 295 uninstall_command = 'uninstall %s' % package |
282 | 296 |
283 logging.info('>>> $' + uninstall_command) | 297 logging.info('>>> $' + uninstall_command) |
284 self._adb.SendCommand(uninstall_command, timeout_time=60) | 298 return self._adb.SendCommand(uninstall_command, timeout_time=60) |
285 | 299 |
286 def Install(self, package_file_path): | 300 def Install(self, package_file_path): |
287 """Installs the specified package to the device. | 301 """Installs the specified package to the device. |
288 | 302 |
289 Args: | 303 Args: |
290 package_file_path: Path to .apk file to install. | 304 package_file_path: Path to .apk file to install. |
| 305 |
| 306 Returns: |
| 307 A status string returned by adb install |
291 """ | 308 """ |
292 | |
293 assert os.path.isfile(package_file_path) | 309 assert os.path.isfile(package_file_path) |
294 | 310 |
295 install_command = 'install %s' % package_file_path | 311 install_command = 'install %s' % package_file_path |
296 | 312 |
297 logging.info('>>> $' + install_command) | 313 logging.info('>>> $' + install_command) |
298 self._adb.SendCommand(install_command, timeout_time=2*60) | 314 return self._adb.SendCommand(install_command, timeout_time=2*60) |
| 315 |
| 316 def MakeSystemFolderWritable(self): |
| 317 """Remounts the /system folder rw. """ |
| 318 out = self._adb.SendCommand('remount') |
| 319 if out.strip() != 'remount succeeded': |
| 320 raise errors.MsgException('Remount failed: %s' % out) |
299 | 321 |
300 # It is tempting to turn this function into a generator, however this is not | 322 # It is tempting to turn this function into a generator, however this is not |
301 # possible without using a private (local) adb_shell instance (to ensure no | 323 # possible without using a private (local) adb_shell instance (to ensure no |
302 # other command interleaves usage of it), which would defeat the main aim of | 324 # other command interleaves usage of it), which would defeat the main aim of |
303 # being able to reuse the adb shell instance across commands. | 325 # being able to reuse the adb shell instance across commands. |
304 def RunShellCommand(self, command, timeout_time=20, log_result=True): | 326 def RunShellCommand(self, command, timeout_time=20, log_result=True): |
305 """Send a command to the adb shell and return the result. | 327 """Send a command to the adb shell and return the result. |
306 | 328 |
307 Args: | 329 Args: |
308 command: String containing the shell command to send. Must not include | 330 command: String containing the shell command to send. Must not include |
(...skipping 21 matching lines...) Expand all Loading... |
330 process: name of the process to kill off | 352 process: name of the process to kill off |
331 | 353 |
332 Returns: | 354 Returns: |
333 the number of processess killed | 355 the number of processess killed |
334 """ | 356 """ |
335 pids = self.ExtractPid(process) | 357 pids = self.ExtractPid(process) |
336 if pids: | 358 if pids: |
337 self.RunShellCommand('kill ' + ' '.join(pids)) | 359 self.RunShellCommand('kill ' + ' '.join(pids)) |
338 return len(pids) | 360 return len(pids) |
339 | 361 |
340 def StartActivity(self, package, activity, | 362 def StartActivity(self, package, activity, wait_for_completion=False, |
341 action='android.intent.action.VIEW', data=None, | 363 action='android.intent.action.VIEW', |
| 364 category=None, data=None, |
342 extras=None, trace_file_name=None): | 365 extras=None, trace_file_name=None): |
343 """Starts |package|'s activity on the device. | 366 """Starts |package|'s activity on the device. |
344 | 367 |
345 Args: | 368 Args: |
346 package: Name of package to start (e.g. 'com.android.chrome'). | 369 package: Name of package to start (e.g. 'com.google.android.apps.chrome'). |
347 activity: Name of activity (e.g. '.Main' or 'com.android.chrome.Main'). | 370 activity: Name of activity (e.g. '.Main' or |
| 371 'com.google.android.apps.chrome.Main'). |
| 372 wait_for_completion: wait for the activity to finish launching (-W flag). |
| 373 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. |
| 374 category: string (e.g. "android.intent.category.HOME") |
348 data: Data string to pass to activity (e.g. 'http://www.example.com/'). | 375 data: Data string to pass to activity (e.g. 'http://www.example.com/'). |
349 extras: Dict of extras to pass to activity. | 376 extras: Dict of extras to pass to activity. Values are significant. |
350 trace_file_name: If used, turns on and saves the trace to this file name. | 377 trace_file_name: If used, turns on and saves the trace to this file name. |
351 """ | 378 """ |
352 cmd = 'am start -a %s -n %s/%s' % (action, package, activity) | 379 cmd = 'am start -a %s' % action |
| 380 if wait_for_completion: |
| 381 cmd += ' -W' |
| 382 if category: |
| 383 cmd += ' -c %s' % category |
| 384 if package and activity: |
| 385 cmd += ' -n %s/%s' % (package, activity) |
353 if data: | 386 if data: |
354 cmd += ' -d "%s"' % data | 387 cmd += ' -d "%s"' % data |
355 if extras: | 388 if extras: |
356 cmd += ' -e' | |
357 for key in extras: | 389 for key in extras: |
358 cmd += ' %s %s' % (key, extras[key]) | 390 value = extras[key] |
| 391 if isinstance(value, str): |
| 392 cmd += ' --es' |
| 393 elif isinstance(value, bool): |
| 394 cmd += ' --ez' |
| 395 elif isinstance(value, int): |
| 396 cmd += ' --ei' |
| 397 else: |
| 398 raise NotImplementedError( |
| 399 'Need to teach StartActivity how to pass %s extras' % type(value)) |
| 400 cmd += ' %s %s' % (key, value) |
359 if trace_file_name: | 401 if trace_file_name: |
360 cmd += ' -S -P ' + trace_file_name | 402 cmd += ' --start-profiler ' + trace_file_name |
361 self.RunShellCommand(cmd) | 403 self.RunShellCommand(cmd) |
362 | 404 |
363 def EnableAdbRoot(self): | |
364 """Enable root on the device.""" | |
365 self._adb.EnableAdbRoot() | |
366 | 405 |
367 def CloseApplication(self, package): | 406 def CloseApplication(self, package): |
368 """Attempt to close down the application, using increasing violence. | 407 """Attempt to close down the application, using increasing violence. |
369 | 408 |
370 Args: | 409 Args: |
371 package: Name of the process to kill off, e.g. com.android.chrome | 410 package: Name of the process to kill off, e.g. |
| 411 com.google.android.apps.chrome |
372 """ | 412 """ |
373 self.RunShellCommand('am force-stop ' + package) | 413 self.RunShellCommand('am force-stop ' + package) |
374 | 414 |
375 def ClearApplicationState(self, package): | 415 def ClearApplicationState(self, package): |
376 """Closes and clears all state for the given |package|.""" | 416 """Closes and clears all state for the given |package|.""" |
377 self.CloseApplication(package) | 417 self.CloseApplication(package) |
| 418 self.RunShellCommand('rm -r /data/data/%s/app_*' % package) |
378 self.RunShellCommand('rm -r /data/data/%s/cache/*' % package) | 419 self.RunShellCommand('rm -r /data/data/%s/cache/*' % package) |
379 self.RunShellCommand('rm -r /data/data/%s/files/*' % package) | 420 self.RunShellCommand('rm -r /data/data/%s/files/*' % package) |
380 self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package) | 421 self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package) |
381 | 422 |
382 def SendKeyEvent(self, keycode): | 423 def SendKeyEvent(self, keycode): |
383 """Sends keycode to the device. | 424 """Sends keycode to the device. |
384 | 425 |
385 Args: | 426 Args: |
386 keycode: Numeric keycode to send (see "enum" at top of file). | 427 keycode: Numeric keycode to send (see "enum" at top of file). |
387 """ | 428 """ |
(...skipping 28 matching lines...) Expand all Loading... |
416 # They don't match, so remove everything first and then create it. | 457 # They don't match, so remove everything first and then create it. |
417 if os.path.isdir(local_path): | 458 if os.path.isdir(local_path): |
418 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) | 459 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) |
419 self.RunShellCommand('mkdir -p %s' % device_path) | 460 self.RunShellCommand('mkdir -p %s' % device_path) |
420 | 461 |
421 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 462 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of |
422 # 60 seconds which isn't sufficient for a lot of users of this method. | 463 # 60 seconds which isn't sufficient for a lot of users of this method. |
423 push_command = 'push %s %s' % (local_path, device_path) | 464 push_command = 'push %s %s' % (local_path, device_path) |
424 logging.info('>>> $' + push_command) | 465 logging.info('>>> $' + push_command) |
425 output = self._adb.SendCommand(push_command, timeout_time=30*60) | 466 output = self._adb.SendCommand(push_command, timeout_time=30*60) |
| 467 assert output |
426 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" | 468 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
427 # Errors look like this: "failed to copy ... " | 469 # Errors look like this: "failed to copy ... " |
428 if not re.search('^[0-9]', output): | 470 if not re.search('^[0-9]', output.splitlines()[-1]): |
429 logging.critical('PUSH FAILED: ' + output) | 471 logging.critical('PUSH FAILED: ' + output) |
430 | 472 |
431 def GetFileContents(self, filename): | 473 def GetFileContents(self, filename, log_result=True): |
432 """Gets contents from the file specified by |filename|.""" | 474 """Gets contents from the file specified by |filename|.""" |
433 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + | 475 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + |
434 filename + '"; fi') | 476 filename + '"; fi', log_result=log_result) |
435 | 477 |
436 def SetFileContents(self, filename, contents): | 478 def SetFileContents(self, filename, contents): |
437 """Writes |contents| to the file specified by |filename|.""" | 479 """Writes |contents| to the file specified by |filename|.""" |
438 with tempfile.NamedTemporaryFile() as f: | 480 with tempfile.NamedTemporaryFile() as f: |
439 f.write(contents) | 481 f.write(contents) |
440 f.flush() | 482 f.flush() |
441 self._adb.Push(f.name, filename) | 483 self._adb.Push(f.name, filename) |
442 | 484 |
443 def RemovePushedFiles(self): | 485 def RemovePushedFiles(self): |
444 """Removes all files pushed with PushIfNeeded() from the device.""" | 486 """Removes all files pushed with PushIfNeeded() from the device.""" |
(...skipping 14 matching lines...) Expand all Loading... |
459 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt | 501 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt |
460 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' | 502 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' |
461 '(?P<user>[^\s]+)\s+' | 503 '(?P<user>[^\s]+)\s+' |
462 '(?P<group>[^\s]+)\s+' | 504 '(?P<group>[^\s]+)\s+' |
463 '(?P<size>[^\s]+)\s+' | 505 '(?P<size>[^\s]+)\s+' |
464 '(?P<date>[^\s]+)\s+' | 506 '(?P<date>[^\s]+)\s+' |
465 '(?P<time>[^\s]+)\s+' | 507 '(?P<time>[^\s]+)\s+' |
466 '(?P<filename>[^\s]+)$') | 508 '(?P<filename>[^\s]+)$') |
467 return _GetFilesFromRecursiveLsOutput( | 509 return _GetFilesFromRecursiveLsOutput( |
468 path, self.RunShellCommand('ls -lR %s' % path), re_file, | 510 path, self.RunShellCommand('ls -lR %s' % path), re_file, |
469 self.RunShellCommand('date +%z')[0]) | 511 self._device_utc_offset) |
470 | 512 |
471 def SetupPerformanceTest(self): | 513 def SetupPerformanceTest(self): |
472 """Sets up performance tests.""" | 514 """Sets up performance tests.""" |
473 # Disable CPU scaling to reduce noise in tests | 515 # Disable CPU scaling to reduce noise in tests |
474 if not self._original_governor: | 516 if not self._original_governor: |
475 self._original_governor = self.RunShellCommand('cat ' + SCALING_GOVERNOR) | 517 self._original_governor = self.GetFileContents( |
| 518 SCALING_GOVERNOR, log_result=False) |
476 self.RunShellCommand('echo performance > ' + SCALING_GOVERNOR) | 519 self.RunShellCommand('echo performance > ' + SCALING_GOVERNOR) |
477 self.DropRamCaches() | 520 self.DropRamCaches() |
478 | 521 |
479 def TearDownPerformanceTest(self): | 522 def TearDownPerformanceTest(self): |
480 """Tears down performance tests.""" | 523 """Tears down performance tests.""" |
481 if self._original_governor: | 524 if self._original_governor: |
482 self.RunShellCommand('echo %s > %s' % (self._original_governor[0], | 525 self.RunShellCommand('echo %s > %s' % (self._original_governor[0], |
483 SCALING_GOVERNOR)) | 526 SCALING_GOVERNOR)) |
484 self._original_governor = None | 527 self._original_governor = None |
485 | 528 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
528 """Starts monitoring the output of logcat, for use with WaitForLogMatch. | 571 """Starts monitoring the output of logcat, for use with WaitForLogMatch. |
529 | 572 |
530 Args: | 573 Args: |
531 clear: If True the existing logcat output will be cleared, to avoiding | 574 clear: If True the existing logcat output will be cleared, to avoiding |
532 matching historical output lurking in the log. | 575 matching historical output lurking in the log. |
533 timeout: How long WaitForLogMatch will wait for the given match | 576 timeout: How long WaitForLogMatch will wait for the given match |
534 filters: A list of logcat filters to be used. | 577 filters: A list of logcat filters to be used. |
535 """ | 578 """ |
536 if clear: | 579 if clear: |
537 self.RunShellCommand('logcat -c') | 580 self.RunShellCommand('logcat -c') |
538 args = ['logcat', '-v', 'threadtime'] | 581 args = [] |
| 582 if self._adb._target_arg: |
| 583 args += shlex.split(self._adb._target_arg) |
| 584 args += ['logcat', '-v', 'threadtime'] |
539 if filters: | 585 if filters: |
540 args.extend(filters) | 586 args.extend(filters) |
541 else: | 587 else: |
542 args.append('*:v') | 588 args.append('*:v') |
543 | 589 |
544 # Spawn logcat and syncronize with it. | 590 # Spawn logcat and syncronize with it. |
545 for _ in range(4): | 591 for _ in range(4): |
546 self._logcat = pexpect.spawn('adb', args, timeout=timeout, | 592 self._logcat = pexpect.spawn('adb', args, timeout=timeout, |
547 logfile=logfile) | 593 logfile=logfile) |
548 self.RunShellCommand('log startup_sync') | 594 self.RunShellCommand('log startup_sync') |
549 if self._logcat.expect(['startup_sync', pexpect.EOF, | 595 if self._logcat.expect(['startup_sync', pexpect.EOF, |
550 pexpect.TIMEOUT]) == 0: | 596 pexpect.TIMEOUT]) == 0: |
551 break | 597 break |
552 self._logcat.close(force=True) | 598 self._logcat.close(force=True) |
553 else: | 599 else: |
554 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) | 600 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) |
555 sys.exit(1) | 601 sys.exit(1) |
556 | 602 |
557 def GetMonitoredLogCat(self): | 603 def GetMonitoredLogCat(self): |
558 """Returns an "adb logcat" command as created by pexpected.spawn.""" | 604 """Returns an "adb logcat" command as created by pexpected.spawn.""" |
559 if not self._logcat: | 605 if not self._logcat: |
560 self.StartMonitoringLogcat(clear=False) | 606 self.StartMonitoringLogcat(clear=False) |
561 return self._logcat | 607 return self._logcat |
562 | 608 |
563 def WaitForLogMatch(self, search_re): | 609 def WaitForLogMatch(self, success_re, error_re, clear=False): |
564 """Blocks until a line containing |line_re| is logged or a timeout occurs. | 610 """Blocks until a matching line is logged or a timeout occurs. |
565 | 611 |
566 Args: | 612 Args: |
567 search_re: The compiled re to search each line for. | 613 success_re: A compiled re to search each line for. |
| 614 error_re: A compiled re which, if found, terminates the search for |
| 615 |success_re|. If None is given, no error condition will be detected. |
| 616 clear: If True the existing logcat output will be cleared, defaults to |
| 617 false. |
| 618 |
| 619 Raises: |
| 620 pexpect.TIMEOUT upon the timeout specified by StartMonitoringLogcat(). |
568 | 621 |
569 Returns: | 622 Returns: |
570 The re match object. | 623 The re match object if |success_re| is matched first or None if |error_re| |
| 624 is matched first. |
571 """ | 625 """ |
572 if not self._logcat: | 626 if not self._logcat: |
573 self.StartMonitoringLogcat(clear=False) | 627 self.StartMonitoringLogcat(clear) |
574 logging.info('<<< Waiting for logcat:' + str(search_re.pattern)) | 628 logging.info('<<< Waiting for logcat:' + str(success_re.pattern)) |
575 t0 = time.time() | 629 t0 = time.time() |
576 try: | 630 try: |
577 while True: | 631 while True: |
578 # Note this will block for upto the timeout _per log line_, so we need | 632 # Note this will block for upto the timeout _per log line_, so we need |
579 # to calculate the overall timeout remaining since t0. | 633 # to calculate the overall timeout remaining since t0. |
580 time_remaining = t0 + self._logcat.timeout - time.time() | 634 time_remaining = t0 + self._logcat.timeout - time.time() |
581 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) | 635 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) |
582 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) | 636 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) |
583 line = self._logcat.match.group(1) | 637 line = self._logcat.match.group(1) |
584 search_match = search_re.search(line) | 638 if error_re: |
585 if search_match: | 639 error_match = error_re.search(line) |
586 return search_match | 640 if error_match: |
| 641 return None |
| 642 success_match = success_re.search(line) |
| 643 if success_match: |
| 644 return success_match |
587 logging.info('<<< Skipped Logcat Line:' + str(line)) | 645 logging.info('<<< Skipped Logcat Line:' + str(line)) |
588 except pexpect.TIMEOUT: | 646 except pexpect.TIMEOUT: |
589 raise pexpect.TIMEOUT( | 647 raise pexpect.TIMEOUT( |
590 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' | 648 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' |
591 'to debug)' % | 649 'to debug)' % |
592 (self._logcat.timeout, search_re.pattern)) | 650 (self._logcat.timeout, success_re.pattern)) |
593 | 651 |
594 def StartRecordingLogcat(self, clear=True, filters=['*:v']): | 652 def StartRecordingLogcat(self, clear=True, filters=['*:v']): |
595 """Starts recording logcat output to eventually be saved as a string. | 653 """Starts recording logcat output to eventually be saved as a string. |
596 | 654 |
597 This call should come before some series of tests are run, with either | 655 This call should come before some series of tests are run, with either |
598 StopRecordingLogcat or SearchLogcatRecord following the tests. | 656 StopRecordingLogcat or SearchLogcatRecord following the tests. |
599 | 657 |
600 Args: | 658 Args: |
601 clear: True if existing log output should be cleared. | 659 clear: True if existing log output should be cleared. |
602 filters: A list of logcat filters to be used. | 660 filters: A list of logcat filters to be used. |
603 """ | 661 """ |
604 if clear: | 662 if clear: |
605 self._adb.SendCommand('logcat -c') | 663 self._adb.SendCommand('logcat -c') |
606 logcat_command = 'adb logcat -v threadtime %s' % ' '.join(filters) | 664 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg, |
| 665 ' '.join(filters)) |
607 self.logcat_process = subprocess.Popen(logcat_command, shell=True, | 666 self.logcat_process = subprocess.Popen(logcat_command, shell=True, |
608 stdout=subprocess.PIPE) | 667 stdout=subprocess.PIPE) |
609 | 668 |
610 def StopRecordingLogcat(self): | 669 def StopRecordingLogcat(self): |
611 """Stops an existing logcat recording subprocess and returns output. | 670 """Stops an existing logcat recording subprocess and returns output. |
612 | 671 |
613 Returns: | 672 Returns: |
614 The logcat output as a string or an empty string if logcat was not | 673 The logcat output as a string or an empty string if logcat was not |
615 being recorded at the time. | 674 being recorded at the time. |
616 """ | 675 """ |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
665 return results | 724 return results |
666 | 725 |
667 def ExtractPid(self, process_name): | 726 def ExtractPid(self, process_name): |
668 """Extracts Process Ids for a given process name from Android Shell. | 727 """Extracts Process Ids for a given process name from Android Shell. |
669 | 728 |
670 Args: | 729 Args: |
671 process_name: name of the process on the device. | 730 process_name: name of the process on the device. |
672 | 731 |
673 Returns: | 732 Returns: |
674 List of all the process ids (as strings) that match the given name. | 733 List of all the process ids (as strings) that match the given name. |
| 734 If the name of a process exactly matches the given name, the pid of |
| 735 that process will be inserted to the front of the pid list. |
675 """ | 736 """ |
676 pids = [] | 737 pids = [] |
677 for line in self.RunShellCommand('ps'): | 738 for line in self.RunShellCommand('ps', log_result=False): |
678 data = line.split() | 739 data = line.split() |
679 try: | 740 try: |
680 if process_name in data[-1]: # name is in the last column | 741 if process_name in data[-1]: # name is in the last column |
681 pids.append(data[1]) # PID is in the second column | 742 if process_name == data[-1]: |
| 743 pids.insert(0, data[1]) # PID is in the second column |
| 744 else: |
| 745 pids.append(data[1]) |
682 except IndexError: | 746 except IndexError: |
683 pass | 747 pass |
684 return pids | 748 return pids |
685 | 749 |
686 def GetIoStats(self): | 750 def GetIoStats(self): |
687 """Gets cumulative disk IO stats since boot (for all processes). | 751 """Gets cumulative disk IO stats since boot (for all processes). |
688 | 752 |
689 Returns: | 753 Returns: |
690 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there | 754 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there |
691 was an error. | 755 was an error. |
692 """ | 756 """ |
693 # Field definitions. | 757 for line in self.GetFileContents('/proc/diskstats', log_result=False): |
694 # http://www.kernel.org/doc/Documentation/iostats.txt | 758 stats = io_stats_parser.ParseIoStatsLine(line) |
695 device = 2 | 759 if stats.device == 'mmcblk0': |
696 num_reads_issued_idx = 3 | |
697 num_reads_merged_idx = 4 | |
698 num_sectors_read_idx = 5 | |
699 ms_spent_reading_idx = 6 | |
700 num_writes_completed_idx = 7 | |
701 num_writes_merged_idx = 8 | |
702 num_sectors_written_idx = 9 | |
703 ms_spent_writing_idx = 10 | |
704 num_ios_in_progress_idx = 11 | |
705 ms_spent_doing_io_idx = 12 | |
706 ms_spent_doing_io_weighted_idx = 13 | |
707 | |
708 for line in self.RunShellCommand('cat /proc/diskstats'): | |
709 fields = line.split() | |
710 if fields[device] == 'mmcblk0': | |
711 return { | 760 return { |
712 'num_reads': int(fields[num_reads_issued_idx]), | 761 'num_reads': stats.num_reads_issued, |
713 'num_writes': int(fields[num_writes_completed_idx]), | 762 'num_writes': stats.num_writes_completed, |
714 'read_ms': int(fields[ms_spent_reading_idx]), | 763 'read_ms': stats.ms_spent_reading, |
715 'write_ms': int(fields[ms_spent_writing_idx]), | 764 'write_ms': stats.ms_spent_writing, |
716 } | 765 } |
717 logging.warning('Could not find disk IO stats.') | 766 logging.warning('Could not find disk IO stats.') |
718 return None | 767 return None |
719 | 768 |
720 def GetMemoryUsage(self, package): | 769 def GetMemoryUsageForPid(self, pid): |
| 770 """Returns the memory usage for given pid. |
| 771 |
| 772 Args: |
| 773 pid: The pid number of the specific process running on device. |
| 774 |
| 775 Returns: |
| 776 A tuple containg: |
| 777 [0]: Dict of {metric:usage_kb}, for the process which has specified pid. |
| 778 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, |
| 779 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, |
| 780 KernelPageSize, MMUPageSize, Nvidia (tablet only). |
| 781 [1]: Detailed /proc/[PID]/smaps information. |
| 782 """ |
| 783 usage_dict = collections.defaultdict(int) |
| 784 smaps = collections.defaultdict(dict) |
| 785 current_smap = '' |
| 786 for line in self.GetFileContents('/proc/%s/smaps' % pid, log_result=False): |
| 787 items = line.split() |
| 788 # See man 5 proc for more details. The format is: |
| 789 # address perms offset dev inode pathname |
| 790 if len(items) > 5: |
| 791 current_smap = ' '.join(items[5:]) |
| 792 elif len(items) > 3: |
| 793 current_smap = ' '.join(items[3:]) |
| 794 match = re.match(MEMORY_INFO_RE, line) |
| 795 if match: |
| 796 key = match.group('key') |
| 797 usage_kb = int(match.group('usage_kb')) |
| 798 usage_dict[key] += usage_kb |
| 799 if key not in smaps[current_smap]: |
| 800 smaps[current_smap][key] = 0 |
| 801 smaps[current_smap][key] += usage_kb |
| 802 if not usage_dict or not any(usage_dict.values()): |
| 803 # Presumably the process died between ps and calling this method. |
| 804 logging.warning('Could not find memory usage for pid ' + str(pid)) |
| 805 |
| 806 for line in self.GetFileContents('/d/nvmap/generic-0/clients', |
| 807 log_result=False): |
| 808 match = re.match(NVIDIA_MEMORY_INFO_RE, line) |
| 809 if match and match.group('pid') == pid: |
| 810 usage_bytes = int(match.group('usage_bytes')) |
| 811 usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB |
| 812 break |
| 813 |
| 814 return (usage_dict, smaps) |
| 815 |
| 816 def GetMemoryUsageForPackage(self, package): |
721 """Returns the memory usage for all processes whose name contains |pacakge|. | 817 """Returns the memory usage for all processes whose name contains |pacakge|. |
722 | 818 |
723 Args: | 819 Args: |
724 name: A string holding process name to lookup pid list for. | 820 name: A string holding process name to lookup pid list for. |
725 | 821 |
726 Returns: | 822 Returns: |
727 Dict of {metric:usage_kb}, summed over all pids associated with |name|. | 823 A tuple containg: |
728 The metric keys retruned are: Size, Rss, Pss, Shared_Clean, Shared_Dirty, | 824 [0]: Dict of {metric:usage_kb}, summed over all pids associated with |
729 Private_Clean, Private_Dirty, Referenced, Swap, KernelPageSize, | 825 |name|. |
730 MMUPageSize. | 826 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, |
| 827 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, |
| 828 KernelPageSize, MMUPageSize, Nvidia (tablet only). |
| 829 [1]: a list with detailed /proc/[PID]/smaps information. |
731 """ | 830 """ |
732 usage_dict = collections.defaultdict(int) | 831 usage_dict = collections.defaultdict(int) |
733 pid_list = self.ExtractPid(package) | 832 pid_list = self.ExtractPid(package) |
734 # We used to use the showmap command, but it is currently broken on | 833 smaps = collections.defaultdict(dict) |
735 # stingray so it's easier to just parse /proc/<pid>/smaps directly. | 834 |
736 memory_stat_re = re.compile('^(?P<key>\w+):\s+(?P<value>\d+) kB$') | |
737 for pid in pid_list: | 835 for pid in pid_list: |
738 for line in self.RunShellCommand('cat /proc/%s/smaps' % pid, | 836 usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid) |
739 log_result=False): | 837 smaps[pid] = smaps_per_pid |
740 match = re.match(memory_stat_re, line) | 838 for (key, value) in usage_dict_per_pid.items(): |
741 if match: usage_dict[match.group('key')] += int(match.group('value')) | 839 usage_dict[key] += value |
742 if not usage_dict or not any(usage_dict.values()): | |
743 # Presumably the process died between ps and showmap. | |
744 logging.warning('Could not find memory usage for pid ' + str(pid)) | |
745 return usage_dict | |
746 | 840 |
747 def UnlockDevice(self): | 841 return usage_dict, smaps |
748 """Unlocks the screen of the device.""" | |
749 # Make sure a menu button event will actually unlock the screen. | |
750 if IsRunningAsBuildbot(): | |
751 assert self.RunShellCommand('getprop ro.test_harness')[0].strip() == '1' | |
752 # The following keyevent unlocks the screen if locked. | |
753 self.SendKeyEvent(KEYCODE_MENU) | |
754 # If the screen wasn't locked the previous command will bring up the menu, | |
755 # which this will dismiss. Otherwise this shouldn't change anything. | |
756 self.SendKeyEvent(KEYCODE_BACK) | |
OLD | NEW |