Skip to content
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
What is this?
-------------

This is a set of scripts to enhance your development time with adb.

* LOGCAT:

`./super-logcat.sh <interesting-process-name-regex-or-tag>`

So, if your process name is `com.example.myapp`, then running `./super-logcat.sh com.example` will show all messages related to your application.

Running the `super-logcat.sh` script with no arguments will display the logcat output from every process, piped through the same formatting and coloring algorithm.


* DEVICES:

`./pickdevice.pl`

This will help you easily select the device to run the commands on. This is usefull when using multiple devices/emulators at the same time.


* METHOD_ANALYSING

`./dexdumpmethods.sh <apk or dx'd jar file>`

You will see all methods in this package. It is usefull to pipe it into `wc -l` to see an overall number of functions.
45 changes: 29 additions & 16 deletions coloredlogcat.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def colorToFormat(color):
LAST_USED = map(colorToFormat, [RED,GREEN,YELLOW,BLUE,MAGENTA,CYAN,WHITE])
KNOWN_TAGS = {
"dalvikvm": format(fg=BLACK, bold=True),
"AndroidRuntime": format(fg=MAGENTA, bold=True),
"Process": format(fg=YELLOW, bold=True),
"ActivityManager": format(fg=CYAN, bold=True),
"ActivityThread": format(fg=CYAN, bold=True),
Expand All @@ -67,15 +68,6 @@ def allocate_color(tag):
return color


RULES = {
#re.compile(r"([\w\.@]+)=([\w\.@]+)"): r"%s\1%s=%s\2%s" % (format(fg=BLUE), format(fg=GREEN), format(fg=BLUE), format(reset=True)),
}

TAGTYPE_WIDTH = 1
TAG_WIDTH = 20
PROCESS_WIDTH = 8 # 8 or -1
HEADER_SIZE = TAGTYPE_WIDTH + 1 + TAG_WIDTH + 1 + PROCESS_WIDTH + 1

TAGTYPES = {
"V": "",
"D": format(fg=BLUE, bold=True),
Expand All @@ -84,7 +76,17 @@ def allocate_color(tag):
"E": format(fg=RED, bold=True),
}


TAGTYPENAMES = {
"V": "Verb ",
"D": "Debug",
"I": "Info ",
"W": "WARN ",
"E": "ERROR"
}

retag = re.compile("^([A-Z])/(.+?)\((\s*\d+)\): (.*)$")
newRetag = re.compile("^([0-9-\s:.]+?)\s+(\d+)\s+(\d+)\s+([A-Z])\s+(.*?)\s*: (.*)$")

# to pick up -d or -e
adb_args = ' '.join(sys.argv[1:])
Expand All @@ -100,28 +102,39 @@ def allocate_color(tag):
line = input.readline().rstrip()
except KeyboardInterrupt:
break
if len(line) == 0: break

match = retag.match(line)
newMatch = newRetag.match(line)
if not match is None:
tagtype, tag, owner, message = match.groups()
linebuf = StringIO.StringIO()

# write out tagtype colored edge
if not tagtype in TAGTYPES: break
linebuf.write("%s%s%s" % (TAGTYPES[tagtype], tagtype, format(reset=True)))
linebuf.write("%s%s%s" % (TAGTYPES[tagtype], TAGTYPENAMES[tagtype], format(reset=True)))

colorfmt = allocate_color(tag)
linebuf.write(" / %s%s%s" % (colorfmt, tag, format(reset=True)))

linebuf.write(" (%s): " % owner)

# format tag message using rules
for matcher in RULES:
replace = RULES[matcher]
message = matcher.sub(replace, message)

linebuf.write("%s%s%s" % (colorfmt, message, format(reset=True)))
line = linebuf.getvalue()
elif not newMatch is None:
time, owner, ppid, tagtype, tag, message = newMatch.groups()
if not tagtype in TAGTYPES: break

linebuf = StringIO.StringIO()
linebuf.write("%s %05s | %05s" % (time, owner, ppid))


# write out tagtype colored edge
linebuf.write(" %s%s%s" % (TAGTYPES[tagtype], TAGTYPENAMES[tagtype], format(reset=True)))

colorfmt = allocate_color(tag)
linebuf.write(" / %s%s%s: " % (colorfmt, tag, format(reset=True)))

linebuf.write("%s%s%s" % (colorfmt, message, format(reset=True)))
line = linebuf.getvalue()
print line
if len(line) == 0: break
75 changes: 52 additions & 23 deletions proclogcat
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ use Data::Dumper;

###############################################################################

@ARGV > 0 or usage($0);
@ARGV > 0 and not -t STDIN or usage($0);

# We support tracking multiple process names. Fill %trackingInfo keys with the
# process names, where the values are to be filled with the current pid for
# that process or -1 if it is presumed dead.
my $trackingInfo = { map { $_ => -1 } @ARGV };
# We support tracking multiple process names. Fill @trackedNames with the
# process names to match (via regular expressions). The process IDs that we
# are currently tracking will be stored in %trackedPids.
my @trackedNames = @ARGV;
my %trackedPids = ();

# Flush all writes immediately. This is necessary as we expect this script to
# be placed between two other programs in a pipe which outputs text very slowly
Expand All @@ -38,36 +39,51 @@ $| = 1;

# Lookup the pids of the processes before we start. From then on, rely on
# the ActivityManager to tell us as the processes dies and starts.
my $numPids = get_pids($trackingInfo);
my $numPids = get_pids();
if ($numPids == 0) {
print "- waiting for process ", join(' or ', keys %$trackingInfo), " -\n";
print "- waiting for process ", join(' or ', @trackedNames), " -\n";
}

while (<STDIN>) {
my $line = $_;
my ($level, $tag, $pid, $message) = $line =~
my ($time, $pid, $ppid, $level, $tag, $message) = $line =~
m/^([0-9-\s:.]+)\s+(\d+)\s+(\d+)\s+([A-Z])\s+(.*?)\s*: (.*)$/;
if ($pid == 0) {
($level, $tag, $pid, $message) = $line =~
m/^([A-Z])\/(.*?)\(\s*(\d+)\s*\): (.*)$/;
}

# print line if does not match the regex
if ($pid == 0 && $tag == 0) {
print $line;
next;
}

chomp $message;

if ($tag eq 'ActivityManager') {
if ($message =~ m/^Start proc (.*?) .*?: pid=(\d+) /) {
if (exists $trackingInfo->{$1}) {
$trackingInfo->{$1} = $2;
if (match_name($1)) {
$trackedPids{$2} = 1;
print $line;
}
} elsif ($message =~ m/Start proc (\d+):(.*?)\//) {
if (exists $trackingInfo->{$2}) {
$trackingInfo->{$2} = $1;
if (match_name($2)) {
$trackedPids{$1} = 1;
print $line;
}
} elsif ($message =~ m/Process (.*?) \(pid (\d+)\) has died./) {
if (exists $trackingInfo->{$1}) {
$trackingInfo->{$1} = -1;
if (match_name($1)) {
delete $trackedPids{$2};
print $line;
}
} elsif ($message =~ m/Killing (\d+):(.*?)\//) {
if (match_name($2)) {
delete $trackedPids{$1};
print $line;
}
}
} elsif (in_list($pid, values %$trackingInfo)) {
} elsif ($trackedPids{$pid} || in_list($tag, @trackedNames)) {
print $line;
}
}
Expand All @@ -87,16 +103,17 @@ sub in_list($@) {
}

sub get_pids {
my $info = shift;
my @ps = qx{adb shell ps};
my @ps = qx{adb wait-for-device && adb shell ps};
if (@ps == 0) {
return -1;
}
my @columns = split /\s+/, (shift @ps);

# There's a "STATE" column slipped in between WCHAN and NAME that has no
# room for a column...
splice @columns, $#columns, 0, 'STATE';
if (! in_list("S", @columns)) {
# There's a "STATE" column slipped in between WCHAN and NAME that has no
# room for a column name...
splice @columns, $#columns, 0, 'STATE';
}

my $numFound = 0;

Expand All @@ -105,19 +122,31 @@ sub get_pids {
my @data = split /\s+/, $_, scalar @columns;
my %row = map { $_ => (shift @data) } @columns;

if (exists $info->{$row{NAME}}) {
$info->{$row{NAME}} = $row{PID};
if (match_name($row{NAME})) {
$trackedPids{$row{PID}} = 1;
$numFound++;
}
}

return $numFound;
}

sub match_name {
my $name = shift;
foreach (@trackedNames) {
return 1 if $_ and $name =~ $_;
}
return 0;
}

sub usage {
my $prog = shift;
die <<"EOF"
Usage: adb logcat | $0 <process-name>
Usage: adb logcat | $0 <process-name-regex-or-tag> [<process2-name-regex-or-tag> ...]

Process names will be matched against the given regular expression(s),
and only the matching processes will be shown in the output.
Or the tag is equals to the it and it will be shown.

Usually, `process-name' is usually the same as your package, but not
necessarily. To make sure, type `adb shell ps' and look through the list.
Expand Down
14 changes: 14 additions & 0 deletions super-logcat.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

if [ ! "$*" ]; then
"$0" ".+"
exit $?
fi

dir=`dirname "$0"`

if [ "`readlink $0`" ]; then
dir=`dirname "$(readlink $0)"`
fi

adb logcat | "$dir/proclogcat" "$@" | "$dir/coloredlogcat.py"