pw_protobuf: Add basic size report for decoder

This change adds a simple size report for the pw::protobuf::Decoder
class with a simple decode callback.

Change-Id: I773cc31df43c5aab1db7044d77439cf8513e6b68
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 050b326..dee8a15 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -52,6 +52,7 @@
     "$dir_pw_module:docs",
     "$dir_pw_preprocessor:docs",
     "$dir_pw_presubmit:docs",
+    "$dir_pw_protobuf:docs",
     "$dir_pw_protobuf_compiler:docs",
     "$dir_pw_span:docs",
     "$dir_pw_status:docs",
diff --git a/pw_bloat/bloat.gni b/pw_bloat/bloat.gni
index 61e83d5..d711598 100644
--- a/pw_bloat/bloat.gni
+++ b/pw_bloat/bloat.gni
@@ -149,6 +149,9 @@
     ]
     deps = _all_target_dependencies
     args = _bloat_script_args + _binary_paths
+
+    # Print size reports to stdout when they are generated.
+    capture_output = false
   }
 }
 
diff --git a/pw_bloat/py/bloat.py b/pw_bloat/py/bloat.py
index 8aa1054..d55ec6a 100755
--- a/pw_bloat/py/bloat.py
+++ b/pw_bloat/py/bloat.py
@@ -16,6 +16,7 @@
 """
 
 import argparse
+import logging
 import os
 import subprocess
 import sys
@@ -25,6 +26,10 @@
 from binary_diff import BinaryDiff
 import bloat_output
 
+import pw_cli.log
+
+_LOG = logging.getLogger(__name__)
+
 
 def parse_args() -> argparse.Namespace:
     """Parses the script's arguments."""
@@ -135,7 +140,7 @@
             diff_binaries.append(binary)
             base_binaries.append(base)
     except RuntimeError as err:
-        print(f'{sys.argv[0]}: {err}', file=sys.stderr)
+        _LOG.error('%s: %s', sys.argv[0], err)
         return 1
 
     data_sources = ['segment_names']
@@ -172,15 +177,14 @@
             bloaty_csv = output.decode().splitlines()[1:]
             diffs.append(BinaryDiff.from_csv(binary_name, bloaty_csv))
         except subprocess.CalledProcessError:
-            print(f'{sys.argv[0]}: failed to run diff on {binary}',
-                  file=sys.stderr)
+            _LOG.error('%s: failed to run diff on %s', sys.argv[0], binary)
             return 1
 
     def write_file(filename: str, contents: str) -> None:
         path = os.path.join(args.out_dir, filename)
         with open(path, 'w') as output_file:
             output_file.write(contents)
-        print(f'Output written to {path}')
+        _LOG.debug('Output written to %s', path)
 
     # TODO(frolv): Remove when custom output for full mode is added.
     if not args.full:
@@ -200,4 +204,5 @@
 
 
 if __name__ == '__main__':
+    pw_cli.log.install()
     sys.exit(main())
diff --git a/pw_protobuf/BUILD b/pw_protobuf/BUILD
index 6df8e2e..08b43d5 100644
--- a/pw_protobuf/BUILD
+++ b/pw_protobuf/BUILD
@@ -61,3 +61,11 @@
         "codegen_test.cc",
     ],
 )
+
+# TODO(frolv): Figure out what to do about size reports in Bazel.
+filegroup(
+    name = "size_reports",
+    srcs = glob([
+        "size_report/*.cc",
+    ]),
+)
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index baa04f6..01dfefa 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2019 The Pigweed Authors
+# 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
@@ -12,9 +12,9 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-import("$dir_pw_bloat/bloat.gni")
 import("$dir_pw_build/input_group.gni")
 import("$dir_pw_build/pw_executable.gni")
+import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_unit_test/test.gni")
 
@@ -44,6 +44,17 @@
   sources += public
 }
 
