Split PerfDataReader
Split PerfDataReader into two classes, one that does buffering and one
that does the reading.
This way we can later reuse the reading bit in the parser code.
Bug: b/334978369
Change-Id: Icb48d2d7c827df65ff1b4e13056f4d823f28fed4
diff --git a/Android.bp b/Android.bp
index c43694e..ecc4362 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12236,6 +12236,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",
],
}
@@ -13333,6 +13334,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",
@@ -13428,6 +13437,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",
@@ -15012,6 +15022,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 5fac4da..c170d09 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