1#!/usr/bin/python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6""""Processes a log file and resolves IPC message identifiers.
7
8Resolves IPC messages of the form [unknown type NNNNNN] to named IPC messages.
9
10e.g. logfile containing
11
12I/stderr  ( 3915): ipc 3915.3.1370207904 2147483647 S [unknown type 66372]
13
14will be transformed to:
15
16I/stderr  ( 3915): ipc 3915.3.1370207904 2147483647 S ViewMsg_SetCSSColors
17
18In order to find the message header files efficiently, it requires that
19Chromium is checked out using git.
20"""
21
22import optparse
23import os
24import re
25import subprocess
26import sys
27
28
29def _SourceDir():
30  """Get chromium's source directory."""
31  return os.path.join(sys.path[0], '..')
32
33
34def _ReadLines(f):
35  """Read from file f and generate right-stripped lines."""
36  for line in f:
37    yield line.rstrip()
38
39
40def _GetMsgStartTable():
41  """Read MsgStart enumeration from ipc/ipc_message_utils.h.
42
43  Determines the message type identifiers by reading.
44  header file ipc/ipc_message_utils.h and looking for
45  enum IPCMessageStart.  Assumes following code format in header file:
46  enum IPCMessageStart {
47     Type1MsgStart ...,
48     Type2MsgStart,
49  };
50
51  Returns:
52      A dictionary mapping StartName to enumeration value.
53  """
54  ipc_message_file = _SourceDir() + '/ipc/ipc_message_utils.h'
55  ipc_message_lines = _ReadLines(open(ipc_message_file))
56  is_msg_start = False
57  count = 0
58  msg_start_table = dict()
59  for line in ipc_message_lines:
60    if is_msg_start:
61      if line.strip() == '};':
62        break
63      msgstart_index = line.find('MsgStart')
64      msg_type = line[:msgstart_index] + 'MsgStart'
65      msg_start_table[msg_type.strip()] = count
66      count+=1
67    elif line.strip() == 'enum IPCMessageStart {':
68      is_msg_start = True
69
70  return msg_start_table
71
72
73def _FindMessageHeaderFiles():
74  """Look through the source directory for *_messages.h."""
75  os.chdir(_SourceDir())
76  pipe = subprocess.Popen(['git', 'ls-files', '--', '*_messages.h'],
77                          stdout=subprocess.PIPE)
78  return _ReadLines(pipe.stdout)
79
80
81def _GetMsgId(msg_start, line_number, msg_start_table):
82  """Construct the meessage id given the msg_start and the line number."""
83  hex_str = '%x%04x' % (msg_start_table[msg_start], line_number)
84  return int(hex_str, 16)
85
86
87def _ReadHeaderFile(f, msg_start_table, msg_map):
88  """Read a header file and construct a map from message_id to message name."""
89  msg_def_re = re.compile(
90      '^IPC_(?:SYNC_)?MESSAGE_[A-Z0-9_]+\(([A-Za-z0-9_]+).*')
91  msg_start_re = re.compile(
92      '^\s*#define\s+IPC_MESSAGE_START\s+([a-zA-Z0-9_]+MsgStart).*')
93  msg_start = None
94  msg_name = None
95  line_number = 0
96
97  for line in f:
98    line_number+=1
99    match = re.match(msg_start_re, line)
100    if match:
101      msg_start = match.group(1)
102      # print "msg_start = " + msg_start
103    match = re.match(msg_def_re, line)
104    if match:
105      msg_name = match.group(1)
106      # print "msg_name = " + msg_name
107    if msg_start and msg_name:
108      msg_id = _GetMsgId(msg_start, line_number, msg_start_table)
109      msg_map[msg_id] = msg_name
110  return msg_map
111
112
113def _ResolveMsg(msg_type, msg_map):
114  """Fully resolve a message type to a name."""
115  if msg_type in msg_map:
116    return msg_map[msg_type]
117  else:
118    return '[Unknown message %d (0x%x)]x' % (msg_type, msg_type)
119
120
121def _ProcessLog(f, msg_map):
122  """Read lines from f and resolve the IPC messages according to msg_map."""
123  unknown_msg_re = re.compile('\[unknown type (\d+)\]')
124  for line in f:
125    line = line.rstrip()
126    match = re.search(unknown_msg_re, line)
127    if match:
128      line = re.sub(unknown_msg_re,
129                    _ResolveMsg(int(match.group(1)), msg_map),
130                    line)
131    print line
132
133
134def _GetMsgMap():
135  """Returns a dictionary mapping from message number to message name."""
136  msg_start_table = _GetMsgStartTable()
137  msg_map = dict()
138  for header_file in _FindMessageHeaderFiles():
139    _ReadHeaderFile(open(header_file),
140                    msg_start_table,
141                    msg_map)
142  return msg_map
143
144
145def main():
146  """Processes one or more log files with IPC logging messages.
147
148     Replaces '[unknown type NNNNNN]' with resolved
149     IPC messages.
150
151     Reads from standard input if no log files specified on the
152     command line.
153  """
154  parser = optparse.OptionParser('usage: %prog [LOGFILE...]')
155  (_, args) = parser.parse_args()
156
157  msg_map = _GetMsgMap()
158  log_files = args
159
160  if log_files:
161    for log_file in log_files:
162      _ProcessLog(open(log_file), msg_map)
163  else:
164    _ProcessLog(sys.stdin, msg_map)
165
166
167if __name__ == '__main__':
168  main()
169