Support for identifying startup classes from instrumentation

This also updates generate_startup_descriptors.py to store the artifacts in an (optional) out directory, so that the generated profiles are accessible after script executing.

Change-Id: I3fa67d13d10a110f6c32e0696297f8a292b5c759
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index 981bcbd..589bc7f 100644
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -4,11 +4,33 @@
 # BSD-style license that can be found in the LICENSE file.
 
 from enum import Enum
+import os
 import subprocess
+import sys
+import threading
 import time
 
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+import utils
+
 DEVNULL=subprocess.DEVNULL
 
+class ProcessReader(threading.Thread):
+
+  def __init__(self, process):
+    threading.Thread.__init__(self)
+    self.lines = []
+    self.process = process
+
+  def run(self):
+    for line in self.process.stdout:
+      line = line.decode('utf-8').strip()
+      self.lines.append(line)
+
+  def stop(self):
+    self.process.kill()
+
 class ScreenState(Enum):
   OFF_LOCKED = 1,
   OFF_UNLOCKED = 2
@@ -39,7 +61,7 @@
 def capture_app_profile_data(app_id, device_id=None):
   cmd = create_adb_cmd(
       'shell killall -s SIGUSR1 %s' % app_id, device_id)
-  subprocess.check_output(cmd)
+  subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
   time.sleep(5)
 
 def check_app_has_profile_data(app_id, device_id=None):
@@ -54,6 +76,10 @@
   if size == 4:
     raise ValueError('Expected size of profile at %s to be > 4K' % profile_path)
 
+def clear_logcat(device_id=None):
+  cmd = create_adb_cmd('logcat -c', device_id)
+  subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
 def clear_profile_data(app_id, device_id=None):
   cmd = create_adb_cmd(
       'shell cmd package compile --reset %s' % app_id, device_id)
@@ -86,6 +112,15 @@
         'Expected stdout to end with ".apk", was: %s' % stdout)
   return apk_path
 
+def get_profile_data(app_id, device_id=None):
+  with utils.TempDir() as temp:
+    source = get_profile_path(app_id)
+    target = os.path.join(temp, 'primary.prof')
+    cmd = create_adb_cmd('pull %s %s' % (source, target), device_id)
+    subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+    with open(target, 'rb') as f:
+      return f.read()
+
 def get_profile_path(app_id):
   return '/data/misc/profiles/cur/0/%s/primary.prof' % app_id
 
@@ -226,6 +261,24 @@
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
   assert len(stdout) == 0
 
+def start_logcat(device_id=None, format=None, filter=None):
+  args = ['logcat']
+  if format:
+    args.extend(['--format', format])
+  if filter:
+    args.append(filter)
+  cmd = create_adb_cmd(args, device_id)
+  logcat_process = subprocess.Popen(
+      cmd, bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  reader = ProcessReader(logcat_process)
+  reader.start()
+  return reader
+
+def stop_logcat(logcat_reader):
+  logcat_reader.stop()
+  logcat_reader.join()
+  return logcat_reader.lines
+
 def stop_app(app_id, device_id=None):
   cmd = create_adb_cmd('shell am force-stop %s' % app_id, device_id)
   subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)