1 2import datetime 3import re 4 5BUFFER_BEGIN = re.compile("^--------- beginning of (.*)$") 6BUFFER_SWITCH = re.compile("^--------- switch to (.*)$") 7HEADER = re.compile("^\\[ (\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d) +(.+?): *(\\d+): *(\\d+) *([EWIDV])/(.*?) *\\]$") 8CHATTY_IDENTICAL = re.compile("^.* identical (\\d+) lines$") 9 10STATE_BEGIN = 0 11STATE_BUFFER = 1 12STATE_HEADER = 2 13STATE_TEXT = 3 14STATE_BLANK = 4 15 16class LogLine(object): 17 """Represents a line of android logs.""" 18 def __init__(self, buf=None, timestamp=None, uid=None, pid=None, tid=None, level=None, 19 tag=None, text=""): 20 self.buf = buf 21 self.timestamp = timestamp 22 self.uid = uid 23 self.pid = pid 24 self.tid = tid 25 self.level = level 26 self.tag = tag 27 self.text = text 28 self.process = None 29 30 def __str__(self): 31 return "{%s} {%s} {%s} {%s} {%s} {%s}/{%s}: {%s}" % (self.buf, self.timestamp, self.uid, 32 self.pid, self.tid, self.level, self.tag, self.text) 33 34 def __eq__(self, other): 35 return ( 36 self.buf == other.buf 37 and self.timestamp == other.timestamp 38 and self.uid == other.uid 39 and self.pid == other.pid 40 and self.tid == other.tid 41 and self.level == other.level 42 and self.tag == other.tag 43 and self.text == other.text 44 ) 45 46 def clone(self): 47 logLine = LogLine(self.buf, self.timestamp, self.uid, self.pid, self.tid, self.level, 48 self.tag, self.text) 49 logLine.process = self.process 50 return logLine 51 52 def memory(self): 53 """Return an estimate of how much memory is used for the log. 54 32 bytes of header + 8 bytes for the pointer + the length of the tag and the text. 55 This ignores the overhead of the list of log lines itself.""" 56 return 32 + 8 + len(self.tag) + 1 + len(self.text) + 1 57 58 59def ParseLogcat(f, processes, duration=None): 60 previous = None 61 for logLine in ParseLogcatInner(f, processes, duration): 62 if logLine.tag == "chatty" and logLine.level == "I": 63 m = CHATTY_IDENTICAL.match(logLine.text) 64 if m: 65 for i in range(int(m.group(1))): 66 clone = previous.clone() 67 clone.timestamp = logLine.timestamp 68 yield clone 69 continue 70 previous = logLine 71 yield logLine 72 73 74def ParseLogcatInner(f, processes, duration=None): 75 """Parses a file object containing log text and returns a list of LogLine objects.""" 76 result = [] 77 78 buf = None 79 timestamp = None 80 uid = None 81 pid = None 82 tid = None 83 level = None 84 tag = None 85 86 state = STATE_BEGIN 87 logLine = None 88 previous = None 89 90 if duration: 91 endTime = datetime.datetime.now() + datetime.timedelta(seconds=duration) 92 93 # TODO: use a nonblocking / timeout read so we stop if there are 94 # no logs coming out (haha joke, right!) 95 for line in f: 96 if duration and endTime <= datetime.datetime.now(): 97 break 98 99 if len(line) > 0 and line[-1] == '\n': 100 line = line[0:-1] 101 102 m = BUFFER_BEGIN.match(line) 103 if m: 104 if logLine: 105 yield logLine 106 logLine = None 107 buf = m.group(1) 108 state = STATE_BUFFER 109 continue 110 111 m = BUFFER_SWITCH.match(line) 112 if m: 113 if logLine: 114 yield logLine 115 logLine = None 116 buf = m.group(1) 117 state = STATE_BUFFER 118 continue 119 120 m = HEADER.match(line) 121 if m: 122 if logLine: 123 yield logLine 124 logLine = LogLine( 125 buf=buf, 126 timestamp=m.group(1), 127 uid=m.group(2), 128 pid=m.group(3), 129 tid=m.group(4), 130 level=m.group(5), 131 tag=m.group(6) 132 ) 133 previous = logLine 134 logLine.process = processes.FindPid(logLine.pid, logLine.uid) 135 state = STATE_HEADER 136 continue 137 138 if not len(line): 139 if state == STATE_BLANK: 140 if logLine: 141 logLine.text += "\n" 142 state = STATE_BLANK 143 continue 144 145 if logLine: 146 if state == STATE_HEADER: 147 logLine.text += line 148 elif state == STATE_TEXT: 149 logLine.text += "\n" 150 logLine.text += line 151 elif state == STATE_BLANK: 152 if len(logLine.text): 153 logLine.text += "\n" 154 logLine.text += "\n" 155 logLine.text += line 156 state = STATE_TEXT 157 158 if logLine: 159 yield logLine 160 161 162# vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab: 163