media/webrtc/trunk/build/android/pylib/debug_info.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/media/webrtc/trunk/build/android/pylib/debug_info.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,196 @@
     1.4 +# Copyright (c) 2012 The Chromium Authors. All rights reserved.
     1.5 +# Use of this source code is governed by a BSD-style license that can be
     1.6 +# found in the LICENSE file.
     1.7 +
     1.8 +"""Collect debug info for a test."""
     1.9 +
    1.10 +import datetime
    1.11 +import logging
    1.12 +import os
    1.13 +import re
    1.14 +import shutil
    1.15 +import string
    1.16 +import subprocess
    1.17 +import tempfile
    1.18 +
    1.19 +import cmd_helper
    1.20 +
    1.21 +
    1.22 +TOMBSTONE_DIR = '/data/tombstones/'
    1.23 +
    1.24 +
    1.25 +class GTestDebugInfo(object):
    1.26 +  """A helper class to collect related debug information for a gtest.
    1.27 +
    1.28 +  Debug info is collected in two steps:
    1.29 +  - first, object(s) of this class (one per device), accumulate logs
    1.30 +  and screenshots in tempdir.
    1.31 +  - once the test has finished, call ZipAndCleanResults to create
    1.32 +  a zip containing the logs from all devices, and clean them up.
    1.33 +
    1.34 +  Args:
    1.35 +    adb: ADB interface the tests are using.
    1.36 +    device: Serial# of the Android device in which the specified gtest runs.
    1.37 +    testsuite_name: Name of the specified gtest.
    1.38 +    gtest_filter: Test filter used by the specified gtest.
    1.39 +  """
    1.40 +
    1.41 +  def __init__(self, adb, device, testsuite_name, gtest_filter):
    1.42 +    """Initializes the DebugInfo class for a specified gtest."""
    1.43 +    self.adb = adb
    1.44 +    self.device = device
    1.45 +    self.testsuite_name = testsuite_name
    1.46 +    self.gtest_filter = gtest_filter
    1.47 +    self.logcat_process = None
    1.48 +    self.has_storage = False
    1.49 +    self.log_dir = os.path.join(tempfile.gettempdir(),
    1.50 +                                'gtest_debug_info',
    1.51 +                                self.testsuite_name,
    1.52 +                                self.device)
    1.53 +    if not os.path.exists(self.log_dir):
    1.54 +      os.makedirs(self.log_dir)
    1.55 +    self.log_file_name = os.path.join(self.log_dir,
    1.56 +                                      self._GeneratePrefixName() + '_log.txt')
    1.57 +    self.old_crash_files = self._ListCrashFiles()
    1.58 +
    1.59 +  def _GetSignatureFromGTestFilter(self):
    1.60 +    """Gets a signature from gtest_filter.
    1.61 +
    1.62 +    Signature is used to identify the tests from which we collect debug
    1.63 +    information.
    1.64 +
    1.65 +    Returns:
    1.66 +      A signature string. Returns 'all' if there is no gtest filter.
    1.67 +    """
    1.68 +    if not self.gtest_filter:
    1.69 +      return 'all'
    1.70 +    filename_chars = "-_()%s%s" % (string.ascii_letters, string.digits)
    1.71 +    signature = ''.join(c for c in self.gtest_filter if c in filename_chars)
    1.72 +    if len(signature) > 64:
    1.73 +      # The signature can't be too long, as it'll be part of a file name.
    1.74 +      signature = signature[:64]
    1.75 +    return signature
    1.76 +
    1.77 +  def _GeneratePrefixName(self):
    1.78 +    """Generates a prefix name for debug information of the test.
    1.79 +
    1.80 +    The prefix name consists of the following:
    1.81 +    (1) root name of test_suite_base.
    1.82 +    (2) device serial number.
    1.83 +    (3) prefix of filter signature generate from gtest_filter.
    1.84 +    (4) date & time when calling this method.
    1.85 +
    1.86 +    Returns:
    1.87 +      Name of the log file.
    1.88 +    """
    1.89 +    return (os.path.splitext(self.testsuite_name)[0] + '_' + self.device + '_' +
    1.90 +            self._GetSignatureFromGTestFilter() + '_' +
    1.91 +            datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S-%f'))
    1.92 +
    1.93 +  def StartRecordingLog(self, clear=True, filters=['*:v']):
    1.94 +    """Starts recording logcat output to a file.
    1.95 +
    1.96 +    This call should come before running test, with calling StopRecordingLog
    1.97 +    following the tests.
    1.98 +
    1.99 +    Args:
   1.100 +      clear: True if existing log output should be cleared.
   1.101 +      filters: A list of logcat filters to be used.
   1.102 +    """
   1.103 +    self.StopRecordingLog()
   1.104 +    if clear:
   1.105 +      cmd_helper.RunCmd(['adb', '-s', self.device, 'logcat', '-c'])
   1.106 +    logging.info('Start dumping log to %s ...', self.log_file_name)
   1.107 +    command = 'adb -s %s logcat -v threadtime %s > %s' % (self.device,
   1.108 +                                                          ' '.join(filters),
   1.109 +                                                          self.log_file_name)
   1.110 +    self.logcat_process = subprocess.Popen(command, shell=True)
   1.111 +
   1.112 +  def StopRecordingLog(self):
   1.113 +    """Stops an existing logcat recording subprocess."""
   1.114 +    if not self.logcat_process:
   1.115 +      return
   1.116 +    # Cannot evaluate directly as 0 is a possible value.
   1.117 +    if self.logcat_process.poll() is None:
   1.118 +      self.logcat_process.kill()
   1.119 +    self.logcat_process = None
   1.120 +    logging.info('Finish log dump.')
   1.121 +
   1.122 +  def TakeScreenshot(self, identifier_mark):
   1.123 +    """Takes a screen shot from current specified device.
   1.124 +
   1.125 +    Args:
   1.126 +      identifier_mark: A string to identify the screen shot DebugInfo will take.
   1.127 +                       It will be part of filename of the screen shot. Empty
   1.128 +                       string is acceptable.
   1.129 +    Returns:
   1.130 +      Returns the file name on the host of the screenshot if successful,
   1.131 +      None otherwise.
   1.132 +    """
   1.133 +    assert isinstance(identifier_mark, str)
   1.134 +    screenshot_path = os.path.join(os.getenv('ANDROID_HOST_OUT', ''),
   1.135 +                                   'bin',
   1.136 +                                   'screenshot2')
   1.137 +    if not os.path.exists(screenshot_path):
   1.138 +      logging.error('Failed to take screen shot from device %s', self.device)
   1.139 +      return None
   1.140 +    shot_path = os.path.join(self.log_dir, ''.join([self._GeneratePrefixName(),
   1.141 +                                                    identifier_mark,
   1.142 +                                                    '_screenshot.png']))
   1.143 +    re_success = re.compile(re.escape('Success.'), re.MULTILINE)
   1.144 +    if re_success.findall(cmd_helper.GetCmdOutput([screenshot_path, '-s',
   1.145 +                                                   self.device, shot_path])):
   1.146 +      logging.info('Successfully took a screen shot to %s', shot_path)
   1.147 +      return shot_path
   1.148 +    logging.error('Failed to take screen shot from device %s', self.device)
   1.149 +    return None
   1.150 +
   1.151 +  def _ListCrashFiles(self):
   1.152 +    """Collects crash files from current specified device.
   1.153 +
   1.154 +    Returns:
   1.155 +      A dict of crash files in format {"name": (size, lastmod), ...}.
   1.156 +    """
   1.157 +    return self.adb.ListPathContents(TOMBSTONE_DIR)
   1.158 +
   1.159 +  def ArchiveNewCrashFiles(self):
   1.160 +    """Archives the crash files newly generated until calling this method."""
   1.161 +    current_crash_files = self._ListCrashFiles()
   1.162 +    files = []
   1.163 +    for f in current_crash_files:
   1.164 +      if f not in self.old_crash_files:
   1.165 +        files += [f]
   1.166 +      elif current_crash_files[f] != self.old_crash_files[f]:
   1.167 +        # Tombstones dir can only have maximum 10 files, so we need to compare
   1.168 +        # size and timestamp information of file if the file exists.
   1.169 +        files += [f]
   1.170 +    if files:
   1.171 +      logging.info('New crash file(s):%s' % ' '.join(files))
   1.172 +      for f in files:
   1.173 +        self.adb.Adb().Pull(TOMBSTONE_DIR + f,
   1.174 +                            os.path.join(self.log_dir, f))
   1.175 +
   1.176 +  @staticmethod
   1.177 +  def ZipAndCleanResults(dest_dir, dump_file_name):
   1.178 +    """A helper method to zip all debug information results into a dump file.
   1.179 +
   1.180 +    Args:
   1.181 +      dest_dir: Dir path in where we put the dump file.
   1.182 +      dump_file_name: Desired name of the dump file. This method makes sure
   1.183 +                      '.zip' will be added as ext name.
   1.184 +    """
   1.185 +    if not dest_dir or not dump_file_name:
   1.186 +      return
   1.187 +    cmd_helper.RunCmd(['mkdir', '-p', dest_dir])
   1.188 +    log_basename = os.path.basename(dump_file_name)
   1.189 +    log_zip_file = os.path.join(dest_dir,
   1.190 +                                os.path.splitext(log_basename)[0] + '.zip')
   1.191 +    logging.info('Zipping debug dumps into %s ...', log_zip_file)
   1.192 +    # Add new dumps into the zip file. The zip may exist already if previous
   1.193 +    # gtest also dumps the debug information. It's OK since we clean up the old
   1.194 +    # dumps in each build step.
   1.195 +    log_src_dir = os.path.join(tempfile.gettempdir(), 'gtest_debug_info')
   1.196 +    cmd_helper.RunCmd(['zip', '-q', '-r', log_zip_file, log_src_dir])
   1.197 +    assert os.path.exists(log_zip_file)
   1.198 +    assert os.path.exists(log_src_dir)
   1.199 +    shutil.rmtree(log_src_dir)

mercurial