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