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!