2013-06-18 18:32:11 -07:00
|
|
|
#!/usr/bin/python -u
|
2013-06-11 22:59:50 -07:00
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Copyright 2009, The Android Open Source Project
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
# Script to highlight adb logcat output for console
|
|
|
|
|
# Written by Jeff Sharkey, http://jsharkey.org/
|
|
|
|
|
# Piping detection and popen() added by other Android team members
|
|
|
|
|
# Package name restriction by Jake Wharton, http://jakewharton.com
|
2013-08-21 16:27:10 -07:00
|
|
|
# PID detection with ps command by Takahiro "Poly" Horikawa <horikawa.takahiro@gmail.com>
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-12 08:34:23 -07:00
|
|
|
import argparse
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import re
|
2013-06-18 18:32:11 -07:00
|
|
|
import subprocess
|
2013-08-21 16:27:10 -07:00
|
|
|
import time
|
|
|
|
|
import thread
|
|
|
|
|
import threading
|
2013-06-18 18:32:11 -07:00
|
|
|
from subprocess import PIPE
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-13 14:08:13 -07:00
|
|
|
|
2013-06-24 09:16:07 -07:00
|
|
|
LOG_LEVELS = 'VDIWEF'
|
2013-06-13 14:08:13 -07:00
|
|
|
LOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))])
|
2013-06-12 08:34:23 -07:00
|
|
|
parser = argparse.ArgumentParser(description='Filter logcat by package name')
|
2013-06-12 14:53:14 -07:00
|
|
|
parser.add_argument('package', nargs='+', help='Application package name(s)')
|
2013-06-13 14:08:13 -07:00
|
|
|
parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=22, help='Width of log tag')
|
2013-06-15 11:00:47 -07:00
|
|
|
parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS, default='V', help='Minimum level to be displayed')
|
2013-06-12 14:45:48 -07:00
|
|
|
parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection')
|
2013-06-19 08:36:09 -03:00
|
|
|
parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)')
|
2013-07-07 16:23:39 +02:00
|
|
|
parser.add_argument('-d', '--device', dest='use_device', action='store_true', help='Use first device for log input (adb -d option).')
|
|
|
|
|
parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true', help='Use first emulator for log input (adb -e option).')
|
2013-06-12 08:34:23 -07:00
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
2013-06-19 11:05:43 -07:00
|
|
|
min_level = LOG_LEVELS_MAP[args.min_level]
|
2013-06-12 08:34:23 -07:00
|
|
|
|
2013-06-12 10:13:27 -07:00
|
|
|
header_size = args.tag_width + 1 + 3 + 1 # space, level, space
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-18 18:32:11 -07:00
|
|
|
width = -1
|
|
|
|
|
try:
|
|
|
|
|
# Get the current terminal width
|
|
|
|
|
import fcntl, termios, struct
|
|
|
|
|
h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0)))
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2013-06-11 22:59:50 -07:00
|
|
|
|
|
|
|
|
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
|
|
|
|
|
2013-06-12 14:45:48 -07:00
|
|
|
RESET = '\033[0m'
|
|
|
|
|
|
|
|
|
|
def termcolor(fg=None, bg=None):
|
2013-06-11 22:59:50 -07:00
|
|
|
codes = []
|
|
|
|
|
if fg is not None: codes.append('3%d' % fg)
|
|
|
|
|
if bg is not None: codes.append('10%d' % bg)
|
2013-06-12 14:45:48 -07:00
|
|
|
return '\033[%sm' % ';'.join(codes) if codes else ''
|
|
|
|
|
|
|
|
|
|
def colorize(message, fg=None, bg=None):
|
|
|
|
|
return termcolor(fg, bg) + message + RESET
|
2013-06-11 22:59:50 -07:00
|
|
|
|
|
|
|
|
def indent_wrap(message):
|
2013-06-18 18:32:11 -07:00
|
|
|
if width == -1:
|
|
|
|
|
return message
|
2013-07-18 16:38:04 -04:00
|
|
|
message = message.replace('\t', ' ')
|
2013-06-18 18:32:11 -07:00
|
|
|
wrap_area = width - header_size
|
2013-06-12 08:18:53 -07:00
|
|
|
messagebuf = ''
|
2013-06-11 22:59:50 -07:00
|
|
|
current = 0
|
|
|
|
|
while current < len(message):
|
|
|
|
|
next = min(current + wrap_area, len(message))
|
2013-06-12 08:18:53 -07:00
|
|
|
messagebuf += message[current:next]
|
2013-06-11 22:59:50 -07:00
|
|
|
if next < len(message):
|
2013-06-12 08:18:53 -07:00
|
|
|
messagebuf += '\n'
|
2013-06-12 08:34:23 -07:00
|
|
|
messagebuf += ' ' * header_size
|
2013-06-11 22:59:50 -07:00
|
|
|
current = next
|
2013-06-12 08:18:53 -07:00
|
|
|
return messagebuf
|
2013-06-11 22:59:50 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
|
|
|
|
|
KNOWN_TAGS = {
|
|
|
|
|
'dalvikvm': WHITE,
|
|
|
|
|
'Process': WHITE,
|
|
|
|
|
'ActivityManager': WHITE,
|
|
|
|
|
'ActivityThread': WHITE,
|
|
|
|
|
'AndroidRuntime': CYAN,
|
|
|
|
|
'jdwp': WHITE,
|
2013-06-12 14:45:48 -07:00
|
|
|
'StrictMode': WHITE,
|
2013-06-11 22:59:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def allocate_color(tag):
|
|
|
|
|
# this will allocate a unique format for the given tag
|
|
|
|
|
# since we dont have very many colors, we always keep track of the LRU
|
|
|
|
|
if tag not in KNOWN_TAGS:
|
|
|
|
|
KNOWN_TAGS[tag] = LAST_USED[0]
|
|
|
|
|
color = KNOWN_TAGS[tag]
|
|
|
|
|
if color in LAST_USED:
|
|
|
|
|
LAST_USED.remove(color)
|
|
|
|
|
LAST_USED.append(color)
|
|
|
|
|
return color
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RULES = {
|
2013-06-12 14:45:48 -07:00
|
|
|
# StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1
|
|
|
|
|
re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)')
|
|
|
|
|
: r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET),
|
2013-06-11 22:59:50 -07:00
|
|
|
}
|
|
|
|
|
|
2013-06-12 14:45:48 -07:00
|
|
|
# Only enable GC coloring if the user opted-in
|
|
|
|
|
if args.color_gc:
|
|
|
|
|
# GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms
|
2013-06-12 22:26:58 -07:00
|
|
|
key = re.compile(r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed <?\d+.)(, \d+\% free \d+./\d+., )(paused \d+ms(?:\+\d+ms)?)')
|
2013-06-12 14:45:48 -07:00
|
|
|
val = r'\1%s\2%s\3%s\4%s' % (termcolor(GREEN), RESET, termcolor(YELLOW), RESET)
|
|
|
|
|
|
|
|
|
|
RULES[key] = val
|
|
|
|
|
|
|
|
|
|
|
2013-06-11 22:59:50 -07:00
|
|
|
TAGTYPES = {
|
|
|
|
|
'V': colorize(' V ', fg=WHITE, bg=BLACK),
|
|
|
|
|
'D': colorize(' D ', fg=BLACK, bg=BLUE),
|
|
|
|
|
'I': colorize(' I ', fg=BLACK, bg=GREEN),
|
|
|
|
|
'W': colorize(' W ', fg=BLACK, bg=YELLOW),
|
|
|
|
|
'E': colorize(' E ', fg=BLACK, bg=RED),
|
2013-06-15 01:48:04 +02:00
|
|
|
'F': colorize(' F ', fg=BLACK, bg=RED),
|
2013-06-11 22:59:50 -07:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 15:03:40 +02:00
|
|
|
LOG_LINE = re.compile(r'^([A-Z])/(.+?)\( *(\d+)\): (.*?)$')
|
2013-07-03 16:35:50 -07:00
|
|
|
BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*')
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-08-21 17:32:46 -07:00
|
|
|
PS_POLLING_INTERVAL_SECS = 1
|
2013-08-21 16:27:10 -07:00
|
|
|
|
2013-08-21 15:02:07 -07:00
|
|
|
adb_options = []
|
2013-06-19 11:05:43 -07:00
|
|
|
if args.device_serial:
|
2013-08-21 15:02:07 -07:00
|
|
|
adb_options.extend(['-s', args.device_serial])
|
2013-07-07 16:23:39 +02:00
|
|
|
if args.use_device:
|
2013-08-21 15:02:07 -07:00
|
|
|
adb_options.append('-d')
|
2013-07-07 16:23:39 +02:00
|
|
|
if args.use_emulator:
|
2013-08-21 15:02:07 -07:00
|
|
|
adb_options.append('-e')
|
|
|
|
|
adb_command = ['adb']
|
|
|
|
|
adb_command.extend(adb_options)
|
2013-06-19 11:05:43 -07:00
|
|
|
adb_command.append('logcat')
|
|
|
|
|
|
2013-08-21 15:02:07 -07:00
|
|
|
ps_command = ['adb']
|
|
|
|
|
ps_command.extend(adb_options)
|
|
|
|
|
ps_command.extend(['shell', 'ps'])
|
|
|
|
|
|
2013-06-18 18:32:11 -07:00
|
|
|
adb = subprocess.Popen(adb_command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
2013-06-12 09:38:00 -07:00
|
|
|
pids = set()
|
2013-06-12 10:17:22 -07:00
|
|
|
last_tag = None
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-15 17:50:12 +02:00
|
|
|
def match_packages(token):
|
2013-06-13 14:45:37 -04:00
|
|
|
index = token.find(':')
|
|
|
|
|
return (token in args.package) if index == -1 else (token[:index] in args.package)
|
2013-06-12 18:52:34 -04:00
|
|
|
|
2013-08-21 16:27:10 -07:00
|
|
|
def parse_ps(ps_out):
|
|
|
|
|
new_pids = set()
|
2013-08-21 15:02:07 -07:00
|
|
|
processes = ps_out.split('\n')
|
|
|
|
|
fields = processes[0].split();
|
|
|
|
|
nfields = len(fields)
|
|
|
|
|
for row in processes[1:]:
|
|
|
|
|
row = row.rstrip()
|
|
|
|
|
if not row:
|
|
|
|
|
continue
|
|
|
|
|
fields = row.split(None, nfields)
|
|
|
|
|
name = fields[8]
|
|
|
|
|
pid = fields[1]
|
|
|
|
|
if match_packages(name):
|
2013-08-21 16:27:10 -07:00
|
|
|
new_pids.add(pid)
|
|
|
|
|
return new_pids
|
|
|
|
|
|
|
|
|
|
def print_diff(pids, new_pids):
|
|
|
|
|
for new_pid in new_pids:
|
|
|
|
|
if new_pid not in pids:
|
2013-08-21 15:02:07 -07:00
|
|
|
linebuf = '\n'
|
|
|
|
|
linebuf += colorize(' ' * (header_size - 1), bg=WHITE)
|
2013-08-21 16:27:10 -07:00
|
|
|
linebuf += ' Process %s is found' % (new_pid)
|
2013-08-21 15:02:07 -07:00
|
|
|
linebuf += '\n'
|
|
|
|
|
print(linebuf)
|
|
|
|
|
|
2013-08-21 16:27:10 -07:00
|
|
|
deleted_pids = pids.difference(new_pids)
|
|
|
|
|
for deleted_pid in deleted_pids:
|
|
|
|
|
linebuf = '\n'
|
|
|
|
|
linebuf += colorize(' ' * (header_size - 1), bg=WHITE)
|
|
|
|
|
linebuf += ' Process %s is deleted' % (deleted_pid)
|
|
|
|
|
linebuf += '\n'
|
|
|
|
|
print(linebuf)
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def get_pids():
|
2013-08-21 15:02:07 -07:00
|
|
|
ps = subprocess.Popen(ps_command, stdout=PIPE)
|
|
|
|
|
ps_out, ps_err = ps.communicate()
|
2013-08-21 16:27:10 -07:00
|
|
|
return parse_ps(ps_out)
|
|
|
|
|
|
|
|
|
|
def update_pids():
|
|
|
|
|
with lock:
|
|
|
|
|
new_pids = get_pids()
|
|
|
|
|
global pids
|
|
|
|
|
print_diff(pids, new_pids)
|
|
|
|
|
pids = new_pids
|
|
|
|
|
last_ps_check = time.time()
|
|
|
|
|
|
|
|
|
|
def update_pids_background():
|
|
|
|
|
while True:
|
|
|
|
|
update_pids()
|
2013-08-21 17:32:46 -07:00
|
|
|
time.sleep(PS_POLLING_INTERVAL_SECS)
|
2013-08-21 16:27:10 -07:00
|
|
|
|
|
|
|
|
lock = threading.RLock()
|
|
|
|
|
thread.start_new_thread(update_pids_background, ())
|
2013-08-21 15:02:07 -07:00
|
|
|
|
2013-06-18 18:32:11 -07:00
|
|
|
while adb.poll() is None:
|
2013-06-11 22:59:50 -07:00
|
|
|
try:
|
2013-07-15 11:27:20 -07:00
|
|
|
line = adb.stdout.readline().decode('utf-8', 'replace').strip()
|
2013-06-11 22:59:50 -07:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
break
|
2013-06-12 09:31:26 -07:00
|
|
|
if len(line) == 0:
|
|
|
|
|
break
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-12 13:30:58 -06:00
|
|
|
bug_line = BUG_LINE.match(line)
|
2013-07-07 16:09:59 +02:00
|
|
|
if bug_line is not None:
|
2013-06-12 13:30:58 -06:00
|
|
|
continue
|
|
|
|
|
|
2013-06-11 22:59:50 -07:00
|
|
|
log_line = LOG_LINE.match(line)
|
2013-06-19 14:18:52 -07:00
|
|
|
if log_line is None:
|
|
|
|
|
continue
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-19 14:18:52 -07:00
|
|
|
level, tag, owner, message = log_line.groups()
|
2013-06-11 22:59:50 -07:00
|
|
|
|
2013-06-19 14:18:52 -07:00
|
|
|
if owner not in pids:
|
|
|
|
|
continue
|
|
|
|
|
if LOG_LEVELS_MAP[level] < min_level:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
linebuf = ''
|
|
|
|
|
|
|
|
|
|
# right-align tag title and allocate color if needed
|
|
|
|
|
tag = tag.strip()
|
|
|
|
|
if tag != last_tag:
|
|
|
|
|
last_tag = tag
|
|
|
|
|
color = allocate_color(tag)
|
|
|
|
|
tag = tag[-args.tag_width:].rjust(args.tag_width)
|
|
|
|
|
linebuf += colorize(tag, fg=color)
|
|
|
|
|
else:
|
|
|
|
|
linebuf += ' ' * args.tag_width
|
|
|
|
|
linebuf += ' '
|
|
|
|
|
|
|
|
|
|
# write out level colored edge
|
|
|
|
|
if level not in TAGTYPES: break
|
|
|
|
|
linebuf += TAGTYPES[level]
|
|
|
|
|
linebuf += ' '
|
|
|
|
|
|
|
|
|
|
# format tag message using rules
|
|
|
|
|
for matcher in RULES:
|
|
|
|
|
replace = RULES[matcher]
|
|
|
|
|
message = matcher.sub(replace, message)
|
|
|
|
|
|
|
|
|
|
linebuf += indent_wrap(message)
|
|
|
|
|
print(linebuf)
|