xref: /aosp_15_r20/external/cronet/build/android/adb_logcat_printer.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6777b538SAndroid Build Coastguard Worker#
3*6777b538SAndroid Build Coastguard Worker# Copyright 2012 The Chromium Authors
4*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
5*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker"""Shutdown adb_logcat_monitor and print accumulated logs.
8*6777b538SAndroid Build Coastguard Worker
9*6777b538SAndroid Build Coastguard WorkerTo test, call './adb_logcat_printer.py <base_dir>' where
10*6777b538SAndroid Build Coastguard Worker<base_dir> contains 'adb logcat -v threadtime' files named as
11*6777b538SAndroid Build Coastguard Workerlogcat_<deviceID>_<sequenceNum>
12*6777b538SAndroid Build Coastguard Worker
13*6777b538SAndroid Build Coastguard WorkerThe script will print the files to out, and will combine multiple
14*6777b538SAndroid Build Coastguard Workerlogcats from a single device if there is overlap.
15*6777b538SAndroid Build Coastguard Worker
16*6777b538SAndroid Build Coastguard WorkerAdditionally, if a <base_dir>/LOGCAT_MONITOR_PID exists, the script
17*6777b538SAndroid Build Coastguard Workerwill attempt to terminate the contained PID by sending a SIGINT and
18*6777b538SAndroid Build Coastguard Workermonitoring for the deletion of the aforementioned file.
19*6777b538SAndroid Build Coastguard Worker"""
20*6777b538SAndroid Build Coastguard Worker# pylint: disable=W0702
21*6777b538SAndroid Build Coastguard Worker
22*6777b538SAndroid Build Coastguard Workerimport argparse
23*6777b538SAndroid Build Coastguard Workerimport io
24*6777b538SAndroid Build Coastguard Workerimport logging
25*6777b538SAndroid Build Coastguard Workerimport os
26*6777b538SAndroid Build Coastguard Workerimport re
27*6777b538SAndroid Build Coastguard Workerimport signal
28*6777b538SAndroid Build Coastguard Workerimport sys
29*6777b538SAndroid Build Coastguard Workerimport time
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker
32*6777b538SAndroid Build Coastguard Worker# Set this to debug for more verbose output
33*6777b538SAndroid Build Coastguard WorkerLOG_LEVEL = logging.INFO
34*6777b538SAndroid Build Coastguard Worker
35*6777b538SAndroid Build Coastguard Worker
36*6777b538SAndroid Build Coastguard Workerdef CombineLogFiles(list_of_lists, logger):
37*6777b538SAndroid Build Coastguard Worker  """Splices together multiple logcats from the same device.
38*6777b538SAndroid Build Coastguard Worker
39*6777b538SAndroid Build Coastguard Worker  Args:
40*6777b538SAndroid Build Coastguard Worker    list_of_lists: list of pairs (filename, list of timestamped lines)
41*6777b538SAndroid Build Coastguard Worker    logger: handler to log events
42*6777b538SAndroid Build Coastguard Worker
43*6777b538SAndroid Build Coastguard Worker  Returns:
44*6777b538SAndroid Build Coastguard Worker    list of lines with duplicates removed
45*6777b538SAndroid Build Coastguard Worker  """
46*6777b538SAndroid Build Coastguard Worker  cur_device_log = ['']
47*6777b538SAndroid Build Coastguard Worker  for cur_file, cur_file_lines in list_of_lists:
48*6777b538SAndroid Build Coastguard Worker    # Ignore files with just the logcat header
49*6777b538SAndroid Build Coastguard Worker    if len(cur_file_lines) < 2:
50*6777b538SAndroid Build Coastguard Worker      continue
51*6777b538SAndroid Build Coastguard Worker    common_index = 0
52*6777b538SAndroid Build Coastguard Worker    # Skip this step if list just has empty string
53*6777b538SAndroid Build Coastguard Worker    if len(cur_device_log) > 1:
54*6777b538SAndroid Build Coastguard Worker      try:
55*6777b538SAndroid Build Coastguard Worker        line = cur_device_log[-1]
56*6777b538SAndroid Build Coastguard Worker        # Used to make sure we only splice on a timestamped line
57*6777b538SAndroid Build Coastguard Worker        if re.match(r'^\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3} ', line):
58*6777b538SAndroid Build Coastguard Worker          common_index = cur_file_lines.index(line)
59*6777b538SAndroid Build Coastguard Worker        else:
60*6777b538SAndroid Build Coastguard Worker          logger.warning('splice error - no timestamp in "%s"?', line.strip())
61*6777b538SAndroid Build Coastguard Worker      except ValueError:
62*6777b538SAndroid Build Coastguard Worker        # The last line was valid but wasn't found in the next file
63*6777b538SAndroid Build Coastguard Worker        cur_device_log += ['***** POSSIBLE INCOMPLETE LOGCAT *****']
64*6777b538SAndroid Build Coastguard Worker        logger.info('Unable to splice %s. Incomplete logcat?', cur_file)
65*6777b538SAndroid Build Coastguard Worker
66*6777b538SAndroid Build Coastguard Worker    cur_device_log += ['*'*30 + '  %s' % cur_file]
67*6777b538SAndroid Build Coastguard Worker    cur_device_log.extend(cur_file_lines[common_index:])
68*6777b538SAndroid Build Coastguard Worker
69*6777b538SAndroid Build Coastguard Worker  return cur_device_log
70*6777b538SAndroid Build Coastguard Worker
71*6777b538SAndroid Build Coastguard Worker
72*6777b538SAndroid Build Coastguard Workerdef FindLogFiles(base_dir):
73*6777b538SAndroid Build Coastguard Worker  """Search a directory for logcat files.
74*6777b538SAndroid Build Coastguard Worker
75*6777b538SAndroid Build Coastguard Worker  Args:
76*6777b538SAndroid Build Coastguard Worker    base_dir: directory to search
77*6777b538SAndroid Build Coastguard Worker
78*6777b538SAndroid Build Coastguard Worker  Returns:
79*6777b538SAndroid Build Coastguard Worker    Mapping of device_id to a sorted list of file paths for a given device
80*6777b538SAndroid Build Coastguard Worker  """
81*6777b538SAndroid Build Coastguard Worker  logcat_filter = re.compile(r'^logcat_(\S+)_(\d+)$')
82*6777b538SAndroid Build Coastguard Worker  # list of tuples (<device_id>, <seq num>, <full file path>)
83*6777b538SAndroid Build Coastguard Worker  filtered_list = []
84*6777b538SAndroid Build Coastguard Worker  for cur_file in os.listdir(base_dir):
85*6777b538SAndroid Build Coastguard Worker    matcher = logcat_filter.match(cur_file)
86*6777b538SAndroid Build Coastguard Worker    if matcher:
87*6777b538SAndroid Build Coastguard Worker      filtered_list += [(matcher.group(1), int(matcher.group(2)),
88*6777b538SAndroid Build Coastguard Worker                         os.path.join(base_dir, cur_file))]
89*6777b538SAndroid Build Coastguard Worker  filtered_list.sort()
90*6777b538SAndroid Build Coastguard Worker  file_map = {}
91*6777b538SAndroid Build Coastguard Worker  for device_id, _, cur_file in filtered_list:
92*6777b538SAndroid Build Coastguard Worker    if device_id not in file_map:
93*6777b538SAndroid Build Coastguard Worker      file_map[device_id] = []
94*6777b538SAndroid Build Coastguard Worker
95*6777b538SAndroid Build Coastguard Worker    file_map[device_id] += [cur_file]
96*6777b538SAndroid Build Coastguard Worker  return file_map
97*6777b538SAndroid Build Coastguard Worker
98*6777b538SAndroid Build Coastguard Worker
99*6777b538SAndroid Build Coastguard Workerdef GetDeviceLogs(log_filenames, logger):
100*6777b538SAndroid Build Coastguard Worker  """Read log files, combine and format.
101*6777b538SAndroid Build Coastguard Worker
102*6777b538SAndroid Build Coastguard Worker  Args:
103*6777b538SAndroid Build Coastguard Worker    log_filenames: mapping of device_id to sorted list of file paths
104*6777b538SAndroid Build Coastguard Worker    logger: logger handle for logging events
105*6777b538SAndroid Build Coastguard Worker
106*6777b538SAndroid Build Coastguard Worker  Returns:
107*6777b538SAndroid Build Coastguard Worker    list of formatted device logs, one for each device.
108*6777b538SAndroid Build Coastguard Worker  """
109*6777b538SAndroid Build Coastguard Worker  device_logs = []
110*6777b538SAndroid Build Coastguard Worker
111*6777b538SAndroid Build Coastguard Worker  for device, device_files in log_filenames.items():
112*6777b538SAndroid Build Coastguard Worker    logger.debug('%s: %s', device, str(device_files))
113*6777b538SAndroid Build Coastguard Worker    device_file_lines = []
114*6777b538SAndroid Build Coastguard Worker    for cur_file in device_files:
115*6777b538SAndroid Build Coastguard Worker      with open(cur_file, encoding='latin1') as f:
116*6777b538SAndroid Build Coastguard Worker        device_file_lines += [(cur_file, f.read().splitlines())]
117*6777b538SAndroid Build Coastguard Worker    combined_lines = CombineLogFiles(device_file_lines, logger)
118*6777b538SAndroid Build Coastguard Worker    # Prepend each line with a short unique ID so it's easy to see
119*6777b538SAndroid Build Coastguard Worker    # when the device changes.  We don't use the start of the device
120*6777b538SAndroid Build Coastguard Worker    # ID because it can be the same among devices.  Example lines:
121*6777b538SAndroid Build Coastguard Worker    # AB324:  foo
122*6777b538SAndroid Build Coastguard Worker    # AB324:  blah
123*6777b538SAndroid Build Coastguard Worker    device_logs += [('\n' + device[-5:] + ':  ').join(combined_lines)]
124*6777b538SAndroid Build Coastguard Worker  return device_logs
125*6777b538SAndroid Build Coastguard Worker
126*6777b538SAndroid Build Coastguard Worker
127*6777b538SAndroid Build Coastguard Workerdef ShutdownLogcatMonitor(base_dir, logger):
128*6777b538SAndroid Build Coastguard Worker  """Attempts to shutdown adb_logcat_monitor and blocks while waiting."""
129*6777b538SAndroid Build Coastguard Worker  try:
130*6777b538SAndroid Build Coastguard Worker    monitor_pid_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
131*6777b538SAndroid Build Coastguard Worker    with open(monitor_pid_path) as f:
132*6777b538SAndroid Build Coastguard Worker      monitor_pid = int(f.readline())
133*6777b538SAndroid Build Coastguard Worker
134*6777b538SAndroid Build Coastguard Worker    logger.info('Sending SIGTERM to %d', monitor_pid)
135*6777b538SAndroid Build Coastguard Worker    os.kill(monitor_pid, signal.SIGTERM)
136*6777b538SAndroid Build Coastguard Worker    i = 0
137*6777b538SAndroid Build Coastguard Worker    while True:
138*6777b538SAndroid Build Coastguard Worker      time.sleep(.2)
139*6777b538SAndroid Build Coastguard Worker      if not os.path.exists(monitor_pid_path):
140*6777b538SAndroid Build Coastguard Worker        return
141*6777b538SAndroid Build Coastguard Worker      if not os.path.exists('/proc/%d' % monitor_pid):
142*6777b538SAndroid Build Coastguard Worker        logger.warning('Monitor (pid %d) terminated uncleanly?', monitor_pid)
143*6777b538SAndroid Build Coastguard Worker        return
144*6777b538SAndroid Build Coastguard Worker      logger.info('Waiting for logcat process to terminate.')
145*6777b538SAndroid Build Coastguard Worker      i += 1
146*6777b538SAndroid Build Coastguard Worker      if i >= 10:
147*6777b538SAndroid Build Coastguard Worker        logger.warning('Monitor pid did not terminate. Continuing anyway.')
148*6777b538SAndroid Build Coastguard Worker        return
149*6777b538SAndroid Build Coastguard Worker
150*6777b538SAndroid Build Coastguard Worker  except (ValueError, IOError, OSError):
151*6777b538SAndroid Build Coastguard Worker    logger.exception('Error signaling logcat monitor - continuing')
152*6777b538SAndroid Build Coastguard Worker
153*6777b538SAndroid Build Coastguard Worker
154*6777b538SAndroid Build Coastguard Workerdef main(argv):
155*6777b538SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
156*6777b538SAndroid Build Coastguard Worker  parser.add_argument(
157*6777b538SAndroid Build Coastguard Worker      '--output-path',
158*6777b538SAndroid Build Coastguard Worker      help='Output file path (if unspecified, prints to stdout)')
159*6777b538SAndroid Build Coastguard Worker  parser.add_argument('log_dir')
160*6777b538SAndroid Build Coastguard Worker  args = parser.parse_args(argv)
161*6777b538SAndroid Build Coastguard Worker  base_dir = args.log_dir
162*6777b538SAndroid Build Coastguard Worker
163*6777b538SAndroid Build Coastguard Worker  log_stringio = io.StringIO()
164*6777b538SAndroid Build Coastguard Worker  logger = logging.getLogger('LogcatPrinter')
165*6777b538SAndroid Build Coastguard Worker  logger.setLevel(LOG_LEVEL)
166*6777b538SAndroid Build Coastguard Worker  sh = logging.StreamHandler(log_stringio)
167*6777b538SAndroid Build Coastguard Worker  sh.setFormatter(logging.Formatter('%(asctime)-2s %(levelname)-8s'
168*6777b538SAndroid Build Coastguard Worker                                    ' %(message)s'))
169*6777b538SAndroid Build Coastguard Worker  logger.addHandler(sh)
170*6777b538SAndroid Build Coastguard Worker
171*6777b538SAndroid Build Coastguard Worker  if args.output_path:
172*6777b538SAndroid Build Coastguard Worker    if not os.path.exists(os.path.dirname(args.output_path)):
173*6777b538SAndroid Build Coastguard Worker      logger.warning('Output dir %s doesn\'t exist. Creating it.',
174*6777b538SAndroid Build Coastguard Worker                     os.path.dirname(args.output_path))
175*6777b538SAndroid Build Coastguard Worker      os.makedirs(os.path.dirname(args.output_path))
176*6777b538SAndroid Build Coastguard Worker    output_file = open(args.output_path, 'w')
177*6777b538SAndroid Build Coastguard Worker    logger.info(
178*6777b538SAndroid Build Coastguard Worker        'Dumping logcat to local file %s. If running in a build, '
179*6777b538SAndroid Build Coastguard Worker        'this file will likely will be uploaded to google storage '
180*6777b538SAndroid Build Coastguard Worker        'in a later step. It can be downloaded from there.', args.output_path)
181*6777b538SAndroid Build Coastguard Worker  else:
182*6777b538SAndroid Build Coastguard Worker    output_file = sys.stdout
183*6777b538SAndroid Build Coastguard Worker
184*6777b538SAndroid Build Coastguard Worker  try:
185*6777b538SAndroid Build Coastguard Worker    # Wait at least 5 seconds after base_dir is created before printing.
186*6777b538SAndroid Build Coastguard Worker    #
187*6777b538SAndroid Build Coastguard Worker    # The idea is that 'adb logcat > file' output consists of 2 phases:
188*6777b538SAndroid Build Coastguard Worker    #  1 Dump all the saved logs to the file
189*6777b538SAndroid Build Coastguard Worker    #  2 Stream log messages as they are generated
190*6777b538SAndroid Build Coastguard Worker    #
191*6777b538SAndroid Build Coastguard Worker    # We want to give enough time for phase 1 to complete.  There's no
192*6777b538SAndroid Build Coastguard Worker    # good method to tell how long to wait, but it usually only takes a
193*6777b538SAndroid Build Coastguard Worker    # second.  On most bots, this code path won't occur at all, since
194*6777b538SAndroid Build Coastguard Worker    # adb_logcat_monitor.py command will have spawned more than 5 seconds
195*6777b538SAndroid Build Coastguard Worker    # prior to called this shell script.
196*6777b538SAndroid Build Coastguard Worker    try:
197*6777b538SAndroid Build Coastguard Worker      sleep_time = 5 - (time.time() - os.path.getctime(base_dir))
198*6777b538SAndroid Build Coastguard Worker    except OSError:
199*6777b538SAndroid Build Coastguard Worker      sleep_time = 5
200*6777b538SAndroid Build Coastguard Worker    if sleep_time > 0:
201*6777b538SAndroid Build Coastguard Worker      logger.warning('Monitor just started? Sleeping %.1fs', sleep_time)
202*6777b538SAndroid Build Coastguard Worker      time.sleep(sleep_time)
203*6777b538SAndroid Build Coastguard Worker
204*6777b538SAndroid Build Coastguard Worker    assert os.path.exists(base_dir), '%s does not exist' % base_dir
205*6777b538SAndroid Build Coastguard Worker    ShutdownLogcatMonitor(base_dir, logger)
206*6777b538SAndroid Build Coastguard Worker    separator = '\n' + '*' * 80 + '\n\n'
207*6777b538SAndroid Build Coastguard Worker    for log in GetDeviceLogs(FindLogFiles(base_dir), logger):
208*6777b538SAndroid Build Coastguard Worker      output_file.write(log)
209*6777b538SAndroid Build Coastguard Worker      output_file.write(separator)
210*6777b538SAndroid Build Coastguard Worker    with open(os.path.join(base_dir, 'eventlog')) as f:
211*6777b538SAndroid Build Coastguard Worker      output_file.write('\nLogcat Monitor Event Log\n')
212*6777b538SAndroid Build Coastguard Worker      output_file.write(f.read())
213*6777b538SAndroid Build Coastguard Worker  except:
214*6777b538SAndroid Build Coastguard Worker    logger.exception('Unexpected exception')
215*6777b538SAndroid Build Coastguard Worker
216*6777b538SAndroid Build Coastguard Worker  logger.info('Done.')
217*6777b538SAndroid Build Coastguard Worker  sh.flush()
218*6777b538SAndroid Build Coastguard Worker  output_file.write('\nLogcat Printer Event Log\n')
219*6777b538SAndroid Build Coastguard Worker  output_file.write(log_stringio.getvalue())
220*6777b538SAndroid Build Coastguard Worker
221*6777b538SAndroid Build Coastguard Workerif __name__ == '__main__':
222*6777b538SAndroid Build Coastguard Worker  sys.exit(main(sys.argv[1:]))
223