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