+pw_doc_group("docs") {
+  sources = [
+    "decoder.rst",
+    "docs.rst",
+  ]
+  report_deps = [
+    "size_report:decoder_full",
+    "size_report:decoder_incremental",
+  ]
+}
+
 # Source files for pw_protobuf's protoc plugin.
 pw_input_group("codegen_protoc_plugin") {
   inputs = [
diff --git a/pw_protobuf/decoder.rst b/pw_protobuf/decoder.rst
new file mode 100644
index 0000000..2178b72
--- /dev/null
+++ b/pw_protobuf/decoder.rst
@@ -0,0 +1,31 @@
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+.. _chapter-protobuf-decoder:
+
+-------
+Decoder
+-------
+
+Size report
+===========
+
+Full size report
+^^^^^^^^^^^^^^^^
+
+This report demonstrates the size of using the entire decoder with all of its
+decode methods and a decode callback for a proto message containing each of the
+protobuf field types.
+
+.. include:: size_report/decoder_full.rst
+
+
+Incremental size report
+^^^^^^^^^^^^^^^^^^^^^^^
+
+This report is generated using the full report as a base and adding some int32
+fields to the decode callback to demonstrate the incremental cost of decoding
+fields in a message.
+
+.. include:: size_report/decoder_incremental.rst
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
new file mode 100644
index 0000000..fcb5981
--- /dev/null
+++ b/pw_protobuf/docs.rst
@@ -0,0 +1,16 @@
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+.. _chapter-protobuf:
+
+-----------
+pw_protobuf
+-----------
+The protobuf module provides a lightweight interface for encoding and decoding
+the Protocol Buffer wire format.
+
+.. toctree::
+  :maxdepth: 1
+
+  decoder
diff --git a/pw_protobuf/size_report/BUILD.gn b/pw_protobuf/size_report/BUILD.gn
new file mode 100644
index 0000000..d6621ae
--- /dev/null
+++ b/pw_protobuf/size_report/BUILD.gn
@@ -0,0 +1,47 @@
+# 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.
+
+import("$dir_pw_bloat/bloat.gni")
+
+_decoder_full = {
+  deps = [
+    "$dir_pw_bloat:bloat_this_binary",
+    "$dir_pw_preprocessor",
+    "$dir_pw_protobuf:pw_protobuf",
+  ]
+  sources = [
+    "decoder_full.cc",
+  ]
+}
+
+pw_toolchain_size_report("decoder_full") {
+  base_executable = pw_bloat_empty_base
+  diff_executable = _decoder_full
+  title = "Size of all decoder methods"
+}
+
+pw_toolchain_size_report("decoder_incremental") {
+  base_executable = _decoder_full
+  diff_executable = {
+    deps = [
+      "$dir_pw_bloat:bloat_this_binary",
+      "$dir_pw_preprocessor",
+      "$dir_pw_protobuf:pw_protobuf",
+    ]
+    sources = [
+      "decoder_incremental.cc",
+    ]
+  }
+  title = "Adding more fields to decode callback"
+}
diff --git a/pw_protobuf/size_report/decoder_full.cc b/pw_protobuf/size_report/decoder_full.cc
new file mode 100644
index 0000000..37ac958
--- /dev/null
+++ b/pw_protobuf/size_report/decoder_full.cc
@@ -0,0 +1,67 @@
+// 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_bloat/bloat_this_binary.h"
+#include "pw_protobuf/decoder.h"
+
+namespace {
+// clang-format off
+constexpr uint8_t encoded_proto[] = {
+  // type=int32, k=1, v=42
+  0x08, 0x2a,
+  // type=sint32, k=2, v=-13
+  0x10, 0x19,
+};
+// clang-format on
+}  // namespace
+
+class TestDecodeHandler : public pw::protobuf::DecodeHandler {
+ public:
+  pw::Status ProcessField(pw::protobuf::Decoder* decoder,
+                          uint32_t field_number) override {
+    switch (field_number) {
+      case 1:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 2:
+        if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
+          test_sint32 = 0;
+        }
+        break;
+    }
+
+    return pw::Status::OK;
+  }
+
+  int32_t test_int32 = 0;
+  int32_t test_sint32 = 0;
+};
+
+int* volatile non_optimizable_pointer;
+
+int main() {
+  pw::bloat::BloatThisBinary();
+
+  pw::protobuf::Decoder decoder;
+  TestDecodeHandler handler;
+
+  decoder.set_handler(&handler);
+  decoder.Decode(pw::as_bytes(pw::span(encoded_proto)));
+
+  *non_optimizable_pointer = handler.test_int32 + handler.test_sint32;
+
+  return 0;
+}
diff --git a/pw_protobuf/size_report/decoder_incremental.cc b/pw_protobuf/size_report/decoder_incremental.cc
new file mode 100644
index 0000000..3955aa7
--- /dev/null
+++ b/pw_protobuf/size_report/decoder_incremental.cc
@@ -0,0 +1,92 @@
+// 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_bloat/bloat_this_binary.h"
+#include "pw_protobuf/decoder.h"
+
+namespace {
+// clang-format off
+constexpr uint8_t encoded_proto[] = {
+  // type=int32, k=1, v=42
+  0x08, 0x2a,
+  // type=sint32, k=2, v=-13
+  0x10, 0x19,
+};
+// clang-format on
+}  // namespace
+
+class TestDecodeHandler : public pw::protobuf::DecodeHandler {
+ public:
+  pw::Status ProcessField(pw::protobuf::Decoder* decoder,
+                          uint32_t field_number) override {
+    switch (field_number) {
+      case 1:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 2:
+        if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
+          test_sint32 = 0;
+        }
+        break;
+      case 3:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 4:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 5:
+        if (!decoder->ReadInt32(field_number, &test_int32).ok()) {
+          test_int32 = 0;
+        }
+        break;
+      case 6:
+        if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
+          test_sint32 = 0;
+        }
+        break;
+      case 7:
+        if (!decoder->ReadSint32(field_number, &test_sint32).ok()) {
+          test_sint32 = 0;
+        }
+        break;
+    }
+
+    return pw::Status::OK;
+  }
+
+  int32_t test_int32 = 0;
+  int32_t test_sint32 = 0;
+};
+
+int* volatile non_optimizable_pointer;
+
+int main() {
+  pw::bloat::BloatThisBinary();
+
+  pw::protobuf::Decoder decoder;
+  TestDecodeHandler handler;
+
+  decoder.set_handler(&handler);
+  decoder.Decode(pw::as_bytes(pw::span(encoded_proto)));
+
+  *non_optimizable_pointer = handler.test_int32 + handler.test_sint32;
+
+  return 0;
+}