pw_ring_buffer: Add bloat reporting

Adds single-reader and multi-reader implementations for bloat testing.
Compares single-reader w/base, multi-reader w/base, and single-reader
vs. multi-reader implementations.

Change-Id: I27b501ce2d72f3ad504693579f9ee2c9a61b1cd3
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/32584
Reviewed-by: David Rogers <davidrogers@google.com>
Commit-Queue: Prashanth Swaminathan <prashanthsw@google.com>
diff --git a/pw_ring_buffer/BUILD.gn b/pw_ring_buffer/BUILD.gn
index f5ac741..8871caa 100644
--- a/pw_ring_buffer/BUILD.gn
+++ b/pw_ring_buffer/BUILD.gn
@@ -14,6 +14,7 @@
 
 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_unit_test/test.gni")
@@ -50,4 +51,27 @@
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
+  report_deps = [ ":ring_buffer_size" ]
+}
+
+pw_size_report("ring_buffer_size") {
+  title = "pw::ring_buffer::PrefixedEntryRingBuffer"
+
+  binaries = [
+    {
+      target = "size_report:ring_buffer_simple"
+      base = "$dir_pw_bloat:bloat_base"
+      label = "Initialize single-reader ring buffer"
+    },
+    {
+      target = "size_report:ring_buffer_multi"
+      base = "$dir_pw_bloat:bloat_base"
+      label = "Initialize multi-reader ring buffer"
+    },
+    {
+      target = "size_report:ring_buffer_multi"
+      base = "size_report:ring_buffer_simple"
+      label = "Initialized multi-reader vs. single-reader"
+    },
+  ]
 }
