Merge "Split PerfDataReader" into main
diff --git a/Android.bp b/Android.bp
index 51e8609..bcf2f57 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12246,6 +12246,7 @@
     srcs: [
         "src/trace_processor/importers/perf/perf_data_reader_unittest.cc",
         "src/trace_processor/importers/perf/perf_data_tracker_unittest.cc",
+        "src/trace_processor/importers/perf/reader_unittest.cc",
     ],
 }
 
@@ -13344,6 +13345,14 @@
     ],
 }
 
+// GN: //src/trace_processor/util:file_buffer
+filegroup {
+    name: "perfetto_src_trace_processor_util_file_buffer",
+    srcs: [
+        "src/trace_processor/util/file_buffer.cc",
+    ],
+}
+
 // GN: //src/trace_processor/util:glob
 filegroup {
     name: "perfetto_src_trace_processor_util_glob",
@@ -13439,6 +13448,7 @@
     srcs: [
         "src/trace_processor/util/bump_allocator_unittest.cc",
         "src/trace_processor/util/debug_annotation_parser_unittest.cc",
+        "src/trace_processor/util/file_buffer_unittest.cc",
         "src/trace_processor/util/glob_unittest.cc",
         "src/trace_processor/util/gzip_utils_unittest.cc",
         "src/trace_processor/util/proto_profiler_unittest.cc",
@@ -15023,6 +15033,7 @@
         ":perfetto_src_trace_processor_util_build_id",
         ":perfetto_src_trace_processor_util_bump_allocator",
         ":perfetto_src_trace_processor_util_descriptors",
+        ":perfetto_src_trace_processor_util_file_buffer",
         ":perfetto_src_trace_processor_util_glob",
         ":perfetto_src_trace_processor_util_gzip",
         ":perfetto_src_trace_processor_util_interned_message_view",
diff --git a/BUILD b/BUILD
index 1f39840..0db59db 100644
--- a/BUILD
+++ b/BUILD
@@ -1707,6 +1707,8 @@
         "src/trace_processor/importers/perf/perf_data_tracker.cc",
         "src/trace_processor/importers/perf/perf_data_tracker.h",
         "src/trace_processor/importers/perf/perf_event.h",
+        "src/trace_processor/importers/perf/perf_file.h",
+        "src/trace_processor/importers/perf/reader.h",
     ],
 )
 
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index 960cd27..d442d30 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -25,6 +25,8 @@
     "perf_data_tracker.cc",
     "perf_data_tracker.h",
     "perf_event.h",
