Merge "Use std::equal instead of memcmp in file_buffer_unittest.cc" into main
diff --git a/Android.bp b/Android.bp
index 1e7b327..ee7f589 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12241,9 +12241,11 @@
     name: "perfetto_src_trace_processor_importers_perf_perf",
     srcs: [
         "src/trace_processor/importers/perf/attrs_section_reader.cc",
+        "src/trace_processor/importers/perf/features.cc",
+        "src/trace_processor/importers/perf/mmap_record.cc",
         "src/trace_processor/importers/perf/perf_data_tokenizer.cc",
-        "src/trace_processor/importers/perf/perf_data_tracker.cc",
         "src/trace_processor/importers/perf/record_parser.cc",
+        "src/trace_processor/importers/perf/sample.cc",
     ],
 }
 
@@ -12251,6 +12253,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_perf_record",
     srcs: [
+        "src/trace_processor/importers/perf/perf_counter.cc",
         "src/trace_processor/importers/perf/perf_event_attr.cc",
         "src/trace_processor/importers/perf/perf_session.cc",
     ],
@@ -12260,7 +12263,6 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_perf_unittests",
     srcs: [
-        "src/trace_processor/importers/perf/perf_data_tracker_unittest.cc",
         "src/trace_processor/importers/perf/perf_session_unittest.cc",
         "src/trace_processor/importers/perf/reader_unittest.cc",
     ],
diff --git a/BUILD b/BUILD
index 1c2fe0e..b3ff934 100644
--- a/BUILD
+++ b/BUILD
@@ -1708,13 +1708,17 @@
     srcs = [
         "src/trace_processor/importers/perf/attrs_section_reader.cc",
         "src/trace_processor/importers/perf/attrs_section_reader.h",
+        "src/trace_processor/importers/perf/features.cc",
+        "src/trace_processor/importers/perf/features.h",
+        "src/trace_processor/importers/perf/mmap_record.cc",
+        "src/trace_processor/importers/perf/mmap_record.h",
         "src/trace_processor/importers/perf/perf_data_tokenizer.cc",
         "src/trace_processor/importers/perf/perf_data_tokenizer.h",
-        "src/trace_processor/importers/perf/perf_data_tracker.cc",
-        "src/trace_processor/importers/perf/perf_data_tracker.h",
         "src/trace_processor/importers/perf/perf_file.h",
         "src/trace_processor/importers/perf/record_parser.cc",
         "src/trace_processor/importers/perf/record_parser.h",
+        "src/trace_processor/importers/perf/sample.cc",
+        "src/trace_processor/importers/perf/sample.h",
     ],
 )
 
