Thanks to visit codestin.com
Credit goes to chromium.googlesource.com

blob: 848aa633687bfe62a51a07840f4f80abc3777e33 [file] [log] [blame]
Andrew Williamsbd5ab98f2024-04-25 23:26:291#!/usr/bin/env python3
2# Copyright 2024 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import os
7import sys
8from collections import defaultdict
9
10DESCRIPTION = \
11'''This script takes in a Chromium trace file and extracts info about Mojo
12messages that were sent/received.
13
14Trace files can be created using chrome://tracing or from passing
15'--enable-tracing' to a Chrome or browser test executable. In the
16chrome://tracing UI, ensure that the 'mojom' and 'toplevel' categories are
17selected when setting up a new trace. Also, the trace events available will
18have much more information (including message contents and return values) if
19the executable generating the trace file is built with the
20`extended_tracing_enabled = true` gn arg.
21'''
22
23PERFETTO_NOT_FOUND_HELP_TEXT = \
24'''Error: perfetto module not found.
25
26This script requires the perfetto Python module. To install it, use something
27like `pip install perfetto`, or for Googlers on gLinux use the following (in a
28Chromium checkout):
29```
30sudo apt-get install python3-venv
31python3 -m venv venv
32./venv/bin/python3 -mpip install perfetto
33./venv/bin/python3 tools/mojo_messages_log.py <script args>
34```
35'''
36
37# Note: Ignore 'mojo::Message::Message' (from the disabled by default 'mojom'
38# category) because there is usually higher-level information that's more
39# helpful, even in release builds.
40
41# TODO(awillia): The 'Send mojo message' and 'Receive mojo sync reply' trace
42# events (both from the toplevel.flow category) should have a message ID
43# associated with them but I'm not sure how to access it. With the former we
44# could figure out the sender of a message, but without the message ID the
45# events aren't very helpful.
46MOJO_EVENTS_QUERY = \
47'''INCLUDE PERFETTO MODULE slices.with_context;
48SELECT
49 (ts - (SELECT start_ts FROM trace_bounds)) / 1000000000.0 AS ts_delta,
50 process_name,
51 pid, -- Useful for distinguishing renderer processes
52 thread_name,
53 name,
54 category AS event_category,
55 GROUP_CONCAT(args.key || ": " ||
56 COALESCE(args.int_value,
57 args.string_value,
58 args.real_value)) AS parameters
59 -- Note that we could get argument type info as well if that's worthwhile
60 FROM thread_slice
61 LEFT JOIN args on args.arg_set_id = thread_slice.arg_set_id
62 WHERE (category IS 'mojom' AND name GLOB 'Send *') OR
63 (category IS 'mojom' AND name GLOB 'Call *') OR
64 (category IS 'toplevel' AND name GLOB 'Receive *') OR
65 (category IS 'toplevel' AND name IS 'Closed mojo endpoint')
66 GROUP BY thread_slice.id, args.arg_set_id
67 ORDER BY ts;
68'''
69
70SUMMARY_FIELDS = ['ts_delta', 'process_name', 'name']
71
72VERBOSE_FIELDS = ['ts_delta', 'process_name', 'pid', 'thread_name', 'name']
73ADDITIONAL_DATA_FIELDS = ['name', 'event_category', 'parameters']
74
75
76def is_valid_path(parser, path):
77 if not os.path.exists(path):
78 parser.error("Invalid path: %s" % (path))
79 else:
80 return path
81
82
83def process_mojo_msg_info(extra, spacing=2):
84 if not extra or len(extra) != len(ADDITIONAL_DATA_FIELDS):
85 return
86 output = ''
87 spacer = ' ' * spacing
88 event_name, event_category, parameters = extra
89
90 # The parameters exist as a single comma separated line, so break it into
91 # separate lines. Each if statement block here corresponds to a WHERE
92 # condition in the SQL query.
93 if (event_category == 'mojom' and event_name.startswith("Send ")) or \
94 (event_category == 'mojom' and event_name.startswith("Call ")):
95 if parameters is None:
96 # The call has no parameters
97 parameters = []
98 else:
99 assert (parameters.startswith('debug.'))
100 parameters = parameters.replace('debug.', '', 1)
101 parameters = parameters.split(',debug.')
102
103 elif (event_category == 'toplevel' and event_name.startswith("Receive ")) or \
104 (event_category == 'toplevel' and event_name == "Closed mojo endpoint"):
105 if parameters is None:
106 parameters = []
107 elif parameters.startswith('chrome_mojo_event_info.'):
108 parameters = parameters.replace('chrome_mojo_event_info.', '', 1)
109 parameters = parameters.split(',chrome_mojo_event_info.')
110 parameters = ['chrome_mojo_event_info.' + x for x in parameters]
111 else:
112 assert (parameters.startswith('args.'))
113 parameters = parameters.replace('args.', '', 1)
114 parameters = parameters.split(',args.')
115
116 results = defaultdict(lambda: [])
117 for parameter in parameters:
118 info_type, info = parameter.split('.', 1)
119 results[info_type].append(info)
120
121 for info_type in results:
122 output += spacer + info_type + ':\n'
123 for entry in results[info_type]:
124 output += spacer * 2 + entry + '\n'
125 return output
126
127
128# Formats the event data into the structured data that can be shown in the
129# displayed table and additional unstructured data that should be shown
130# underneath each event.
131def process_events(args, events):
132 rows = []
133 extras = []
134 for row_data in events:
135 row = []
136 extra = []
137 if args.summary:
138 for field in SUMMARY_FIELDS:
139 row.append(str(getattr(row_data, field)))
140 else:
141 for field in VERBOSE_FIELDS:
142 row.append(str(getattr(row_data, field)))
143
144 for field in ADDITIONAL_DATA_FIELDS:
145 extra.append(getattr(row_data, field))
146 extra = process_mojo_msg_info(extra)
147 rows.append(row)
148 extras.append(extra)
149 return rows, extras
150
151
152try:
153 from perfetto.trace_processor import TraceProcessor
154except ModuleNotFoundError:
155 print(PERFETTO_NOT_FOUND_HELP_TEXT)
156 sys.exit(1)
157
158
159def main():
160 import argparse
161 parser = argparse.ArgumentParser(
162 formatter_class=argparse.RawDescriptionHelpFormatter,
163 description=DESCRIPTION)
164 parser.add_argument('tracefile',
165 type=lambda path: is_valid_path(parser, path))
166 parser.add_argument('--summary', action="store_true")
167 args = parser.parse_args()
168
169 tp = TraceProcessor(file_path=args.tracefile)
170
171 results = tp.query(MOJO_EVENTS_QUERY)
172
173 rows, extras = process_events(args, results)
174
175 # Add headers for the table.
176 if args.summary:
177 rows.insert(0, SUMMARY_FIELDS)
178 else:
179 rows.insert(0, VERBOSE_FIELDS)
180 # Keep `extras` the same length as `rows`.
181 extras.insert(0, None)
182
183 # Calculate the appropriate widths of each column.
184 widths = [max(map(len, column)) for column in zip(*rows)]
185
186 for i in range(len(rows)):
187 row = rows[i]
188 extra = extras[i]
189 # Format the structured data so the fields align with the table headers.
190 out = (value.ljust(width) for value, width in zip(row, widths))
191 out = " ".join(out).rstrip()
192 print(out)
193 if extra:
194 print(extra)
195
196
197if __name__ == '__main__':
198 sys.exit(main())