+    "perf_file.h",
+    "reader.h",
   ]
   deps = [
     "../../../../gn:default_deps",
@@ -43,6 +45,7 @@
   sources = [
     "perf_data_reader_unittest.cc",
     "perf_data_tracker_unittest.cc",
+    "reader_unittest.cc",
   ]
   deps = [
     ":perf",
diff --git a/src/trace_processor/importers/perf/perf_file.h b/src/trace_processor/importers/perf/perf_file.h
new file mode 100644
index 0000000..66e28de
--- /dev/null
+++ b/src/trace_processor/importers/perf/perf_file.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_FILE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_FILE_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/importers/perf/perf_event.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+struct PerfFile {
+  static constexpr char kPerfMagic[] = {'P', 'E', 'R', 'F', 'I', 'L', 'E', '2'};
+  struct Section {
+    uint64_t offset;
+    uint64_t size;
+    uint64_t end() const { return offset + size; }
+  };
+
+  struct AttrsEntry {
+    perf_event_attr attr;
+    Section ids;
+  };
+
+  struct Header {
+    char magic[8];
+    uint64_t size;
+    // Size of PerfFileAttr struct and section pointing to ids.
+    uint64_t attr_size;
+    Section attrs;
+    Section data;
+    Section event_types;
+    uint64_t flags;
+    uint64_t flags1[3];
+
+    uint64_t num_attrs() const { return attrs.size / attr_size; }
+  };
+};
+
+}  // namespace perfetto::trace_processor::perf_importer
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_FILE_H_
diff --git a/src/trace_processor/importers/perf/reader.h b/src/trace_processor/importers/perf/reader.h
new file mode 100644
index 0000000..faf31a2
--- /dev/null
+++ b/src/trace_processor/importers/perf/reader.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PERF_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_READER_H_
+
+#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "perfetto/trace_processor/trace_blob_view.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// Helper to read various types of data fields contained in a TraceBlobView.
+// All methods return a boolean indicating whether the read was successful. A
+// false value means there was not enough data in the underlying buffer to
+// satisfy the read.
+class Reader {
+ public:
+  explicit Reader(TraceBlobView tbv)
+      : buffer_(tbv.blob()),
+        current_(tbv.data()),
+        end_(current_ + tbv.size()) {}
+
+  // Data left to be read. The value returned here decrements as read or skip
+  // methods are called.
+  size_t size_left() const { return static_cast<size_t>(end_ - current_); }
+
+  template <typename T>
+  bool Read(T& obj) {
+    static_assert(std::has_unique_object_representations_v<T>);
+    return Read(&obj, sizeof(T));
+  }
+
+  bool Read(void* dest, size_t size) {
+    if (size_left() < size) {
+      return false;
+    }
+    memcpy(dest, current_, size);
+    current_ += size;
+    return true;
+  }
+
+  bool Skip(size_t size) {
+    if (size_left() < size) {
+      return false;
+    }
+    current_ += size;
+    return true;
+  }
+
+  template <typename T>
+  bool Skip() {
+    return Skip(sizeof(T));
+  }
+
+  // Reads consecutive values and stores them in the given vector. Reads as many
+  // entries as the current vector size.
+  template <typename T>
+  bool ReadVector(std::vector<T>& vec) {
+    static_assert(std::has_unique_object_representations_v<T>);
+    size_t size = sizeof(T) * vec.size();
+    if (size_left() < size) {
+      return false;
+    }
+    memcpy(vec.data(), current_, size);
+    current_ += size;
+    return true;
+  }
+
+  // Convenience helper for reading values and storing them in an optional<>
+  // wrapper.
+  template <typename T>
+  bool ReadOptional(std::optional<T>& obj) {
+    T val;
+    if (!Read(val)) {
+      return false;
+    }
+    obj = val;
+    return true;
+  }
+
+  // Reads a null terminated string.
+  bool ReadCString(std::string& out) {
+    for (const uint8_t* ptr = current_; ptr != end_; ++ptr) {
+      if (*ptr == 0) {
+        out = std::string(reinterpret_cast<const char*>(current_),
+                          static_cast<size_t>(ptr - current_));
+        current_ = ptr;
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+ private:
+  RefPtr<TraceBlob> buffer_;
+  const uint8_t* current_;
+  const uint8_t* end_;
+};
+
+}  // namespace perfetto::trace_processor::perf_importer
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_READER_H_
diff --git a/src/trace_processor/importers/perf/reader_unittest.cc b/src/trace_processor/importers/perf/reader_unittest.cc
new file mode 100644
index 0000000..d1cc474
--- /dev/null
+++ b/src/trace_processor/importers/perf/reader_unittest.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "src/trace_processor/importers/perf/reader.h"
+
+#include <stddef.h>
+#include <cstdint>
+
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_processor::perf_importer {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::SizeIs;
+
+template <typename T>
+TraceBlobView TraceBlobViewFromVector(std::vector<T> nums) {
+  size_t data_size = sizeof(T) * nums.size();
+  auto blob = TraceBlob::Allocate(data_size);
+  memcpy(blob.data(), nums.data(), data_size);
+  return TraceBlobView(std::move(blob));
+}
+
+TEST(ReaderUnittest, Read) {
+  TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
+  Reader reader(std::move(tbv));
+  uint64_t val;
+  reader.Read(val);
+  EXPECT_EQ(val, 2u);
+}
+
+TEST(ReaderUnittest, ReadOptional) {
+  TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
+  Reader reader(std::move(tbv));
+  std::optional<uint64_t> val;
+  reader.ReadOptional(val);
+  EXPECT_EQ(val, 2u);
+}
+
+TEST(ReaderUnittest, ReadVector) {
+  TraceBlobView tbv =
+      TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8, 16, 32});
+  Reader reader(std::move(tbv));
+
+  std::vector<uint64_t> res(3);
+  reader.ReadVector(res);
+
+  std::vector<uint64_t> valid{2, 4, 8};
+  EXPECT_EQ(res, valid);
+}
+
+TEST(ReaderUnittest, Skip) {
+  TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
+  Reader reader(std::move(tbv));
+
+  reader.Skip<uint64_t>();
+
+  uint64_t val;
+  reader.Read(val);
+  EXPECT_EQ(val, 4u);
+}
+
+}  // namespace
+}  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 13aefe5..86ecf9c 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -254,10 +254,23 @@
   ]
 }
 