@@ -1722,6 +1726,8 @@
 perfetto_filegroup(
     name = "src_trace_processor_importers_perf_record",
     srcs = [
+        "src/trace_processor/importers/perf/perf_counter.cc",
+        "src/trace_processor/importers/perf/perf_counter.h",
         "src/trace_processor/importers/perf/perf_event.h",
         "src/trace_processor/importers/perf/perf_event_attr.cc",
         "src/trace_processor/importers/perf/perf_event_attr.h",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9d84571..0e6a83d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,4 +1,14 @@
 {
+  "art-mainline-presubmit": [
+    {
+      "name": "CtsPerfettoTestCases",
+      "options": [
+        {
+          "include-filter": "HeapprofdJavaCtsTest*"
+        }
+      ]
+    }
+  ],
   "presubmit": [
     {
       "name": "CtsPerfettoTestCases"
diff --git a/src/trace_processor/importers/common/mapping_tracker.cc b/src/trace_processor/importers/common/mapping_tracker.cc
index 0dec3e5..965c15f 100644
--- a/src/trace_processor/importers/common/mapping_tracker.cc
+++ b/src/trace_processor/importers/common/mapping_tracker.cc
@@ -164,5 +164,15 @@
       });
 }
 
+VirtualMemoryMapping* MappingTracker::GetDummyMapping() {
+  if (!dummy_mapping_) {
+    CreateMappingParams params;
+    params.memory_range =
+        AddressRange::FromStartAndSize(0, std::numeric_limits<uint64_t>::max());
+    dummy_mapping_ = &InternMemoryMapping(params);
+  }
+  return dummy_mapping_;
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/mapping_tracker.h b/src/trace_processor/importers/common/mapping_tracker.h
index bc45bef..08c767e 100644
--- a/src/trace_processor/importers/common/mapping_tracker.h
+++ b/src/trace_processor/importers/common/mapping_tracker.h
@@ -91,6 +91,10 @@
   // Jitted ranges will only be applied to UserMemoryMappings
   void AddJitRange(UniquePid upid, AddressRange range, JitCache* jit_cache);
 
+  // Sometimes we just need a mapping and we are lacking trace data to create a
+  // proper one. Use this mapping in those cases.
+  VirtualMemoryMapping* GetDummyMapping();
+
  private:
   template <typename MappingImpl>
   MappingImpl& AddMapping(std::unique_ptr<MappingImpl> mapping);
@@ -136,6 +140,8 @@
   KernelMemoryMapping* kernel_ = nullptr;
 
   base::FlatHashMap<UniquePid, AddressRangeMap<JitCache*>> jit_caches_;
+
+  VirtualMemoryMapping* dummy_mapping_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/perf/BUILD.gn b/src/trace_processor/importers/perf/BUILD.gn
index f87eeda..7a3f2a3 100644
--- a/src/trace_processor/importers/perf/BUILD.gn
+++ b/src/trace_processor/importers/perf/BUILD.gn
@@ -16,6 +16,8 @@
 
 source_set("record") {
   sources = [
+    "perf_counter.cc",
+    "perf_counter.h",
     "perf_event.h",
     "perf_event_attr.cc",
     "perf_event_attr.h",
@@ -27,7 +29,6 @@
   deps = [
     "../../../../gn:default_deps",
     "../../../../include/perfetto/ext/base:base",
-    "../../../../include/perfetto/trace_processor:storage",
     "../../../../include/perfetto/trace_processor:trace_processor",
     "../../../../protos/perfetto/trace/profiling:zero",
     "../../storage",
@@ -40,13 +41,17 @@
   sources = [
     "attrs_section_reader.cc",
     "attrs_section_reader.h",
+    "features.cc",
+    "features.h",
+    "mmap_record.cc",
+    "mmap_record.h",
     "perf_data_tokenizer.cc",
     "perf_data_tokenizer.h",
-    "perf_data_tracker.cc",
-    "perf_data_tracker.h",
     "perf_file.h",
     "record_parser.cc",
     "record_parser.h",
+    "sample.cc",
+    "sample.h",
   ]
   public_deps = [ ":record" ]
   deps = [
@@ -57,9 +62,10 @@
     "../../storage",
     "../../tables:tables_python",
     "../../types",
+    "../../util:build_id",
     "../../util:file_buffer",
-    "../common",
-    "../common:parser_types",
+    "../../util:util",
+    "../common:common",
     "../proto:minimal",
   ]
 }
@@ -67,7 +73,6 @@
 perfetto_unittest_source_set("unittests") {
   testonly = true
   sources = [
-    "perf_data_tracker_unittest.cc",
     "perf_session_unittest.cc",
     "reader_unittest.cc",
   ]
diff --git a/src/trace_processor/importers/perf/attrs_section_reader.cc b/src/trace_processor/importers/perf/attrs_section_reader.cc
index ffea386..f19ea3e 100644
--- a/src/trace_processor/importers/perf/attrs_section_reader.cc
+++ b/src/trace_processor/importers/perf/attrs_section_reader.cc
@@ -56,20 +56,11 @@
   }
   const size_t attr_size = header.attr_size - kSectionSize;
 
-  const size_t attr_bytes_to_read =
-      std::min(attr_size, sizeof(PerfFile::AttrsEntry::attr));
-  const size_t attr_bytes_to_skip = attr_size - attr_bytes_to_read;
-
-  return AttrsSectionReader(std::move(section), num_attr, attr_size,
-                            attr_bytes_to_read, attr_bytes_to_skip);
+  return AttrsSectionReader(std::move(section), num_attr, attr_size);
 }
 
 base::Status AttrsSectionReader::ReadNext(PerfFile::AttrsEntry& entry) {
-  static_assert(std::has_unique_object_representations_v<PerfFile::AttrsEntry>);
-  memset(&entry, 0, sizeof(entry));
-
-  PERFETTO_CHECK(reader_.Read(&entry.attr, attr_bytes_to_read_) &&
-                 reader_.Skip(attr_bytes_to_skip_));
+  PERFETTO_CHECK(reader_.ReadPerfEventAttr(entry.attr, attr_size_));
 
   if (entry.attr.size != attr_size_) {
     return base::ErrStatus(
diff --git a/src/trace_processor/importers/perf/attrs_section_reader.h b/src/trace_processor/importers/perf/attrs_section_reader.h
index ae71ae0..a7eaaa3 100644
--- a/src/trace_processor/importers/perf/attrs_section_reader.h
+++ b/src/trace_processor/importers/perf/attrs_section_reader.h
@@ -41,22 +41,14 @@
   base::Status ReadNext(PerfFile::AttrsEntry& entry);
 
  private:
-  AttrsSectionReader(TraceBlobView section,
-                     size_t num_attr,
-                     size_t attr_size,
-                     size_t attr_bytes_to_read,
-                     size_t attr_bytes_to_skip)
+  AttrsSectionReader(TraceBlobView section, size_t num_attr, size_t attr_size)
       : reader_(std::move(section)),
         num_attr_(num_attr),
-        attr_size_(attr_size),
-        attr_bytes_to_read_(attr_bytes_to_read),
-        attr_bytes_to_skip_(attr_bytes_to_skip) {}
+        attr_size_(attr_size) {}
 
   Reader reader_;
   size_t num_attr_;
   const size_t attr_size_;
-  const size_t attr_bytes_to_read_;
-  const size_t attr_bytes_to_skip_;
 };
 
 }  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/features.cc b/src/trace_processor/importers/perf/features.cc
new file mode 100644
index 0000000..861a8bf
--- /dev/null
+++ b/src/trace_processor/importers/perf/features.cc
@@ -0,0 +1,258 @@
+/*
+ * 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/importers/perf/features.h"
+
+#include <cstdint>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/status.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor::perf_importer::feature {
+namespace {
+
+bool ParseString(Reader& reader, std::string& out) {
+  uint32_t len;
+  base::StringView str;
+  if (!reader.Read(len) || len == 0 || !reader.ReadStringView(str, len)) {
+    return false;
+  }
+
+  if (str.at(len - 1) != '\0') {
+    return false;
+  }
+
+  out = std::string(str.data(), len - 1);
+  return true;
+}
+
+bool ParseBuildId(const perf_event_header& header,
+                  TraceBlobView blob,
+                  BuildId& out) {
+  Reader reader(std::move(blob));
+  struct {
+    char data[20];
+    uint8_t size;
+    uint8_t reserved[3];
+  } build_id;
+
+  if (!reader.Read(out.pid) || !reader.Read(build_id) ||
+      !reader.ReadStringUntilEndOrNull(out.filename)) {
+    return false;
+  }
+
+  if (header.misc & PERF_RECORD_MISC_EXT_RESERVED) {
+    if (build_id.size > sizeof(build_id.data)) {
+      return false;
+    }
+  } else {
+    // Probably a simpleperf trace. Simpleperf fills build_ids with zeros up
+    // to a length of 20 and leaves the rest uninitialized :( so we can not read
+    // build_id.size or build_id.reserved to do any checks.
+    // TODO(b/334978369): We should be able to tell for sure whether this is
+    // simpleperf or not by checking the existence of SimpleperfMetaInfo.
+    build_id.size = 20;
+    // BuildIds are usually SHA-1 hashes (20 bytes), sometimes MD5 (16 bites).
+    // Simpleperf adds trailing zeros. But zeros could be in the MD5 hash.
+    // But it the last 4 bytes are zeros there is a high chance this was an
+    // MD5.
+    if (build_id.data[16] == 0 && build_id.data[17] == 0 &&
+        build_id.data[18] == 0 && build_id.data[19] == 0) {
+      build_id.size = 16;
+    }
+  }
+  out.build_id = std::string(build_id.data, build_id.size);
+  return true;
+}
+
+util::Status ParseEventTypeInfo(std::string value, SimpleperfMetaInfo& out) {
+  for (const auto& line : base::SplitString(value, "\n")) {
+    auto tokens = base::SplitString(line, ",");
+    if (tokens.size() != 3) {
+      return util::ErrStatus("Invalid event_type_info: '%s'", line.c_str());
+    }
+
+    auto type = base::StringToUInt32(tokens[1]);
+    if (!type) {
+      return util::ErrStatus("Could not parse type in event_type_info: '%s'",
+                             tokens[1].c_str());
+    }
+    auto config = base::StringToUInt64(tokens[2]);
+    if (!config) {
+      return util::ErrStatus("Could not parse config in event_type_info: '%s'",
+                             tokens[2].c_str());
+    }
+
+    out.event_type_info.Insert({*type, *config}, std::move(tokens[0]));
+  }
+
+  return util::OkStatus();
+}
+
+util::Status ParseSimpleperfMetaInfoEntry(
+    std::pair<std::string, std::string> entry,
+    SimpleperfMetaInfo& out) {
+  static constexpr char kEventTypeInfoKey[] = "event_type_info";
+  if (entry.first == kEventTypeInfoKey) {
+    return ParseEventTypeInfo(std::move(entry.second), out);
+  }
+
+  PERFETTO_CHECK(
+      out.entries.Insert(std::move(entry.first), std::move(entry.second))
+          .second);
+  return util::OkStatus();
+}
+
+}  // namespace
+
+// static
+util::Status BuildId::Parse(TraceBlobView bytes,
+                            std::function<util::Status(BuildId)> cb) {
+  Reader reader(std::move(bytes));
+  while (reader.size_left() != 0) {
+    perf_event_header header;
+    TraceBlobView payload;
+    if (!reader.Read(header)) {
+      return base::ErrStatus(
+          "Failed to parse feature BuildId. Could not read header.");
+    }
+    if (header.size < sizeof(header)) {
+      return base::ErrStatus(
+          "Failed to parse feature BuildId. Invalid size in header.");
+    }
+    if (!reader.ReadBlob(payload, header.size - sizeof(header))) {
+      return base::ErrStatus(
+          "Failed to parse feature BuildId. Could not read payload.");
+    }
+
+    BuildId build_id;
+    if (!ParseBuildId(header, std::move(payload), build_id)) {
+      return base::ErrStatus(
+          "Failed to parse feature BuildId. Could not read entry.");
+    }
+
+    RETURN_IF_ERROR(cb(std::move(build_id)));
+  }
+  return util::OkStatus();
+}
+
+// static
+util::Status SimpleperfMetaInfo::Parse(const TraceBlobView& bytes,
+                                       SimpleperfMetaInfo& out) {
+  auto* it_end = reinterpret_cast<const char*>(bytes.data() + bytes.size());
+  for (auto* it = reinterpret_cast<const char*>(bytes.data()); it != it_end;) {
+    auto end = std::find(it, it_end, '\0');
+    if (end == it_end) {
+      return util::ErrStatus("Failed to read key from Simpleperf MetaInfo");
+    }
+    std::string key(it, end);
+    it = end;
+    ++it;
+    if (it == it_end) {
+      return util::ErrStatus("Missing value in Simpleperf MetaInfo");
+    }
+    end = std::find(it, it_end, '\0');
+    if (end == it_end) {
+      return util::ErrStatus("Failed to read value from Simpleperf MetaInfo");
+    }
+    std::string value(it, end);
+    it = end;
+    ++it;
+
+    RETURN_IF_ERROR(ParseSimpleperfMetaInfoEntry(
+        std::make_pair(std::move(key), std::move(value)), out));
+  }
+  return util::OkStatus();
+}
+
+// static
+util::Status EventDescription::Parse(
+    TraceBlobView bytes,
+    std::function<util::Status(EventDescription)> cb) {
+  Reader reader(std::move(bytes));
+  uint32_t nr;
+  uint32_t attr_size;
+  if (!reader.Read(nr) || !reader.Read(attr_size)) {
+    return util::ErrStatus("Failed to parse header for PERF_EVENT_DESC");
+  }
+
+  for (; nr != 0; --nr) {
+    EventDescription desc;
+    uint32_t nr_ids;
+    if (!reader.ReadPerfEventAttr(desc.attr, attr_size) ||
+        !reader.Read(nr_ids) || !ParseString(reader, desc.event_string)) {
+      return util::ErrStatus("Failed to parse record for PERF_EVENT_DESC");
+    }
+
+    desc.ids.resize(nr_ids);
+    for (uint64_t& id : desc.ids) {
+      if (!reader.Read(id)) {
+        return util::ErrStatus("Failed to parse ids for PERF_EVENT_DESC");
+      }
+    }
+    RETURN_IF_ERROR(cb(std::move(desc)));
+  }
+  return util::OkStatus();
+}
+
+util::Status ParseSimpleperfFile2(
+    TraceBlobView bytes,
+    std::function<util::Status(TraceBlobView)> cb) {
+  Reader reader(std::move(bytes));
+  while (reader.size_left() != 0) {
+    uint32_t len;
+    if (!reader.Read(len)) {
+      return base::ErrStatus("Failed to parse len in FEATURE_SIMPLEPERF_FILE2");
+    }
+    TraceBlobView payload;
+    if (!reader.ReadBlob(payload, len)) {
+      return base::ErrStatus(
+          "Failed to parse payload in FEATURE_SIMPLEPERF_FILE2");
+    }
+    RETURN_IF_ERROR(cb(std::move(payload)));
+  }
+  return util::OkStatus();
+}
+
+// static
+util::Status HeaderGroupDesc::Parse(TraceBlobView bytes, HeaderGroupDesc& out) {
+  Reader reader(std::move(bytes));
+  uint32_t nr;
+  if (!reader.Read(nr)) {
+    return util::ErrStatus("Failed to parse header for HEADER_GROUP_DESC");
+  }
+
+  HeaderGroupDesc group_desc;
+  group_desc.entries.resize(nr);
+  for (auto& e : group_desc.entries) {
+    if (!ParseString(reader, e.string) || !reader.Read(e.leader_idx) ||
+        !reader.Read(e.nr_members)) {
+      return util::ErrStatus("Failed to parse HEADER_GROUP_DESC entry");
+    }
+  }
+  out = std::move(group_desc);
+  return base::OkStatus();
+}
+
+}  // namespace perfetto::trace_processor::perf_importer::feature
diff --git a/src/trace_processor/importers/perf/features.h b/src/trace_processor/importers/perf/features.h
new file mode 100644
index 0000000..2a6c22b
--- /dev/null
+++ b/src/trace_processor/importers/perf/features.h
@@ -0,0 +1,132 @@
+/*
+ * 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_FEATURES_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_
+
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
+
+namespace perfetto ::trace_processor {
+class TraceBlobView;
+
+namespace perf_importer::feature {
+
+enum Id : uint8_t {
+  ID_RESERVED = 0,
+  ID_TRACING_DATA = 1,
+  ID_BUILD_ID = 2,
+  ID_HOSTNAME = 3,
+  ID_OS_RELEASE = 4,
+  ID_VERSION = 5,
+  ID_ARCH = 6,
+  ID_NR_CPUS = 7,
+  ID_CPU_DESC = 8,
+  ID_CPU_ID = 9,
+  ID_TOTAL_MEM = 10,
+  ID_CMD_LINE = 11,
+  ID_EVENT_DESC = 12,
+  ID_CPU_TOPOLOGY = 13,
+  ID_NUMA_TOPOLOGY = 14,
+  ID_BRANCH_STACK = 15,
+  ID_PMU_MAPPINGS = 16,
+  ID_GROUP_DESC = 17,
+  ID_AUX_TRACE = 18,
+  ID_STAT = 19,
+  ID_CACHE = 20,
+  ID_SAMPLE_TIME = 21,
+  ID_SAMPLE_TOPOLOGY = 22,
+  ID_CLOCK_ID = 23,
+  ID_DIR_FORMAT = 24,
+  ID_BPF_PROG_INFO = 25,
+  ID_BPF_BTF = 26,
+  ID_COMPRESSED = 27,
+  ID_CPU_PUM_CAPS = 28,
+  ID_CLOCK_DATA = 29,
+  ID_HYBRID_TOPOLOGY = 30,
+  ID_PMU_CAPS = 31,
+  ID_SIMPLEPERF_FILE = 128,
+  ID_SIMPLEPERF_META_INFO = 129,
+  ID_SIMPLEPERF_FILE2 = 132,
+  ID_MAX = std::numeric_limits<uint8_t>::max(),
+};
+
+struct BuildId {
+  static util::Status Parse(TraceBlobView,
+                            std::function<util::Status(BuildId)> cb);
+  int32_t pid;
+  std::string build_id;
+  std::string filename;
+};
+
+struct HeaderGroupDesc {
+  static util::Status Parse(TraceBlobView, HeaderGroupDesc& out);
+  struct Entry {
+    std::string string;
+    uint32_t leader_idx;
+    uint32_t nr_members;
+  };
+  std::vector<Entry> entries;
+};
+
+struct EventDescription {
+  static util::Status Parse(TraceBlobView,
+                            std::function<util::Status(EventDescription)> cb);
+  perf_event_attr attr;
+  std::string event_string;
+  std::vector<uint64_t> ids;
+};
+
+struct SimpleperfMetaInfo {
+  static util::Status Parse(const TraceBlobView&, SimpleperfMetaInfo& out);
+  base::FlatHashMap<std::string, std::string> entries;
+  struct EventTypeAndConfig {
+    uint32_t type;
+    uint64_t config;
+    bool operator==(const EventTypeAndConfig& other) {
+      return type == other.type && config == other.config;
+    }
+    bool operator!=(const EventTypeAndConfig& other) {
+      return !(*this == other);
+    }
+    struct Hasher {
+      size_t operator()(const EventTypeAndConfig& o) const {
+        return static_cast<size_t>(base::Hasher::Combine(o.config, o.type));
+      }
+    };
+  };
+  using EventName = std::string;
+  base::FlatHashMap<EventTypeAndConfig, EventName, EventTypeAndConfig::Hasher>
+      event_type_info;
+};
+
+util::Status ParseSimpleperfFile2(
+    TraceBlobView,
+    std::function<util::Status(TraceBlobView)> cb);
+
+}  // namespace perf_importer::feature
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_FEATURES_H_
diff --git a/src/trace_processor/importers/perf/mmap_record.cc b/src/trace_processor/importers/perf/mmap_record.cc
new file mode 100644
index 0000000..d11fdd5
--- /dev/null
+++ b/src/trace_processor/importers/perf/mmap_record.cc
@@ -0,0 +1,66 @@
+/*
+ * 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/importers/perf/mmap_record.h"
+
+#include <optional>
+
+#include "perfetto/base/status.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/importers/perf/record.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+base::Status MmapRecord::Parse(const Record& record) {
+  Reader reader(record.payload.copy());
+  if (!reader.Read(*static_cast<CommonMmapRecordFields*>(this)) ||
+      !reader.ReadCString(filename)) {
+    return base::ErrStatus("Failed to parse MMAP record");
+  }
+  cpu_mode = record.GetCpuMode();
+  return base::OkStatus();
+}
+
+base::Status Mmap2Record::Parse(const Record& record) {
+  Reader reader(record.payload.copy());
+  if (!reader.Read(*static_cast<BaseMmap2Record*>(this)) ||
+      !reader.ReadCString(filename)) {
+    return base::ErrStatus("Failed to parse MMAP record");
+  }
+
+  has_build_id = record.mmap_has_build_id();
+
+  if (has_build_id && build_id.build_id_size >
+                          BaseMmap2Record::BuildIdFields::kMaxBuildIdSize) {
+    return base::ErrStatus(
+        "Invalid build_id_size in MMAP2 record. Expected <= %zu but found "
+        "%" PRIu8,
+        BaseMmap2Record::BuildIdFields::kMaxBuildIdSize,
+        build_id.build_id_size);
+  }
+
+  cpu_mode = record.GetCpuMode();
+
+  return base::OkStatus();
+}
+
+std::optional<BuildId> Mmap2Record::GetBuildId() const {
+  return has_build_id ? std::make_optional(BuildId::FromRaw(std::string(
+                            build_id.build_id_buf, build_id.build_id_size)))
+                      : std::nullopt;
+}
+
+}  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/mmap_record.h b/src/trace_processor/importers/perf/mmap_record.h
new file mode 100644
index 0000000..37b3939
--- /dev/null
+++ b/src/trace_processor/importers/perf/mmap_record.h
@@ -0,0 +1,81 @@
+/*
+ * 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_MMAP_RECORD_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include "perfetto/base/status.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+#include "src/trace_processor/util/build_id.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+struct Record;
+
+struct CommonMmapRecordFields {
+  uint32_t pid;
+  uint32_t tid;
+  uint64_t addr;
+  uint64_t len;
+  uint64_t pgoff;
+};
+
+struct MmapRecord : public CommonMmapRecordFields {
+  std::string filename;
+  protos::pbzero::Profiling::CpuMode cpu_mode;
+
+  base::Status Parse(const Record& record);
+};
+
+struct BaseMmap2Record : public CommonMmapRecordFields {
+  struct BuildIdFields {
+    static constexpr size_t kMaxBuildIdSize = 20;
+    uint8_t build_id_size;
+    uint8_t reserved_1;
+    uint16_t reserved_2;
+    char build_id_buf[kMaxBuildIdSize];
+  };
+  struct InodeFields {
+    uint32_t maj;
+    uint32_t min;
+    int64_t ino;
+    uint64_t ino_generation;
+  };
+  static_assert(sizeof(BuildIdFields) == sizeof(InodeFields));
+
+  union {
+    BuildIdFields build_id;
+    InodeFields inode;
+  };
+  uint32_t prot;
+  uint32_t flags;
+};
+
+struct Mmap2Record : public BaseMmap2Record {
+  std::string filename;
+  protos::pbzero::Profiling::CpuMode cpu_mode;
+  bool has_build_id;
+
+  base::Status Parse(const Record& record);
+  std::optional<BuildId> GetBuildId() const;
+};
+
+}  // namespace perfetto::trace_processor::perf_importer
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_MMAP_RECORD_H_
diff --git a/src/trace_processor/importers/perf/perf_counter.cc b/src/trace_processor/importers/perf/perf_counter.cc
new file mode 100644
index 0000000..685e940
--- /dev/null
+++ b/src/trace_processor/importers/perf/perf_counter.cc
@@ -0,0 +1,37 @@
+/*
+ * 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/importers/perf/perf_counter.h"
+
+#include <cstdint>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/tables/counter_tables_py.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+void PerfCounter::AddDelta(int64_t ts, double delta) {
+  last_count_ += delta;
+  counter_table_.Insert({ts, track_id_, last_count_});
+}
+
+void PerfCounter::AddCount(int64_t ts, double count) {
+  PERFETTO_CHECK(count >= last_count_);
+  last_count_ = count;
+  counter_table_.Insert({ts, track_id_, last_count_});
+}
+
+}  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/perf_counter.h b/src/trace_processor/importers/perf/perf_counter.h
new file mode 100644
index 0000000..fb7a28c
--- /dev/null
+++ b/src/trace_processor/importers/perf/perf_counter.h
@@ -0,0 +1,51 @@
+/*
+ * 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_COUNTER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_
+
+#include <cstdint>
+
+#include "src/trace_processor/tables/counter_tables_py.h"
+#include "src/trace_processor/tables/track_tables_py.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+// Helper class to keep track of perf counters and convert delta values found in
+// perf files to absolute values needed for the perfetto counter table.
+class PerfCounter {
+ public:
+  PerfCounter(tables::CounterTable* counter_table,
+              const tables::PerfCounterTrackTable::ConstRowReference& track)
+      : counter_table_(*counter_table),
+        track_id_(track.id()),
+        is_timebase_(track.is_timebase()) {}
+
+  bool is_timebase() const { return is_timebase_; }
+
+  void AddDelta(int64_t ts, double delta);
+  void AddCount(int64_t ts, double count);
+
+ private:
+  tables::CounterTable& counter_table_;
+  tables::PerfCounterTrackTable::Id track_id_;
+  const bool is_timebase_;
+  double last_count_{0};
+};
+
+}  // namespace perfetto::trace_processor::perf_importer
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_COUNTER_H_
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.cc b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
index dd1d604..25cfb46 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.cc
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.cc
@@ -33,6 +33,7 @@
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/perf/attrs_section_reader.h"
+#include "src/trace_processor/importers/perf/features.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "src/trace_processor/importers/perf/perf_file.h"
 #include "src/trace_processor/importers/perf/perf_session.h"
@@ -40,6 +41,7 @@
 #include "src/trace_processor/importers/perf/record.h"
 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
@@ -194,7 +196,7 @@
                    AttrsSectionReader::Create(header_, std::move(*tbv)));
 
   PerfSession::Builder builder(
-      context_->perf_sample_tracker->CreatePerfSession());
+      context_, context_->perf_sample_tracker->CreatePerfSession());
   while (attr_reader.CanReadNext()) {
     PerfFile::AttrsEntry entry;
     RETURN_IF_ERROR(attr_reader.ReadNext(entry));
@@ -371,7 +373,45 @@
   return ParsingResult::kSuccess;
 }
 
-base::Status PerfDataTokenizer::ParseFeature(uint8_t, TraceBlobView) {
+base::Status PerfDataTokenizer::ParseFeature(uint8_t feature_id,
+                                             TraceBlobView data) {
+  switch (feature_id) {
+    case feature::ID_EVENT_DESC: {
+      RETURN_IF_ERROR(feature::EventDescription::Parse(
+          std::move(data),
+          [](feature::EventDescription) { return base::OkStatus(); }));
+      break;
+    }
+
+    case feature::ID_BUILD_ID:
+      return feature::BuildId::Parse(
+          std::move(data), [](feature::BuildId) { return base::OkStatus(); });
+
+    case feature::ID_GROUP_DESC: {
+      feature::HeaderGroupDesc group_desc;
+      RETURN_IF_ERROR(
+          feature::HeaderGroupDesc::Parse(std::move(data), group_desc));
+      // TODO(carlscab): Do someting
+      break;
+    }
+
+    case feature::ID_SIMPLEPERF_META_INFO: {
+      feature::SimpleperfMetaInfo meta_info;
+      RETURN_IF_ERROR(
+          feature::SimpleperfMetaInfo::Parse(std::move(data), meta_info));
+      break;
+    }
+    case feature::ID_SIMPLEPERF_FILE2: {
+      RETURN_IF_ERROR(feature::ParseSimpleperfFile2(
+          std::move(data), [&](TraceBlobView) { return util::OkStatus(); }));
+
+      break;
+    }
+    default:
+      context_->storage->IncrementIndexedStats(stats::perf_features_skipped,
+                                               feature_id);
+  }
+
   return base::OkStatus();
 }
 
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.cc b/src/trace_processor/importers/perf/perf_data_tracker.cc
deleted file mode 100644
index 67557ee..0000000
--- a/src/trace_processor/importers/perf/perf_data_tracker.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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/perf_data_tracker.h"
-
-#include <cstdint>
-#include <optional>
-
-#include "perfetto/base/status.h"
-#include "src/trace_processor/importers/common/address_range.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/perf/reader.h"
-#include "src/trace_processor/storage/stats.h"
-#include "src/trace_processor/storage/trace_storage.h"
-
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-namespace {
-
-bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
-  switch (cpu_mode) {
-    case protos::pbzero::Profiling::MODE_UNKNOWN:
-      PERFETTO_CHECK(false);
-    case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
-    case protos::pbzero::Profiling::MODE_KERNEL:
-      return true;
-    case protos::pbzero::Profiling::MODE_USER:
-    case protos::pbzero::Profiling::MODE_HYPERVISOR:
-    case protos::pbzero::Profiling::MODE_GUEST_USER:
-      return false;
-  }
-  PERFETTO_CHECK(false);
-}
-
-CreateMappingParams BuildCreateMappingParams(
-    PerfDataTracker::Mmap2Record record) {
-  return {AddressRange::FromStartAndSize(record.num.addr, record.num.len),
-          record.num.pgoff,
-          // start_offset: This is the offset into the file where the ELF header
-          // starts. We assume all file mappings are ELF files an thus this
-          // offset is 0.
-          0,
-          // load_bias: This can only be read out of the actual ELF file, which
-          // we do not have here, so we set it to 0. When symbolizing we will
-          // hopefully have the real load bias and we can compensate there for a
-          // possible mismatch.
-          0, record.filename, std::nullopt};
-}
-}  // namespace
-
-PerfDataTracker::~PerfDataTracker() = default;
-
-void PerfDataTracker::PushMmap2Record(Mmap2Record record) {
-  if (IsInKernel(record.cpu_mode)) {
-    context_->mapping_tracker->CreateKernelMemoryMapping(
-        BuildCreateMappingParams(std::move(record)));
-  } else {
-    UniquePid upid =
-        context_->process_tracker->GetOrCreateProcess(record.num.pid);
-    context_->mapping_tracker->CreateUserMemoryMapping(
-        upid, BuildCreateMappingParams(std::move(record)));
-  }
-}
-
-base::StatusOr<PerfDataTracker::PerfSample> PerfDataTracker::ParseSample(
-    Reader& reader,
-    uint64_t sample_type) {
-  PerfDataTracker::PerfSample sample;
-
-  if (sample_type & PERF_SAMPLE_IDENTIFIER) {
-    reader.ReadOptional(sample.id);
-  }
-
-  if (sample_type & PERF_SAMPLE_IP) {
-    reader.Skip<uint64_t>();
-  }
-
-  if (sample_type & PERF_SAMPLE_TID) {
-    reader.ReadOptional(sample.pid);
-    reader.ReadOptional(sample.tid);
-  }
-
-  if (sample_type & PERF_SAMPLE_TIME) {
-    reader.ReadOptional(sample.ts);
-  }
-
-  // Ignored. Checked because we need to access later parts of sample.
-  if (sample_type & PERF_SAMPLE_ADDR) {
-    reader.Skip<uint64_t>();
-  }
-
-  // The same value as PERF_SAMPLE_IDENTIFIER, so should be ignored.
-  if (sample_type & PERF_SAMPLE_ID) {
-    reader.Skip<uint64_t>();
-  }
-
-  // Ignored. Checked because we need to access later parts of sample.
-  if (sample_type & PERF_SAMPLE_STREAM_ID) {
-    reader.Skip<uint64_t>();
-  }
-
-  if (sample_type & PERF_SAMPLE_CPU) {
-    reader.ReadOptional(sample.cpu);
-    // Ignore next uint32_t res.
-    reader.Skip<uint32_t>();
-  }
-
-  // Ignored. Checked because we need to access later parts of sample.
-  if (sample_type & PERF_SAMPLE_PERIOD) {
-    reader.Skip<uint64_t>();
-  }
-
-  // Ignored.
-  // TODO(mayzner): Implement.
-  if (sample_type & PERF_SAMPLE_READ) {
-    context_->storage->IncrementStats(stats::perf_samples_skipped);
-    return base::ErrStatus("PERF_SAMPLE_READ is not supported");
-  }
-
-  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
-    uint64_t vec_size;
-    reader.Read(vec_size);
-
-    sample.callchain.resize(static_cast<size_t>(vec_size));
-    reader.ReadVector(sample.callchain);
-  }
-
-  return sample;
-}
-
-PerfDataTracker* PerfDataTracker::GetOrCreate(TraceProcessorContext* context) {
-  if (!context->perf_data_tracker) {
-    context->perf_data_tracker.reset(new PerfDataTracker(context));
-  }
-  return static_cast<PerfDataTracker*>(context->perf_data_tracker.get());
-}
-}  // namespace perf_importer
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.h b/src/trace_processor/importers/perf/perf_data_tracker.h
deleted file mode 100644
index 2ed24ed..0000000
--- a/src/trace_processor/importers/perf/perf_data_tracker.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2022 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_DATA_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_
-
-#include <cstdint>
-#include <string>
-#include <vector>
-
-#include "perfetto/ext/base/status_or.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-#include "src/trace_processor/importers/perf/perf_event.h"
-#include "src/trace_processor/tables/profiler_tables_py.h"
-#include "src/trace_processor/types/destructible.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-
-class Reader;
-using MappingTable = tables::StackProfileMappingTable;
-
-class PerfDataTracker : public Destructible {
- public:
-  struct AttrAndIds {
-    perf_event_attr attr;
-    std::vector<uint64_t> ids;
-  };
-  struct PerfSample {
-    std::optional<uint64_t> id = 0;
-    std::optional<uint32_t> pid = 0;
-    std::optional<uint32_t> tid = 0;
-    std::optional<uint64_t> ts = 0;
-    std::optional<uint32_t> cpu = 0;
-    std::vector<uint64_t> callchain;
-  };
-  struct Mmap2Record {
-    struct Numeric {
-      uint32_t pid;
-      uint32_t tid;
-      uint64_t addr;
-      uint64_t len;
-      uint64_t pgoff;
-      uint32_t maj;
-      uint32_t min;
-      uint64_t ino;
-      uint64_t ino_generation;
-      uint32_t prot;
-      uint32_t flags;
-    };
-    protos::pbzero::Profiling::CpuMode cpu_mode;
-    Numeric num;
-    std::string filename;
-  };
-
-  PerfDataTracker(const PerfDataTracker&) = delete;
-  PerfDataTracker& operator=(const PerfDataTracker&) = delete;
-  explicit PerfDataTracker(TraceProcessorContext* context)
-      : context_(context) {}
-  ~PerfDataTracker() override;
-  static PerfDataTracker* GetOrCreate(TraceProcessorContext* context);
-
-  void PushMmap2Record(Mmap2Record record);
-
-  base::StatusOr<PerfSample> ParseSample(Reader&, uint64_t sample_type);
-
- private:
-  TraceProcessorContext* context_;
-};
-}  // namespace perf_importer
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_DATA_TRACKER_H_
diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
deleted file mode 100644
index 02f58bb..0000000
--- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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/perf_data_tracker.h"
-
-#include <stddef.h>
-#include <cstdint>
-#include <cstring>
-#include <memory>
-#include <vector>
-
-#include "perfetto/base/build_config.h"
-#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
-#include "src/trace_processor/importers/common/address_range.h"
-#include "src/trace_processor/importers/common/mapping_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
-#include "src/trace_processor/importers/perf/perf_event.h"
-#include "src/trace_processor/importers/perf/reader.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace perf_importer {
-namespace {
-
-class PerfDataTrackerUnittest : public testing::Test {
- public:
-  PerfDataTrackerUnittest() {
-    context_.storage = std::make_unique<TraceStorage>();
-    context_.process_tracker = std::make_unique<ProcessTracker>(&context_);
-    context_.stack_profile_tracker =
-        std::make_unique<StackProfileTracker>(&context_);
-    context_.mapping_tracker = std::make_unique<MappingTracker>(&context_);
-  }
-
- protected:
-  TraceProcessorContext context_;
-};
-
-TEST_F(PerfDataTrackerUnittest, FindMapping) {
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
-  PerfDataTracker::Mmap2Record rec;
-  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
-  rec.filename = "file1";
-  rec.num.addr = 1000;
-  rec.num.len = 100;
-  rec.num.pid = 1;
-  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
-  tracker->PushMmap2Record(rec);
-
-  rec.num.addr = 2000;
-  tracker->PushMmap2Record(rec);
-
-  rec.num.addr = 3000;
-  tracker->PushMmap2Record(rec);
-
-  UserMemoryMapping* mapping =
-      context_.mapping_tracker->FindUserMappingForAddress(
-          context_.process_tracker->GetOrCreateProcess(1), 2050);
-  ASSERT_NE(mapping, nullptr);
-  EXPECT_EQ(mapping->memory_range().start(), 2000u);
-  EXPECT_EQ(mapping->memory_range().end(), 2100u);
-}
-
-TEST_F(PerfDataTrackerUnittest, FindMappingFalse) {
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
-  PerfDataTracker::Mmap2Record rec;
-  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
-  rec.filename = "file1";
-  rec.num.addr = 1000;
-  rec.num.len = 100;
-  rec.num.pid = 1;
-  rec.cpu_mode = protos::pbzero::Profiling::MODE_USER;
-  tracker->PushMmap2Record(rec);
-
-  UserMemoryMapping* mapping =
-      context_.mapping_tracker->FindUserMappingForAddress(
-          context_.process_tracker->GetOrCreateProcess(2), 2050);
-  EXPECT_EQ(mapping, nullptr);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleTrivial) {
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
-  uint64_t sample_type = PERF_SAMPLE_TIME;
-
-  uint64_t ts = 100;
-
-  TraceBlob blob =
-      TraceBlob::CopyFrom(static_cast<const void*>(&ts), sizeof(uint64_t));
-  Reader reader(TraceBlobView(std::move(blob)));
-
-  auto parsed_sample = tracker->ParseSample(reader, sample_type);
-  EXPECT_TRUE(parsed_sample.ok());
-  EXPECT_EQ(parsed_sample->ts, 100u);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleCallchain) {
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
-  uint64_t sample_type = PERF_SAMPLE_CALLCHAIN;
-
-  struct Sample {
-    uint64_t callchain_size;         /* if PERF_SAMPLE_CALLCHAIN */
-    std::vector<uint64_t> callchain; /* if PERF_SAMPLE_CALLCHAIN */
-  };
-
-  Sample sample;
-  sample.callchain_size = 3;
-  sample.callchain = std::vector<uint64_t>{1, 2, 3};
-
-  TraceBlob blob = TraceBlob::Allocate(4 * sizeof(uint64_t));
-  memcpy(blob.data(), &sample.callchain_size, sizeof(uint64_t));
-  memcpy(blob.data() + sizeof(uint64_t), sample.callchain.data(),
-         sizeof(uint64_t) * 3);
-  Reader reader(TraceBlobView(std::move(blob)));
-
-  auto parsed_sample = tracker->ParseSample(reader, sample_type);
-  EXPECT_TRUE(parsed_sample.ok());
-  EXPECT_EQ(parsed_sample->callchain.size(), 3u);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleWithoutId) {
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
-  uint64_t sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU |
-                         PERF_SAMPLE_CALLCHAIN;
-
-  struct Sample {
-    uint32_t pid;            /* if PERF_SAMPLE_TID */
-    uint32_t tid;            /* if PERF_SAMPLE_TID */
-    uint64_t ts;             /* if PERF_SAMPLE_TIME */
-    uint32_t cpu;            /* if PERF_SAMPLE_CPU */
-    uint32_t res_ignore;     /* if PERF_SAMPLE_CPU */
-    uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */
-  };
-
-  Sample sample;
-  sample.pid = 2;
-  sample.ts = 100;
-  sample.cpu = 1;
-  sample.callchain_size = 3;
-  std::vector<uint64_t> callchain{1, 2, 3};
-
-  TraceBlob blob = TraceBlob::Allocate(sizeof(Sample) + sizeof(uint64_t) * 3);
-  memcpy(blob.data(), &sample, sizeof(Sample));
-  memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3);
-
-  Reader reader(TraceBlobView(std::move(blob)));
-  EXPECT_TRUE(reader.size_left() >= sizeof(Sample));
-
-  auto parsed_sample = tracker->ParseSample(reader, sample_type);
-  EXPECT_TRUE(parsed_sample.ok());
-  EXPECT_EQ(parsed_sample->callchain.size(), 3u);
-  EXPECT_EQ(sample.ts, parsed_sample->ts);
-}
-
-TEST_F(PerfDataTrackerUnittest, ParseSampleWithId) {
-  PerfDataTracker* tracker = PerfDataTracker::GetOrCreate(&context_);
-
-  uint64_t sample_type = PERF_SAMPLE_CPU | PERF_SAMPLE_TID |
-                         PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_ID |
-                         PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_TIME;
-
-  struct Sample {
-    uint64_t identifier;     /* if PERF_SAMPLE_IDENTIFIER */
-    uint32_t pid;            /* if PERF_SAMPLE_TID */
-    uint32_t tid;            /* if PERF_SAMPLE_TID */
-    uint64_t ts;             /* if PERF_SAMPLE_TIME */
-    uint64_t id;             /* if PERF_SAMPLE_ID */
-    uint32_t cpu;            /* if PERF_SAMPLE_CPU */
-    uint32_t res_ignore;     /* if PERF_SAMPLE_CPU */
-    uint64_t callchain_size; /* if PERF_SAMPLE_CALLCHAIN */
-  };
-
-  Sample sample;
-  sample.id = 10;
-  sample.identifier = 10;
-  sample.cpu = 1;
-  sample.pid = 2;
-  sample.ts = 100;
-  sample.callchain_size = 3;
-  std::vector<uint64_t> callchain{1, 2, 3};
-
-  TraceBlob blob = TraceBlob::Allocate(sizeof(Sample) + sizeof(uint64_t) * 3);
-  memcpy(blob.data(), &sample, sizeof(Sample));
-  memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3);
-
-  Reader reader(TraceBlobView(std::move(blob)));
-
-  auto parsed_sample = tracker->ParseSample(reader, sample_type);
-  EXPECT_TRUE(parsed_sample.ok());
-  EXPECT_EQ(parsed_sample->callchain.size(), 3u);
-  EXPECT_EQ(100u, parsed_sample->ts);
-}
-
-}  // namespace
-}  // namespace perf_importer
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/importers/perf/perf_event.h b/src/trace_processor/importers/perf/perf_event.h
index 4763e23..c45f4de 100644
--- a/src/trace_processor/importers/perf/perf_event.h
+++ b/src/trace_processor/importers/perf/perf_event.h
@@ -238,6 +238,7 @@
   PERF_RECORD_MISC_GUEST_USER = 5,
 
   PERF_RECORD_MISC_MMAP_BUILD_ID = 1U << 14,
