pw_system: Thread Snapshot RPC service upstream integration
Change-Id: I6e22610d69db432a1e62dc56539680e37a7fe3ae
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/108055
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: Medha Kini <medhakini@google.com>
diff --git a/pw_system/BUILD.bazel b/pw_system/BUILD.bazel
index f0346fb..f51d1ee 100644
--- a/pw_system/BUILD.bazel
+++ b/pw_system/BUILD.bazel
@@ -107,6 +107,21 @@
)
pw_cc_library(
+ name = "thread_snapshot_service",
+ srcs = [
+ "thread_snapshot_service.cc",
+ ],
+ hdrs = [
+ "public/pw_system/thread_snapshot_service.h",
+ ],
+ includes = ["public"],
+ deps = [
+ "//pw_rpc:server",
+ "//pw_thread:thread_snapshot_service",
+ ],
+)
+
+pw_cc_library(
name = "io",
hdrs = [
"public/pw_system/io.h",
@@ -129,6 +144,7 @@
":log",
":rpc_server",
":target_hooks.facade",
+ ":thread_snapshot_service",
":work_queue",
"//pw_metric:global",
"//pw_metric:metric_service_pwpb",
diff --git a/pw_system/BUILD.gn b/pw_system/BUILD.gn
index 21b2444..78cf35a 100644
--- a/pw_system/BUILD.gn
+++ b/pw_system/BUILD.gn
@@ -118,6 +118,7 @@
":log",
":rpc_server",
":target_hooks.facade",
+ ":thread_snapshot_service",
":work_queue",
"$dir_pw_metric:global",
"$dir_pw_metric:metric_service_pwpb",
@@ -168,6 +169,14 @@
]
}
+pw_source_set("thread_snapshot_service") {
+ public = [ "public/pw_system/thread_snapshot_service.h" ]
+ public_configs = [ ":public_include_path" ]
+ public_deps = [ "$dir_pw_rpc:server" ]
+ sources = [ "thread_snapshot_service.cc" ]
+ deps = [ "$dir_pw_thread:thread_snapshot_service" ]
+}
+
pw_facade("target_hooks") {
backend = pw_system_TARGET_HOOKS_BACKEND
public = [ "public/pw_system/target_hooks.h" ]
diff --git a/pw_system/init.cc b/pw_system/init.cc
index 75e18bd..91e9017 100644
--- a/pw_system/init.cc
+++ b/pw_system/init.cc
@@ -18,12 +18,17 @@
#include "pw_metric/global.h"
#include "pw_metric/metric_service_pwpb.h"
#include "pw_rpc/echo_service_pwpb.h"
+#include "pw_system/config.h"
#include "pw_system/rpc_server.h"
#include "pw_system/target_hooks.h"
#include "pw_system/work_queue.h"
#include "pw_system_private/log.h"
#include "pw_thread/detached_thread.h"
+#if PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE
+#include "pw_system/thread_snapshot_service.h"
+#endif // PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE
+
namespace pw::system {
namespace {
metric::MetricService metric_service(metric::global_metrics,
@@ -46,6 +51,9 @@
GetRpcServer().RegisterService(echo_service);
GetRpcServer().RegisterService(GetLogService());
GetRpcServer().RegisterService(metric_service);
+#if PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE
+ RegisterThreadSnapshotService(GetRpcServer());
+#endif // PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE
PW_LOG_INFO("Starting threads");
// Start threads.
diff --git a/pw_system/public/pw_system/config.h b/pw_system/public/pw_system/config.h
index e8400d7..1dc3c3c 100644
--- a/pw_system/public/pw_system/config.h
+++ b/pw_system/public/pw_system/config.h
@@ -51,6 +51,14 @@
#define PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS 82
#endif // PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS
+// PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE specifies if the thread snapshot
+// RPC service is enabled.
+//
+// Defaults to 1.
+#ifndef PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE
+#define PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE 1
+#endif // PW_SYSTEM_ENABLE_THREAD_SNAPSHOT_SERVICE
+
// PW_SYSTEM_WORK_QUEUE_MAX_ENTRIES specifies the maximum number of work queue
// entries that may be staged at once.
//
diff --git a/pw_system/public/pw_system/thread_snapshot_service.h b/pw_system/public/pw_system/thread_snapshot_service.h
new file mode 100644
index 0000000..0b1d715
--- /dev/null
+++ b/pw_system/public/pw_system/thread_snapshot_service.h
@@ -0,0 +1,22 @@
+// Copyright 2022 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 RegisterThreadSnapshotService(rpc::Server& rpc_server);
+
+} // namespace pw::system
diff --git a/pw_system/py/BUILD.gn b/pw_system/py/BUILD.gn
index 16ceb82..b56e60c 100644
--- a/pw_system/py/BUILD.gn
+++ b/pw_system/py/BUILD.gn
@@ -36,6 +36,8 @@
"$dir_pw_metric/py",
"$dir_pw_protobuf_compiler/py",
"$dir_pw_rpc/py",
+ "$dir_pw_thread:protos.python",
+ "$dir_pw_thread/py",
"$dir_pw_tokenizer/py",
"$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 1ab1e66..ba491a1 100644
--- a/pw_system/py/pw_system/console.py
+++ b/pw_system/py/pw_system/console.py
@@ -61,6 +61,7 @@
from pw_console.log_store import LogStore
from pw_log.proto import log_pb2
from pw_metric_proto import metric_service_pb2
+from pw_thread_protos import thread_snapshot_service_pb2
from pw_rpc.console_tools.console import flattened_rpc_completions
from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
from pw_unit_test_proto import unit_test_pb2
@@ -278,6 +279,7 @@
compiled_protos.append(unit_test_pb2)
protos.extend(compiled_protos)
protos.append(metric_service_pb2)
+ protos.append(thread_snapshot_service_pb2)
if not protos:
_LOG.critical('No .proto files were found with %s',
diff --git a/pw_system/py/pw_system/device.py b/pw_system/py/pw_system/device.py
index 15f2a77..adbf419 100644
--- a/pw_system/py/pw_system/device.py
+++ b/pw_system/py/pw_system/device.py
@@ -25,6 +25,7 @@
from pw_metric import metric_parser
from pw_rpc import callback_client, console_tools
from pw_status import Status
+from pw_thread.thread_analyzer import ThreadSnapshotAnalyzer
from pw_tokenizer import detokenize
from pw_tokenizer.proto import decode_optionally_tokenized
import pw_unit_test.rpc
@@ -190,3 +191,9 @@
print_metrics(metrics, '')
return metrics
+
+ def snapshot_peak_stack_usage(self):
+ _, rsp = self.rpcs.pw.thread.ThreadSnapshotService.GetPeakStackUsage()
+ for thread_info in rsp:
+ for line in str(ThreadSnapshotAnalyzer(thread_info)).splitlines():
+ _LOG.info('%s', line)
diff --git a/pw_system/thread_snapshot_service.cc b/pw_system/thread_snapshot_service.cc
new file mode 100644
index 0000000..5b82eef
--- /dev/null
+++ b/pw_system/thread_snapshot_service.cc
@@ -0,0 +1,34 @@
+// Copyright 2022 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/thread_snapshot_service.h"
+
+#include "pw_thread/thread_snapshot_service.h"
+
+namespace pw::system {
+namespace {
+
+constexpr size_t kEncodeBufferSize = thread::RequiredServiceBufferSize();
+
+std::array<std::byte, kEncodeBufferSize> encode_buffer;
+
+thread::ThreadSnapshotService system_thread_snapshot_service(encode_buffer);
+
+} // namespace
+
+void RegisterThreadSnapshotService(rpc::Server& rpc_server) {
+ rpc_server.RegisterService(system_thread_snapshot_service);
+}
+
+} // namespace pw::system
diff --git a/pw_thread_freertos/thread_iteration.cc b/pw_thread_freertos/thread_iteration.cc
index 9d72bce..dcf699c 100644
--- a/pw_thread_freertos/thread_iteration.cc
+++ b/pw_thread_freertos/thread_iteration.cc
@@ -33,13 +33,13 @@
const tskTCB& tcb = *reinterpret_cast<tskTCB*>(current_thread);
ThreadInfo thread_info;
-#if configRECORD_STACK_HIGH_ADDRESS
span<const std::byte> current_name =
as_bytes(span(std::string_view(tcb.pcTaskName)));
thread_info.set_thread_name(current_name);
// Current thread stack bounds.
thread_info.set_stack_low_addr(reinterpret_cast<uintptr_t>(tcb.pxStack));
+#if configRECORD_STACK_HIGH_ADDRESS
thread_info.set_stack_high_addr(
reinterpret_cast<uintptr_t>(tcb.pxEndOfStack));
#if INCLUDE_uxTaskGetStackHighWaterMark
diff --git a/targets/stm32f429i_disc1_stm32cube/target_docs.rst b/targets/stm32f429i_disc1_stm32cube/target_docs.rst
index 2896657..39ecd46 100644
--- a/targets/stm32f429i_disc1_stm32cube/target_docs.rst
+++ b/targets/stm32f429i_disc1_stm32cube/target_docs.rst
@@ -63,4 +63,28 @@
>>> device.rpcs.pw.rpc.EchoService.Echo(msg="Hello, Pigweed!")
(Status.OK, pw.rpc.EchoMessage(msg='Hello, Pigweed!'))
+You can also try out our thread snapshot RPC service, which should return a
+stack usage overview of all running threads on the device in Host Logs.
+
+.. code:: sh
+
+ >>> device.snapshot_peak_stack_usage()
+
+Example output:
+
+.. code:: sh
+
+ 20220826 09:47:22 INF PendingRpc(channel=1, method=pw.thread.ThreadSnapshotService.GetPeakStackUsage) completed: Status.OK
+ 20220826 09:47:22 INF Thread State
+ 20220826 09:47:22 INF 5 threads running.
+ 20220826 09:47:22 INF
+ 20220826 09:47:22 INF Thread (UNKNOWN): IDLE
+ 20220826 09:47:22 INF Est CPU usage: unknown
+ 20220826 09:47:22 INF Stack info
+ 20220826 09:47:22 INF Current usage: 0x20002da0 - 0x???????? (size unknown)
+ 20220826 09:47:22 INF Est peak usage: 390 bytes, 76.77%
+ 20220826 09:47:22 INF Stack limits: 0x20002da0 - 0x20002ba4 (508 bytes)
+ 20220826 09:47:22 INF
+ 20220826 09:47:22 INF ...
+
You are now up and running!