+source_set("file_buffer") {
+  sources = [
+    "file_buffer.cc",
+    "file_buffer.h",
+  ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../../include/perfetto/ext/base",
+    "../../../include/perfetto/trace_processor:storage",
+  ]
+}
+
 source_set("unittests") {
   sources = [
     "bump_allocator_unittest.cc",
     "debug_annotation_parser_unittest.cc",
+    "file_buffer_unittest.cc",
     "glob_unittest.cc",
     "proto_profiler_unittest.cc",
     "proto_to_args_parser_unittest.cc",
@@ -271,6 +284,7 @@
   deps = [
     ":bump_allocator",
     ":descriptors",
+    ":file_buffer",
     ":glob",
     ":gzip",
     ":proto_profiler",
diff --git a/src/trace_processor/util/file_buffer.cc b/src/trace_processor/util/file_buffer.cc
new file mode 100644
index 0000000..aa92348
--- /dev/null
+++ b/src/trace_processor/util/file_buffer.cc
@@ -0,0 +1,119 @@
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "src/trace_processor/util/file_buffer.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <optional>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+
+namespace perfetto::trace_processor::util {
+
+void FileBuffer::PushBack(TraceBlobView data) {
+  if (data.size() == 0) {
+    return;
+  }
+  const size_t size = data.size();
+  data_.emplace_back(Entry{end_offset_, std::move(data)});
+  end_offset_ += size;
+}
+
+bool FileBuffer::PopFrontBytesUntil(const size_t target_offset) {
+  while (!data_.empty()) {
+    Entry& entry = data_.front();
+    if (target_offset <= entry.file_offset) {
+      return true;
+    }
+    const size_t bytes_to_pop = target_offset - entry.file_offset;
+    if (entry.data.size() > bytes_to_pop) {
+      entry.data =
+          entry.data.slice_off(bytes_to_pop, entry.data.size() - bytes_to_pop);
+      entry.file_offset += bytes_to_pop;
+      return true;
+    }
+    data_.pop_front();
+  }
+
+  return target_offset == end_offset_;
+}
+
+std::optional<TraceBlobView> FileBuffer::SliceOff(size_t start_offset,
+                                                  size_t length) const {
+  if (length == 0) {
+    return TraceBlobView();
+  }
+
+  if (start_offset + length > end_offset_) {
+    return std::nullopt;
+  }
+
+  Iterator it = FindEntryWithOffset(start_offset);
+  if (it == end()) {
+    return std::nullopt;
+  }
+
+  const size_t offset_from_entry_start = start_offset - it->file_offset;
+  const size_t bytes_in_entry = it->data.size() - offset_from_entry_start;
+  TraceBlobView first_blob = it->data.slice_off(
+      offset_from_entry_start, std::min(bytes_in_entry, length));
+
+  if (first_blob.size() == length) {
+    return std::move(first_blob);
+  }
+
+  auto buffer = TraceBlob::Allocate(length);
+  uint8_t* ptr = buffer.data();
+
+  memcpy(ptr, first_blob.data(), first_blob.size());
+  ptr += first_blob.size();
+  length -= first_blob.size();
+  ++it;
+
+  while (length != 0) {
+    PERFETTO_DCHECK(it != end());
+    const size_t bytes_to_copy = std::min(length, it->data.size());
+    memcpy(ptr, it->data.data(), bytes_to_copy);
+    ptr += bytes_to_copy;
+    length -= bytes_to_copy;
+    ++it;
+  }
+
+  return TraceBlobView(std::move(buffer));
+}
+
+FileBuffer::Iterator FileBuffer::FindEntryWithOffset(size_t offset) const {
+  if (offset >= end_offset_) {
+    return end();
+  }
+
+  auto it = std::upper_bound(
+      data_.begin(), data_.end(), offset,
+      [](size_t offset, const Entry& rhs) { return offset < rhs.file_offset; });
+  if (it == data_.begin()) {
+    return end();
+  }
+  return std::prev(it);
+}
+
+}  // namespace perfetto::trace_processor::util
diff --git a/src/trace_processor/util/file_buffer.h b/src/trace_processor/util/file_buffer.h
new file mode 100644
index 0000000..07de20f
--- /dev/null
+++ b/src/trace_processor/util/file_buffer.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_UTIL_FILE_BUFFER_H_
+#define SRC_TRACE_PROCESSOR_UTIL_FILE_BUFFER_H_
+
+#include <cstddef>
+#include <optional>
+
+#include "perfetto/ext/base/circular_queue.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+
+namespace perfetto::trace_processor::util {
+
+// Helper class that exposes a window into the contents of a file. Data can be
+// appended to the end of the buffer (increasing the size of the window) or
+// removed from the front (decreasing the size of the window).
+//
+// TraceProcessor reads trace files in chunks and streams those to the
+// `ChunkedTraceReader` instance. But sometimes the reader needs to look into
+// the future (i.e. some data that has not yet arrived) before being able to
+// process the current data. In such a case the reader would have to buffer data
+// until the "future" data arrives. This class encapsulates that functionality.
+class FileBuffer {
+ public:
+  // Trivial empty ctor.
+  FileBuffer() = default;
+
+  // Returns the offset to the start of the buffered window of data.
+  size_t file_offset() const {
+    return data_.empty() ? end_offset_ : data_.front().file_offset;
+  }
+
+  // Adds a `TraceBlobView` at the back.
+  void PushBack(TraceBlobView view);
+
+  // Shrinks the buffer by dropping bytes from the front of the buffer until the
+  // given offset is reached. If not enough data is present as much data as
+  // possible will be dropped and `false` will be returned.
+  bool PopFrontBytesUntil(size_t offset);
+
+  // Similar to `TraceBlobView::slice_off`, creates a slice with data starting
+  // at `offset` and of the given `length`. This method might need to allocate a
+  // new buffer and copy data into it (if the requested data spans multiple
+  // TraceBlobView instances). If not enough data is present `std::nullopt` is
+  // returned.
+  //
+  // ATTENTION: If `offset` < 'file_offset()' this method will never return a
+  // value.
+  std::optional<TraceBlobView> SliceOff(size_t offset, size_t length) const;
+
+ private:
+  struct Entry {
+    // File offset of the first byte in `data`.
+    size_t file_offset;
+    TraceBlobView data;
+  };
+  using Iterator = base::CircularQueue<Entry>::Iterator;
+  // Finds the `TraceBlobView` at `offset` and returns a slice starting at that
+  // offset and spanning the rest of the `TraceBlobView`. It also returns an
+  // iterator to the next `TraceBlobView` instance (which might be `end()`).
+  Iterator FindEntryWithOffset(size_t offset) const;
+
+  Iterator end() const { return data_.end(); }
+
+  // CircularQueue has no const_iterator, so mutable is needed to access it from
+  // const methods.
+  // CircularQueue has no const_iterator, so mutable is needed to access it from
+  // const methods.
+  mutable base::CircularQueue<Entry> data_;
+  size_t end_offset_ = 0;
+};
+
+}  // namespace perfetto::trace_processor::util
+
+#endif  // SRC_TRACE_PROCESSOR_UTIL_FILE_BUFFER_H_
diff --git a/src/trace_processor/util/file_buffer_unittest.cc b/src/trace_processor/util/file_buffer_unittest.cc
new file mode 100644
index 0000000..418cf76
--- /dev/null
+++ b/src/trace_processor/util/file_buffer_unittest.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "src/trace_processor/util/file_buffer.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_processor::util {
+namespace {
+
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::Optional;
+using ::testing::Property;
+using ::testing::SizeIs;
+
+class SameDataAsMatcher {
+ public:
+  template <typename ArgType>
+  class MatcherImpl : public ::testing ::MatcherInterface<const ArgType&> {
+   public:
+    explicit MatcherImpl(const TraceBlobView& expected_data)
+        : expected_data_(expected_data) {}
+    bool MatchAndExplain(const ArgType& arg,
+                         ::testing ::MatchResultListener*) const override {
+      if (expected_data_.size() != arg.size()) {
+        return false;
+      }
+      return memcmp(expected_data_.data(), arg.data(), expected_data_.size()) ==
+             0;
+    }
+    void DescribeTo(::std ::ostream*) const override {}
+    void DescribeNegationTo(::std ::ostream*) const override {}
+
+   private:
+    const TraceBlobView& expected_data_;
+  };
+
+  explicit SameDataAsMatcher(const TraceBlobView& expected_data)
+      : expected_data_(expected_data) {}
+
+  template <typename ArgType>
+  operator ::testing::Matcher<ArgType>() const {
+    return ::testing::Matcher<ArgType>(
+        new MatcherImpl<ArgType>(expected_data_));
+  }
+
+ private:
+  const TraceBlobView& expected_data_;
+};
+
+SameDataAsMatcher SameDataAs(const TraceBlobView& expected_data) {
+  return SameDataAsMatcher(expected_data);
+}
+
+TraceBlobView CreateExpectedData(size_t expected_size) {
+  TraceBlob tb = TraceBlob::Allocate(expected_size);
+  for (size_t i = 0; i < expected_size; ++i) {
+    tb.data()[i] = static_cast<uint8_t>(i);
+  }
+  return TraceBlobView(std::move(tb));
+}
+
+std::vector<TraceBlobView> Slice(const TraceBlobView& blob, size_t chunk_size) {
+  std::vector<TraceBlobView> chunks;
+  size_t size = blob.size();
+  for (size_t off = 0; size != 0;) {
+    chunk_size = std::min(chunk_size, size);
+    chunks.push_back(blob.slice_off(off, chunk_size));
+    size -= chunk_size;
+    off += chunk_size;
+  }
+  return chunks;
+}
+
+FileBuffer CreateFileBuffer(const std::vector<TraceBlobView>& chunks) {
+  FileBuffer chunked_buffer;
+  for (const auto& chunk : chunks) {
+    chunked_buffer.PushBack(chunk.copy());
+  }
+  return chunked_buffer;
+}
+
+TEST(FileBuffer, ContiguousAccessAtOffset) {
+  constexpr size_t kExpectedSize = 256;
+  constexpr size_t kChunkSize = kExpectedSize / 4;
+  TraceBlobView expected_data = CreateExpectedData(kExpectedSize);
+  FileBuffer buffer = CreateFileBuffer(Slice(expected_data, kChunkSize));
+
+  for (size_t file_offset = 0; file_offset <= kExpectedSize; ++file_offset) {
+    EXPECT_TRUE(buffer.PopFrontBytesUntil(file_offset));
+    for (size_t off = file_offset; off <= kExpectedSize; ++off) {
+      auto expected = expected_data.slice_off(off, kExpectedSize - off);
+      std::optional<TraceBlobView> tbv = buffer.SliceOff(off, expected.size());
+      EXPECT_THAT(tbv, Optional(SameDataAs(expected)));
+    }
+  }
+}
+
+TEST(FileBuffer, NoCopyIfDataIsContiguous) {
+  constexpr size_t kExpectedSize = 256;
+  constexpr size_t kChunkSize = kExpectedSize / 4;
+  std::vector<TraceBlobView> chunks =
+      Slice(CreateExpectedData(kExpectedSize), kChunkSize);
+  FileBuffer buffer = CreateFileBuffer(chunks);
+
+  for (size_t i = 0; i < chunks.size(); ++i) {
+    for (size_t off = 0; off < kChunkSize; ++off) {
+      const size_t expected_size = kChunkSize - off;
+      EXPECT_THAT(
+          buffer.SliceOff(i * kChunkSize + off, expected_size),
+          Optional(Property(&TraceBlobView::data, Eq(chunks[i].data() + off))));
+    }
+  }
+}
+
+TEST(FileBuffer, PopRemovesData) {
+  size_t expected_size = 256;
+  size_t expected_file_offset = 0;
+  const size_t kChunkSize = expected_size / 4;
+  TraceBlobView expected_data = CreateExpectedData(expected_size);
+  FileBuffer buffer = CreateFileBuffer(Slice(expected_data, kChunkSize));
+
+  --expected_size;
+  ++expected_file_offset;
+  buffer.PopFrontBytesUntil(expected_file_offset);
+  EXPECT_THAT(buffer.file_offset(), Eq(expected_file_offset));
+  EXPECT_THAT(buffer.SliceOff(expected_file_offset - 1, 1), Eq(std::nullopt));
+  EXPECT_THAT(buffer.SliceOff(expected_file_offset, expected_size),
+              Optional(SameDataAs(expected_data.slice_off(
+                  expected_data.size() - expected_size, expected_size))));
+
+  expected_size -= kChunkSize;
+  expected_file_offset += kChunkSize;
+  buffer.PopFrontBytesUntil(expected_file_offset);
+  EXPECT_THAT(buffer.file_offset(), Eq(expected_file_offset));
+  EXPECT_THAT(buffer.SliceOff(expected_file_offset - 1, 1), Eq(std::nullopt));
+  EXPECT_THAT(buffer.SliceOff(expected_file_offset, expected_size),
+              Optional(SameDataAs(expected_data.slice_off(
+                  expected_data.size() - expected_size, expected_size))));
+}
+
+}  // namespace
+}  // namespace perfetto::trace_processor::util