+  PERF_RECORD_MISC_EXT_RESERVED = 1U << 15,
 };
 
 enum perf_event_read_format {
diff --git a/src/trace_processor/importers/perf/perf_event_attr.cc b/src/trace_processor/importers/perf/perf_event_attr.cc
index 1abf8d0..f00f4eb 100644
--- a/src/trace_processor/importers/perf/perf_event_attr.cc
+++ b/src/trace_processor/importers/perf/perf_event_attr.cc
@@ -21,7 +21,10 @@
 #include <cstring>
 #include <optional>
 
+#include "src/trace_processor/importers/perf/perf_counter.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
 
 namespace perfetto::trace_processor::perf_importer {
 
@@ -90,11 +93,36 @@
 }
 }  // namespace
 
-PerfEventAttr::PerfEventAttr(perf_event_attr attr)
-    : attr_(std::move(attr)),
+PerfEventAttr::PerfEventAttr(TraceProcessorContext* context,
+                             uint32_t perf_session_id,
+                             perf_event_attr attr)
+    : context_(context),
+      perf_session_id_(perf_session_id),
+      attr_(std::move(attr)),
       time_offset_from_start_(TimeOffsetFromStartOfSampleRecord(attr_)),
       time_offset_from_end_(TimeOffsetFromEndOfNonSampleRecord(attr_)),
       id_offset_from_start_(IdOffsetFromStartOfSampleRecord(attr_)),
       id_offset_from_end_(IdOffsetFromEndOfNonSampleRecord(attr_)) {}
 
