1#!/usr/bin/env python3 2# Copyright 2021 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. 15r""" 16Generates json trace files viewable using chrome://tracing using RPCs from a 17connected HdlcRpcClient. 18 19Example usage: 20python pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py -s localhost:33000 21 -o trace.json 22 -t out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc 23 pw_trace_tokenized/pw_trace_protos/trace_rpc.proto 24""" 25import argparse 26import logging 27import glob 28from pathlib import Path 29import sys 30from typing import Collection, Iterable, Iterator 31import serial # type: ignore 32from pw_tokenizer import database 33from pw_trace import trace 34from pw_hdlc.rpc import HdlcRpcClient, default_channels 35from pw_hdlc.rpc_console import SocketClientImpl 36from pw_trace_tokenized import trace_tokenized 37 38_LOG = logging.getLogger('pw_trace_tokenizer') 39 40PW_RPC_MAX_PACKET_SIZE = 256 41SOCKET_SERVER = 'localhost' 42SOCKET_PORT = 33000 43MKFIFO_MODE = 0o666 44 45 46def _expand_globs(globs: Iterable[str]) -> Iterator[Path]: 47 for pattern in globs: 48 for file in glob.glob(pattern, recursive=True): 49 yield Path(file) 50 51 52def get_hdlc_rpc_client(device: str, baudrate: int, 53 proto_globs: Collection[str], socket_addr: str, 54 **kwargs): 55 """Get the HdlcRpcClient based on arguments.""" 56 del kwargs # ignore 57 if not proto_globs: 58 proto_globs = ['**/*.proto'] 59 60 protos = list(_expand_globs(proto_globs)) 61 62 if not protos: 63 _LOG.critical('No .proto files were found with %s', 64 ', '.join(proto_globs)) 65 _LOG.critical('At least one .proto file is required') 66 return 1 67 68 _LOG.debug('Found %d .proto files found with %s', len(protos), 69 ', '.join(proto_globs)) 70 71 # TODO(rgoliver): When pw has a generalized transport for RPC this should 72 # use it so it isn't specific to HDLC 73 if socket_addr is None: 74 serial_device = serial.Serial(device, baudrate, timeout=1) 75 read = lambda: serial_device.read(8192) 76 write = serial_device.write 77 else: 78 try: 79 socket_device = SocketClientImpl(socket_addr) 80 read = socket_device.read 81 write = socket_device.write 82 except ValueError: 83 _LOG.exception('Failed to initialize socket at %s', socket_addr) 84 return 1 85 86 return HdlcRpcClient(read, protos, default_channels(write)) 87 88 89def get_trace_data_from_device(client): 90 """ Get the trace data using RPC from a Client""" 91 data = b'' 92 result = \ 93 client.client.channel(1).rpcs.pw.trace.TraceService.GetTraceData().get() 94 for streamed_data in result: 95 data = data + bytes([len(streamed_data.data)]) 96 data = data + streamed_data.data 97 _LOG.debug(''.join(format(x, '02x') for x in streamed_data.data)) 98 return data 99 100 101def _parse_args(): 102 """Parse and return command line arguments.""" 103 104 parser = argparse.ArgumentParser( 105 description=__doc__, 106 formatter_class=argparse.RawDescriptionHelpFormatter) 107 group = parser.add_mutually_exclusive_group(required=True) 108 group.add_argument('-d', '--device', help='the serial port to use') 109 parser.add_argument('-b', 110 '--baudrate', 111 type=int, 112 default=115200, 113 help='the baud rate to use') 114 group.add_argument('-s', 115 '--socket-addr', 116 type=str, 117 help='use socket to connect to server, type default for\ 118 localhost:33000, or manually input the server address:port') 119 parser.add_argument('-o', 120 '--trace_output', 121 dest='trace_output_file', 122 help=('The json file to which to write the output.')) 123 parser.add_argument( 124 '-t', 125 '--trace_token_database', 126 help='Databases (ELF, binary, or CSV) to use to lookup trace tokens.') 127 parser.add_argument('proto_globs', 128 nargs='+', 129 help='glob pattern for .proto files') 130 131 return parser.parse_args() 132 133 134def _main(args): 135 token_database = \ 136 database.load_token_database(args.trace_token_database, domain="trace") 137 _LOG.info(database.database_summary(token_database)) 138 client = get_hdlc_rpc_client(**vars(args)) 139 data = get_trace_data_from_device(client) 140 events = trace_tokenized.get_trace_events([token_database], data) 141 json_lines = trace.generate_trace_json(events) 142 trace_tokenized.save_trace_file(json_lines, args.trace_output_file) 143 144 145if __name__ == '__main__': 146 if sys.version_info[0] < 3: 147 sys.exit('ERROR: The detokenizer command line tools require Python 3.') 148 _main(_parse_args()) 149