aboutsummaryrefslogtreecommitdiff
path: root/pw_system/py/pw_system/trace_client.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_system/py/pw_system/trace_client.py')
-rw-r--r--pw_system/py/pw_system/trace_client.py211
1 files changed, 211 insertions, 0 deletions
diff --git a/pw_system/py/pw_system/trace_client.py b/pw_system/py/pw_system/trace_client.py
new file mode 100644
index 000000000..575659b26
--- /dev/null
+++ b/pw_system/py/pw_system/trace_client.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Generates json trace files viewable using chrome://tracing using RPCs from a
+connected trace service.
+
+Example usage:
+python pw_console/py/pw_console/trace_client.py
+ -o trace.json
+ -t out/host_device_simulator.speed_optimized/obj/pw_system/bin/system_example
+"""
+
+import argparse
+import logging
+import sys
+from pathlib import Path
+from types import ModuleType
+from typing import List, Union
+
+
+from pw_transfer import transfer_pb2
+from pw_log.proto import log_pb2
+from pw_trace_protos import trace_service_pb2
+from pw_trace import trace
+from pw_trace_tokenized import trace_tokenized
+import pw_transfer
+from pw_file import file_pb2
+from pw_hdlc import rpc
+from pw_system.device_tracing import DeviceWithTracing
+from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
+from pw_console.socket_client import SocketClient
+
+
+_LOG = logging.getLogger('pw_console_trace_client')
+_LOG.level = logging.DEBUG
+_LOG.addHandler(logging.StreamHandler(sys.stdout))
+
+
+def start_tracing_on_device(client):
+ """Start tracing on the device"""
+ service = client.rpcs.pw.trace.proto.TraceService
+ service.Start()
+
+
+def stop_tracing_on_device(client):
+ """Stop tracing on the device"""
+ service = client.rpcs.pw.trace.proto.TraceService
+ return service.Stop()
+
+
+def list_files_on_device(client):
+ """List files on the device"""
+ service = client.rpcs.pw.file.FileSystem
+ return service.List()
+
+
+def delete_file_on_device(client, path):
+ """Delete a file on the device"""
+ service = client.rpcs.pw.file.FileSystem
+ req = file_pb2.DeleteRequest(path=path)
+ return service.Delete(req)
+
+
+def _parse_args():
+ """Parse and return command line arguments."""
+
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+ group = parser.add_mutually_exclusive_group(required=False)
+ group.add_argument('-d', '--device', help='the serial port to use')
+ parser.add_argument(
+ '-b',
+ '--baudrate',
+ type=int,
+ default=115200,
+ help='the baud rate to use',
+ )
+ group.add_argument(
+ '-s',
+ '--socket-addr',
+ type=str,
+ default="default",
+ help='use socket to connect to server, type default for\
+ localhost:33000, or manually input the server address:port',
+ )
+ parser.add_argument(
+ '-o',
+ '--trace_output',
+ dest='trace_output_file',
+ help=('The json file to which to write the output.'),
+ )
+ parser.add_argument(
+ '-t',
+ '--trace_token_database',
+ help='Databases (ELF, binary, or CSV) to use to lookup trace tokens.',
+ )
+ parser.add_argument(
+ '-f',
+ '--ticks_per_second',
+ type=int,
+ dest='ticks_per_second',
+ help=('The clock rate of the trace events.'),
+ )
+ parser.add_argument(
+ '--time_offset',
+ type=int,
+ dest='time_offset',
+ default=0,
+ help=('Time offset (us) of the trace events (Default 0).'),
+ )
+ parser.add_argument(
+ '--channel-id',
+ type=int,
+ dest='channel_id',
+ default=rpc.DEFAULT_CHANNEL_ID,
+ help="Channel ID used in RPC communications.",
+ )
+ return parser.parse_args()
+
+
+def _main(args) -> int:
+ detokenizer = AutoUpdatingDetokenizer(args.trace_token_database + "#trace")
+ detokenizer.show_errors = True
+
+ socket_impl = SocketClient
+ try:
+ socket_device = socket_impl(args.socket_addr)
+ reader = rpc.SelectableReader(socket_device)
+ write = socket_device.write
+ except ValueError:
+ _LOG.exception('Failed to initialize socket at %s', args.socket_addr)
+ return 1
+
+ protos: List[Union[ModuleType, Path]] = [
+ log_pb2,
+ file_pb2,
+ transfer_pb2,
+ trace_service_pb2,
+ ]
+
+ with reader:
+ device_client = DeviceWithTracing(
+ args.ticks_per_second,
+ args.channel_id,
+ reader,
+ write,
+ protos,
+ detokenizer=detokenizer,
+ timestamp_decoder=None,
+ rpc_timeout_s=5,
+ use_rpc_logging=True,
+ use_hdlc_encoding=True,
+ )
+
+ with device_client:
+ _LOG.info("Starting tracing")
+ start_tracing_on_device(device_client)
+
+ _LOG.info("Stopping tracing")
+ file_id = stop_tracing_on_device(device_client)
+ _LOG.info("Trace file id = %d", file_id.response.file_id)
+
+ _LOG.info("Listing Files")
+ stream_response = list_files_on_device(device_client)
+
+ if not stream_response.status.ok():
+ _LOG.error('Failed to list files %s', stream_response.status)
+ return 1
+
+ for list_response in stream_response.responses:
+ for file in list_response.paths:
+ _LOG.info("Transfering File: %s", file.path)
+ try:
+ data = device_client.transfer_manager.read(file.file_id)
+ events = trace_tokenized.get_trace_events(
+ [detokenizer.database],
+ data,
+ device_client.ticks_per_second,
+ args.time_offset,
+ )
+ json_lines = trace.generate_trace_json(events)
+ trace_tokenized.save_trace_file(
+ json_lines, args.trace_output_file
+ )
+ except pw_transfer.Error as err:
+ print('Failed to read:', err.status)
+
+ _LOG.info("Deleting File: %s", file.path)
+ delete_file_on_device(device_client, file.path)
+
+ _LOG.info("All trace transfers completed successfully")
+
+ return 0
+
+
+if __name__ == '__main__':
+ _main(_parse_args())