+PerfEventAttr::~PerfEventAttr() = default;
+
+PerfCounter& PerfEventAttr::GetOrCreateCounter(uint32_t cpu) const {
+  auto it = counters_.find(cpu);
+  if (it == counters_.end()) {
+    it = counters_.emplace(cpu, CreateCounter(cpu)).first;
+  }
+  return it->second;
+}
+
+PerfCounter PerfEventAttr::CreateCounter(uint32_t cpu) const {
+  return PerfCounter(context_->storage->mutable_counter_table(),
+                     context_->storage->mutable_perf_counter_track_table()
+                         ->Insert({context_->storage->InternString(""),
+                                   std::nullopt, std::nullopt, std::nullopt,
+                                   context_->storage->InternString(""),
+                                   context_->storage->InternString(""),
+                                   perf_session_id_, cpu, is_timebase()})
+                         .row_reference);
+}
+
 }  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/perf_event_attr.h b/src/trace_processor/importers/perf/perf_event_attr.h
index 4f219aa..77e52f4 100644
--- a/src/trace_processor/importers/perf/perf_event_attr.h
+++ b/src/trace_processor/importers/perf/perf_event_attr.h
@@ -21,17 +21,26 @@
 #include <cstddef>
 #include <cstdint>
 #include <optional>
+#include <unordered_map>
 
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/trace_processor/ref_counted.h"
+#include "src/trace_processor/importers/perf/perf_counter.h"
 #include "src/trace_processor/importers/perf/perf_event.h"
 
