Home | History | Annotate | Download | only in logblame
      1 import csv
      2 import re
      3 import subprocess
      4 
      5 HEADER_RE = re.compile("USER\\s*PID\\s*PPID\\s*VSIZE\\s*RSS\\s*WCHAN\\s*PC\\s*NAME")
      6 PROCESS_RE = re.compile("(\\S+)\\s+(\\d+)\\s+(\\d+)\\s+\\d+\\s+\\d+\\s+\\S+\\s+.\\S+\\s+\\S+\\s+(.*)")
      7 
      8 ANDROID_UID_RE = re.compile("u(\\d)+_([0-9a-fA-F]+)")
      9 UID_RE = re.compile("(\\d)+")
     10 
     11 class 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 
     28 class 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 
     36 class 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 
    112 def 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 
    126 def 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 
    132 def 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