From effa6cb1c98d9f3c486b6288ccedc4e9532d7a2d Mon Sep 17 00:00:00 2001 From: Dave Roth Date: Mon, 13 Nov 2023 19:14:11 +0000 Subject: [PATCH] pw_system: Add tracing to the demo system Adds a sample implementation of the trace service with built on top of the flat filesystem and transfer services. The trace data is backed by a persistent buffer, again as an sample implementation. Also fixes an issue with pw_transfer failing to compile on systems with different periods. Note: - the bazel build doesn't link due to tracing not being supported in the bazel build(b/260641850), so tracing is disabled in the sample bazel build. - the cmake build doesn't compile, but it was updated we new targets so as not to dig the hole any deeper. Change-Id: I483d049d486647c10be3db8f104ea2419a31a312 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/168834 Commit-Queue: Dave Roth Presubmit-Verified: CQ Bot Account Reviewed-by: Armando Montanez Reviewed-by: Alexei Frolov --- .gitignore | 4 + pw_system/BUILD.bazel | 114 +++++++++- pw_system/BUILD.gn | 61 +++++ pw_system/CMakeLists.txt | 75 ++++++- pw_system/example_user_app_init.cc | 4 +- pw_system/file_manager.cc | 47 ++++ pw_system/file_service.cc | 32 +++ pw_system/freertos_backends.gni | 1 + pw_system/freertos_target_hooks.cc | 18 ++ pw_system/hdlc_rpc_server.cc | 2 + pw_system/init.cc | 33 +++ pw_system/public/pw_system/config.h | 15 ++ pw_system/public/pw_system/file_manager.h | 61 +++++ pw_system/public/pw_system/file_service.h | 22 ++ pw_system/public/pw_system/target_hooks.h | 2 + pw_system/public/pw_system/trace_service.h | 25 +++ .../public/pw_system/transfer_handlers.h | 37 +++ pw_system/public/pw_system/transfer_service.h | 27 +++ pw_system/py/BUILD.bazel | 1 + pw_system/py/BUILD.gn | 5 + pw_system/py/pw_system/console.py | 24 +- pw_system/py/pw_system/device.py | 6 +- pw_system/py/pw_system/device_tracing.py | 162 ++++++++++++++ pw_system/py/pw_system/trace_client.py | 211 ++++++++++++++++++ pw_system/stl_backends.gni | 1 + pw_system/stl_target_hooks.cc | 8 + pw_system/system_target.gni | 4 + pw_system/trace_service.cc | 40 ++++ pw_system/transfer_handlers.cc | 39 ++++ pw_system/transfer_service.cc | 68 ++++++ pw_trace_tokenized/BUILD.bazel | 12 +- .../public/pw_transfer/internal/config.h | 6 +- .../public/pw_transfer/internal/context.h | 2 +- 33 files changed, 1159 insertions(+), 10 deletions(-) create mode 100644 pw_system/file_manager.cc create mode 100644 pw_system/file_service.cc create mode 100644 pw_system/public/pw_system/file_manager.h create mode 100644 pw_system/public/pw_system/file_service.h create mode 100644 pw_system/public/pw_system/trace_service.h create mode 100644 pw_system/public/pw_system/transfer_handlers.h create mode 100644 pw_system/public/pw_system/transfer_service.h create mode 100644 pw_system/py/pw_system/device_tracing.py create mode 100644 pw_system/py/pw_system/trace_client.py create mode 100644 pw_system/trace_service.cc create mode 100644 pw_system/transfer_handlers.cc create mode 100644 pw_system/transfer_service.cc diff --git a/.gitignore b/.gitignore index 00e8ebbee2..e8b70a59fa 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,7 @@ yarn-error.log # Emboss *.emb.h + +# Console Logs +pw_console-logs.txt +pw_console-device-logs.txt diff --git a/pw_system/BUILD.bazel b/pw_system/BUILD.bazel index f09a709229..5bdd83dbc6 100644 --- a/pw_system/BUILD.bazel +++ b/pw_system/BUILD.bazel @@ -22,11 +22,25 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) +constraint_setting( + name = "system_example_tracing_setting", +) + +constraint_value( + name = "system_example_tracing", + constraint_setting = ":system_example_tracing_setting", +) + pw_cc_library( name = "config", hdrs = [ "public/pw_system/config.h", ], + defines = select({ + "//conditions:default": [ + "PW_SYSTEM_ENABLE_TRACE_SERVICE=0", + ], + }), ) pw_cc_library( @@ -98,6 +112,7 @@ pw_cc_library( "//pw_hdlc:rpc_channel_output", "//pw_sync:mutex", "//pw_thread:thread_core", + "//pw_trace", ], ) @@ -136,16 +151,26 @@ pw_cc_library( ], includes = ["public"], deps = [ + ":file_manager", ":log", ":rpc_server", ":target_hooks", ":thread_snapshot_service", + ":transfer_service", ":work_queue", "//pw_metric:global", "//pw_metric:metric_service_pwpb", "//pw_rpc/pwpb:echo_service", "//pw_thread:thread", - ], + ] + select({ + ":system_example_tracing": [ + ":file_service", + ":trace_service", + "//pw_trace", + ], + "//conditions:default": [ + ], + }), ) pw_cc_library( @@ -191,6 +216,90 @@ pw_cc_library( ], ) +pw_cc_library( + name = "transfer_handlers", + srcs = [ + "transfer_handlers.cc", + ], + hdrs = [ + "public/pw_system/transfer_handlers.h", + ], + includes = ["public"], + deps = [ + "//pw_persistent_ram", + "//pw_trace_tokenized:config", + "//pw_transfer", + ], +) + +pw_cc_library( + name = "file_manager", + srcs = [ + "file_manager.cc", + ], + hdrs = [ + "public/pw_system/file_manager.h", + ], + includes = ["public"], + deps = [ + ":config", + ":transfer_handlers", + "//pw_file:flat_file_system", + "//pw_persistent_ram:flat_file_system_entry", + ] + select({ + ":system_example_tracing": [ + ":trace_service", + ], + "//conditions:default": [ + ], + }), +) + +pw_cc_library( + name = "transfer_service", + srcs = [ + "transfer_service.cc", + ], + hdrs = [ + "public/pw_system/transfer_service.h", + ], + includes = ["public"], + deps = [ + ":file_manager", + "//pw_transfer", + ], +) + +pw_cc_library( + name = "file_service", + srcs = [ + "file_service.cc", + ], + hdrs = [ + "public/pw_system/file_service.h", + ], + includes = ["public"], + deps = [ + ":file_manager", + ], +) + +pw_cc_library( + name = "trace_service", + srcs = [ + "trace_service.cc", + ], + hdrs = [ + "public/pw_system/trace_service.h", + ], + includes = ["public"], + deps = [ + ":transfer_handlers", + "//pw_persistent_ram", + "//pw_trace_tokenized:trace_service_pwpb", + ], +) + pw_cc_library( name = "target_hooks", hdrs = [ @@ -220,7 +329,9 @@ pw_cc_library( srcs = [ "stl_target_hooks.cc", ], + includes = ["public"], deps = [ + ":config", "//pw_thread:thread", "//pw_thread_stl:thread", ], @@ -239,6 +350,7 @@ pw_cc_library( "//pw_build/constraints/rtos:freertos", ], deps = [ + ":config", "//pw_thread:thread", "//pw_thread_freertos:thread", ], diff --git a/pw_system/BUILD.gn b/pw_system/BUILD.gn index 7d88a5c697..993b748ce8 100644 --- a/pw_system/BUILD.gn +++ b/pw_system/BUILD.gn @@ -142,15 +142,20 @@ pw_source_set("init") { public = [ "public/pw_system/init.h" ] sources = [ "init.cc" ] deps = [ + ":file_manager", + ":file_service", ":log", ":rpc_server", ":target_hooks.facade", ":thread_snapshot_service", + ":trace_service", + ":transfer_service", ":work_queue", "$dir_pw_metric:global", "$dir_pw_metric:metric_service_pwpb", "$dir_pw_rpc/pwpb:echo_service", "$dir_pw_thread:thread", + "$dir_pw_trace", ] } @@ -165,6 +170,7 @@ pw_source_set("hdlc_rpc_server") { "$dir_pw_hdlc:rpc_channel_output", "$dir_pw_log", "$dir_pw_sync:mutex", + "$dir_pw_trace", ] } @@ -196,6 +202,58 @@ pw_source_set("socket_target_io") { ] } +pw_source_set("transfer_handlers") { + public = [ "public/pw_system/transfer_handlers.h" ] + public_configs = [ ":public_include_path" ] + public_deps = [ + "$dir_pw_persistent_ram", + "$dir_pw_trace_tokenized:config", + "$dir_pw_transfer", + ] + sources = [ "transfer_handlers.cc" ] + deps = [] +} + +pw_source_set("file_manager") { + public = [ "public/pw_system/file_manager.h" ] + public_configs = [ ":public_include_path" ] + public_deps = [ + ":config", + ":transfer_handlers", + "$dir_pw_file:flat_file_system", + "$dir_pw_persistent_ram:flat_file_system_entry", + ] + sources = [ "file_manager.cc" ] + deps = [ ":trace_service" ] +} + +pw_source_set("transfer_service") { + public = [ "public/pw_system/transfer_service.h" ] + public_configs = [ ":public_include_path" ] + public_deps = [ "$dir_pw_transfer" ] + sources = [ "transfer_service.cc" ] + deps = [ ":file_manager" ] +} + +pw_source_set("file_service") { + public = [ "public/pw_system/file_service.h" ] + public_configs = [ ":public_include_path" ] + public_deps = [] + sources = [ "file_service.cc" ] + deps = [ ":file_manager" ] +} + +pw_source_set("trace_service") { + public = [ "public/pw_system/trace_service.h" ] + public_configs = [ ":public_include_path" ] + public_deps = [ ":transfer_handlers" ] + sources = [ "trace_service.cc" ] + deps = [ + "$dir_pw_persistent_ram", + "$dir_pw_trace_tokenized:trace_service_pwpb", + ] +} + pw_source_set("thread_snapshot_service") { public = [ "public/pw_system/thread_snapshot_service.h" ] public_configs = [ ":public_include_path" ] @@ -219,6 +277,7 @@ if (pw_system_TARGET_HOOKS_BACKEND == "") { get_label_info(":stl_target_hooks", "label_no_toolchain")) { pw_source_set("stl_target_hooks") { deps = [ + ":config", "$dir_pw_thread:thread", "$dir_pw_thread_stl:thread", ] @@ -229,6 +288,7 @@ if (pw_system_TARGET_HOOKS_BACKEND == "") { get_label_info(":freertos_target_hooks", "label_no_toolchain")) { pw_source_set("freertos_target_hooks") { deps = [ + ":config", ":init", "$dir_pw_third_party/freertos", "$dir_pw_thread:thread", @@ -258,6 +318,7 @@ pw_executable("system_example") { ":pw_system", "$dir_pw_log", "$dir_pw_thread:sleep", + "$dir_pw_trace", "$dir_pw_unit_test:rpc_service", # Adds a test that the test server can run. diff --git a/pw_system/CMakeLists.txt b/pw_system/CMakeLists.txt index e8da3dc899..7e1afc9756 100644 --- a/pw_system/CMakeLists.txt +++ b/pw_system/CMakeLists.txt @@ -101,6 +101,73 @@ pw_add_library(pw_system.thread_snapshot_service STATIC thread_snapshot_service.cc ) +pw_add_library(pw_system.transfer_handlers STATIC + HEADERS + public/pw_system/transfer_handlers.h + PUBLIC_INCLUDES + public + PUBLIC_DEPS + pw_persistent_ram + pw_trace_tokenized.config + pw_transfer + pw_transfer.proto.pwpb + SOURCES + transfer_handlers.cc +) + +pw_add_library(pw_system.file_manager STATIC + HEADERS + public/pw_system/file_manager.h + PUBLIC_INCLUDES + public + PUBLIC_DEPS + pw_system.config + pw_system.transfer_handlers + pw_persistent_ram.flat_file_system_entry + PRIVATE_DEPS + pw_system.trace_service + SOURCES + file_manager.cc +) + +pw_add_library(pw_system.transfer_service STATIC + HEADERS + public/pw_system/transfer_service.h + PUBLIC_INCLUDES + public + PUBLIC_DEPS + pw_transfer + PRIVATE_DEPS + pw_system.file_manager + SOURCES + transfer_service.cc +) + +pw_add_library(pw_system.file_service STATIC + HEADERS + public/pw_system/file_service.h + PUBLIC_INCLUDES + public + PRIVATE_DEPS + pw_system.file_manager + SOURCES + file_service.cc +) + +pw_add_library(pw_system.trace_service STATIC + HEADERS + public/pw_system/trace_service.h + PUBLIC_INCLUDES + public + PUBLIC_DEPS + pw_system.transfer_handlers + PRIVATE_DEPS + pw_persistent_ram + pw_trace_tokenized.trace_service_pwpb + SOURCES + trace_service.cc +) + pw_add_library(pw_system.io INTERFACE HEADERS public/pw_system/io.h @@ -118,14 +185,19 @@ pw_add_library(pw_system.init STATIC SOURCES init.cc PRIVATE_DEPS + pw_system.file_service pw_system.log pw_system.rpc_server pw_system.target_hooks pw_system.thread_snapshot_service + pw_system.trace_service + pw_system.transfer_service + pw_system.file_manager pw_system.work_queue pw_rpc.pwpb.echo_service pw_metric.metric_service_pwpb pw_thread.thread + pw_trace ) pw_add_library(pw_system.work_queue STATIC @@ -163,10 +235,10 @@ pw_add_facade(pw_system.target_hooks INTERFACE pw_add_library(pw_system.stl_target_hooks STATIC PRIVATE_DEPS + pw_system.config pw_thread.sleep pw_thread.thread pw_thread_stl.thread - SOURCES stl_target_hooks.cc ) @@ -175,6 +247,7 @@ pw_add_library(pw_system.freertos_target_hooks STATIC SOURCES freertos_target_hooks.cc PRIVATE_DEPS + pw_system.config pw_thread.thread pw_thread_freertos.thread # TODO: b/234876414 - This should depend on FreeRTOS but our third parties diff --git a/pw_system/example_user_app_init.cc b/pw_system/example_user_app_init.cc index 025dc7b8f5..e8951d91a5 100644 --- a/pw_system/example_user_app_init.cc +++ b/pw_system/example_user_app_init.cc @@ -15,7 +15,7 @@ #include "pw_log/log.h" #include "pw_system/rpc_server.h" -#include "pw_thread/sleep.h" +#include "pw_trace/trace.h" #include "pw_unit_test/unit_test_service.h" namespace pw::system { @@ -25,6 +25,8 @@ pw::unit_test::UnitTestService unit_test_service; // This will run once after pw::system::Init() completes. This callback must // return or it will block the work queue. void UserAppInit() { + PW_TRACE_FUNCTION(); + PW_LOG_INFO("Pigweed is fun!"); GetRpcServer().RegisterService(unit_test_service); } diff --git a/pw_system/file_manager.cc b/pw_system/file_manager.cc new file mode 100644 index 0000000000..97fb4987bc --- /dev/null +++ b/pw_system/file_manager.cc @@ -0,0 +1,47 @@ +// 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. + +#include "pw_system/file_manager.h" + +#if PW_SYSTEM_ENABLE_TRACE_SERVICE +#include "pw_system/trace_service.h" +#endif + +namespace pw::system { + +namespace { +FileManager file_manager; +} + +FileManager& GetFileManager() { return file_manager; } + +FileManager::FileManager() +#if PW_SYSTEM_ENABLE_TRACE_SERVICE + : trace_data_handler_(kTraceTransferHandlerId, GetTraceData()), + trace_data_filesystem_entry_( + "/trace/0.bin", + kTraceTransferHandlerId, + file::FlatFileSystemService::Entry::FilePermissions::READ, + GetTraceData()) +#endif +{ + // Every handler & filesystem element must be added to the collections, using + // the associated handler ID as the index. +#if PW_SYSTEM_ENABLE_TRACE_SERVICE + transfer_handlers_[kTraceTransferHandlerId] = &trace_data_handler_; + file_system_entries_[kTraceTransferHandlerId] = &trace_data_filesystem_entry_; +#endif +} + +} // namespace pw::system diff --git a/pw_system/file_service.cc b/pw_system/file_service.cc new file mode 100644 index 0000000000..4e3f3519a4 --- /dev/null +++ b/pw_system/file_service.cc @@ -0,0 +1,32 @@ +// 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. + +#include "pw_system/file_service.h" + +#include "public/pw_system/file_manager.h" + +namespace pw::system { +namespace { + +constexpr size_t kMaxFileNameLength = 48; +file::FlatFileSystemServiceWithBuffer file_service( + GetFileManager().GetFileSystemEntries()); + +} // namespace + +void RegisterFileService(rpc::Server& rpc_server) { + rpc_server.RegisterService(file_service); +} + +} // namespace pw::system diff --git a/pw_system/freertos_backends.gni b/pw_system/freertos_backends.gni index 8829bc2bb8..2c5b85a5d4 100644 --- a/pw_system/freertos_backends.gni +++ b/pw_system/freertos_backends.gni @@ -33,6 +33,7 @@ PW_SYSTEM_FREERTOS_BACKENDS = { "$dir_pw_thread_freertos:thread_iteration" pw_thread_YIELD_BACKEND = "$dir_pw_thread_freertos:yield" pw_system_TARGET_HOOKS_BACKEND = "$dir_pw_system:freertos_target_hooks" + pw_trace_tokenizer_time = "$dir_pw_trace_tokenized:fake_trace_time" # Enable pw_third_party_freertos_DISABLE_TASKS_STATICS so thread iteration # works out-of-the-box. diff --git a/pw_system/freertos_target_hooks.cc b/pw_system/freertos_target_hooks.cc index d533308a69..c617f41d40 100644 --- a/pw_system/freertos_target_hooks.cc +++ b/pw_system/freertos_target_hooks.cc @@ -13,6 +13,7 @@ // the License. #include "FreeRTOS.h" +#include "pw_system/config.h" #include "pw_thread/detached_thread.h" #include "pw_thread/thread.h" #include "pw_thread_freertos/context.h" @@ -27,6 +28,9 @@ enum class ThreadPriority : UBaseType_t { // there's synchronization issues when they are. kLog = kWorkQueue, kRpc = kWorkQueue, +#if PW_SYSTEM_ENABLE_TRANSFER_SERVICE + kTransfer = kWorkQueue, +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE kNumPriorities, }; @@ -57,6 +61,20 @@ const thread::Options& RpcThreadOptions() { return options; } +#if PW_SYSTEM_ENABLE_TRANSFER_SERVICE +static constexpr size_t kTransferThreadStackWords = 512; +static thread::freertos::StaticContextWithStack + transfer_thread_context; +const thread::Options& TransferThreadOptions() { + static constexpr auto options = + pw::thread::freertos::Options() + .set_name("TransferThread") + .set_static_context(transfer_thread_context) + .set_priority(static_cast(ThreadPriority::kTransfer)); + return options; +} +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE + static constexpr size_t kWorkQueueThreadStackWords = 512; static thread::freertos::StaticContextWithStack work_queue_thread_context; diff --git a/pw_system/hdlc_rpc_server.cc b/pw_system/hdlc_rpc_server.cc index 3923d4ff02..72c6c925bf 100644 --- a/pw_system/hdlc_rpc_server.cc +++ b/pw_system/hdlc_rpc_server.cc @@ -28,6 +28,7 @@ #include "pw_system/config.h" #include "pw_system/io.h" #include "pw_system/rpc_server.h" +#include "pw_trace/trace.h" #if PW_SYSTEM_DEFAULT_CHANNEL_ID != PW_SYSTEM_LOGGING_CHANNEL_ID && \ PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS == PW_SYSTEM_LOGGING_RPC_HDLC_ADDRESS @@ -117,6 +118,7 @@ class RpcDispatchThread final : public thread::ThreadCore { for (std::byte byte : ret_val.value()) { if (auto result = decoder.Process(byte); result.ok()) { hdlc::Frame& frame = result.value(); + PW_TRACE_SCOPE("RPC process frame"); if (frame.address() == PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS || frame.address() == PW_SYSTEM_LOGGING_RPC_HDLC_ADDRESS) { server.ProcessPacket(frame.data()); diff --git a/pw_system/init.cc b/pw_system/init.cc index 4196115d3b..96d5dad39a 100644 --- a/pw_system/init.cc +++ b/pw_system/init.cc @@ -26,6 +26,18 @@ #include "pw_system_private/log.h" #include "pw_thread/detached_thread.h" +#if PW_SYSTEM_ENABLE_TRANSFER_SERVICE +#include "pw_system/transfer_service.h" +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE + +#if PW_SYSTEM_ENABLE_TRACE_SERVICE +#include "pw_system/file_service.h" +#include "pw_system/trace_service.h" +#include "pw_trace/trace.h" +#endif // PW_SYSTEM_ENABLE_TRACE_SERVICE + +#include "pw_system/file_manager.h" + #if PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE #include "pw_system/thread_snapshot_service.h" #endif // PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE @@ -38,6 +50,12 @@ metric::MetricService metric_service(metric::global_metrics, rpc::EchoService echo_service; void InitImpl() { +#if PW_SYSTEM_ENABLE_TRACE_SERVICE + // tracing is off by default, requring a user to enable it through + // the trace service + PW_TRACE_SET_ENABLED(false); +#endif + PW_LOG_INFO("System init"); // Setup logging. @@ -52,6 +70,16 @@ void InitImpl() { GetRpcServer().RegisterService(echo_service); GetRpcServer().RegisterService(GetLogService()); GetRpcServer().RegisterService(metric_service); + +#if PW_SYSTEM_ENABLE_TRANSFER_SERVICE + RegisterTransferService(GetRpcServer()); +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE + +#if PW_SYSTEM_ENABLE_TRACE_SERVICE + RegisterFileService(GetRpcServer()); + RegisterTraceService(GetRpcServer(), FileManager::kTraceTransferHandlerId); +#endif // PW_SYSTEM_ENABLE_TRACE_SERVICE + #if PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE RegisterThreadSnapshotService(GetRpcServer()); #endif // PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE @@ -61,6 +89,11 @@ void InitImpl() { thread::DetachedThread(system::LogThreadOptions(), GetLogThread()); thread::DetachedThread(system::RpcThreadOptions(), GetRpcDispatchThread()); +#if PW_SYSTEM_ENABLE_TRANSFER_SERVICE + thread::DetachedThread(system::TransferThreadOptions(), GetTransferThread()); + InitTransferService(); +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE + GetWorkQueue().CheckPushWork(UserAppInit); } diff --git a/pw_system/public/pw_system/config.h b/pw_system/public/pw_system/config.h index 0d04158cbf..b4024ca898 100644 --- a/pw_system/public/pw_system/config.h +++ b/pw_system/public/pw_system/config.h @@ -68,6 +68,21 @@ #define PW_SYSTEM_LOGGING_RPC_HDLC_ADDRESS PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS #endif // PW_SYSTEM_LOGGING_RPC_HDLC_ADDRESS +// PW_SYSTEM_ENABLE_TRACE_SERVICE specifies if the trace RPC service is enabled. +// +// Defaults to 1. +#ifndef PW_SYSTEM_ENABLE_TRACE_SERVICE +#define PW_SYSTEM_ENABLE_TRACE_SERVICE 1 +#endif // PW_SYSTEM_ENABLE_TRACE_SERVICE + +// PW_SYSTEM_ENABLE_TRANSFER_SERVICE specifies if the transfer RPC service is +// enabled. +// +// Defaults to 1. +#ifndef PW_SYSTEM_ENABLE_TRANSFER_SERVICE +#define PW_SYSTEM_ENABLE_TRANSFER_SERVICE 1 +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE + // PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE specifies if the thread snapshot // RPC service is enabled. // diff --git a/pw_system/public/pw_system/file_manager.h b/pw_system/public/pw_system/file_manager.h new file mode 100644 index 0000000000..a061cd5a33 --- /dev/null +++ b/pw_system/public/pw_system/file_manager.h @@ -0,0 +1,61 @@ +// 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. +#pragma once + +#include "pw_persistent_ram/flat_file_system_entry.h" +#include "pw_system/config.h" +#include "pw_system/transfer_handlers.h" + +namespace pw::system { + +class FileManager { + public: + // Each transfer handler ID corresponds 1:1 with a transfer handler and + // filesystem element pair. The ID must be unique and increment from 0 to + // ensure no gaps in the FileManager handler & filesystem arrays. + // NOTE: the enumerators should never have values defined, to ensure they + // increment from zero and kNumFileSystemEntries is correct + enum TransferHandlerId : uint32_t { +#if PW_SYSTEM_ENABLE_TRACE_SERVICE + kTraceTransferHandlerId, +#endif + kNumFileSystemEntries + }; + + FileManager(); + + std::array& GetTransferHandlers() { + return transfer_handlers_; + } + std::array& + GetFileSystemEntries() { + return file_system_entries_; + } + + private: +#if PW_SYSTEM_ENABLE_TRACE_SERVICE + TracePersistentBufferTransfer trace_data_handler_; + persistent_ram::FlatFileSystemPersistentBufferEntry< + PW_TRACE_BUFFER_SIZE_BYTES> + trace_data_filesystem_entry_; +#endif + + std::array transfer_handlers_; + std::array + file_system_entries_; +}; + +FileManager& GetFileManager(); + +} // namespace pw::system diff --git a/pw_system/public/pw_system/file_service.h b/pw_system/public/pw_system/file_service.h new file mode 100644 index 0000000000..1610bff012 --- /dev/null +++ b/pw_system/public/pw_system/file_service.h @@ -0,0 +1,22 @@ +// 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. +#pragma once + +#include "pw_rpc/server.h" + +namespace pw::system { + +void RegisterFileService(rpc::Server& rpc_server); + +} // namespace pw::system diff --git a/pw_system/public/pw_system/target_hooks.h b/pw_system/public/pw_system/target_hooks.h index 58460ca8b1..19628c2ad5 100644 --- a/pw_system/public/pw_system/target_hooks.h +++ b/pw_system/public/pw_system/target_hooks.h @@ -21,6 +21,8 @@ const thread::Options& LogThreadOptions(); const thread::Options& RpcThreadOptions(); +const thread::Options& TransferThreadOptions(); + const thread::Options& WorkQueueThreadOptions(); // This will run once after pw::system::Init() completes. This callback must diff --git a/pw_system/public/pw_system/trace_service.h b/pw_system/public/pw_system/trace_service.h new file mode 100644 index 0000000000..7c16c0e382 --- /dev/null +++ b/pw_system/public/pw_system/trace_service.h @@ -0,0 +1,25 @@ +// 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. +#pragma once + +#include "pw_rpc/server.h" +#include "pw_system/transfer_handlers.h" + +namespace pw::system { + +void RegisterTraceService(rpc::Server& rpc_server, uint32_t transfer_id); + +TracePersistentBuffer& GetTraceData(); + +} // namespace pw::system diff --git a/pw_system/public/pw_system/transfer_handlers.h b/pw_system/public/pw_system/transfer_handlers.h new file mode 100644 index 0000000000..f823085599 --- /dev/null +++ b/pw_system/public/pw_system/transfer_handlers.h @@ -0,0 +1,37 @@ +// 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. +#pragma once + +#include "pw_persistent_ram/persistent_buffer.h" +#include "pw_trace_tokenized/config.h" +#include "pw_transfer/transfer.h" + +namespace pw::system { + +using TracePersistentBuffer = + persistent_ram::PersistentBuffer; + +class TracePersistentBufferTransfer : public transfer::ReadOnlyHandler { + public: + TracePersistentBufferTransfer(uint32_t id, + TracePersistentBuffer& persistent_buffer); + + Status PrepareRead() final; + + private: + TracePersistentBuffer& persistent_buffer_; + stream::MemoryReader reader_; +}; + +} // namespace pw::system diff --git a/pw_system/public/pw_system/transfer_service.h b/pw_system/public/pw_system/transfer_service.h new file mode 100644 index 0000000000..64a60b99c7 --- /dev/null +++ b/pw_system/public/pw_system/transfer_service.h @@ -0,0 +1,27 @@ +// 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. +#pragma once + +#include "pw_rpc/server.h" +#include "pw_transfer/transfer_thread.h" + +namespace pw::system { + +void RegisterTransferService(rpc::Server& rpc_server); + +void InitTransferService(); + +transfer::TransferThread& GetTransferThread(); + +} // namespace pw::system diff --git a/pw_system/py/BUILD.bazel b/pw_system/py/BUILD.bazel index a45c81dd92..61c72038d6 100644 --- a/pw_system/py/BUILD.bazel +++ b/pw_system/py/BUILD.bazel @@ -33,6 +33,7 @@ py_library( "pw_system/__init__.py", "pw_system/console.py", "pw_system/device.py", + "pw_system/device_tracing.py", ], imports = ["."], tags = ["manual"], diff --git a/pw_system/py/BUILD.gn b/pw_system/py/BUILD.gn index 755fb508ac..7799436a37 100644 --- a/pw_system/py/BUILD.gn +++ b/pw_system/py/BUILD.gn @@ -25,10 +25,13 @@ pw_python_package("py") { "pw_system/__init__.py", "pw_system/console.py", "pw_system/device.py", + "pw_system/device_tracing.py", + "pw_system/trace_client.py", ] python_deps = [ "$dir_pw_cli/py", "$dir_pw_console/py", + "$dir_pw_file/py", "$dir_pw_hdlc/py", "$dir_pw_log:protos.python", "$dir_pw_log/py", @@ -40,6 +43,8 @@ pw_python_package("py") { "$dir_pw_thread:protos.python", "$dir_pw_thread/py", "$dir_pw_tokenizer/py", + "$dir_pw_trace_tokenized:protos.python", + "$dir_pw_transfer:proto.python", "$dir_pw_unit_test:unit_test_proto.python", "$dir_pw_unit_test/py", ] diff --git a/pw_system/py/pw_system/console.py b/pw_system/py/pw_system/console.py index 172d370d71..6f6405cacf 100644 --- a/pw_system/py/pw_system/console.py +++ b/pw_system/py/pw_system/console.py @@ -64,6 +64,7 @@ from pw_hdlc import rpc from pw_rpc.console_tools.console import flattened_rpc_completions from pw_system.device import Device +from pw_system.device_tracing import DeviceWithTracing from pw_tokenizer.detokenize import AutoUpdatingDetokenizer # Default proto imports: @@ -71,6 +72,9 @@ from pw_metric_proto import metric_service_pb2 from pw_thread_protos import thread_snapshot_service_pb2 from pw_unit_test_proto import unit_test_pb2 +from pw_file import file_pb2 +from pw_trace_protos import trace_service_pb2 +from pw_transfer import transfer_pb2 _LOG = logging.getLogger('tools') _DEVICE_LOG = logging.getLogger('rpc_device') @@ -183,6 +187,13 @@ def get_parser() -> argparse.ArgumentParser: default=[], help='glob pattern for .proto files.', ) + parser.add_argument( + '-f', + '--ticks_per_second', + type=int, + dest='ticks_per_second', + help=('The clock rate of the trace events.'), + ) parser.add_argument( '-v', '--verbose', @@ -351,6 +362,7 @@ def console( device: str, baudrate: int, proto_globs: Collection[str], + ticks_per_second: Optional[int], token_databases: Collection[Path], socket_addr: str, logfile: str, @@ -438,7 +450,11 @@ def console( detokenizer = None if token_databases: - detokenizer = AutoUpdatingDetokenizer(*token_databases) + token_databases_with_domains = [] * len(token_databases) + for token_database in token_databases: + token_databases_with_domains.append(str(token_database) + "#trace") + + detokenizer = AutoUpdatingDetokenizer(*token_databases_with_domains) detokenizer.show_errors = True protos: List[Union[ModuleType, Path]] = list(_expand_globs(proto_globs)) @@ -455,6 +471,9 @@ def console( protos.extend(compiled_protos) protos.append(metric_service_pb2) protos.append(thread_snapshot_service_pb2) + protos.append(file_pb2) + protos.append(trace_service_pb2) + protos.append(transfer_pb2) if not protos: _LOG.critical( @@ -516,7 +535,8 @@ def disconnect_handler(socket_device: SocketClient) -> None: return 1 with reader: - device_client = Device( + device_client = DeviceWithTracing( + ticks_per_second, channel_id, reader, write, diff --git a/pw_system/py/pw_system/device.py b/pw_system/py/pw_system/device.py index 0dc2155f45..470ddc2368 100644 --- a/pw_system/py/pw_system/device.py +++ b/pw_system/py/pw_system/device.py @@ -18,6 +18,7 @@ from types import ModuleType from typing import Any, Callable, List, Union, Optional +from pw_thread_protos import thread_pb2 from pw_hdlc.rpc import ( HdlcRpcClient, channel_output, @@ -35,7 +36,6 @@ from pw_metric import metric_parser from pw_rpc import callback_client, Channel, console_tools from pw_thread.thread_analyzer import ThreadSnapshotAnalyzer -from pw_thread_protos import thread_pb2 from pw_tokenizer import detokenize from pw_tokenizer.proto import decode_optionally_tokenized from pw_unit_test.rpc import run_tests as pw_unit_test_run_tests, TestRecord @@ -53,7 +53,9 @@ class Device: Note: use this class as a base for specialized device representations. """ + # pylint: disable=too-many-instance-attributes def __init__( + # pylint: disable=too-many-arguments self, channel_id: int, reader: CancellableReader, @@ -62,6 +64,7 @@ def __init__( detokenizer: Optional[detokenize.Detokenizer] = None, timestamp_decoder: Optional[Callable[[int], str]] = None, rpc_timeout_s: float = 5, + time_offset: int = 0, use_rpc_logging: bool = True, use_hdlc_encoding: bool = True, logger: logging.Logger = DEFAULT_DEVICE_LOGGER, @@ -70,6 +73,7 @@ def __init__( self.protos = proto_library self.detokenizer = detokenizer self.rpc_timeout_s = rpc_timeout_s + self.time_offset = time_offset self.logger = logger self.logger.setLevel(logging.DEBUG) # Allow all device logs through. diff --git a/pw_system/py/pw_system/device_tracing.py b/pw_system/py/pw_system/device_tracing.py new file mode 100644 index 0000000000..c97dabe081 --- /dev/null +++ b/pw_system/py/pw_system/device_tracing.py @@ -0,0 +1,162 @@ +# Copyright 2021 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. +"""Device tracing classes to interact with targets via RPC.""" + +import os +import logging +import tempfile + +# from pathlib import Path +# from types import ModuleType +# from typing import Callable, List, Optional, Union +from typing import List, Optional + +import pw_transfer +from pw_file import file_pb2 +from pw_rpc.callback_client.errors import RpcError +from pw_system.device import Device +from pw_trace import trace +from pw_trace_tokenized import trace_tokenized + +_LOG = logging.getLogger('tracing') +DEFAULT_TICKS_PER_SECOND = 1000 + + +class DeviceWithTracing(Device): + """Represents an RPC Client for a device running a Pigweed target with + tracing. + + The target must have and RPC support for the following services: + - logging + - file + - transfer + - tracing + Note: use this class as a base for specialized device representations. + """ + + def __init__(self, ticks_per_second: Optional[int], *argv, **kargv): + super().__init__(*argv, **kargv) + + # Create the transfer manager + self.transfer_service = self.rpcs.pw.transfer.Transfer + self.transfer_manager = pw_transfer.Manager( + self.transfer_service, + default_response_timeout_s=self.rpc_timeout_s, + initial_response_timeout_s=self.rpc_timeout_s, + default_protocol_version=pw_transfer.ProtocolVersion.LATEST, + ) + + if ticks_per_second: + self.ticks_per_second = ticks_per_second + else: + self.ticks_per_second = self.get_ticks_per_second() + _LOG.info('ticks_per_second set to %i', self.ticks_per_second) + + def get_ticks_per_second(self) -> int: + trace_service = self.rpcs.pw.trace.proto.TraceService + try: + resp = trace_service.GetClockParameters() + if not resp.status.ok(): + _LOG.error( + 'Failed to get clock parameters: %s. Using default \ + value', + resp.status, + ) + return DEFAULT_TICKS_PER_SECOND + except RpcError as rpc_err: + _LOG.exception('%s. Using default value', rpc_err) + return DEFAULT_TICKS_PER_SECOND + + return resp.response.clock_parameters.tick_period_seconds_denominator + + def list_files(self) -> List: + """Lists all files on this device.""" + fs_service = self.rpcs.pw.file.FileSystem + stream_response = fs_service.List() + + if not stream_response.status.ok(): + _LOG.error('Failed to list files %s', stream_response.status) + return [] + + return stream_response.responses + + def delete_file(self, path: str) -> bool: + """Delete a file on this device.""" + fs_service = self.rpcs.pw.file.FileSystem + req = file_pb2.DeleteRequest(path=path) + stream_response = fs_service.Delete(req) + if not stream_response.status.ok(): + _LOG.error( + 'Failed to delete file %s file: %s', + path, + stream_response.status, + ) + return False + + return True + + def transfer_file(self, file_id: int, dest_path: str) -> bool: + """Transfer a file on this device to the host.""" + try: + data = self.transfer_manager.read(file_id) + with open(dest_path, "wb") as bin_file: + bin_file.write(data) + except pw_transfer.Error: + _LOG.exception('Failed to transfer file_id %i', file_id) + return False + + return True + + def start_tracing(self) -> None: + """Turns on tracing on this device.""" + trace_service = self.rpcs.pw.trace.proto.TraceService + trace_service.Start() + + def stop_tracing(self, trace_output_path: str = "trace.json") -> None: + """Turns off tracing on this device and downloads the trace file.""" + trace_service = self.rpcs.pw.trace.proto.TraceService + resp = trace_service.Stop() + + # If there's no tokenizer, there's no need to transfer the trace + # file from the device after stopping tracing, as there's not much + # that can be done with it. + if not self.detokenizer: + _LOG.error('No tokenizer specified. Not transfering trace') + return + + trace_bin_path = tempfile.NamedTemporaryFile(delete=False) + trace_bin_path.close() + try: + if not self.transfer_file( + resp.response.file_id, trace_bin_path.name + ): + return + + with open(trace_bin_path.name, 'rb') as bin_file: + trace_data = bin_file.read() + events = trace_tokenized.get_trace_events( + [self.detokenizer.database], + trace_data, + self.ticks_per_second, + self.time_offset, + ) + json_lines = trace.generate_trace_json(events) + trace_tokenized.save_trace_file(json_lines, trace_output_path) + + _LOG.info( + 'Wrote trace file %s', + trace_output_path, + ) + finally: + os.remove(trace_bin_path.name) 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 0000000000..575659b262 --- /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()) diff --git a/pw_system/stl_backends.gni b/pw_system/stl_backends.gni index 06d901ebd2..f8d60a89e8 100644 --- a/pw_system/stl_backends.gni +++ b/pw_system/stl_backends.gni @@ -36,4 +36,5 @@ PW_SYSTEM_STL_BACKENDS = { pw_thread_THREAD_ITERATION_BACKEND = "$dir_pw_thread_stl:thread_iteration" pw_thread_YIELD_BACKEND = "$dir_pw_thread_stl:yield" pw_system_TARGET_HOOKS_BACKEND = "$dir_pw_system:stl_target_hooks" + pw_trace_tokenizer_time = "$dir_pw_trace_tokenized:host_trace_time" } diff --git a/pw_system/stl_target_hooks.cc b/pw_system/stl_target_hooks.cc index 048153d4c2..e470a97af1 100644 --- a/pw_system/stl_target_hooks.cc +++ b/pw_system/stl_target_hooks.cc @@ -12,6 +12,7 @@ // License for the specific language governing permissions and limitations under // the License. +#include "pw_system/config.h" #include "pw_thread/thread.h" #include "pw_thread_stl/options.h" @@ -27,6 +28,13 @@ const thread::Options& RpcThreadOptions() { return rpc_thread_options; } +#if PW_SYSTEM_ENABLE_TRANSFER_SERVICE +const thread::Options& TransferThreadOptions() { + static thread::stl::Options transfer_thread_options; + return transfer_thread_options; +} +#endif // PW_SYSTEM_ENABLE_TRANSFER_SERVICE + const thread::Options& WorkQueueThreadOptions() { static thread::stl::Options work_queue_thread_options; return work_queue_thread_options; diff --git a/pw_system/system_target.gni b/pw_system/system_target.gni index 736dd7427b..55dbcf1b78 100644 --- a/pw_system/system_target.gni +++ b/pw_system/system_target.gni @@ -30,6 +30,8 @@ import("$dir_pw_toolchain/arm_gcc/toolchains.gni") import("$dir_pw_toolchain/generate_toolchain.gni") import("$dir_pw_toolchain/host_clang/toolchains.gni") import("$dir_pw_toolchain/host_gcc/toolchains.gni") +import("$dir_pw_trace/backend.gni") +import("$dir_pw_trace_tokenized/config.gni") import("$dir_pw_unit_test/test.gni") import("backend.gni") import("freertos_backends.gni") @@ -126,6 +128,8 @@ template("pw_system_target") { # when RPC is working. pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main" + pw_trace_BACKEND = "$dir_pw_trace_tokenized" + if (pw_system_USE_MULTI_ENDPOINT_CONFIG) { pw_system_CONFIG = "$dir_pw_system:multi_endpoint_rpc_config" } diff --git a/pw_system/trace_service.cc b/pw_system/trace_service.cc new file mode 100644 index 0000000000..c0a92dcbaf --- /dev/null +++ b/pw_system/trace_service.cc @@ -0,0 +1,40 @@ +// 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. + +#include "pw_system/trace_service.h" + +#include "pw_trace_tokenized/trace_service_pwpb.h" +#include "pw_trace_tokenized/trace_tokenized.h" + +namespace pw::system { +namespace { + +// TODO: b/305795949 - place trace_data in a persistent region of memory +TracePersistentBuffer trace_data; +persistent_ram::PersistentBufferWriter trace_data_writer( + trace_data.GetWriter()); + +trace::TraceService trace_service(trace::GetTokenizedTracer(), + trace_data_writer); + +} // namespace + +TracePersistentBuffer& GetTraceData() { return trace_data; } + +void RegisterTraceService(rpc::Server& rpc_server, uint32_t transfer_id) { + rpc_server.RegisterService(trace_service); + trace_service.SetTransferId(transfer_id); +} + +} // namespace pw::system diff --git a/pw_system/transfer_handlers.cc b/pw_system/transfer_handlers.cc new file mode 100644 index 0000000000..2737f8e8fb --- /dev/null +++ b/pw_system/transfer_handlers.cc @@ -0,0 +1,39 @@ +// 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. +#include "pw_system/transfer_handlers.h" + +namespace pw::system { + +TracePersistentBufferTransfer::TracePersistentBufferTransfer( + uint32_t id, TracePersistentBuffer& persistent_buffer) + : transfer::ReadOnlyHandler(id), + persistent_buffer_(persistent_buffer), + reader_({}) { + set_reader(reader_); +} + +Status TracePersistentBufferTransfer::PrepareRead() { + if (!persistent_buffer_.has_value()) { + return Status::Unavailable(); + } + + // As seeking is not yet supported, reinitialize the reader from the start + // of the snapshot buffer. + new (&reader_) stream::MemoryReader( + pw::ConstByteSpan{persistent_buffer_.data(), persistent_buffer_.size()}); + + return OkStatus(); +} + +} // namespace pw::system diff --git a/pw_system/transfer_service.cc b/pw_system/transfer_service.cc new file mode 100644 index 0000000000..7a68376cc2 --- /dev/null +++ b/pw_system/transfer_service.cc @@ -0,0 +1,68 @@ +// 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. + +#include "pw_system/transfer_service.h" + +#include "pw_system/file_manager.h" + +namespace pw::system { +namespace { +// The maximum number of concurrent transfers the thread should support as +// either a client or a server. These can be set to 0 (if only using one or +// the other). +constexpr size_t kMaxConcurrentClientTransfers = 5; +constexpr size_t kMaxConcurrentServerTransfers = 3; + +// The maximum payload size that can be transmitted by the system's +// transport stack. This would typically be defined within some transport +// header. +constexpr size_t kMaxTransmissionUnit = 512; + +// The maximum amount of data that should be sent within a single transfer +// packet. By necessity, this should be less than the max transmission unit. +// +// pw_transfer requires some additional per-packet overhead, so the actual +// amount of data it sends may be lower than this. +constexpr size_t kMaxTransferChunkSizeBytes = 480; + +// In a write transfer, the maximum number of bytes to receive at one time +// (potentially across multiple chunks), unless specified otherwise by the +// transfer handler's stream::Writer. +constexpr size_t kDefaultMaxBytesToReceive = 1024; + +// Buffers for storing and encoding chunks (see documentation above). +std::array chunk_buffer; +std::array encode_buffer; + +transfer::Thread + transfer_thread(chunk_buffer, encode_buffer); + +transfer::TransferService transfer_service(transfer_thread, + kDefaultMaxBytesToReceive); +} // namespace + +void RegisterTransferService(rpc::Server& rpc_server) { + rpc_server.RegisterService(transfer_service); +} + +void InitTransferService() { + // the handlers need to be registered after the transfer thread has started + for (auto handler : GetFileManager().GetTransferHandlers()) { + transfer_service.RegisterHandler(*handler); + } +} + +transfer::TransferThread& GetTransferThread() { return transfer_thread; } + +} // namespace pw::system diff --git a/pw_trace_tokenized/BUILD.bazel b/pw_trace_tokenized/BUILD.bazel index 6e76328e94..72c5357dc0 100644 --- a/pw_trace_tokenized/BUILD.bazel +++ b/pw_trace_tokenized/BUILD.bazel @@ -31,13 +31,22 @@ pw_cc_library( tags = ["manual"], ) +pw_cc_library( + name = "config", + hdrs = [ + "public/pw_trace_tokenized/config.h", + ], + includes = [ + "public", + ], +) + pw_cc_library( name = "pw_trace_tokenized", srcs = [ "trace.cc", ], hdrs = [ - "public/pw_trace_tokenized/config.h", "public/pw_trace_tokenized/internal/trace_tokenized_internal.h", "public/pw_trace_tokenized/trace_callback.h", "public/pw_trace_tokenized/trace_tokenized.h", @@ -48,6 +57,7 @@ pw_cc_library( "public_overrides", ], deps = [ + ":config", "//pw_assert", "//pw_log", "//pw_preprocessor", diff --git a/pw_transfer/public/pw_transfer/internal/config.h b/pw_transfer/public/pw_transfer/internal/config.h index e17f748d79..04bfa78f8f 100644 --- a/pw_transfer/public/pw_transfer/internal/config.h +++ b/pw_transfer/public/pw_transfer/internal/config.h @@ -87,9 +87,11 @@ inline constexpr uint16_t kDefaultMaxLifetimeRetries = PW_TRANSFER_DEFAULT_MAX_LIFETIME_RETRIES; inline constexpr chrono::SystemClock::duration kDefaultChunkTimeout = - std::chrono::milliseconds(PW_TRANSFER_DEFAULT_TIMEOUT_MS); + chrono::SystemClock::for_at_least( + std::chrono::milliseconds(PW_TRANSFER_DEFAULT_TIMEOUT_MS)); inline constexpr chrono::SystemClock::duration kDefaultInitialChunkTimeout = - std::chrono::milliseconds(PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS); + chrono::SystemClock::for_at_least( + std::chrono::milliseconds(PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS)); inline constexpr uint32_t kDefaultExtendWindowDivisor = PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR; diff --git a/pw_transfer/public/pw_transfer/internal/context.h b/pw_transfer/public/pw_transfer/internal/context.h index 6baaa6eddb..b4a80ec985 100644 --- a/pw_transfer/public/pw_transfer/internal/context.h +++ b/pw_transfer/public/pw_transfer/internal/context.h @@ -348,7 +348,7 @@ class Context { // status chunk will be re-sent for every non-ACK chunk received, // continually notifying the other end that the transfer is over. static constexpr chrono::SystemClock::duration kFinalChunkAckTimeout = - std::chrono::milliseconds(5000); + chrono::SystemClock::for_at_least(std::chrono::milliseconds(5000)); static constexpr chrono::SystemClock::time_point kNoTimeout = chrono::SystemClock::time_point(chrono::SystemClock::duration(0));