-namespace perfetto::trace_processor::perf_importer {
+namespace perfetto::trace_processor {
+
+class TraceProcessorContext;
+
+namespace perf_importer {
 
 // Wrapper around a `perf_event_attr` object that add some helper methods.
 class PerfEventAttr : public RefCounted {
  public:
-  explicit PerfEventAttr(perf_event_attr attr);
+  PerfEventAttr(TraceProcessorContext* context,
+                uint32_t perf_session_id_,
+                perf_event_attr attr);
+  ~PerfEventAttr();
   uint64_t sample_type() const { return attr_.sample_type; }
   uint64_t read_format() const { return attr_.read_format; }
   bool sample_id_all() const { return !!attr_.sample_id_all; }
@@ -48,12 +57,6 @@
     return attr_.freq ? std::make_optional(attr_.sample_freq) : std::nullopt;
   }
 
-  bool is_timebase() const {
-    // This is what simpleperf uses for events that are not supposed to sample
-    // TODO(b/334978369): Determine if there is a better way to figure this out.
-    return attr_.sample_period < (1ull << 62);
-  }
-
   // Offset from the end of a record's payload to the time filed (if present).
   // To be used with non `PERF_RECORD_SAMPLE` records
   std::optional<size_t> time_offset_from_end() const {
@@ -81,14 +84,28 @@
     return id_offset_from_end_;
   }
 
+  PerfCounter& GetOrCreateCounter(uint32_t cpu) const;
+
  private:
+  bool is_timebase() const {
+    // This is what simpleperf uses for events that are not supposed to sample
+    // TODO(b/334978369): Determine if there is a better way to figure this out.
+    return attr_.sample_period < (1ull << 62);
+  }
+
+  PerfCounter CreateCounter(uint32_t cpu) const;
+
+  TraceProcessorContext* const context_;
+  uint32_t perf_session_id_;
   perf_event_attr attr_;
   std::optional<size_t> time_offset_from_start_;
   std::optional<size_t> time_offset_from_end_;
   std::optional<size_t> id_offset_from_start_;
   std::optional<size_t> id_offset_from_end_;
+  mutable std::unordered_map<uint32_t, PerfCounter> counters_;
 };
 
-}  // namespace perfetto::trace_processor::perf_importer
+}  // namespace perf_importer
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_EVENT_ATTR_H_
diff --git a/src/trace_processor/importers/perf/perf_session.cc b/src/trace_processor/importers/perf/perf_session.cc
index 86d983d..7619066 100644
--- a/src/trace_processor/importers/perf/perf_session.cc
+++ b/src/trace_processor/importers/perf/perf_session.cc
@@ -46,11 +46,13 @@
     return base::ErrStatus("No perf_event_attr");
   }
 
-  const PerfEventAttr base_attr(attr_with_ids_[0].attr);
+  const PerfEventAttr base_attr(context_, perf_session_id_,
+                                attr_with_ids_[0].attr);
 
   base::FlatHashMap<uint64_t, RefPtr<PerfEventAttr>> attrs_by_id;
   for (const auto& entry : attr_with_ids_) {
-    RefPtr<PerfEventAttr> attr(new PerfEventAttr(entry.attr));
+    RefPtr<PerfEventAttr> attr(
+        new PerfEventAttr(context_, perf_session_id_, entry.attr));
     if (base_attr.sample_id_all() != attr->sample_id_all()) {
       return base::ErrStatus(
           "perf_event_attr with different sample_id_all values");
diff --git a/src/trace_processor/importers/perf/perf_session.h b/src/trace_processor/importers/perf/perf_session.h
index abea41b..eb509d0 100644
--- a/src/trace_processor/importers/perf/perf_session.h
+++ b/src/trace_processor/importers/perf/perf_session.h
@@ -20,7 +20,6 @@
 #include <sys/types.h>
 #include <cstddef>
 #include <cstdint>
-#include <optional>
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
@@ -29,15 +28,19 @@
 #include "src/trace_processor/importers/perf/perf_event.h"
 #include "src/trace_processor/importers/perf/perf_event_attr.h"
 
-namespace perfetto::trace_processor::perf_importer {
+namespace perfetto::trace_processor {
+
+class TraceProcessorContext;
+
+namespace perf_importer {
 
 // Helper to deal with perf_event_attr instances in a perf file.
 class PerfSession : public RefCounted {
  public:
   class Builder {
    public:
-    explicit Builder(uint32_t perf_session_id)
-        : perf_session_id_(perf_session_id) {}
+    Builder(TraceProcessorContext* context, uint32_t perf_session_id)
+        : context_(context), perf_session_id_(perf_session_id) {}
     base::StatusOr<RefPtr<PerfSession>> Build();
     Builder& AddAttrAndIds(perf_event_attr attr, std::vector<uint64_t> ids) {
       attr_with_ids_.push_back({std::move(attr), std::move(ids)});
@@ -50,6 +53,7 @@
       std::vector<uint64_t> ids;
     };
 
+    TraceProcessorContext* const context_;
     uint32_t perf_session_id_;
     std::vector<PerfEventAttrWithIds> attr_with_ids_;
   };
@@ -83,6 +87,7 @@
   bool has_single_perf_event_attr_;
 };
 
-}  // namespace perfetto::trace_processor::perf_importer
+}  // namespace perf_importer
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_PERF_SESSION_H_
diff --git a/src/trace_processor/importers/perf/perf_session_unittest.cc b/src/trace_processor/importers/perf/perf_session_unittest.cc
index 0ab85f2..fd6bfa3 100644
--- a/src/trace_processor/importers/perf/perf_session_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_session_unittest.cc
@@ -41,12 +41,12 @@
 }
 
 TEST(PerfSessionTest, NoAttrBuildFails) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   EXPECT_FALSE(builder.Build().ok());
 }
 
 TEST(PerfSessionTest, OneAttrAndNoIdBuildSucceeds) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = false;
   attr.sample_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
@@ -61,7 +61,7 @@
 }
 
 TEST(PerfSessionTest, MultipleAttrsAndNoIdBuildFails) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