diff --git a/pw_ring_buffer/size_report/BUILD b/pw_ring_buffer/size_report/BUILD
new file mode 100644
index 0000000..8339bc5
--- /dev/null
+++ b/pw_ring_buffer/size_report/BUILD
@@ -0,0 +1,32 @@
+# 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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "ring_buffer_simple",
+    srcs = ["ring_buffer_simple.cc"],
+)
+
+pw_cc_binary(
+    name = "ring_buffer_multi",
+    srcs = ["ring_buffer_multi.cc"],
+)
diff --git a/pw_ring_buffer/size_report/BUILD.gn b/pw_ring_buffer/size_report/BUILD.gn
new file mode 100644
index 0000000..25159db8
--- /dev/null
+++ b/pw_ring_buffer/size_report/BUILD.gn
@@ -0,0 +1,27 @@
+# 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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+pw_executable("ring_buffer_simple") {
+  sources = [ "ring_buffer_simple.cc" ]
+  deps = [ ".." ]
+}
+
+pw_executable("ring_buffer_multi") {
+  sources = [ "ring_buffer_multi.cc" ]
+  deps = [ ".." ]
+}
diff --git a/pw_ring_buffer/size_report/ring_buffer_multi.cc b/pw_ring_buffer/size_report/ring_buffer_multi.cc
new file mode 100644
index 0000000..564e8f8
--- /dev/null
+++ b/pw_ring_buffer/size_report/ring_buffer_multi.cc
@@ -0,0 +1,96 @@
+// 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.
+
+#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
+#include "pw_status/status.h"
+
+constexpr size_t kRingBufferSize = 1024;
+constexpr size_t kReaderCount = 4;
+constexpr std::byte kValue = (std::byte)0xFF;
+constexpr std::byte kData[1] = {kValue};
+
+int main() {
+  pw::ring_buffer::PrefixedEntryRingBufferMulti ring(true /* user_preamble */);
+  std::byte buffer[kRingBufferSize];
+
+  pw::Status status = ring.SetBuffer(buffer);
+  if (!status.ok()) {
+    return 1;
+  }
+
+  // Attach readers.
+  pw::ring_buffer::PrefixedEntryRingBufferMulti::Reader readers[kReaderCount];
+  for (auto& reader : readers) {
+    ring.AttachReader(reader);
+  }
+
+  // Push entries until the buffer is full.
+  size_t total_entries = 0;
+  while (true) {
+    status = ring.TryPushBack(kData);
+    if (status == pw::Status::ResourceExhausted()) {
+      break;
+    } else if (!status.ok()) {
+      return 2;
+    }
+    total_entries++;
+  }
+
+  // Forcefully push an entry.
+  status = ring.PushBack(kData);
+  if (!status.ok()) {
+    return 3;
+  }
+
+  // Dering the buffer.
+  status = ring.Dering();
+  if (!status.ok()) {
+    return 4;
+  }
+
+  // Peek and pop all entries.
+  __attribute__((unused)) std::byte value[1];
+  __attribute__((unused)) size_t value_size;
+  for (size_t i = 0; i < total_entries; ++i) {
+    for (auto& reader : readers) {
+      status = reader.PeekFront(value, &value_size);
+      if (!status.ok()) {
+        return 5;
+      }
+      status = reader.PeekFrontWithPreamble(value, &value_size);
+      if (!status.ok()) {
+        return 6;
+      }
+      if (reader.FrontEntryDataSizeBytes() == 0) {
+        return 7;
+      }
+      if (reader.FrontEntryTotalSizeBytes() == 0) {
+        return 8;
+      }
+      if (reader.EntryCount() == 0) {
+        return 9;
+      }
+      status = reader.PopFront();
+      if (!status.ok()) {
+        return 10;
+      }
+    }
+  }
+
+  for (auto& reader : readers) {
+    ring.DetachReader(reader);
+  }
+  ring.Clear();
+  return 0;
+}
diff --git a/pw_ring_buffer/size_report/ring_buffer_simple.cc b/pw_ring_buffer/size_report/ring_buffer_simple.cc
new file mode 100644
index 0000000..0be77ba
--- /dev/null
+++ b/pw_ring_buffer/size_report/ring_buffer_simple.cc
@@ -0,0 +1,83 @@
+// 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.
+
+#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
+#include "pw_status/status.h"
+
+constexpr size_t kRingBufferSize = 1024;
+constexpr std::byte kValue = (std::byte)0xFF;
+constexpr std::byte kData[1] = {kValue};
+
+int main() {
+  pw::ring_buffer::PrefixedEntryRingBuffer ring(true /* user_preamble */);
+  std::byte buffer[kRingBufferSize];
+
+  pw::Status status = ring.SetBuffer(buffer);
+  if (!status.ok()) {
+    return 1;
+  }
+
+  // Push entries until the buffer is full.
+  size_t total_entries = 0;
+  while (true) {
+    status = ring.TryPushBack(kData);
+    if (status == pw::Status::ResourceExhausted()) {
+      break;
+    } else if (!status.ok()) {
+      return 2;
+    }
+    total_entries++;
+  }
+
+  // Forcefully push an entry.
+  status = ring.PushBack(kData);
+  if (!status.ok()) {
+    return 3;
+  }
+
+  // Dering the buffer.
+  status = ring.Dering();
+  if (!status.ok()) {
+    return 4;
+  }
+
+  // Peek and pop all entries.
+  __attribute__((unused)) std::byte value[1];
+  __attribute__((unused)) size_t value_size;
+  for (size_t i = 0; i < total_entries; ++i) {
+    status = ring.PeekFront(value, &value_size);
+    if (!status.ok()) {
+      return 5;
+    }
+    status = ring.PeekFrontWithPreamble(value, &value_size);
+    if (!status.ok()) {
+      return 6;
+    }
+    if (ring.FrontEntryDataSizeBytes() == 0) {
+      return 7;
+    }
+    if (ring.FrontEntryTotalSizeBytes() == 0) {
+      return 8;
+    }
+    if (ring.EntryCount() == 0) {
+      return 9;
+    }
+    status = ring.PopFront();
+    if (!status.ok()) {
+      return 10;
+    }
+  }
+  ring.Clear();
+  return 0;
+}