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

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: Address comments 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') | 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/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(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)
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 self._adb.SendCommand(push_command, timeout_time=30*60)
427
428 def GetFileContents(self, filename):
429 """Gets contents from the file specified by |filename|."""
430 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' +
431 filename + '"; fi')
432
433 def SetFileContents(self, filename, contents):
434 """Writes |contents| to the file specified by |filename|."""
435 with tempfile.NamedTemporaryFile() as f:
436 f.write(contents)
437 f.flush()
438 self._adb.Push(f.name, filename)
439
440 def RemovePushedFiles(self):
441 """Removes all files pushed with PushIfNeeded() from the device."""
442 for p in self._pushed_files:
443 self.RunShellCommand('rm -r %s' % p, timeout_time=2*60)
444
445 def ListPathContents(self, path):
446 """Lists files in all subdirectories of |path|.
447
448 Args:
449 path: The path to list.
450
451 Returns:
452 A dict of {"name": (size, lastmod), ...}.
453 """
454 # Example output:
455 # /foo/bar:
456 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt
457 re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
458 '(?P<user>[^\s]+)\s+'
459 '(?P<group>[^\s]+)\s+'
460 '(?P<size>[^\s]+)\s+'
461 '(?P<date>[^\s]+)\s+'
462 '(?P<time>[^\s]+)\s+'
463 '(?P<filename>[^\s]+)$')
464 return _GetFilesFromRecursiveLsOutput(
465 path, self.RunShellCommand('ls -lR %s' % path), re_file,
466 self.RunShellCommand('date +%z')[0])
467
468 def SetupPerformanceTest(self):
469 """Sets up performance tests."""
470 # Disable CPU scaling to reduce noise in tests
471 if not self._original_governor:
472 self._original_governor = self.RunShellCommand('cat ' + SCALING_GOVERNOR)
473 self.RunShellCommand('echo performance > ' + SCALING_GOVERNOR)
474 self.DropRamCaches()
475
476 def TearDownPerformanceTest(self):
477 """Tears down performance tests."""
478 if self._original_governor:
479 self.RunShellCommand('echo %s > %s' % (self._original_governor[0],
480 SCALING_GOVERNOR))
481 self._original_governor = None
482
483 def SetJavaAssertsEnabled(self, enable):
484 """Sets or removes the device java assertions property.
485
486 Args:
487 enable: If True the property will be set.
488
489 Returns:
490 True if the file was modified (reboot is required for it to take effect).
491 """
492 # First ensure the desired property is persisted.
493 temp_props_file = tempfile.NamedTemporaryFile()
494 properties = ''
495 if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
496 properties = file(temp_props_file.name).read()
497 re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
498 r'\s*=\s*all\s*$', re.MULTILINE)
499 if enable != bool(re.search(re_search, properties)):
500 re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
501 r'\s*=\s*\w+\s*$', re.MULTILINE)
502 properties = re.sub(re_replace, '', properties)
503 if enable:
504 properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
505
506 file(temp_props_file.name, 'w').write(properties)
507 self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
508
509 # Next, check the current runtime value is what we need, and
510 # if not, set it and report that a reboot is required.
511 was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY)
512 if was_set == enable:
513 return False
514
515 self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY,
516 enable and 'all' or ''))
517 return True
518
519 def DropRamCaches(self):
520 """Drops the filesystem ram caches for performance testing."""
521 self.RunShellCommand('echo 3 > ' + DROP_CACHES)
522
523 def StartMonitoringLogcat(self, clear=True, timeout=10, logfile=None,
524 filters=[]):
525 """Starts monitoring the output of logcat, for use with WaitForLogMatch.
526
527 Args:
528 clear: If True the existing logcat output will be cleared, to avoiding
529 matching historical output lurking in the log.
530 timeout: How long WaitForLogMatch will wait for the given match
531 filters: A list of logcat filters to be used.
532 """
533 if clear:
534 self.RunShellCommand('logcat -c')
535 args = ['logcat', '-v', 'threadtime']
536 if filters:
537 args.extend(filters)
538 else:
539 args.append('*:v')
540
541 # Spawn logcat and syncronize with it.
542 for _ in range(4):
543 self._logcat = pexpect.spawn('adb', args, timeout=timeout,
544 logfile=logfile)
545 self.RunShellCommand('log startup_sync')
546 if self._logcat.expect(['startup_sync', pexpect.EOF,
547 pexpect.TIMEOUT]) == 0:
548 break
549 self._logcat.close(force=True)
550 else:
551 logging.critical('Error reading from logcat: ' + str(self._logcat.match))
552 sys.exit(1)
553
554 def GetMonitoredLogCat(self):
555 """Returns an "adb logcat" command as created by pexpected.spawn."""
556 if not self._logcat:
557 self.StartMonitoringLogcat(clear=False)
558 return self._logcat
559
560 def WaitForLogMatch(self, search_re):
561 """Blocks until a line containing |line_re| is logged or a timeout occurs.
562
563 Args:
564 search_re: The compiled re to search each line for.
565
566 Returns:
567 The re match object.
568 """
569 if not self._logcat:
570 self.StartMonitoringLogcat(clear=False)
571 logging.info('<<< Waiting for logcat:' + str(search_re.pattern))
572 t0 = time.time()
573 try:
574 while True:
575 # Note this will block for upto the timeout _per log line_, so we need
576 # to calculate the overall timeout remaining since t0.
577 time_remaining = t0 + self._logcat.timeout - time.time()
578 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat)
579 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
580 line = self._logcat.match.group(1)
581 search_match = search_re.search(line)
582 if search_match:
583 return search_match
584 logging.info('<<< Skipped Logcat Line:' + str(line))
585 except pexpect.TIMEOUT:
586 raise pexpect.TIMEOUT(
587 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
588 'to debug)' %
589 (self._logcat.timeout, search_re.pattern))
590
591 def StartRecordingLogcat(self, clear=True, filters=['*:v']):
592 """Starts recording logcat output to eventually be saved as a string.
593
594 This call should come before some series of tests are run, with either
595 StopRecordingLogcat or SearchLogcatRecord following the tests.
596
597 Args:
598 clear: True if existing log output should be cleared.
599 filters: A list of logcat filters to be used.
600 """
601 if clear:
602 self._adb.SendCommand('logcat -c')
603 logcat_command = 'adb logcat -v threadtime %s' % ' '.join(filters)
604 self.logcat_process = subprocess.Popen(logcat_command, shell=True,
605 stdout=subprocess.PIPE)
606
607 def StopRecordingLogcat(self):
608 """Stops an existing logcat recording subprocess and returns output.
609
610 Returns:
611 The logcat output as a string or an empty string if logcat was not
612 being recorded at the time.
613 """
614 if not self.logcat_process:
615 return ''
616 # Cannot evaluate directly as 0 is a possible value.
617 # Better to read the self.logcat_process.stdout before killing it,
618 # Otherwise the communicate may return incomplete output due to pipe break.
619 if self.logcat_process.poll() == None:
620 self.logcat_process.kill()
621 (output, _) = self.logcat_process.communicate()
622 self.logcat_process = None
623 return output
624
625 def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None,
626 log_level=None, component=None):
627 """Searches the specified logcat output and returns results.
628
629 This method searches through the logcat output specified by record for a
630 certain message, narrowing results by matching them against any other
631 specified criteria. It returns all matching lines as described below.
632
633 Args:
634 record: A string generated by Start/StopRecordingLogcat to search.
635 message: An output string to search for.
636 thread_id: The thread id that is the origin of the message.
637 proc_id: The process that is the origin of the message.
638 log_level: The log level of the message.
639 component: The name of the component that would create the message.
640
641 Returns:
642 A list of dictionaries represeting matching entries, each containing keys
643 thread_id, proc_id, log_level, component, and message.
644 """
645 if thread_id:
646 thread_id = str(thread_id)
647 if proc_id:
648 proc_id = str(proc_id)
649 results = []
650 reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
651 re.MULTILINE)
652 log_list = reg.findall(record)
653 for (tid, pid, log_lev, comp, msg) in log_list:
654 if ((not thread_id or thread_id == tid) and
655 (not proc_id or proc_id == pid) and
656 (not log_level or log_level == log_lev) and
657 (not component or component == comp) and msg.find(message) > -1):
658 match = dict({'thread_id': tid, 'proc_id': pid,
659 'log_level': log_lev, 'component': comp,
660 'message': msg})
661 results.append(match)
662 return results
663
664 def ExtractPid(self, process_name):
665 """Extracts Process Ids for a given process name from Android Shell.
666
667 Args:
668 process_name: name of the process on the device.
669
670 Returns:
671 List of all the process ids (as strings) that match the given name.
672 """
673 pids = []
674 for line in self.RunShellCommand('ps'):
675 data = line.split()
676 try:
677 if process_name in data[-1]: # name is in the last column
678 pids.append(data[1]) # PID is in the second column
679 except IndexError:
680 pass
681 return pids
682
683 def GetIoStats(self):
684 """Gets cumulative disk IO stats since boot (for all processes).
685
686 Returns:
687 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
688 was an error.
689 """
690 # Field definitions.
691 # http://www.kernel.org/doc/Documentation/iostats.txt
692 device = 2
693 num_reads_issued_idx = 3
694 num_reads_merged_idx = 4
695 num_sectors_read_idx = 5
696 ms_spent_reading_idx = 6
697 num_writes_completed_idx = 7
698 num_writes_merged_idx = 8
699 num_sectors_written_idx = 9
700 ms_spent_writing_idx = 10
701 num_ios_in_progress_idx = 11
702 ms_spent_doing_io_idx = 12
703 ms_spent_doing_io_weighted_idx = 13
704
705 for line in self.RunShellCommand('cat /proc/diskstats'):
706 fields = line.split()
707 if fields[device] == 'mmcblk0':
708 return {
709 'num_reads': int(fields[num_reads_issued_idx]),
710 'num_writes': int(fields[num_writes_completed_idx]),
711 'read_ms': int(fields[ms_spent_reading_idx]),
712 'write_ms': int(fields[ms_spent_writing_idx]),
713 }
714 logging.warning('Could not find disk IO stats.')
715 return None
716
717 def GetMemoryUsage(self, package):
718 """Returns the memory usage for all processes whose name contains |pacakge|.
719
720 Args:
721 name: A string holding process name to lookup pid list for.
722
723 Returns:
724 Dict of {metric:usage_kb}, summed over all pids associated with |name|.
725 The metric keys retruned are: Size, Rss, Pss, Shared_Clean, Shared_Dirty,
726 Private_Clean, Private_Dirty, Referenced, Swap, KernelPageSize,
727 MMUPageSize.
728 """
729 usage_dict = collections.defaultdict(int)
730 pid_list = self.ExtractPid(package)
731 # We used to use the showmap command, but it is currently broken on
732 # stingray so it's easier to just parse /proc/<pid>/smaps directly.
733 memory_stat_re = re.compile('^(?P<key>\w+):\s+(?P<value>\d+) kB$')
734 for pid in pid_list:
735 for line in self.RunShellCommand('cat /proc/%s/smaps' % pid,
736 log_result=False):
737 match = re.match(memory_stat_re, line)
738 if match: usage_dict[match.group('key')] += int(match.group('value'))
739 if not usage_dict or not any(usage_dict.values()):
740 # Presumably the process died between ps and showmap.
741 logging.warning('Could not find memory usage for pid ' + str(pid))
742 return usage_dict
743
744 def UnlockDevice(self):
745 """Unlocks the screen of the device."""
746 # Make sure a menu button event will actually unlock the screen.
747 if IsRunningAsBuildbot():
748 assert self.RunShellCommand('getprop ro.test_harness')[0].strip() == '1'
749 # The following keyevent unlocks the screen if locked.
750 self.SendKeyEvent(KEYCODE_MENU)
751 # If the screen wasn't locked the previous command will bring up the menu,
752 # which this will dismiss. Otherwise this shouldn't change anything.
753 self.SendKeyEvent(KEYCODE_BACK)
754
755
756 def main(argv):
757 option_parser = optparse.OptionParser()
758 option_parser.add_option('-w', '--wait_for_pm', action='store_true',
759 default=False, dest='wait_for_pm',
760 help='Waits for Device Package Manager to become available')
761 option_parser.add_option('--enable_asserts', dest='set_asserts',
762 action='store_true', default=None,
763 help='Sets the dalvik.vm.enableassertions property to "all"')
764 option_parser.add_option('--disable_asserts', dest='set_asserts',
765 action='store_false', default=None,
766 help='Removes the dalvik.vm.enableassertions property')
767 options, args = option_parser.parse_args(argv)
768
769 commands = AndroidCommands(wait_for_pm=options.wait_for_pm)
770 if options.set_asserts != None:
771 if commands.SetJavaAssertsEnabled(options.set_asserts):
772 commands.Reboot(full_reboot=False)
773
774
775 if __name__ == '__main__':
776 print os.path.abspath(os.path.dirname(__file__))
777 main(sys.argv)
OLDNEW
« no previous file with comments | « no previous file | build/android/cmd_helper.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698