You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
4.0 KiB

import csv
import re
import subprocess
HEADER_RE = re.compile("USER\\s*PID\\s*PPID\\s*VSIZE\\s*RSS\\s*WCHAN\\s*PC\\s*NAME")
PROCESS_RE = re.compile("(\\S+)\\s+(\\d+)\\s+(\\d+)\\s+\\d+\\s+\\d+\\s+\\S+\\s+.\\S+\\s+\\S+\\s+(.*)")
ANDROID_UID_RE = re.compile("u(\\d)+_([0-9a-fA-F]+)")
UID_RE = re.compile("(\\d)+")
class Process(object):
def __init__(self, uid, pid, ppid, name):
self.uid = uid
self.pid = pid
self.ppid = ppid
self.name = name
def DisplayName(self):
if self.name:
return self.name
if self.uid:
return self.uid.name
return self.pid
def __str__(self):
return "Process(uid=%s, pid=%s, name=%s)" % (self.uid, self.pid, self.name)
class Uid(object):
def __init__(self, uid, name):
self.uid = uid
self.name = name
def __str__(self):
return "Uid(id=%s, name=%s)" % (self.uid, self.name)
class ProcessSet(object):
def __init__(self):
self._processes = dict()
self._uids = dict()
self._pidUpdateCount = 0
self._uidUpdateCount = 0
self.doUpdates = False
def Update(self, force=False):
self.UpdateUids(force)
self.UpdateProcesses(force)
def UpdateProcesses(self, force=False):
if not (self.doUpdates or force):
return
self._pidUpdateCount += 1
try:
text = subprocess.check_output(["adb", "shell", "ps"])
except subprocess.CalledProcessError:
return # oh well. we won't get the pid
lines = ParsePs(text)
for line in lines:
if not self._processes.has_key(line[1]):
uid = self.FindUid(ParseUid(line[0]))
self._processes[line[1]] = Process(uid, line[1], line[2], line[3])
def UpdateUids(self, force=False):
if not (self.doUpdates or force):
return
self._uidUpdateCount += 1
try:
text = subprocess.check_output(["adb", "shell", "dumpsys", "package", "--checkin"])
except subprocess.CalledProcessError:
return # oh well. we won't get the pid
lines = ParseUids(text)
for line in lines:
if not self._uids.has_key(line[0]):
self._uids[line[1]] = Uid(*line)
def FindPid(self, pid, uid=None):
"""Try to find the Process object for the given pid.
If it can't be found, do an update. If it still can't be found after that,
create a syntheitc Process object, add that to the list, and return that.
That can only happen after the process has died, and we just missed our
chance to find it. The pid won't come back.
"""
result = self._processes.get(pid)
if not result:
self.UpdateProcesses()
result = self._processes.get(pid)
if not result:
if uid:
uid = self._uids.get(uid)
result = Process(uid, pid, None, None)
self._processes[pid] = result
return result
def FindUid(self, uid):
result = self._uids.get(uid)
if not result:
self.UpdateUids()
result = self._uids.get(uid)
if not result:
result = Uid(uid, uid)
self._uids[uid] = result
return result
def UpdateCount(self):
return (self._pidUpdateCount, self._uidUpdateCount)
def Print(self):
for process in self._processes:
print process
for uid in self._uids:
print uid
def ParsePs(text):
"""Parses the output of ps, and returns it as a list of tuples of (user, pid, ppid, name)"""
result = []
for line in text.splitlines():
m = HEADER_RE.match(line)
if m:
continue
m = PROCESS_RE.match(line)
if m:
result.append((m.group(1), m.group(2), m.group(3), m.group(4)))
continue
return result
def ParseUids(text):
"""Parses the output of dumpsys package --checkin and returns the uids as a list of
tuples of (uid, name)"""
return [(x[2], x[1]) for x in csv.reader(text.split("\n")) if len(x) and x[0] == "pkg"]
def ParseUid(text):
m = ANDROID_UID_RE.match(text)
if m:
result = int("0x" + m.group(2), 16)
return "(%s/%s/%s)" % (m.group(1), m.group(2), result)
m = UID_RE.match(text)
if m:
return "[%s]" % m.group(1)
return text
# vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab: