Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: build/android/android_commands.py

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

Powered by Google App Engine
This is Rietveld 408576698