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

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

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

Powered by Google App Engine
This is Rietveld 408576698