pw_rpc: Add size report for server

This change adds a size report showing the memory cost of the RPC server
both by itself and with a nanopb service.

Change-Id: I5ed465888b757e706de90fc4b4a7247f2930dd3a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/18246
Commit-Queue: Alexei Frolov <frolv@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 9a3478c..fbf3a74 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -15,9 +15,11 @@
 # gn-format disable
 import("//build_overrides/pigweed.gni")
 
+import("$dir_pw_bloat/bloat.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
+import("$dir_pw_third_party/nanopb/nanopb.gni")
 import("$dir_pw_unit_test/test.gni")
 config("default_config") {
   include_dirs = [ "public" ]
@@ -116,6 +118,29 @@
     "pw_rpc_protos/packet.proto",
   ]
   group_deps = [ "nanopb:docs" ]
+  report_deps = [ ":server_size" ]
+}
+
+pw_size_report("server_size") {
+  title = "Pigweed RPC server size report"
+
+  binaries = [
+    {
+      target = "size_report:server_only"
+      base = "size_report:base"
+      label = "Server by itself"
+    },
+  ]
+
+  if (dir_pw_third_party_nanopb != "") {
+    binaries += [
+      {
+        target = "size_report:server_with_echo_service"
+        base = "size_report:base_with_nanopb"
+        label = "Server with a registered nanopb EchoService"
+      },
+    ]
+  }
 }
 
 pw_test_group("tests") {
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index b521b60..520d030 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -443,6 +443,17 @@
 
   Document the public interface
 
+Size report
+-----------
+The following size report showcases the memory usage of the core RPC server. It
+is configured with a single channel using a basic transport interface that
+directly reads from and writes to ``pw_sys_io``. The transport has a 128-byte
+packet buffer, which comprises the plurality of the example's RAM usage. This is
+not a suitable transport for an actual product; a real implementation would have
+additional overhead proportional to the complexity of the transport.
+
+.. include:: server_size
+
 RPC server implementation
 -------------------------
 
diff --git a/pw_rpc/size_report/BUILD b/pw_rpc/size_report/BUILD
new file mode 100644
index 0000000..e4e46ca
--- /dev/null
+++ b/pw_rpc/size_report/BUILD
@@ -0,0 +1,55 @@
+# Copyright 2020 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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "base",
+    srcs = ["base.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_rpc:server",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_binary(
+    name = "server_only",
+    srcs = ["server_only.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_rpc:server",
+        "//pw_sys_io",
+    ],
+)
+
+# TODO(frolv): Figure out how to add third-party nanopb to Bazel.
+filegroup(
+    name = "nanopb_reports",
+    srcs = [
+        "base_with_nanopb.cc",
+        "server_with_echo_service.cc",
+    ],
+)
diff --git a/pw_rpc/size_report/BUILD.gn b/pw_rpc/size_report/BUILD.gn
new file mode 100644
index 0000000..195e2f4
--- /dev/null
+++ b/pw_rpc/size_report/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2020 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.
+
+# gn-format disable
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+_deps = [
+  "$dir_pw_bloat:bloat_this_binary",
+  "..:server",
+  dir_pw_assert,
+  dir_pw_log,
+  dir_pw_sys_io,
+]
+
+pw_executable("base") {
+  sources = [ "base.cc" ]
+  deps = _deps
+}
+
+pw_executable("base_with_nanopb") {
+  sources = [ "base_with_nanopb.cc" ]
+  deps = _deps + [ "$dir_pw_third_party/nanopb" ]
+}
+
+pw_executable("server_only") {
+  sources = [ "server_only.cc" ]
+  deps = _deps
+}
+
+pw_executable("server_with_echo_service") {
+  sources = [ "server_with_echo_service.cc" ]
+  deps = _deps + [ "../nanopb:echo_service" ]
+}
diff --git a/pw_rpc/size_report/base.cc b/pw_rpc/size_report/base.cc
new file mode 100644
index 0000000..031afee
--- /dev/null
+++ b/pw_rpc/size_report/base.cc
@@ -0,0 +1,34 @@
+// Copyright 2020 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_assert/assert.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_log/log.h"
+#include "pw_sys_io/sys_io.h"
+
+int volatile* unoptimizable;
+
+int main() {
+  pw::bloat::BloatThisBinary();
+
+  // Ensure we are paying the cost for log and assert.
+  PW_CHECK_INT_GE(*unoptimizable, 0, "Ensure this CHECK logic stays");
+  PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
+
+  std::byte packet_buffer[128];
+  pw::sys_io::ReadBytes(packet_buffer);
+  pw::sys_io::WriteBytes(packet_buffer);
+
+  return static_cast<int>(packet_buffer[92]);
+}
diff --git a/pw_rpc/size_report/base_with_nanopb.cc b/pw_rpc/size_report/base_with_nanopb.cc
new file mode 100644
index 0000000..a5333db
--- /dev/null
+++ b/pw_rpc/size_report/base_with_nanopb.cc
@@ -0,0 +1,66 @@
+// Copyright 2020 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 "pb_decode.h"
+#include "pb_encode.h"
+#include "pw_assert/assert.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_log/log.h"
+#include "pw_sys_io/sys_io.h"
+
+int volatile* unoptimizable;
+
+namespace my_product {
+
+template <typename DecodeFunction>
+struct NanopbTraits;
+
+template <typename FieldsType>
+struct NanopbTraits<bool(pb_istream_t*, FieldsType, void*)> {
+  using Fields = FieldsType;
+};
+
+using Fields = typename NanopbTraits<decltype(pb_decode)>::Fields;
+
+// Performs the core nanopb encode and decode operations so that those functions
+// are included in the binary.
+void DoNanopbStuff() {
+  std::byte buffer[128];
+  void* fields = &buffer;
+
+  auto output = pb_ostream_from_buffer(reinterpret_cast<pb_byte_t*>(buffer),
+                                       sizeof(buffer));
+  pb_encode(&output, static_cast<Fields>(fields), buffer);
+
+  auto input = pb_istream_from_buffer(
+      reinterpret_cast<const pb_byte_t*>(buffer), sizeof(buffer));
+  pb_decode(&input, static_cast<Fields>(fields), buffer);
+}
+
+}  // namespace my_product
+
+int main() {
+  pw::bloat::BloatThisBinary();
+  my_product::DoNanopbStuff();
+
+  // Ensure we are paying the cost for log and assert.
+  PW_CHECK_INT_GE(*unoptimizable, 0, "Ensure this CHECK logic stays");
+  PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
+
+  std::byte packet_buffer[128];
+  pw::sys_io::ReadBytes(packet_buffer);
+  pw::sys_io::WriteBytes(packet_buffer);
+
+  return static_cast<int>(packet_buffer[92]);
+}
diff --git a/pw_rpc/size_report/server_only.cc b/pw_rpc/size_report/server_only.cc
new file mode 100644
index 0000000..a4e7bba
--- /dev/null
+++ b/pw_rpc/size_report/server_only.cc
@@ -0,0 +1,59 @@
+// Copyright 2020 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_assert/assert.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_log/log.h"
+#include "pw_rpc/server.h"
+#include "pw_sys_io/sys_io.h"
+
+int volatile* unoptimizable;
+
+class Output : public pw::rpc::ChannelOutput {
+ public:
+  Output() : ChannelOutput("output") {}
+
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  void SendAndReleaseBuffer(size_t size) override {
+    pw::sys_io::WriteBytes(std::span(buffer_, size));
+  }
+
+ private:
+  std::byte buffer_[128];
+};
+
+namespace my_product {
+
+Output output;
+pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)};
+pw::rpc::Server server(channels);
+
+}  // namespace my_product
+
+int main() {
+  pw::bloat::BloatThisBinary();
+
+  // Ensure we are paying the cost for log and assert.
+  PW_CHECK_INT_GE(*unoptimizable, 0, "Ensure this CHECK logic stays");
+  PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
+
+  std::byte packet_buffer[128];
+  pw::sys_io::ReadBytes(packet_buffer);
+  pw::sys_io::WriteBytes(packet_buffer);
+
+  my_product::server.ProcessPacket(packet_buffer, my_product::output);
+
+  return static_cast<int>(packet_buffer[92]);
+}
diff --git a/pw_rpc/size_report/server_with_echo_service.cc b/pw_rpc/size_report/server_with_echo_service.cc
new file mode 100644
index 0000000..92e9b34
--- /dev/null
+++ b/pw_rpc/size_report/server_with_echo_service.cc
@@ -0,0 +1,90 @@
+// Copyright 2020 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 "pb_decode.h"
+#include "pb_encode.h"
+#include "pw_assert/assert.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_log/log.h"
+#include "pw_rpc/echo_service_nanopb.h"
+#include "pw_rpc/server.h"
+#include "pw_sys_io/sys_io.h"
+
+int volatile* unoptimizable;
+
+class Output : public pw::rpc::ChannelOutput {
+ public:
+  Output() : ChannelOutput("output") {}
+
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  void SendAndReleaseBuffer(size_t size) override {
+    pw::sys_io::WriteBytes(std::span(buffer_, size));
+  }
+
+ private:
+  std::byte buffer_[128];
+};
+
+namespace my_product {
+
+template <typename DecodeFunction>
+struct NanopbTraits;
+
+template <typename FieldsType>
+struct NanopbTraits<bool(pb_istream_t*, FieldsType, void*)> {
+  using Fields = FieldsType;
+};
+
+using Fields = typename NanopbTraits<decltype(pb_decode)>::Fields;
+
+// Performs the core nanopb encode and decode operations so that those functions
+// are included in the binary.
+void DoNanopbStuff() {
+  std::byte buffer[128];
+  void* fields = &buffer;
+
+  auto output = pb_ostream_from_buffer(reinterpret_cast<pb_byte_t*>(buffer),
+                                       sizeof(buffer));
+  pb_encode(&output, static_cast<Fields>(fields), buffer);
+
+  auto input = pb_istream_from_buffer(
+      reinterpret_cast<const pb_byte_t*>(buffer), sizeof(buffer));
+  pb_decode(&input, static_cast<Fields>(fields), buffer);
+}
+
+Output output;
+pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)};
+pw::rpc::Server server(channels);
+pw::rpc::EchoService echo_service;
+
+}  // namespace my_product
+
+int main() {
+  pw::bloat::BloatThisBinary();
+  my_product::DoNanopbStuff();
+
+  // Ensure we are paying the cost for log and assert.
+  PW_CHECK_INT_GE(*unoptimizable, 0, "Ensure this CHECK logic stays");
+  PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
+
+  std::byte packet_buffer[128];
+  pw::sys_io::ReadBytes(packet_buffer);
+  pw::sys_io::WriteBytes(packet_buffer);
+
+  my_product::server.RegisterService(my_product::echo_service);
+  my_product::server.ProcessPacket(packet_buffer, my_product::output);
+
+  return static_cast<int>(packet_buffer[92]);
+}
diff --git a/targets/docs/BUILD.gn b/targets/docs/BUILD.gn
index b98a20c..ec19cf1 100644
--- a/targets/docs/BUILD.gn
+++ b/targets/docs/BUILD.gn
@@ -17,6 +17,8 @@
 
 import("$dir_pigweed/targets/stm32f429i-disc1/target_toolchains.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_protobuf_compiler/proto.gni")
+import("$dir_pw_third_party/nanopb/nanopb.gni")
 import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
 import("$dir_pw_toolchain/generate_toolchain.gni")
 
@@ -33,6 +35,12 @@
   defaults = {
     forward_variables_from(_base_toolchain.defaults, "*")
 
+    _has_nanopb_rpc = pw_protobuf_GENERATORS + [ "nanopb_rpc" ] -
+                      [ "nanopb_rpc" ] != pw_protobuf_GENERATORS
+    if (dir_pw_third_party_nanopb != "" && !_has_nanopb_rpc) {
+      pw_protobuf_GENERATORS += [ "nanopb_rpc" ]
+    }
+
     # This is the docs target.
     pw_docgen_BUILD_DOCS = true
   }