• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2023 The Pigweed Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may not
5# use this file except in compliance with the License. You may obtain a copy of
6# the License at
7#
8#     https://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations under
14# the License.
15"""
16Generates json trace files viewable using chrome://tracing using RPCs from a
17connected trace service.
18
19Example usage:
20python pw_console/py/pw_console/trace_client.py
21  -o trace.json
22  -t out/host_device_simulator.speed_optimized/obj/pw_system/bin/system_example
23"""
24
25import argparse
26import logging
27import sys
28from pathlib import Path
29from types import ModuleType
30
31
32from pw_transfer import transfer_pb2
33from pw_log.proto import log_pb2
34from pw_trace_protos import trace_service_pb2
35from pw_trace import trace
36from pw_trace_tokenized import trace_tokenized
37import pw_transfer
38from pw_file import file_pb2
39from pw_hdlc import rpc
40from pw_system import device_tracing
41from pw_tokenizer import detokenize
42from pw_console import socket_client
43
44
45_LOG = logging.getLogger('pw_console_trace_client')
46_LOG.level = logging.DEBUG
47_LOG.addHandler(logging.StreamHandler(sys.stdout))
48
49
50def start_tracing_on_device(client):
51    """Start tracing on the device"""
52    service = client.rpcs.pw.trace.proto.TraceService
53    service.Start()
54
55
56def stop_tracing_on_device(client):
57    """Stop tracing on the device"""
58    service = client.rpcs.pw.trace.proto.TraceService
59    return service.Stop()
60
61
62def list_files_on_device(client):
63    """List files on the device"""
64    service = client.rpcs.pw.file.FileSystem
65    return service.List()
66
67
68def delete_file_on_device(client, path):
69    """Delete a file on the device"""
70    service = client.rpcs.pw.file.FileSystem
71    req = file_pb2.DeleteRequest(path=path)
72    return service.Delete(req)
73
74
75def _parse_args():
76    """Parse and return command line arguments."""
77
78    parser = argparse.ArgumentParser(
79        description=__doc__,
80        formatter_class=argparse.RawDescriptionHelpFormatter,
81    )
82    group = parser.add_mutually_exclusive_group(required=False)
83    group.add_argument('-d', '--device', help='the serial port to use')
84    parser.add_argument(
85        '-b',
86        '--baudrate',
87        type=int,
88        default=115200,
89        help='The baud rate to use',
90    )
91    group.add_argument(
92        '-s',
93        '--socket-addr',
94        type=str,
95        default='default',
96        help=(
97            'Use socket to connect to server, type default for '
98            'localhost:33000, or manually input the server address:port'
99        ),
100    )
101    parser.add_argument(
102        '-o',
103        '--trace_output',
104        dest='trace_output_file',
105        help='The json file to which to write the output.',
106    )
107    parser.add_argument(
108        '-t',
109        '--trace_token_database',
110        help='Databases (ELF, binary, or CSV) to use to lookup trace tokens.',
111    )
112    parser.add_argument(
113        '-f',
114        '--ticks_per_second',
115        type=int,
116        dest='ticks_per_second',
117        help='The clock rate of the trace events.',
118    )
119    parser.add_argument(
120        '--time_offset',
121        type=int,
122        dest='time_offset',
123        default=0,
124        help='Time offset (us) of the trace events (Default 0).',
125    )
126    parser.add_argument(
127        '--channel-id',
128        type=int,
129        dest='channel_id',
130        default=rpc.DEFAULT_CHANNEL_ID,
131        help='Channel ID used in RPC communications.',
132    )
133    return parser.parse_args()
134
135
136def _main(args) -> int:
137    detokenizer = detokenize.AutoUpdatingDetokenizer(
138        args.trace_token_database + '#trace'
139    )
140    detokenizer.show_errors = True
141
142    socket_impl = socket_client.SocketClient
143    try:
144        socket_device = socket_impl(args.socket_addr)
145        reader = rpc.SelectableReader(socket_device)
146        write = socket_device.write
147    except ValueError:
148        _LOG.exception('Failed to initialize socket at %s', args.socket_addr)
149        return 1
150
151    protos: list[ModuleType | Path] = [
152        log_pb2,
153        file_pb2,
154        transfer_pb2,
155        trace_service_pb2,
156    ]
157
158    with reader:
159        device_client = device_tracing.DeviceWithTracing(
160            args.channel_id,
161            reader,
162            write,
163            protos,
164            detokenizer=detokenizer,
165            timestamp_decoder=None,
166            rpc_timeout_s=5,
167            use_rpc_logging=True,
168            use_hdlc_encoding=True,
169            ticks_per_second=args.ticks_per_second,
170        )
171
172        with device_client:
173            _LOG.info('Starting tracing')
174            start_tracing_on_device(device_client)
175
176            _LOG.info('Stopping tracing')
177            file_id = stop_tracing_on_device(device_client)
178            _LOG.info('Trace file id = %d', file_id.response.file_id)
179
180            _LOG.info('Listing Files')
181            stream_response = list_files_on_device(device_client)
182
183            if not stream_response.status.ok():
184                _LOG.error('Failed to list files %s', stream_response.status)
185                return 1
186
187            for list_response in stream_response.responses:
188                for file in list_response.paths:
189                    _LOG.info('Transfering File: %s', file.path)
190                    try:
191                        data = device_client.transfer_manager.read(file.file_id)
192                        events = trace_tokenized.get_trace_events(
193                            [detokenizer.database],
194                            data,
195                            device_client.ticks_per_second,
196                            args.time_offset,
197                        )
198                        json_lines = trace.generate_trace_json(events)
199                        trace_tokenized.save_trace_file(
200                            json_lines, args.trace_output_file
201                        )
202                    except pw_transfer.Error as err:
203                        print('Failed to read:', err.status)
204
205                    _LOG.info('Deleting File: %s', file.path)
206                    delete_file_on_device(device_client, file.path)
207
208            _LOG.info('All trace transfers completed successfully')
209
210    return 0
211
212
213if __name__ == '__main__':
214    _main(_parse_args())
215