1import csv
2import re
3import subprocess
4
5HEADER_RE = re.compile("USER\\s*PID\\s*PPID\\s*VSIZE\\s*RSS\\s*WCHAN\\s*PC\\s*NAME")
6PROCESS_RE = re.compile("(\\S+)\\s+(\\d+)\\s+(\\d+)\\s+\\d+\\s+\\d+\\s+\\S+\\s+.\\S+\\s+\\S+\\s+(.*)")
7
8ANDROID_UID_RE = re.compile("u(\\d)+_([0-9a-fA-F]+)")
9UID_RE = re.compile("(\\d)+")
10
11class Process(object):
12  def __init__(self, uid, pid, ppid, name):
13    self.uid = uid
14    self.pid = pid
15    self.ppid = ppid
16    self.name = name
17
18  def DisplayName(self):
19    if self.name:
20      return self.name
21    if self.uid:
22      return self.uid.name
23    return self.pid
24
25  def __str__(self):
26    return "Process(uid=%s, pid=%s, name=%s)" % (self.uid, self.pid, self.name)
27
28class Uid(object):
29  def __init__(self, uid, name):
30    self.uid = uid
31    self.name = name
32
33  def __str__(self):
34    return "Uid(id=%s, name=%s)" % (self.uid, self.name)
35
36class ProcessSet(object):
37  def __init__(self):
38    self._processes = dict()
39    self._uids = dict()
40    self._pidUpdateCount = 0
41    self._uidUpdateCount = 0
42    self.doUpdates = False
43
44  def Update(self, force=False):
45    self.UpdateUids(force)
46    self.UpdateProcesses(force)
47
48  def UpdateProcesses(self, force=False):
49    if not (self.doUpdates or force):
50      return
51    self._pidUpdateCount += 1
52    try:
53      text = subprocess.check_output(["adb", "shell", "ps"])
54    except subprocess.CalledProcessError:
55      return # oh well. we won't get the pid
56    lines = ParsePs(text)
57    for line in lines:
58      if not self._processes.has_key(line[1]):
59        uid = self.FindUid(ParseUid(line[0]))
60        self._processes[line[1]] = Process(uid, line[1], line[2], line[3])
61
62  def UpdateUids(self, force=False):
63    if not (self.doUpdates or force):
64      return
65    self._uidUpdateCount += 1
66    try:
67      text = subprocess.check_output(["adb", "shell", "dumpsys", "package", "--checkin"])
68    except subprocess.CalledProcessError:
69      return # oh well. we won't get the pid
70    lines = ParseUids(text)
71    for line in lines:
72      if not self._uids.has_key(line[0]):
73        self._uids[line[1]] = Uid(*line)
74
75  def FindPid(self, pid, uid=None):
76    """Try to find the Process object for the given pid.
77    If it can't be found, do an update. If it still can't be found after that,
78    create a syntheitc Process object, add that to the list, and return that.
79    That can only happen after the process has died, and we just missed our
80    chance to find it.  The pid won't come back.
81    """
82    result = self._processes.get(pid)
83    if not result:
84      self.UpdateProcesses()
85      result = self._processes.get(pid)
86      if not result:
87        if uid:
88          uid = self._uids.get(uid)
89        result = Process(uid, pid, None, None)
90        self._processes[pid] = result
91    return result
92
93  def FindUid(self, uid):
94    result = self._uids.get(uid)
95    if not result:
96      self.UpdateUids()
97      result = self._uids.get(uid)
98      if not result:
99        result = Uid(uid, uid)
100        self._uids[uid] = result
101    return result
102
103  def UpdateCount(self):
104    return (self._pidUpdateCount, self._uidUpdateCount)
105
106  def Print(self):
107    for process in self._processes:
108      print process
109    for uid in self._uids:
110      print uid
111
112def ParsePs(text):
113  """Parses the output of ps, and returns it as a list of tuples of (user, pid, ppid, name)"""
114  result = []
115  for line in text.splitlines():
116    m = HEADER_RE.match(line)
117    if m:
118      continue
119    m = PROCESS_RE.match(line)
120    if m:
121      result.append((m.group(1), m.group(2), m.group(3), m.group(4)))
122      continue
123  return result
124
125
126def ParseUids(text):
127  """Parses the output of dumpsys package --checkin and returns the uids as a list of
128  tuples of (uid, name)"""
129  return [(x[2], x[1]) for x in csv.reader(text.split("\n")) if len(x) and x[0] == "pkg"]
130
131
132def ParseUid(text):
133  m = ANDROID_UID_RE.match(text)
134  if m:
135    result = int("0x" + m.group(2), 16)
136    return "(%s/%s/%s)" % (m.group(1), m.group(2), result)
137  m = UID_RE.match(text)
138  if m:
139    return "[%s]" % m.group(1)
140  return text
141
142# vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab:
143