@@ -71,7 +71,7 @@
 }
 
 TEST(PerfSessionTest, MultipleIdsSameAttrAndNoIdCanExtractAttrFromRecord) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_CPU | PERF_SAMPLE_TIME;
@@ -95,7 +95,7 @@
 }
 
 TEST(PerfSessionTest, NoCommonSampleIdAllBuildFails) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_IDENTIFIER;
@@ -111,7 +111,7 @@
 }
 
 TEST(PerfSessionTest, NoCommonOffsetForSampleBuildFails) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID;
@@ -122,7 +122,7 @@
 }
 
 TEST(PerfSessionTest, NoCommonOffsetForNonSampleBuildFails) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_ID | PERF_SAMPLE_TID;
@@ -138,7 +138,7 @@
 }
 
 TEST(PerfSessionTest, NoCommonOffsetForNonSampleAndNoSampleIdAllBuildSucceeds) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = false;
   attr.sample_type = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TID;
@@ -149,7 +149,7 @@
 }
 
 TEST(PerfSessionTest, MultiplesessionBuildSucceeds) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID;
@@ -159,7 +159,7 @@
 }
 
 TEST(PerfSessionTest, FindAttrInRecordWithId) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ID;
@@ -194,7 +194,7 @@
 }
 
 TEST(PerfSessionTest, FindAttrInRecordWithIdentifier) {
-  PerfSession::Builder builder(0);
+  PerfSession::Builder builder(nullptr, 0);
   perf_event_attr attr;
   attr.sample_id_all = true;
   attr.sample_type = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP;
diff --git a/src/trace_processor/importers/perf/reader.h b/src/trace_processor/importers/perf/reader.h
index faf31a2..91a4253 100644
--- a/src/trace_processor/importers/perf/reader.h
+++ b/src/trace_processor/importers/perf/reader.h
@@ -26,7 +26,9 @@
 #include <type_traits>
 #include <vector>
 
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
 
 namespace perfetto::trace_processor::perf_importer {
 
@@ -45,6 +47,49 @@
   // methods are called.
   size_t size_left() const { return static_cast<size_t>(end_ - current_); }
 
+  bool ReadStringView(base::StringView& str, size_t size) {
+    if (size_left() < size) {
+      return false;
+    }
+    str = base::StringView(reinterpret_cast<const char*>(current_), size);
+    current_ += size;
+    return true;
+  }
+
+  bool ReadPerfEventAttr(perf_event_attr& attr, size_t attr_size) {
+    const size_t bytes_to_read = std::min(attr_size, sizeof(attr));
+    const size_t bytes_to_skip = attr_size - bytes_to_read;
+    static_assert(std::has_unique_object_representations_v<perf_event_attr>);
+
+    if (size_left() < bytes_to_read + bytes_to_skip) {
+      return false;
+    }
+
+    memset(&attr, 0, sizeof(attr));
+
+    return Read(&attr, bytes_to_read) && Skip(bytes_to_skip);
+  }
+
+  bool ReadBlob(TraceBlobView& blob, uint32_t size) {
+    if (size_left() < size) {
+      return false;
+    }
+    blob = TraceBlobView(buffer_, static_cast<size_t>(end_ - current_), size);
+    current_ += size;
+    return true;
+  }
+
+  bool ReadStringUntilEndOrNull(std::string& out) {
+    const uint8_t* ptr = current_;
+    while (ptr != end_ && *ptr != 0) {
+      ++ptr;
+    }
+    out = std::string(reinterpret_cast<const char*>(current_),
+                      static_cast<size_t>(ptr - current_));
+    current_ = ptr;
+    return true;
+  }
+
   template <typename T>
   bool Read(T& obj) {
     static_assert(std::has_unique_object_representations_v<T>);
diff --git a/src/trace_processor/importers/perf/record_parser.cc b/src/trace_processor/importers/perf/record_parser.cc
index 847adb7..7371adf 100644
--- a/src/trace_processor/importers/perf/record_parser.cc
+++ b/src/trace_processor/importers/perf/record_parser.cc
@@ -16,45 +16,98 @@
 
 #include "src/trace_processor/importers/perf/record_parser.h"
 
+#include <cstdint>
 #include <optional>
 #include <string>
 #include <vector>
+
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
+#include "perfetto/trace_processor/ref_counted.h"
 #include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
+#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/perf/perf_counter.h"
+#include "src/trace_processor/importers/perf/perf_event.h"
+#include "src/trace_processor/importers/perf/perf_event_attr.h"
 #include "src/trace_processor/importers/perf/reader.h"
 #include "src/trace_processor/importers/perf/record.h"
+#include "src/trace_processor/importers/perf/sample.h"
+#include "src/trace_processor/importers/proto/perf_sample_tracker.h"
+#include "src/trace_processor/importers/proto/profile_packet_utils.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/util/build_id.h"
 #include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace perf_importer {
+namespace {
+
+CreateMappingParams BuildCreateMappingParams(
+    const CommonMmapRecordFields& fields,
+    std::string filename,
+    std::optional<BuildId> build_id = std::nullopt) {
+  return {AddressRange::FromStartAndSize(fields.addr, fields.len), fields.pgoff,
+          // start_offset: This is the offset into the file where the ELF header
+          // starts. We assume all file mappings are ELF files an thus this
+          // offset is 0.
+          0,
+          // load_bias: This can only be read out of the actual ELF file, which
+          // we do not have here, so we set it to 0. When symbolizing we will
+          // hopefully have the real load bias and we can compensate there for a
+          // possible mismatch.
+          0, std::move(filename), std::move(build_id)};
+}
+
+bool IsInKernel(protos::pbzero::Profiling::CpuMode cpu_mode) {
+  switch (cpu_mode) {
+    case protos::pbzero::Profiling::MODE_UNKNOWN:
+      PERFETTO_FATAL("Unknown CPU mode");
+    case protos::pbzero::Profiling::MODE_GUEST_KERNEL:
+    case protos::pbzero::Profiling::MODE_KERNEL:
+      return true;
+    case protos::pbzero::Profiling::MODE_USER:
+    case protos::pbzero::Profiling::MODE_HYPERVISOR:
+    case protos::pbzero::Profiling::MODE_GUEST_USER:
+      return false;
+  }
+  PERFETTO_FATAL("For GCC.");
+}
+
+}  // namespace
 
 using FramesTable = tables::StackProfileFrameTable;
 using CallsitesTable = tables::StackProfileCallsiteTable;
 
 RecordParser::RecordParser(TraceProcessorContext* context)
-    : context_(context), tracker_(PerfDataTracker::GetOrCreate(context_)) {}
+    : context_(context) {}
 
 RecordParser::~RecordParser() = default;
 
 void RecordParser::ParsePerfRecord(int64_t ts, Record record) {
   if (base::Status status = ParseRecord(ts, std::move(record)); !status.ok()) {
-    context_->storage->IncrementStats(stats::perf_record_skipped);
+    context_->storage->IncrementStats(record.header.type == PERF_RECORD_SAMPLE
+                                          ? stats::perf_samples_skipped
+                                          : stats::perf_record_skipped);
   }
 }
 
 base::Status RecordParser::ParseRecord(int64_t ts, Record record) {
   switch (record.header.type) {
+    case PERF_RECORD_COMM:
+      return ParseComm(std::move(record));
+
     case PERF_RECORD_SAMPLE:
       return ParseSample(ts, std::move(record));
 
+    case PERF_RECORD_MMAP:
+      return ParseMmap(std::move(record));
+
     case PERF_RECORD_MMAP2:
       return ParseMmap2(std::move(record));
 
@@ -62,115 +115,193 @@
     case PERF_RECORD_AUXTRACE:
     case PERF_RECORD_AUXTRACE_INFO:
       // These should be dealt with at tokenization time
-      PERFETTO_CHECK(false);
+      PERFETTO_FATAL("Unexpected record type at parsing time: %" PRIu32,
+                     record.header.type);
 
     default:
-      PERFETTO_ELOG("Unknown PERF_RECORD with type %" PRIu32,
-                    record.header.type);
+      context_->storage->IncrementIndexedStats(
+          stats::perf_unknown_record_type,
+          static_cast<int>(record.header.type));
+      return base::ErrStatus("Unknown PERF_RECORD with type %" PRIu32,
+                             record.header.type);
   }
-  return base::OkStatus();
 }
 
 base::Status RecordParser::ParseSample(int64_t ts, Record record) {
-  PERFETTO_CHECK(record.attr);
+  Sample sample;
+  RETURN_IF_ERROR(sample.Parse(ts, record));
 
-  Reader reader(record.payload.copy());
-  ASSIGN_OR_RETURN(PerfDataTracker::PerfSample sample,
-                   tracker_->ParseSample(reader, record.attr->sample_type()));
-
-  // The sample has been validated in tokenizer so callchain shouldn't be empty.
-  PERFETTO_CHECK(!sample.callchain.empty());
-
-  // First instruction pointer in the callchain should be from kernel space, so
-  // it shouldn't be available in mappings.
-  UniquePid upid = context_->process_tracker->GetOrCreateProcess(*sample.pid);
-  if (context_->mapping_tracker->FindUserMappingForAddress(
-          upid, sample.callchain.front())) {
-    context_->storage->IncrementStats(stats::perf_samples_skipped);
-    return base::ErrStatus(
-        "Expected kernel mapping for first instruction pointer, but user space "
-        "found.");
+  if (!sample.period.has_value() && record.attr != nullptr) {
+    sample.period = record.attr->sample_period();
   }
 
-  if (sample.callchain.size() == 1) {
-    context_->storage->IncrementStats(stats::perf_samples_skipped);
-    return base::ErrStatus("Invalid callchain size of 1.");
+  return InternSample(std::move(sample));
+}
+
+base::Status RecordParser::InternSample(Sample sample) {
+  if (!sample.time.has_value()) {
+    // We do not really use this TS as this is using the perf clock, but we need
+    // it to be present so that we can compute the trace_ts done during
+    // tokenization. (Actually at tokenization time we do estimate a trace_ts if
+    // no perf ts is present, but for samples we want this to be as accurate as
+    // possible)
+    base::ErrStatus("Can not parse samples with no PERF_SAMPLE_TIME field");
   }
 
-  std::vector<FramesTable::Row> frame_rows;
-  for (uint32_t i = 1; i < sample.callchain.size(); i++) {
-    UserMemoryMapping* mapping =
-        context_->mapping_tracker->FindUserMappingForAddress(
-            upid, sample.callchain[i]);
-    if (!mapping) {
-      context_->storage->IncrementStats(stats::perf_samples_skipped);
-      return base::ErrStatus("Did not find mapping for address %" PRIu64
-                             " in process with upid %" PRIu32,
-                             sample.callchain[i], upid);
+  if (!sample.pid_tid.has_value()) {
+    base::ErrStatus("Can not parse samples with no PERF_SAMPLE_TID field");
+  }
+
+  if (!sample.cpu.has_value()) {
+    base::ErrStatus("Can not parse samples with no PERF_SAMPLE_CPU field");
+  }
+
+  UniqueTid utid = context_->process_tracker->UpdateThread(sample.pid_tid->tid,
+                                                           sample.pid_tid->pid);
+  const auto upid = *context_->storage->thread_table()
+                         .FindById(tables::ThreadTable::Id(utid))
+                         ->upid();
+
+  if (sample.callchain.empty() && sample.ip.has_value()) {
+    sample.callchain.push_back(Sample::Frame{sample.cpu_mode, *sample.ip});
+  }
+  std::optional<CallsiteId> callsite_id =
+      InternCallchain(upid, sample.callchain);
+
+  context_->storage->mutable_perf_sample_table()->Insert(
+      {sample.trace_ts, utid, *sample.cpu,
+       context_->storage->InternString(
+           ProfilePacketUtils::StringifyCpuMode(sample.cpu_mode)),
+       callsite_id, std::nullopt, sample.perf_session->perf_session_id()});
+
+  return UpdateCounters(sample);
+}
+
+std::optional<CallsiteId> RecordParser::InternCallchain(
+    UniquePid upid,
+    const std::vector<Sample::Frame>& callchain) {
+  if (callchain.empty()) {
+    return std::nullopt;
+  }
+
+  auto& stack_profile_tracker = *context_->stack_profile_tracker;
+  auto& mapping_tracker = *context_->mapping_tracker;
+
+  std::optional<CallsiteId> parent;
+  uint32_t depth = 0;
+  for (auto it = callchain.rbegin(); it != callchain.rend(); ++it) {
+    VirtualMemoryMapping* mapping;
+    if (IsInKernel(it->cpu_mode)) {
+      mapping = mapping_tracker.FindKernelMappingForAddress(it->ip);
+    } else {
+      mapping = mapping_tracker.FindUserMappingForAddress(upid, it->ip);
     }
-    FramesTable::Row new_row;
-    std::string mock_name =
-        base::StackString<1024>(
-            "%" PRIu64, sample.callchain[i] - mapping->memory_range().start())
-            .ToStdString();
-    new_row.name = context_->storage->InternString(mock_name.c_str());
-    new_row.mapping = mapping->mapping_id();
-    new_row.rel_pc =
-        static_cast<int64_t>(mapping->ToRelativePc(sample.callchain[i]));
-    frame_rows.push_back(new_row);
+
+    if (!mapping) {
+      context_->storage->IncrementStats(stats::perf_dummy_mapping_used);
+      // Simpleperf will not create mappings for anonymous executable mappings
+      // which are used by JITted code (e.g. V8 JavaScript).
+      mapping = mapping_tracker.GetDummyMapping();
+    }
+
+    const FrameId frame_id =
+        mapping->InternFrame(mapping->ToRelativePc(it->ip), "");
+
+    parent = stack_profile_tracker.InternCallsite(parent, frame_id, depth);
+    depth++;
+  }
+  return parent;
+}
+
+base::Status RecordParser::ParseComm(Record record) {
+  Reader reader(record.payload.copy());
+  uint32_t pid;
+  uint32_t tid;
+  std::string comm;
+  if (!reader.Read(pid) || !reader.Read(tid) || !reader.ReadCString(comm)) {
+    return base::ErrStatus("Failed to parse PERF_RECORD_COMM");
   }
 
-  // Insert frames. We couldn't do it before as no frames should be added if the
-  // mapping couldn't be found for any of them.
-  const auto& frames = context_->storage->mutable_stack_profile_frame_table();
-  std::vector<FramesTable::Id> frame_ids;
-  for (const auto& row : frame_rows) {
-    frame_ids.push_back(frames->Insert(row).id);
-  }
-
-  // Insert callsites.
-  const auto& callsites =
-      context_->storage->mutable_stack_profile_callsite_table();
-
-  std::optional<CallsitesTable::Id> parent_callsite_id;
-  for (uint32_t i = 0; i < frame_ids.size(); i++) {
-    CallsitesTable::Row callsite_row;
-    callsite_row.frame_id = frame_ids[i];
-    callsite_row.depth = i;
-    callsite_row.parent_id = parent_callsite_id;
-    parent_callsite_id = callsites->Insert(callsite_row).id;
-  }
-
-  // Insert stack sample.
-  tables::PerfSampleTable::Row perf_sample_row;
-  perf_sample_row.callsite_id = parent_callsite_id;
-  perf_sample_row.ts = ts;
-  if (sample.cpu) {
-    perf_sample_row.cpu = *sample.cpu;
-  }
-  if (sample.tid) {
-    auto utid = context_->process_tracker->GetOrCreateThread(*sample.tid);
-    perf_sample_row.utid = utid;
-  }
-  context_->storage->mutable_perf_sample_table()->Insert(perf_sample_row);
+  context_->process_tracker->UpdateThread(tid, pid);
+  context_->process_tracker->UpdateThreadName(
+      tid, context_->storage->InternString(base::StringView(comm)),
+      ThreadNamePriority::kFtrace);
 
   return base::OkStatus();
 }
 
-base::Status RecordParser::ParseMmap2(Record record) {
-  Reader reader(record.payload.copy());
-  PerfDataTracker::Mmap2Record mmap2;
-  reader.Read(mmap2.num);
-  std::vector<char> filename_buffer(reader.size_left());
-  reader.ReadVector(filename_buffer);
-  if (filename_buffer.back() != '\0') {
-    return base::ErrStatus(
-        "Invalid MMAP2 record: filename is not null terminated.");
+base::Status RecordParser::ParseMmap(Record record) {
+  MmapRecord mmap;
+  RETURN_IF_ERROR(mmap.Parse(record));
+  if (IsInKernel(record.GetCpuMode())) {
+    context_->mapping_tracker->CreateKernelMemoryMapping(
+        BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+    return base::OkStatus();
   }
-  mmap2.filename = std::string(filename_buffer.begin(), filename_buffer.end());
-  PERFETTO_CHECK(reader.size_left() == 0);
-  mmap2.cpu_mode = record.GetCpuMode();
-  tracker_->PushMmap2Record(std::move(mmap2));
+
+  context_->mapping_tracker->CreateUserMemoryMapping(
+      GetUpid(mmap), BuildCreateMappingParams(mmap, std::move(mmap.filename)));
+
+  return base::OkStatus();
+}
+
+util::Status RecordParser::ParseMmap2(Record record) {
+  Mmap2Record mmap2;
+  RETURN_IF_ERROR(mmap2.Parse(record));
+  if (IsInKernel(record.GetCpuMode())) {
+    context_->mapping_tracker->CreateKernelMemoryMapping(
+        BuildCreateMappingParams(mmap2, std::move(mmap2.filename)));
+    return base::OkStatus();
+  }
+
+  context_->mapping_tracker->CreateUserMemoryMapping(
+      GetUpid(mmap2), BuildCreateMappingParams(mmap2, std::move(mmap2.filename),
+                                               mmap2.GetBuildId()));
+
+  return base::OkStatus();
+}
+
+UniquePid RecordParser::GetUpid(const CommonMmapRecordFields& fields) const {
+  UniqueTid utid =
+      context_->process_tracker->UpdateThread(fields.tid, fields.pid);
+  auto upid = context_->storage->thread_table()
+                  .FindById(tables::ThreadTable::Id(utid))
+                  ->upid();
+  PERFETTO_CHECK(upid.has_value());
+  return *upid;
+}
+
+base::Status RecordParser::UpdateCounters(const Sample& sample) {
+  if (!sample.read_groups.empty()) {
+    return UpdateCountersInReadGroups(sample);
+  }
+
+  if (!sample.period.has_value() && !sample.attr->sample_period().has_value()) {
+    return base::ErrStatus("No period for sample");
+  }
+
+  uint64_t period = sample.period.has_value() ? *sample.period
+                                              : *sample.attr->sample_period();
+  sample.attr->GetOrCreateCounter(*sample.cpu)
+      .AddDelta(sample.trace_ts, static_cast<double>(period));
+  return base::OkStatus();
+}
+
+base::Status RecordParser::UpdateCountersInReadGroups(const Sample& sample) {
+  if (!sample.cpu.has_value()) {
+    return base::ErrStatus("No cpu for sample");
+  }
+
+  for (const auto& entry : sample.read_groups) {
+    RefPtr<const PerfEventAttr> attr =
+        sample.perf_session->FindAttrForEventId(*entry.event_id);
+    if (PERFETTO_UNLIKELY(!attr)) {
+      return base::ErrStatus("No perf_event_attr for id %" PRIu64,
+                             *entry.event_id);
+    }
+    attr->GetOrCreateCounter(*sample.cpu)
+        .AddCount(sample.trace_ts, static_cast<double>(entry.value));
+  }
   return base::OkStatus();
 }
 
diff --git a/src/trace_processor/importers/perf/record_parser.h b/src/trace_processor/importers/perf/record_parser.h
index bae837a..0a71b49 100644
--- a/src/trace_processor/importers/perf/record_parser.h
+++ b/src/trace_processor/importers/perf/record_parser.h
@@ -18,14 +18,26 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_RECORD_PARSER_H_
 
 #include <stdint.h>
+#include <cstdint>
+#include <vector>
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/importers/common/trace_parser.h"
-#include "src/trace_processor/importers/perf/perf_data_tracker.h"
+#include "src/trace_processor/importers/perf/mmap_record.h"
+#include "src/trace_processor/importers/perf/record.h"
+#include "src/trace_processor/importers/perf/sample.h"
+#include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
+
+class TraceProcessorContext;
+
 namespace perf_importer {
 
+class PerfDataTracker;
+class Reader;
+
 // Parses samples from perf.data files.
 class RecordParser : public PerfRecordParser {
  public:
@@ -37,10 +49,22 @@
  private:
   base::Status ParseRecord(int64_t timestamp, Record record);
   base::Status ParseSample(int64_t ts, Record record);
+  base::Status ParseComm(Record record);
+  base::Status ParseMmap(Record record);
   base::Status ParseMmap2(Record record);
 
+  base::Status InternSample(Sample sample);
+
+  base::Status UpdateCounters(const Sample& sample);
+  base::Status UpdateCountersInReadGroups(const Sample& sample);
+
+  std::optional<CallsiteId> InternCallchain(
+      UniquePid upid,
+      const std::vector<Sample::Frame>& callchain);
+
+  UniquePid GetUpid(const CommonMmapRecordFields& fields) const;
+
   TraceProcessorContext* context_ = nullptr;
-  PerfDataTracker* tracker_ = nullptr;
 };
 
 }  // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/sample.cc b/src/trace_processor/importers/perf/sample.cc
new file mode 100644
index 0000000..63ded53
--- /dev/null
+++ b/src/trace_processor/importers/perf/sample.cc
@@ -0,0 +1,252 @@
+/*
+ * 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/importers/perf/sample.h"
+
+#include <cstdint>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/importers/perf/reader.h"
+#include "src/trace_processor/importers/perf/record.h"
+
+namespace perfetto::trace_processor::perf_importer {
+namespace {
+
+bool ParseSampleReadGroup(Reader& reader,
+                          uint64_t read_format,
+                          uint64_t num_records,
+                          std::vector<Sample::ReadGroup>& out) {
+  out.resize(num_records);
+  for (auto& read : out) {
+    if (PERFETTO_UNLIKELY(!reader.Read(read.value))) {
+      return false;
+    }
+
+    if (read_format & PERF_FORMAT_ID) {
+      if (PERFETTO_UNLIKELY(!reader.ReadOptional(read.event_id))) {
+        return false;
+      }
+    }
+
+    if (read_format & PERF_FORMAT_LOST) {
+      uint64_t lost;
+      if (PERFETTO_UNLIKELY(!reader.Read(lost))) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool ParseSampleRead(Reader& reader,
+                     uint64_t read_format,
+                     std::vector<Sample::ReadGroup>& out) {
+  uint64_t value_or_nr;
+
+  if (PERFETTO_UNLIKELY(!reader.Read(value_or_nr))) {
+    return false;
+  }
+
+  if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) {
+    uint64_t total_time_enabled;
+    if (PERFETTO_UNLIKELY(!reader.Read(total_time_enabled))) {
+      return false;
+    }
+  }
+
+  if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) {
+    uint64_t total_time_running;
+    if (PERFETTO_UNLIKELY(!reader.Read(total_time_running))) {
+      return false;
+    }
+  }
+
+  if (read_format & PERF_FORMAT_GROUP) {
+    return ParseSampleReadGroup(reader, read_format, value_or_nr, out);
+  }
+
+  std::optional<uint64_t> event_id;
+  if (read_format & PERF_FORMAT_ID) {
+    event_id.emplace(0);
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(event_id))) {
+      return false;
+    }
+  }
+
+  if (read_format & PERF_FORMAT_LOST) {
+    uint64_t lost;
+    if (PERFETTO_UNLIKELY(!reader.Read(lost))) {
+      return false;
+    }
+  }
+
+  out.push_back({event_id, value_or_nr});
+
+  return true;
+}
+
+protos::pbzero::Profiling::CpuMode PerfCallchainContextToCpuMode(uint64_t ip) {
+  switch (ip) {
+    case PERF_CONTEXT_HV:
+      return protos::pbzero::Profiling::MODE_HYPERVISOR;
+    case PERF_CONTEXT_KERNEL:
+      return protos::pbzero::Profiling::MODE_KERNEL;
+    case PERF_CONTEXT_USER:
+      return protos::pbzero::Profiling::MODE_USER;
+    case PERF_CONTEXT_GUEST_KERNEL:
+      return protos::pbzero::Profiling::MODE_GUEST_KERNEL;
+    case PERF_CONTEXT_GUEST_USER:
+      return protos::pbzero::Profiling::MODE_GUEST_USER;
+    case PERF_CONTEXT_GUEST:
+    default:
+      return protos::pbzero::Profiling::MODE_UNKNOWN;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+bool IsPerfContextMark(uint64_t ip) {
+  return ip >= PERF_CONTEXT_MAX;
+}
+
+bool ParseSampleCallchain(Reader& reader,
+                          protos::pbzero::Profiling::CpuMode cpu_mode,
+                          std::vector<Sample::Frame>& out) {
+  uint64_t nr;
+  if (PERFETTO_UNLIKELY(!reader.Read(nr))) {
+    return false;
+  }
+
+  std::vector<Sample::Frame> frames;
+  frames.reserve(nr);
+  for (; nr != 0; --nr) {
+    uint64_t ip;
+    if (PERFETTO_UNLIKELY(!reader.Read(ip))) {
+      return false;
+    }
+    if (PERFETTO_UNLIKELY(IsPerfContextMark(ip))) {
+      cpu_mode = PerfCallchainContextToCpuMode(ip);
+      continue;
+    }
+    frames.push_back({cpu_mode, ip});
+  }
+
+  out = std::move(frames);
+  return true;
+}
+}  // namespace
+
+base::Status Sample::Parse(int64_t in_trace_ts, const Record& record) {
+  PERFETTO_CHECK(record.attr);
+  const uint64_t sample_type = record.attr->sample_type();
+
+  trace_ts = in_trace_ts;
+  cpu_mode = record.GetCpuMode();
+  perf_session = record.session;
+  attr = record.attr;
+
+  Reader reader(record.payload.copy());
+
+  std::optional<uint64_t> identifier;
+  if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(identifier))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_IDENTIFIER");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_IP) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(ip))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_IP");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_TID) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(pid_tid))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_TID");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_TIME) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(time))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_TIME");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_ADDR) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(addr))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_ADDR");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_ID) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(id))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_ID");
+    }
+  }
+
+  if (identifier.has_value()) {
+    if (!id.has_value()) {
+      id = identifier;
+    } else if (PERFETTO_UNLIKELY(*identifier != *id)) {
+      return base::ErrStatus("ID and IDENTIFIER mismatch");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_STREAM_ID) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(stream_id))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_STREAM_ID");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_CPU) {
+    struct {
+      int32_t cpu;
+      int32_t unused;
+    } tmp;
+    if (PERFETTO_UNLIKELY(!reader.Read(tmp))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_CPU");
+    }
+    cpu = tmp.cpu;
+  }
+
+  if (sample_type & PERF_SAMPLE_PERIOD) {
+    if (PERFETTO_UNLIKELY(!reader.ReadOptional(period))) {
+      return base ::ErrStatus("Not enough data to read PERF_SAMPLE_PERIOD");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_READ) {
+    if (PERFETTO_UNLIKELY(
+            !ParseSampleRead(reader, attr->read_format(), read_groups))) {
+      return base::ErrStatus("Failed to read PERF_SAMPLE_READ field");
+    }
+    if (read_groups.empty()) {
+      return base::ErrStatus("No data in PERF_SAMPLE_READ field");
+    }
+  }
+
+  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+    if (PERFETTO_UNLIKELY(!ParseSampleCallchain(reader, cpu_mode, callchain))) {
+      return base::ErrStatus("Failed to read PERF_SAMPLE_CALLCHAIN field");
+    }
+  }
+
+  return base::OkStatus();
+}
+
+}  // namespace perfetto::trace_processor::perf_importer
diff --git a/src/trace_processor/importers/perf/sample.h b/src/trace_processor/importers/perf/sample.h
new file mode 100644
index 0000000..532b613
--- /dev/null
+++ b/src/trace_processor/importers/perf/sample.h
@@ -0,0 +1,71 @@
+/*
+ * 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_SAMPLE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/ref_counted.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+#include "src/trace_processor/importers/perf/perf_event_attr.h"
+#include "src/trace_processor/importers/perf/perf_session.h"
+
+namespace perfetto::trace_processor::perf_importer {
+
+struct Record;
+
+struct Sample {
+  struct Frame {
+    protos::pbzero::Profiling::CpuMode cpu_mode;
+    uint64_t ip;
+  };
+
+  struct PidTid {
+    uint32_t pid;
+    uint32_t tid;
+  };
+
+  struct ReadGroup {
+    std::optional<uint64_t> event_id;
+    uint64_t value;
+  };
+
+  int64_t trace_ts;
+  protos::pbzero::Profiling::CpuMode cpu_mode;
+  RefPtr<PerfSession> perf_session;
+  RefPtr<const PerfEventAttr> attr;
+
+  std::optional<uint64_t> ip;
+  std::optional<PidTid> pid_tid;
+  std::optional<uint64_t> time;
+  std::optional<uint64_t> addr;
+  std::optional<uint64_t> id;
+  std::optional<uint64_t> stream_id;
+  std::optional<uint32_t> cpu;
+  std::optional<uint64_t> period;
+  std::vector<ReadGroup> read_groups;
+  std::vector<Frame> callchain;
+
+  base::Status Parse(int64_t trace_ts, const Record& record);
+};
+
+}  // namespace perfetto::trace_processor::perf_importer
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PERF_SAMPLE_H_
diff --git a/src/trace_processor/sqlite/module_lifecycle_manager.h b/src/trace_processor/sqlite/module_lifecycle_manager.h
index 66054d5..f1f93e8 100644
--- a/src/trace_processor/sqlite/module_lifecycle_manager.h
+++ b/src/trace_processor/sqlite/module_lifecycle_manager.h
@@ -67,6 +67,11 @@
  public:
   // Per-vtab state. The pointer to this class should be stored in the Vtab.
   struct PerVtabState {
+   private:
+    // The below fields should only be accessed by the manager, use GetState to
+    // access the state from outside this class.
+    friend class ModuleStateManager<Module>;
+
     ModuleStateManager* manager;
     bool disconnected = false;
     std::string table_name;
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 493561b..52f0c20 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -262,9 +262,13 @@
   F(perf_process_shard_count,             kIndexed, kInfo,     kTrace,    ""), \
   F(perf_chosen_process_shard,            kIndexed, kInfo,     kTrace,    ""), \
   F(perf_guardrail_stop_ts,               kIndexed, kDataLoss, kTrace,    ""), \
-  F(perf_record_skipped,                  kSingle,  kInfo,     kTrace,    ""), \
-  F(perf_samples_skipped,                 kSingle,  kInfo,     kTrace,    ""), \
+  F(perf_unknown_record_type,             kIndexed, kInfo,     kAnalysis, ""), \
+  F(perf_record_skipped,                  kSingle,  kError,    kAnalysis, ""), \
+  F(perf_samples_skipped,                 kSingle,  kError,    kAnalysis, ""), \
+  F(perf_features_skipped,                kIndexed, kInfo,     kAnalysis, ""), \
   F(perf_samples_skipped_dataloss,        kSingle,  kDataLoss, kTrace,    ""), \
+  F(perf_dummy_mapping_used,              kSingle,  kInfo,     kAnalysis, ""), \
+  F(perf_invalid_event_id,                kSingle,  kError,    kTrace,    ""), \
   F(memory_snapshot_parser_failure,       kSingle,  kError,    kAnalysis, ""), \
   F(thread_time_in_state_out_of_order,    kSingle,  kError,    kAnalysis, ""), \
   F(thread_time_in_state_unknown_cpu_freq,                                     \