OSDN Git Service

Simpleperf: support multiple event types in record command.
authorYabin Cui <yabinc@google.com>
Wed, 19 Aug 2015 22:46:51 +0000 (15:46 -0700)
committerYabin Cui <yabinc@google.com>
Thu, 20 Aug 2015 02:06:42 +0000 (19:06 -0700)
Change-Id: I0aa0e8c9491370b5e4fafdaf8cdc5613c26c78f5

simpleperf/cmd_record.cpp
simpleperf/cmd_record_test.cpp
simpleperf/event_selection_set.cpp
simpleperf/event_selection_set.h
simpleperf/record_file.h
simpleperf/record_file_test.cpp
simpleperf/record_file_writer.cpp

index 5b7bec2..72664d2 100644 (file)
@@ -67,8 +67,8 @@ class RecordCommand : public Command {
             "    --call-graph fp | dwarf[,<dump_stack_size>]\n"
             "                 Enable call graph recording. Use frame pointer or dwarf as the\n"
             "                 method to parse call graph in stack. Default is dwarf,8192.\n"
-            "    -e event[:modifier]\n"
-            "                 Select the event to sample. Use `simpleperf list` to find\n"
+            "    -e event1[:modifier1],event2[:modifier2],...\n"
+            "                 Select the event list to sample. Use `simpleperf list` to find\n"
             "                 all possible event names. Modifiers can be added to define\n"
             "                 how the event should be monitored. Possible modifiers are:\n"
             "                   u - monitor user space events only\n"
@@ -116,8 +116,9 @@ class RecordCommand : public Command {
 
  private:
   bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
-  bool SetMeasuredEventType(const std::string& event_type_name);
+  bool AddMeasuredEventType(const std::string& event_type_name);
   bool SetEventSelection();
+  bool CreateRecordFile();
   bool WriteData(const char* data, size_t size);
   bool DumpKernelAndModuleMmaps();
   bool DumpThreadCommAndMmaps(bool all_threads, const std::vector<pid_t>& selected_threads);
@@ -136,7 +137,7 @@ class RecordCommand : public Command {
   uint32_t dump_stack_size_in_dwarf_sampling_;
   bool child_inherit_;
   std::vector<pid_t> monitored_threads_;
-  std::unique_ptr<EventTypeAndModifier> measured_event_type_modifier_;
+  std::vector<EventTypeAndModifier> measured_event_types_;
   EventSelectionSet event_selection_set_;
 
   // mmap pages used by each perf event file, should be power of 2.
@@ -154,8 +155,8 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
   if (!ParseOptions(args, &workload_args)) {
     return false;
   }
-  if (measured_event_type_modifier_ == nullptr) {
-    if (!SetMeasuredEventType(default_measured_event_type)) {
+  if (measured_event_types_.empty()) {
+    if (!AddMeasuredEventType(default_measured_event_type)) {
       return false;
     }
   }
@@ -198,17 +199,8 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
   std::vector<pollfd> pollfds;
   event_selection_set_.PreparePollForEventFiles(&pollfds);
 
-  // 4. Open record file writer, and dump kernel/modules/threads mmap information.
-  record_file_writer_ = RecordFileWriter::CreateInstance(
-      record_filename_, event_selection_set_.FindEventAttrByType(*measured_event_type_modifier_),
-      event_selection_set_.FindEventFdsByType(*measured_event_type_modifier_));
-  if (record_file_writer_ == nullptr) {
-    return false;
-  }
-  if (!DumpKernelAndModuleMmaps()) {
-    return false;
-  }
-  if (!DumpThreadCommAndMmaps(system_wide_collection_, monitored_threads_)) {
+  // 4. Create perf.data.
+  if (!CreateRecordFile()) {
     return false;
   }
 
@@ -295,8 +287,11 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      if (!SetMeasuredEventType(args[i])) {
-        return false;
+      std::vector<std::string> event_types = android::base::Split(args[i], ",");
+      for (auto& event_type : event_types) {
+        if (!AddMeasuredEventType(event_type)) {
+          return false;
+        }
       }
     } else if (args[i] == "-f" || args[i] == "-F") {
       if (!NextArgumentOrError(args, &i)) {
@@ -368,18 +363,20 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
   return true;
 }
 
-bool RecordCommand::SetMeasuredEventType(const std::string& event_type_name) {
+bool RecordCommand::AddMeasuredEventType(const std::string& event_type_name) {
   std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name);
   if (event_type_modifier == nullptr) {
     return false;
   }
-  measured_event_type_modifier_ = std::move(event_type_modifier);
+  measured_event_types_.push_back(*event_type_modifier);
   return true;
 }
 
 bool RecordCommand::SetEventSelection() {
-  if (!event_selection_set_.AddEventType(*measured_event_type_modifier_)) {
-    return false;
+  for (auto& event_type : measured_event_types_) {
+    if (!event_selection_set_.AddEventType(event_type)) {
+      return false;
+    }
   }
   if (use_sample_freq_) {
     event_selection_set_.SetSampleFreq(sample_freq_);
@@ -401,6 +398,38 @@ bool RecordCommand::SetEventSelection() {
   return true;
 }
 
+bool RecordCommand::CreateRecordFile() {
+  record_file_writer_ = RecordFileWriter::CreateInstance(record_filename_);
+  if (record_file_writer_ == nullptr) {
+    return false;
+  }
+
+  std::vector<AttrWithId> attr_ids;
+  for (auto& event_type : measured_event_types_) {
+    AttrWithId attr_id;
+    attr_id.attr = event_selection_set_.FindEventAttrByType(event_type);
+    CHECK(attr_id.attr != nullptr);
+    const std::vector<std::unique_ptr<EventFd>>* fds =
+        event_selection_set_.FindEventFdsByType(event_type);
+    CHECK(fds != nullptr);
+    for (auto& fd : *fds) {
+      attr_id.ids.push_back(fd->Id());
+    }
+    attr_ids.push_back(attr_id);
+  }
+  if (!record_file_writer_->WriteAttrSection(attr_ids)) {
+    return false;
+  }
+
+  if (!DumpKernelAndModuleMmaps()) {
+    return false;
+  }
+  if (!DumpThreadCommAndMmaps(system_wide_collection_, monitored_threads_)) {
+    return false;
+  }
+  return true;
+}
+
 bool RecordCommand::WriteData(const char* data, size_t size) {
   return record_file_writer_->WriteData(data, size);
 }
@@ -411,9 +440,9 @@ bool RecordCommand::DumpKernelAndModuleMmaps() {
   if (!GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps)) {
     return false;
   }
-  const perf_event_attr& attr =
-      event_selection_set_.FindEventAttrByType(*measured_event_type_modifier_);
-  MmapRecord mmap_record = CreateMmapRecord(attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
+  const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]);
+  CHECK(attr != nullptr);
+  MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
                                             kernel_mmap.len, kernel_mmap.pgoff, kernel_mmap.name);
   if (!record_file_writer_->WriteData(mmap_record.BinaryFormat())) {
     return false;
@@ -423,7 +452,7 @@ bool RecordCommand::DumpKernelAndModuleMmaps() {
     if (filename.empty()) {
       filename = "[" + module_mmap.name + "]";
     }
-    MmapRecord mmap_record = CreateMmapRecord(attr, true, UINT_MAX, 0, module_mmap.start_addr,
+    MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr,
                                               module_mmap.len, 0, filename);
     if (!record_file_writer_->WriteData(mmap_record.BinaryFormat())) {
       return false;
@@ -450,8 +479,8 @@ bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
     }
   }
 
-  const perf_event_attr& attr =
-      event_selection_set_.FindEventAttrByType(*measured_event_type_modifier_);
+  const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]);
+  CHECK(attr != nullptr);
 
   // Dump processes.
   for (auto& thread : thread_comms) {
@@ -461,7 +490,7 @@ bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
     if (!all_threads && dump_processes.find(thread.pid) == dump_processes.end()) {
       continue;
     }
-    CommRecord record = CreateCommRecord(attr, thread.pid, thread.tid, thread.comm);
+    CommRecord record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
     if (!record_file_writer_->WriteData(record.BinaryFormat())) {
       return false;
     }
@@ -475,7 +504,7 @@ bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
         continue;  // No need to dump non-executable mmap info.
       }
       MmapRecord record =
-          CreateMmapRecord(attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
+          CreateMmapRecord(*attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
                            thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
       if (!record_file_writer_->WriteData(record.BinaryFormat())) {
         return false;
@@ -491,11 +520,11 @@ bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
     if (!all_threads && dump_threads.find(thread.tid) == dump_threads.end()) {
       continue;
     }
-    ForkRecord fork_record = CreateForkRecord(attr, thread.pid, thread.tid, thread.pid, thread.pid);
+    ForkRecord fork_record = CreateForkRecord(*attr, thread.pid, thread.tid, thread.pid, thread.pid);
     if (!record_file_writer_->WriteData(fork_record.BinaryFormat())) {
       return false;
     }
-    CommRecord comm_record = CreateCommRecord(attr, thread.pid, thread.tid, thread.comm);
+    CommRecord comm_record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
     if (!record_file_writer_->WriteData(comm_record.BinaryFormat())) {
       return false;
     }
index b9c3b9f..a68a871 100644 (file)
@@ -141,3 +141,8 @@ TEST(record_cmd, existing_threads) {
 TEST(record_cmd, no_monitored_threads) {
   ASSERT_FALSE(RecordCmd()->Run({""}));
 }
+
+TEST(record_cmd, more_than_one_event_types) {
+  ASSERT_TRUE(RecordCmd()->Run({"-e", "cpu-cycles,cpu-clock", "sleep", "1"}));
+  ASSERT_TRUE(RecordCmd()->Run({"-e", "cpu-cycles", "-e", "cpu-clock", "sleep", "1"}));
+}
index c3a538e..b7fcd58 100644 (file)
@@ -63,9 +63,22 @@ bool EventSelectionSet::AddEventType(const EventTypeAndModifier& event_type_modi
     return false;
   }
   selections_.push_back(std::move(selection));
+  UnionSampleType();
   return true;
 }
 
+// Union the sample type of different event attrs can make reading sample records in perf.data
+// easier.
+void EventSelectionSet::UnionSampleType() {
+  uint64_t sample_type = 0;
+  for (auto& selection : selections_) {
+    sample_type |= selection.event_attr.sample_type;
+  }
+  for (auto& selection : selections_) {
+    selection.event_attr.sample_type = sample_type;
+  }
+}
+
 void EventSelectionSet::SetEnableOnExec(bool enable) {
   for (auto& selection : selections_) {
     selection.event_attr.enable_on_exec = (enable ? 1 : 0);
@@ -291,12 +304,14 @@ EventSelectionSet::EventSelection* EventSelectionSet::FindSelectionByType(
   return nullptr;
 }
 
-const perf_event_attr& EventSelectionSet::FindEventAttrByType(
+const perf_event_attr* EventSelectionSet::FindEventAttrByType(
     const EventTypeAndModifier& event_type_modifier) {
-  return FindSelectionByType(event_type_modifier)->event_attr;
+  EventSelection* selection = FindSelectionByType(event_type_modifier);
+  return (selection != nullptr) ? &selection->event_attr : nullptr;
 }
 
-const std::vector<std::unique_ptr<EventFd>>& EventSelectionSet::FindEventFdsByType(
+const std::vector<std::unique_ptr<EventFd>>* EventSelectionSet::FindEventFdsByType(
     const EventTypeAndModifier& event_type_modifier) {
-  return FindSelectionByType(event_type_modifier)->event_fds;
+  EventSelection* selection = FindSelectionByType(event_type_modifier);
+  return (selection != nullptr) ? &selection->event_fds : nullptr;
 }
index 3fdd71a..54cf3cd 100644 (file)
@@ -77,11 +77,12 @@ class EventSelectionSet {
   bool MmapEventFiles(size_t mmap_pages);
   bool ReadMmapEventData(std::function<bool(const char*, size_t)> callback);
 
-  const perf_event_attr& FindEventAttrByType(const EventTypeAndModifier& event_type_modifier);
-  const std::vector<std::unique_ptr<EventFd>>& FindEventFdsByType(
+  const perf_event_attr* FindEventAttrByType(const EventTypeAndModifier& event_type_modifier);
+  const std::vector<std::unique_ptr<EventFd>>* FindEventFdsByType(
       const EventTypeAndModifier& event_type_modifier);
 
  private:
+  void UnionSampleType();
   bool OpenEventFiles(const std::vector<pid_t>& threads, const std::vector<int>& cpus);
 
   struct EventSelection {
index d94e083..4cc1757 100644 (file)
 #include "record.h"
 #include "record_file_format.h"
 
-class EventFd;
+struct AttrWithId {
+  const perf_event_attr* attr;
+  std::vector<uint64_t> ids;
+};
 
 // RecordFileWriter writes to a perf record file, like perf.data.
 class RecordFileWriter {
  public:
-  static std::unique_ptr<RecordFileWriter> CreateInstance(
-      const std::string& filename, const perf_event_attr& event_attr,
-      const std::vector<std::unique_ptr<EventFd>>& event_fds);
+  static std::unique_ptr<RecordFileWriter> CreateInstance(const std::string& filename);
 
   ~RecordFileWriter();
 
+  bool WriteAttrSection(const std::vector<AttrWithId>& attr_ids);
   bool WriteData(const void* buf, size_t len);
 
   bool WriteData(const std::vector<char>& data) {
@@ -62,8 +64,6 @@ class RecordFileWriter {
 
  private:
   RecordFileWriter(const std::string& filename, FILE* fp);
-  bool WriteAttrSection(const perf_event_attr& event_attr,
-                        const std::vector<std::unique_ptr<EventFd>>& event_fds);
   void GetHitModulesInBuffer(const char* p, const char* end,
                              std::vector<std::string>* hit_kernel_modules,
                              std::vector<std::string>* hit_user_files);
index 3cefb83..73b90ba 100644 (file)
@@ -19,7 +19,6 @@
 #include <string.h>
 #include "environment.h"
 #include "event_attr.h"
-#include "event_fd.h"
 #include "event_type.h"
 #include "record.h"
 #include "record_file.h"
@@ -31,31 +30,37 @@ using namespace PerfFileFormat;
 class RecordFileTest : public ::testing::Test {
  protected:
   virtual void SetUp() {
-    filename = "temporary.record_file";
-    std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+    filename_ = "temporary.record_file";
+  }
+
+  void AddEventType(const std::string& event_type_str) {
+    std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
     ASSERT_TRUE(event_type_modifier != nullptr);
-    event_attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
-    event_attr.sample_id_all = 1;
-    event_attr.sample_type |= PERF_SAMPLE_TIME;
-    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(event_attr, getpid(), -1);
-    ASSERT_TRUE(event_fd != nullptr);
-    event_fds.push_back(std::move(event_fd));
+    perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+    attrs_.push_back(attr);
+    AttrWithId attr_id;
+    attr_id.attr = &attrs_[attrs_.size() - 1];
+    attr_id.ids.push_back(attrs_.size());  // Fake id.
+    attr_ids_.push_back(attr_id);
   }
 
-  std::string filename;
-  perf_event_attr event_attr;
-  std::vector<std::unique_ptr<EventFd>> event_fds;
+  std::string filename_;
+  std::vector<perf_event_attr> attrs_;
+  std::vector<AttrWithId> attr_ids_;
 };
 
 TEST_F(RecordFileTest, smoke) {
   // Write to a record file.
-  std::unique_ptr<RecordFileWriter> writer =
-      RecordFileWriter::CreateInstance(filename, event_attr, event_fds);
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename_);
   ASSERT_TRUE(writer != nullptr);
 
+  // Write attr section.
+  AddEventType("cpu-cycles");
+  ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
   // Write data section.
   MmapRecord mmap_record =
-      CreateMmapRecord(event_attr, true, 1, 1, 0x1000, 0x2000, 0x3000, "mmap_record_example");
+      CreateMmapRecord(attrs_[0], true, 1, 1, 0x1000, 0x2000, 0x3000, "mmap_record_example");
   ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat()));
 
   // Check data section that has been written.
@@ -76,15 +81,15 @@ TEST_F(RecordFileTest, smoke) {
   ASSERT_TRUE(writer->Close());
 
   // Read from a record file.
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename);
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename_);
   ASSERT_TRUE(reader != nullptr);
   const FileHeader* file_header = reader->FileHeader();
   ASSERT_TRUE(file_header != nullptr);
-  std::vector<const FileAttr*> attrs = reader->AttrSection();
-  ASSERT_EQ(1u, attrs.size());
-  ASSERT_EQ(0, memcmp(&attrs[0]->attr, &event_attr, sizeof(perf_event_attr)));
-  std::vector<uint64_t> ids = reader->IdsForAttr(attrs[0]);
-  ASSERT_EQ(1u, ids.size());
+  std::vector<const FileAttr*> file_attrs = reader->AttrSection();
+  ASSERT_EQ(1u, file_attrs.size());
+  ASSERT_EQ(0, memcmp(&file_attrs[0]->attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+  std::vector<uint64_t> ids = reader->IdsForAttr(file_attrs[0]);
+  ASSERT_EQ(ids, attr_ids_[0].ids);
 
   // Read and check data section.
   records = reader->DataSection();
@@ -106,13 +111,18 @@ TEST_F(RecordFileTest, smoke) {
 }
 
 TEST_F(RecordFileTest, records_sorted_by_time) {
-  // Write to a record file;
-  std::unique_ptr<RecordFileWriter> writer =
-      RecordFileWriter::CreateInstance(filename, event_attr, event_fds);
+  // Write to a record file.
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename_);
   ASSERT_TRUE(writer != nullptr);
 
+  // Write attr section.
+  AddEventType("cpu-cycles");
+  attrs_[0].sample_id_all = 1;
+  attrs_[0].sample_type |= PERF_SAMPLE_TIME;
+  ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
   // Write data section.
-  MmapRecord r1 = CreateMmapRecord(event_attr, true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1");
+  MmapRecord r1 = CreateMmapRecord(attrs_[0], true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1");
   MmapRecord r2 = r1;
   MmapRecord r3 = r1;
   r1.sample_id.time_data.time = 2;
@@ -124,7 +134,7 @@ TEST_F(RecordFileTest, records_sorted_by_time) {
   ASSERT_TRUE(writer->Close());
 
   // Read from a record file.
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename);
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename_);
   ASSERT_TRUE(reader != nullptr);
   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
   ASSERT_EQ(3u, records.size());
@@ -134,3 +144,28 @@ TEST_F(RecordFileTest, records_sorted_by_time) {
 
   ASSERT_TRUE(reader->Close());
 }
+
+TEST_F(RecordFileTest, record_more_than_one_attr) {
+  // Write to a record file.
+  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename_);
+  ASSERT_TRUE(writer != nullptr);
+
+  // Write attr section.
+  AddEventType("cpu-cycles");
+  AddEventType("cpu-clock");
+  AddEventType("task-clock");
+  ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
+
+  ASSERT_TRUE(writer->Close());
+
+  // Read from a record file.
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(filename_);
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<const FileAttr*> file_attrs = reader->AttrSection();
+  ASSERT_EQ(3u, file_attrs.size());
+  for (size_t i = 0; i < file_attrs.size(); ++i) {
+    ASSERT_EQ(0, memcmp(&file_attrs[i]->attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+    std::vector<uint64_t> ids = reader->IdsForAttr(file_attrs[i]);
+    ASSERT_EQ(ids, attr_ids_[i].ids);
+  }
+}
index 79bb7cf..48ed325 100644 (file)
 
 #include <base/logging.h>
 
-#include "event_fd.h"
 #include "perf_event.h"
 #include "record.h"
 #include "utils.h"
 
 using namespace PerfFileFormat;
 
-std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(
-    const std::string& filename, const perf_event_attr& event_attr,
-    const std::vector<std::unique_ptr<EventFd>>& event_fds) {
+std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(const std::string& filename) {
   // Remove old perf.data to avoid file ownership problems.
   if (!RemovePossibleFile(filename)) {
     return nullptr;
@@ -45,11 +42,7 @@ std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(
     return nullptr;
   }
 
-  auto writer = std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp));
-  if (!writer->WriteAttrSection(event_attr, event_fds)) {
-    return nullptr;
-  }
-  return writer;
+  return std::unique_ptr<RecordFileWriter>(new RecordFileWriter(filename, fp));
 }
 
 RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp)
@@ -69,38 +62,41 @@ RecordFileWriter::~RecordFileWriter() {
   }
 }
 
-bool RecordFileWriter::WriteAttrSection(const perf_event_attr& event_attr,
-                                        const std::vector<std::unique_ptr<EventFd>>& event_fds) {
+bool RecordFileWriter::WriteAttrSection(const std::vector<AttrWithId>& attr_ids) {
+  if (attr_ids.empty()) {
+    return false;
+  }
+
   // Skip file header part.
   if (fseek(record_fp_, sizeof(FileHeader), SEEK_SET) == -1) {
     return false;
   }
 
   // Write id section.
-  std::vector<uint64_t> ids;
-  for (auto& event_fd : event_fds) {
-    ids.push_back(event_fd->Id());
-  }
   long id_section_offset = ftell(record_fp_);
   if (id_section_offset == -1) {
     return false;
   }
-  if (!Write(ids.data(), ids.size() * sizeof(uint64_t))) {
-    return false;
+  for (auto& attr_id : attr_ids) {
+    if (!Write(attr_id.ids.data(), attr_id.ids.size() * sizeof(uint64_t))) {
+      return false;
+    }
   }
 
   // Write attr section.
-  FileAttr attr;
-  attr.attr = event_attr;
-  attr.ids.offset = id_section_offset;
-  attr.ids.size = ids.size() * sizeof(uint64_t);
-
   long attr_section_offset = ftell(record_fp_);
   if (attr_section_offset == -1) {
     return false;
   }
-  if (!Write(&attr, sizeof(attr))) {
-    return false;
+  for (auto& attr_id : attr_ids) {
+    FileAttr file_attr;
+    file_attr.attr = *attr_id.attr;
+    file_attr.ids.offset = id_section_offset;
+    file_attr.ids.size = attr_id.ids.size() * sizeof(uint64_t);
+    id_section_offset += file_attr.ids.size;
+    if (!Write(&file_attr, sizeof(file_attr))) {
+      return false;
+    }
   }
 
   long data_section_offset = ftell(record_fp_);
@@ -109,11 +105,11 @@ bool RecordFileWriter::WriteAttrSection(const perf_event_attr& event_attr,
   }
 
   attr_section_offset_ = attr_section_offset;
-  attr_section_size_ = sizeof(attr);
+  attr_section_size_ = data_section_offset - attr_section_offset;
   data_section_offset_ = data_section_offset;
 
   // Save event_attr for use when reading records.
-  event_attr_ = event_attr;
+  event_attr_ = *attr_ids[0].attr;
   return true;
 }