simpleperf: dump tracing data when needed.

When monitoring tracepoint events, dump tracing data to perf.data
can enable reporting on a different machine.

Bug: 27403614
Change-Id: Ie1af624717a245cacbeb44b4c1bcd499fc9ad8db
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index b64af01..2e4c512 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -88,6 +88,7 @@
   record.cpp \
   record_file_reader.cpp \
   thread_tree.cpp \
+  tracing.cpp \
   utils.cpp \
 
 libsimpleperf_src_files_linux := \
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index f19cfd3..3e89b82 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -157,20 +157,14 @@
 }
 
 void DumpRecordCommand::DumpAttrSection() {
-  const std::vector<FileAttr>& attrs = record_file_reader_->AttrSection();
+  std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
   for (size_t i = 0; i < attrs.size(); ++i) {
     const auto& attr = attrs[i];
-    printf("file_attr %zu:\n", i + 1);
-    DumpPerfEventAttr(attr.attr, 1);
-    printf("  ids[file_section]: offset %" PRId64 ", size %" PRId64 "\n", attr.ids.offset,
-           attr.ids.size);
-    std::vector<uint64_t> ids;
-    if (!record_file_reader_->ReadIdsForAttr(attr, &ids)) {
-      return;
-    }
-    if (!ids.empty()) {
+    printf("attr %zu:\n", i + 1);
+    DumpPerfEventAttr(*attr.attr, 1);
+    if (!attr.ids.empty()) {
       printf("  ids:");
-      for (const auto& id : ids) {
+      for (const auto& id : attr.ids) {
         printf(" %" PRId64, id);
       }
       printf("\n");
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index 3fa4cbd..a989d37 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -39,6 +39,7 @@
 #include "record_file.h"
 #include "scoped_signal_handler.h"
 #include "thread_tree.h"
+#include "tracing.h"
 #include "utils.h"
 #include "workload.h"
 
@@ -56,6 +57,9 @@
 static volatile bool signaled;
 static void signal_handler(int) { signaled = true; }
 
+constexpr uint64_t DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT = 4000;
+constexpr uint64_t DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT = 1;
+
 class RecordCommand : public Command {
  public:
   RecordCommand()
@@ -66,7 +70,9 @@
 "       Gather sampling information when running [command].\n"
 "-a     System-wide collection.\n"
 "-b     Enable take branch stack sampling. Same as '-j any'\n"
-"-c count     Set event sample period.\n"
+"-c count     Set event sample period. It means recording one sample when\n"
+"             [count] events happen. Can't be used with -f/-F option.\n"
+"             For tracepoint events, the default option is -c 1.\n"
 "--call-graph fp | dwarf[,<dump_stack_size>]\n"
 "             Enable call graph recording. Use frame pointer or dwarf debug\n"
 "             frame as the method to parse call graph in stack.\n"
@@ -84,7 +90,9 @@
 "             Possible modifiers are:\n"
 "                u - monitor user space events only\n"
 "                k - monitor kernel space events only\n"
-"-f freq      Set event sample frequency.\n"
+"-f freq      Set event sample frequency. It means recording at most [freq]\n"
+"             samples every second. For non-tracepoint events, the default\n"
+"             option is -f 4000.\n"
 "-F freq      Same as '-f freq'.\n"
 "-g           Same as '--call-graph dwarf'.\n"
 "-j branch_filter1,branch_filter2,...\n"
@@ -117,8 +125,10 @@
 "-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
             // clang-format on
             ),
-        use_sample_freq_(true),
-        sample_freq_(4000),
+        use_sample_freq_(false),
+        sample_freq_(0),
+        use_sample_period_(false),
+        sample_period_(0),
         system_wide_collection_(false),
         branch_sampling_(0),
         fp_callchain_sampling_(false),
@@ -148,6 +158,7 @@
   std::unique_ptr<RecordFileWriter> CreateRecordFile(
       const std::string& filename);
   bool DumpKernelSymbol();
+  bool DumpTracingData();
   bool DumpKernelAndModuleMmaps(const perf_event_attr* attr, uint64_t event_id);
   bool DumpThreadCommAndMmaps(const perf_event_attr* attr, uint64_t event_id,
                               bool all_threads,
@@ -162,9 +173,9 @@
   void CollectHitFileInfo(Record* record);
   std::pair<std::string, uint64_t> TestForEmbeddedElf(Dso* dso, uint64_t pgoff);
 
-  // Use sample_freq_ when true, otherwise using sample_period_.
   bool use_sample_freq_;
-  uint64_t sample_freq_;    // Sample 'sample_freq_' times per second.
+  uint64_t sample_freq_;  // Sample 'sample_freq_' times per second.
+  bool use_sample_period_;
   uint64_t sample_period_;  // Sample once when 'sample_period_' events occur.
 
   bool system_wide_collection_;
@@ -313,7 +324,7 @@
         LOG(ERROR) << "Invalid sample period: '" << args[i] << "'";
         return false;
       }
-      use_sample_freq_ = false;
+      use_sample_period_ = true;
     } else if (args[i] == "--call-graph") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -434,6 +445,11 @@
     }
   }
 
+  if (use_sample_freq_ && use_sample_period_) {
+    LOG(ERROR) << "-f option can't be used with -c option.";
+    return false;
+  }
+
   if (!dwarf_callchain_sampling_) {
     if (!unwind_dwarf_callchain_) {
       LOG(ERROR)
@@ -482,6 +498,11 @@
   if (event_type_modifier == nullptr) {
     return false;
   }
+  for (const auto& type : measured_event_types_) {
+    if (type.name == event_type_modifier->name) {
+      return true;
+    }
+  }
   measured_event_types_.push_back(*event_type_modifier);
   return true;
 }
@@ -492,10 +513,20 @@
       return false;
     }
   }
-  if (use_sample_freq_) {
-    event_selection_set_.SetSampleFreq(sample_freq_);
-  } else {
-    event_selection_set_.SetSamplePeriod(sample_period_);
+  for (auto& event_type : measured_event_types_) {
+    if (use_sample_freq_) {
+      event_selection_set_.SetSampleFreq(event_type, sample_freq_);
+    } else if (use_sample_period_) {
+      event_selection_set_.SetSamplePeriod(event_type, sample_period_);
+    } else {
+      if (event_type.event_type.type == PERF_TYPE_TRACEPOINT) {
+        event_selection_set_.SetSamplePeriod(
+            event_type, DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT);
+      } else {
+        event_selection_set_.SetSampleFreq(
+            event_type, DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+      }
+    }
   }
   event_selection_set_.SampleIdAll();
   if (!event_selection_set_.SetBranchSampling(branch_sampling_)) {
@@ -527,6 +558,9 @@
   if (!DumpKernelSymbol()) {
     return false;
   }
+  if (!DumpTracingData()) {
+    return false;
+  }
   if (!DumpKernelAndModuleMmaps(attr, event_id)) {
     return false;
   }
@@ -591,6 +625,28 @@
   return true;
 }
 
+bool RecordCommand::DumpTracingData() {
+  bool has_tracepoint = false;
+  for (const auto& type : measured_event_types_) {
+    if (type.event_type.type == PERF_TYPE_TRACEPOINT) {
+      has_tracepoint = true;
+      break;
+    }
+  }
+  if (!has_tracepoint) {
+    return true;  // No need to dump tracing data.
+  }
+  std::vector<char> tracing_data;
+  if (!GetTracingData(measured_event_types_, &tracing_data)) {
+    return false;
+  }
+  TracingDataRecord record = TracingDataRecord::Create(std::move(tracing_data));
+  if (!ProcessRecord(&record)) {
+    return false;
+  }
+  return true;
+}
+
 bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr* attr,
                                              uint64_t event_id) {
   KernelMmap kernel_mmap;
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 863057d..90e0778 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -39,6 +39,7 @@
 #include "record_file.h"
 #include "sample_tree.h"
 #include "thread_tree.h"
+#include "tracing.h"
 #include "utils.h"
 
 namespace {
@@ -224,6 +225,12 @@
 using ReportCmdSampleTreeDisplayer =
     SampleTreeDisplayer<SampleEntry, SampleTree>;
 
+struct EventAttrWithName {
+  perf_event_attr attr;
+  std::string name;
+  std::vector<uint64_t> event_ids;
+};
+
 class ReportCommand : public Command {
  public:
   ReportCommand()
@@ -273,13 +280,14 @@
   bool ReadFeaturesFromRecordFile();
   bool ReadSampleTreeFromRecordFile();
   bool ProcessRecord(std::unique_ptr<Record> record);
+  bool ProcessTracingData(const std::vector<char>& data);
   bool PrintReport();
   void PrintReportContext(FILE* fp);
 
   std::string record_filename_;
   ArchType record_file_arch_;
   std::unique_ptr<RecordFileReader> record_file_reader_;
-  std::vector<perf_event_attr> event_attrs_;
+  std::vector<EventAttrWithName> event_attrs_;
   ThreadTree thread_tree_;
   SampleTree sample_tree_;
   std::unique_ptr<ReportCmdSampleTreeBuilder> sample_tree_builder_;
@@ -499,15 +507,22 @@
 }
 
 bool ReportCommand::ReadEventAttrFromRecordFile() {
-  const std::vector<PerfFileFormat::FileAttr>& file_attrs =
-      record_file_reader_->AttrSection();
-  for (const auto& attr : file_attrs) {
-    event_attrs_.push_back(attr.attr);
+  std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+  for (const auto& attr_with_id : attrs) {
+    EventAttrWithName attr;
+    attr.attr = *attr_with_id.attr;
+    attr.event_ids = attr_with_id.ids;
+    const EventType* event_type =
+        FindEventTypeByConfig(attr.attr.type, attr.attr.config);
+    if (event_type != nullptr) {
+      attr.name = event_type->name;
+    }
+    event_attrs_.push_back(attr);
   }
   if (use_branch_address_) {
     bool has_branch_stack = true;
     for (const auto& attr : event_attrs_) {
-      if ((attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
+      if ((attr.attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
         has_branch_stack = false;
         break;
       }
@@ -558,6 +573,16 @@
       }
     }
   }
+  if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
+    std::vector<char> tracing_data;
+    if (!record_file_reader_->ReadFeatureSection(
+            PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+      return false;
+    }
+    if (!ProcessTracingData(tracing_data)) {
+      return false;
+    }
+  }
   return true;
 }
 
@@ -586,6 +611,22 @@
   if (record->type() == PERF_RECORD_SAMPLE) {
     sample_tree_builder_->ProcessSampleRecord(
         *static_cast<const SampleRecord*>(record.get()));
+  } else if (record->type() == PERF_RECORD_TRACING_DATA) {
+    const auto& r = *static_cast<TracingDataRecord*>(record.get());
+    if (!ProcessTracingData(r.data)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
+  Tracing tracing(data);
+  for (auto& attr : event_attrs_) {
+    if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
+      uint64_t trace_event_id = attr.attr.config;
+      attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
+    }
   }
   return true;
 }
@@ -618,13 +659,8 @@
   }
   fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
   for (const auto& attr : event_attrs_) {
-    const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config);
-    std::string name;
-    if (event_type != nullptr) {
-      name = event_type->name;
-    }
-    fprintf(report_fp, "Event: %s (type %u, config %llu)\n", name.c_str(),
-            attr.type, attr.config);
+    fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
+            attr.attr.type, attr.attr.config);
   }
   fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree_.total_samples);
   fprintf(report_fp, "Event count: %" PRIu64 "\n\n", sample_tree_.total_period);
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index dca7ed0..15e7465 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -240,6 +240,11 @@
   if (event_type_modifier == nullptr) {
     return false;
   }
+  for (const auto& type : measured_event_types_) {
+    if (type.name == event_type_modifier->name) {
+      return true;
+    }
+  }
   measured_event_types_.push_back(*event_type_modifier);
   return true;
 }
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 798d08d..7829110 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -424,6 +424,10 @@
 }
 
 bool CheckPerfEventLimit() {
+  // root is not limited by /proc/sys/kernel/perf_event_paranoid.
+  if (IsRoot()) {
+    return true;
+  }
   int limit_level;
   if (!ReadPerfEventParanoid(&limit_level)) {
     return false;
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index 9222633..12ab1ad 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -91,8 +91,6 @@
       PERF_SAMPLE_CPU | PERF_SAMPLE_ID;
 
   if (attr.type == PERF_TYPE_TRACEPOINT) {
-    attr.sample_freq = 0;
-    attr.sample_period = 1;
     // Tracepoint information are stored in raw data in sample records.
     attr.sample_type |= PERF_SAMPLE_RAW;
   }
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index 49ccd0d..92e559c 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -112,20 +112,18 @@
   }
 }
 
-void EventSelectionSet::SetSampleFreq(uint64_t sample_freq) {
-  for (auto& selection : selections_) {
-    perf_event_attr& attr = selection.event_attr;
-    attr.freq = 1;
-    attr.sample_freq = sample_freq;
-  }
+void EventSelectionSet::SetSampleFreq(const EventTypeAndModifier& event_type_modifier, uint64_t sample_freq) {
+  EventSelection* sel = FindSelectionByType(event_type_modifier);
+  CHECK(sel != nullptr);
+  sel->event_attr.freq = 1;
+  sel->event_attr.sample_freq = sample_freq;
 }
 
-void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) {
-  for (auto& selection : selections_) {
-    perf_event_attr& attr = selection.event_attr;
-    attr.freq = 0;
-    attr.sample_period = sample_period;
-  }
+void EventSelectionSet::SetSamplePeriod(const EventTypeAndModifier& event_type_modifier, uint64_t sample_period) {
+  EventSelection* sel = FindSelectionByType(event_type_modifier);
+  CHECK(sel != nullptr);
+  sel->event_attr.freq = 0;
+  sel->event_attr.sample_period = sample_period;
 }
 
 bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index d2e2511..3cfdb46 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -64,8 +64,8 @@
   void SetEnableOnExec(bool enable);
   bool GetEnableOnExec();
   void SampleIdAll();
-  void SetSampleFreq(uint64_t sample_freq);
-  void SetSamplePeriod(uint64_t sample_period);
+  void SetSampleFreq(const EventTypeAndModifier& event_type_modifier, uint64_t sample_freq);
+  void SetSamplePeriod(const EventTypeAndModifier& event_type_modifier, uint64_t sample_period);
   bool SetBranchSampling(uint64_t branch_sample_type);
   void EnableFpCallChainSampling();
   bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index a9cf67c..0f1a8f4 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -26,6 +26,7 @@
 #include "dso.h"
 #include "environment.h"
 #include "perf_regs.h"
+#include "tracing.h"
 #include "utils.h"
 
 static std::string RecordTypeToString(int record_type) {
@@ -41,6 +42,7 @@
       {PERF_RECORD_SAMPLE, "sample"},
       {PERF_RECORD_BUILD_ID, "build_id"},
       {PERF_RECORD_MMAP2, "mmap2"},
+      {PERF_RECORD_TRACING_DATA, "tracing_data"},
       {SIMPLE_PERF_RECORD_KERNEL_SYMBOL, "kernel_symbol"},
       {SIMPLE_PERF_RECORD_DSO, "dso"},
       {SIMPLE_PERF_RECORD_SYMBOL, "symbol"},
@@ -790,6 +792,41 @@
   return record;
 }
 
+TracingDataRecord::TracingDataRecord(const perf_event_header* pheader) : Record(pheader) {
+  const char* p = reinterpret_cast<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+  uint32_t size;
+  MoveFromBinaryFormat(size, p);
+  data.resize(size);
+  memcpy(data.data(), p, size);
+  p += ALIGN(size, 64);
+  CHECK_EQ(p, end);
+}
+
+std::vector<char> TracingDataRecord::BinaryFormat() const {
+  std::vector<char> buf(size());
+  char* p = buf.data();
+  MoveToBinaryFormat(header, p);
+  uint32_t size = static_cast<uint32_t>(data.size());
+  MoveToBinaryFormat(size, p);
+  memcpy(p, data.data(), size);
+  return buf;
+}
+
+void TracingDataRecord::DumpData(size_t indent) const {
+  Tracing tracing(data);
+  tracing.Dump(indent);
+}
+
+TracingDataRecord TracingDataRecord::Create(std::vector<char> tracing_data) {
+  TracingDataRecord record;
+  record.SetTypeAndMisc(PERF_RECORD_TRACING_DATA, 0);
+  record.data = std::move(tracing_data);
+  record.SetSize(record.header_size() + sizeof(uint32_t) +
+                 ALIGN(record.data.size(), 64));
+  return record;
+}
+
 UnknownRecord::UnknownRecord(const perf_event_header* pheader)
     : Record(pheader) {
   const char* p = reinterpret_cast<const char*>(pheader + 1);
@@ -822,6 +859,8 @@
       return std::unique_ptr<Record>(new ForkRecord(attr, pheader));
     case PERF_RECORD_SAMPLE:
       return std::unique_ptr<Record>(new SampleRecord(attr, pheader));
+    case PERF_RECORD_TRACING_DATA:
+      return std::unique_ptr<Record>(new TracingDataRecord(pheader));
     case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
       return std::unique_ptr<Record>(new KernelSymbolRecord(pheader));
     case SIMPLE_PERF_RECORD_DSO:
diff --git a/simpleperf/record.h b/simpleperf/record.h
index 64789a6..f145e80 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -380,14 +380,26 @@
 
   static SymbolRecord Create(uint64_t addr, uint64_t len,
                              const std::string& name, uint64_t dso_id);
+ protected:
+  void DumpData(size_t indent) const override;
+};
 
+struct TracingDataRecord : public Record {
+  std::vector<char> data;
+
+  TracingDataRecord() {
+  }
+
+  TracingDataRecord(const perf_event_header* pheader);
+  std::vector<char> BinaryFormat() const override;
+
+  static TracingDataRecord Create(std::vector<char> tracing_data);
  protected:
   void DumpData(size_t indent) const override;
 };
 
 // UnknownRecord is used for unknown record types, it makes sure all unknown
-// records
-// are not changed when modifying perf.data.
+// records are not changed when modifying perf.data.
 struct UnknownRecord : public Record {
   std::vector<char> data;
 
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index 3005dee..a0af30e 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -100,15 +100,22 @@
     return header_;
   }
 
-  const std::vector<PerfFileFormat::FileAttr>& AttrSection() const {
-    return file_attrs_;
+  std::vector<AttrWithId> AttrSection() const {
+    std::vector<AttrWithId> result(file_attrs_.size());
+    for (size_t i = 0; i < file_attrs_.size(); ++i) {
+      result[i].attr = &file_attrs_[i].attr;
+      result[i].ids = event_ids_for_file_attrs_[i];
+    }
+    return result;
   }
 
   const std::map<int, PerfFileFormat::SectionDesc>& FeatureSectionDescriptors() const {
     return feature_section_descriptors_;
   }
-
-  bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+  bool HasFeature(int feature) const {
+    return feature_section_descriptors_.find(feature) != feature_section_descriptors_.end();
+  }
+  bool ReadFeatureSection(int feature, std::vector<char>* data);
   // If sorted is true, sort records before passing them to callback function.
   bool ReadDataSection(std::function<bool(std::unique_ptr<Record>)> callback, bool sorted = true);
   std::vector<std::string> ReadCmdlineFeature();
@@ -123,8 +130,8 @@
   RecordFileReader(const std::string& filename, FILE* fp);
   bool ReadHeader();
   bool ReadAttrSection();
+  bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
   bool ReadFeatureSectionDescriptors();
-  bool ReadFeatureSection(int feature, std::vector<char>* data);
   std::unique_ptr<Record> ReadRecord();
 
   const std::string filename_;
@@ -132,6 +139,7 @@
 
   PerfFileFormat::FileHeader header_;
   std::vector<PerfFileFormat::FileAttr> file_attrs_;
+  std::vector<std::vector<uint64_t>> event_ids_for_file_attrs_;
   std::unordered_map<uint64_t, perf_event_attr*> event_id_to_attr_map_;
   std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
 
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index 9b97936..b124d28 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -118,6 +118,7 @@
     if (!ReadIdsForAttr(file_attrs_[i], &ids)) {
       return false;
     }
+    event_ids_for_file_attrs_.push_back(ids);
     for (auto id : ids) {
       event_id_to_attr_map_[id] = &file_attrs_[i].attr;
     }
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 4cc9e50..f9707ef 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -79,12 +79,10 @@
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  const std::vector<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;
-  ASSERT_TRUE(reader->ReadIdsForAttr(file_attrs[0], &ids));
-  ASSERT_EQ(ids, attr_ids_[0].ids);
+  std::vector<AttrWithId> attrs = reader->AttrSection();
+  ASSERT_EQ(1u, attrs.size());
+  ASSERT_EQ(0, memcmp(attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+  ASSERT_EQ(attrs[0].ids, attr_ids_[0].ids);
 
   // Read and check data section.
   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
@@ -152,12 +150,10 @@
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  const std::vector<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;
-    ASSERT_TRUE(reader->ReadIdsForAttr(file_attrs[i], &ids));
-    ASSERT_EQ(ids, attr_ids_[i].ids);
+  std::vector<AttrWithId> attrs = reader->AttrSection();
+  ASSERT_EQ(3u, attrs.size());
+  for (size_t i = 0; i < attrs.size(); ++i) {
+    ASSERT_EQ(0, memcmp(attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+    ASSERT_EQ(attrs[i].ids, attr_ids_[i].ids);
   }
 }
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
new file mode 100644
index 0000000..1757e05
--- /dev/null
+++ b/simpleperf/tracing.cpp
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2016 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 "tracing.h"
+
+#include <string.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "perf_event.h"
+#include "utils.h"
+
+const char TRACING_INFO_MAGIC[10] = {23,  8,   68,  't', 'r',
+                                     'a', 'c', 'i', 'n', 'g'};
+
+template <class T>
+void AppendData(std::vector<char>& data, const T& s) {
+  const char* p = reinterpret_cast<const char*>(&s);
+  data.insert(data.end(), p, p + sizeof(T));
+}
+
+static void AppendData(std::vector<char>& data, const char* s) {
+  data.insert(data.end(), s, s + strlen(s) + 1);
+}
+
+template <>
+void AppendData(std::vector<char>& data, const std::string& s) {
+  data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1);
+}
+
+template <>
+void MoveFromBinaryFormat(std::string& data, const char*& p) {
+  data.clear();
+  while (*p != '\0') {
+    data.push_back(*p++);
+  }
+  p++;
+}
+
+static void AppendFile(std::vector<char>& data, const std::string& file,
+                       uint32_t file_size_bytes = 8) {
+  if (file_size_bytes == 8) {
+    uint64_t file_size = file.size();
+    AppendData(data, file_size);
+  } else if (file_size_bytes == 4) {
+    uint32_t file_size = file.size();
+    AppendData(data, file_size);
+  }
+  data.insert(data.end(), file.begin(), file.end());
+}
+
+static void DetachFile(const char*& p, std::string& file,
+                       uint32_t file_size_bytes = 8) {
+  uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
+  p += file_size_bytes;
+  file.clear();
+  file.insert(file.end(), p, p + file_size);
+  p += file_size;
+}
+
+struct TraceType {
+  std::string system;
+  std::string name;
+};
+
+class TracingFile {
+ public:
+  TracingFile();
+  bool RecordHeaderFiles();
+  void RecordFtraceFiles(const std::vector<TraceType>& trace_types);
+  bool RecordEventFiles(const std::vector<TraceType>& trace_types);
+  bool RecordKallsymsFile();
+  bool RecordPrintkFormatsFile();
+  std::vector<char> BinaryFormat() const;
+  void LoadFromBinary(const std::vector<char>& data);
+  void Dump(size_t indent) const;
+  std::vector<TracingFormat> LoadTracingFormatsFromEventFiles() const;
+  const std::string& GetKallsymsFile() const { return kallsyms_file; }
+  uint32_t GetPageSize() const { return page_size; }
+
+ private:
+  char magic[10];
+  std::string version;
+  char endian;
+  uint8_t size_of_long;
+  uint32_t page_size;
+  std::string header_page_file;
+  std::string header_event_file;
+
+  std::vector<std::string> ftrace_format_files;
+  // pair of system, format_file_data.
+  std::vector<std::pair<std::string, std::string>> event_format_files;
+
+  std::string kallsyms_file;
+  std::string printk_formats_file;
+};
+
+TracingFile::TracingFile() {
+  memcpy(magic, TRACING_INFO_MAGIC, sizeof(TRACING_INFO_MAGIC));
+  version = "0.5";
+  endian = 0;
+  size_of_long = static_cast<int>(sizeof(long));
+  page_size = static_cast<uint32_t>(::GetPageSize());
+}
+
+bool TracingFile::RecordHeaderFiles() {
+  if (!android::base::ReadFileToString(
+          "/sys/kernel/debug/tracing/events/header_page", &header_page_file)) {
+    PLOG(ERROR)
+        << "failed to read /sys/kernel/debug/tracing/events/header_page";
+    return false;
+  }
+  if (!android::base::ReadFileToString(
+          "/sys/kernel/debug/tracing/events/header_event",
+          &header_event_file)) {
+    PLOG(ERROR)
+        << "failed to read /sys/kernel/debug/tracing/events/header_event";
+    return false;
+  }
+  return true;
+}
+
+void TracingFile::RecordFtraceFiles(const std::vector<TraceType>& trace_types) {
+  for (const auto& type : trace_types) {
+    std::string format_path = android::base::StringPrintf(
+        "/sys/kernel/debug/tracing/events/ftrace/%s/format", type.name.c_str());
+    std::string format_data;
+    if (android::base::ReadFileToString(format_path, &format_data)) {
+      ftrace_format_files.push_back(std::move(format_data));
+    }
+  }
+}
+
+bool TracingFile::RecordEventFiles(const std::vector<TraceType>& trace_types) {
+  for (const auto& type : trace_types) {
+    std::string format_path = android::base::StringPrintf(
+        "/sys/kernel/debug/tracing/events/%s/%s/format", type.system.c_str(),
+        type.name.c_str());
+    std::string format_data;
+    if (!android::base::ReadFileToString(format_path, &format_data)) {
+      PLOG(ERROR) << "failed to read " << format_path;
+      return false;
+    }
+    event_format_files.push_back(
+        std::make_pair(type.system, std::move(format_data)));
+  }
+  return true;
+}
+
+bool TracingFile::RecordPrintkFormatsFile() {
+  if (!android::base::ReadFileToString(
+          "/sys/kernel/debug/tracing/printk_formats", &printk_formats_file)) {
+    PLOG(ERROR) << "failed to read /sys/kernel/debug/tracing/printk_formats";
+    return false;
+  }
+  return true;
+}
+
+std::vector<char> TracingFile::BinaryFormat() const {
+  std::vector<char> ret;
+  ret.insert(ret.end(), magic, magic + sizeof(magic));
+  AppendData(ret, version);
+  ret.push_back(endian);
+  AppendData(ret, size_of_long);
+  AppendData(ret, page_size);
+  AppendData(ret, "header_page");
+  AppendFile(ret, header_page_file);
+  AppendData(ret, "header_event");
+  AppendFile(ret, header_event_file);
+  int count = static_cast<int>(ftrace_format_files.size());
+  AppendData(ret, count);
+  for (const auto& format : ftrace_format_files) {
+    AppendFile(ret, format);
+  }
+  count = static_cast<int>(event_format_files.size());
+  AppendData(ret, count);
+  for (const auto& pair : event_format_files) {
+    AppendData(ret, pair.first);
+    AppendData(ret, 1);
+    AppendFile(ret, pair.second);
+  }
+  AppendFile(ret, kallsyms_file, 4);
+  AppendFile(ret, printk_formats_file, 4);
+  return ret;
+}
+
+void TracingFile::LoadFromBinary(const std::vector<char>& data) {
+  const char* p = data.data();
+  const char* end = data.data() + data.size();
+  CHECK(memcmp(p, magic, sizeof(magic)) == 0);
+  p += sizeof(magic);
+  MoveFromBinaryFormat(version, p);
+  MoveFromBinaryFormat(endian, p);
+  MoveFromBinaryFormat(size_of_long, p);
+  MoveFromBinaryFormat(page_size, p);
+  std::string filename;
+  MoveFromBinaryFormat(filename, p);
+  CHECK_EQ(filename, "header_page");
+  DetachFile(p, header_page_file);
+  MoveFromBinaryFormat(filename, p);
+  CHECK_EQ(filename, "header_event");
+  DetachFile(p, header_event_file);
+  uint32_t count;
+  MoveFromBinaryFormat(count, p);
+  ftrace_format_files.resize(count);
+  for (uint32_t i = 0; i < count; ++i) {
+    DetachFile(p, ftrace_format_files[i]);
+  }
+  MoveFromBinaryFormat(count, p);
+  event_format_files.clear();
+  for (uint32_t i = 0; i < count; ++i) {
+    std::string system;
+    MoveFromBinaryFormat(system, p);
+    uint32_t count_in_system;
+    MoveFromBinaryFormat(count_in_system, p);
+    for (uint32_t i = 0; i < count_in_system; ++i) {
+      std::string format;
+      DetachFile(p, format);
+      event_format_files.push_back(std::make_pair(system, std::move(format)));
+    }
+  }
+  DetachFile(p, kallsyms_file, 4);
+  DetachFile(p, printk_formats_file, 4);
+  CHECK_EQ(p, end);
+}
+
+void TracingFile::Dump(size_t indent) const {
+  PrintIndented(indent, "tracing data:\n");
+  PrintIndented(indent + 1, "magic: ");
+  for (size_t i = 0; i < 3u; ++i) {
+    printf("0x%x ", magic[i]);
+  }
+  for (size_t i = 3; i < sizeof(magic); ++i) {
+    printf("%c", magic[i]);
+  }
+  printf("\n");
+  PrintIndented(indent + 1, "version: %s\n", version.c_str());
+  PrintIndented(indent + 1, "endian: %d\n", endian);
+  PrintIndented(indent + 1, "header_page:\n%s\n\n", header_page_file.c_str());
+  PrintIndented(indent + 1, "header_event:\n%s\n\n", header_event_file.c_str());
+  for (size_t i = 0; i < ftrace_format_files.size(); ++i) {
+    PrintIndented(indent + 1, "ftrace format file %zu/%zu:\n%s\n\n", i + 1,
+                  ftrace_format_files.size(), ftrace_format_files[i].c_str());
+  }
+  for (size_t i = 0; i < event_format_files.size(); ++i) {
+    PrintIndented(indent + 1, "event format file %zu/%zu %s:\n%s\n\n", i + 1,
+                  event_format_files.size(),
+                  event_format_files[i].first.c_str(),
+                  event_format_files[i].second.c_str());
+  }
+  PrintIndented(indent + 1, "kallsyms:\n%s\n\n", kallsyms_file.c_str());
+  PrintIndented(indent + 1, "printk_formats:\n%s\n\n",
+                printk_formats_file.c_str());
+}
+
+enum class FormatParsingState {
+  READ_NAME,
+  READ_ID,
+  READ_FIELDS,
+  READ_PRINTFMT,
+};
+
+// Parse lines like: field:char comm[16]; offset:8; size:16;  signed:1;
+static TracingField ParseTracingField(const std::string& s) {
+  TracingField field;
+  size_t start = 0;
+  std::string name;
+  std::string value;
+  for (size_t i = 0; i < s.size(); ++i) {
+    if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) {
+      start = i;
+    } else if (s[i] == ':') {
+      name = s.substr(start, i - start);
+      start = i + 1;
+    } else if (s[i] == ';') {
+      value = s.substr(start, i - start);
+      if (name == "field") {
+        size_t pos = value.find_first_of('[');
+        if (pos == std::string::npos) {
+          field.name = value;
+          field.elem_count = 1;
+        } else {
+          field.name = value.substr(0, pos);
+          field.elem_count =
+              static_cast<size_t>(strtoull(&value[pos + 1], nullptr, 10));
+        }
+      } else if (name == "offset") {
+        field.offset =
+            static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+      } else if (name == "size") {
+        size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+        CHECK_EQ(size % field.elem_count, 0u);
+        field.elem_size = size / field.elem_count;
+      } else if (name == "signed") {
+        int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
+        field.is_signed = (is_signed == 1);
+      }
+    }
+  }
+  return field;
+}
+
+std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles()
+    const {
+  std::vector<TracingFormat> formats;
+  for (const auto& pair : event_format_files) {
+    TracingFormat format;
+    format.system_name = pair.first;
+    std::vector<std::string> strs = android::base::Split(pair.second, "\n");
+    FormatParsingState state = FormatParsingState::READ_NAME;
+    for (const auto& s : strs) {
+      if (state == FormatParsingState::READ_NAME) {
+        size_t pos = s.find_first_of("name:");
+        if (pos != std::string::npos) {
+          format.name = android::base::Trim(s.substr(pos + strlen("name:")));
+          state = FormatParsingState::READ_ID;
+        }
+      } else if (state == FormatParsingState::READ_ID) {
+        size_t pos = s.find_first_of("ID:");
+        if (pos != std::string::npos) {
+          format.id =
+              strtoull(s.substr(pos + strlen("ID:")).c_str(), nullptr, 10);
+          state = FormatParsingState::READ_FIELDS;
+        }
+      } else if (state == FormatParsingState::READ_FIELDS) {
+        size_t pos = s.find_first_of("field:");
+        if (pos != std::string::npos) {
+          TracingField field = ParseTracingField(s);
+          format.fields.push_back(field);
+        }
+      }
+    }
+    formats.push_back(format);
+  }
+  return formats;
+}
+
+Tracing::Tracing(const std::vector<char>& data) {
+  tracing_file_ = new TracingFile;
+  tracing_file_->LoadFromBinary(data);
+}
+
+Tracing::~Tracing() { delete tracing_file_; }
+
+void Tracing::Dump(size_t indent) { tracing_file_->Dump(indent); }
+
+TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
+  if (tracing_formats_.empty()) {
+    tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
+  }
+  for (const auto& format : tracing_formats_) {
+    if (format.id == trace_event_id) {
+      return format;
+    }
+  }
+  LOG(FATAL) << "no tracing format for id " << trace_event_id;
+  return TracingFormat();
+}
+
+std::string Tracing::GetTracingEventNameHavingId(uint64_t trace_event_id) {
+  if (tracing_formats_.empty()) {
+    tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
+  }
+  for (const auto& format : tracing_formats_) {
+    if (format.id == trace_event_id) {
+      return android::base::StringPrintf("%s:%s", format.system_name.c_str(),
+                                         format.name.c_str());
+    }
+  }
+  return "";
+}
+
+const std::string& Tracing::GetKallsyms() const {
+  return tracing_file_->GetKallsymsFile();
+}
+
+uint32_t Tracing::GetPageSize() const { return tracing_file_->GetPageSize(); }
+
+bool GetTracingData(const std::vector<EventTypeAndModifier>& event_types,
+                    std::vector<char>* data) {
+  data->clear();
+  std::vector<TraceType> trace_types;
+  for (const auto& type : event_types) {
+    if (type.event_type.type == PERF_TYPE_TRACEPOINT) {
+      size_t pos = type.event_type.name.find(':');
+      TraceType trace_type;
+      trace_type.system = type.event_type.name.substr(0, pos);
+      trace_type.name = type.event_type.name.substr(pos + 1);
+      trace_types.push_back(trace_type);
+    }
+  }
+  TracingFile tracing_file;
+  if (!tracing_file.RecordHeaderFiles()) {
+    return false;
+  }
+  tracing_file.RecordFtraceFiles(trace_types);
+  if (!tracing_file.RecordEventFiles(trace_types)) {
+    return false;
+  }
+  // Don't record /proc/kallsyms here, as it will be contained in
+  // KernelSymbolRecord.
+  if (!tracing_file.RecordPrintkFormatsFile()) {
+    return false;
+  }
+  *data = tracing_file.BinaryFormat();
+  return true;
+}
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
new file mode 100644
index 0000000..bd3c1bf
--- /dev/null
+++ b/simpleperf/tracing.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 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 SIMPLE_PERF_TRACING_H_
+#define SIMPLE_PERF_TRACING_H_
+
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "event_type.h"
+
+struct TracingField {
+  std::string name;
+  size_t offset;
+  size_t elem_size;
+  size_t elem_count;
+  bool is_signed;
+};
+
+struct TracingFormat {
+  std::string system_name;
+  std::string name;
+  uint64_t id;
+  std::vector<TracingField> fields;
+
+  void GetField(const std::string& name, uint32_t& offset, uint32_t& size) {
+    const TracingField& field = GetField(name);
+    offset = field.offset;
+    size = field.elem_size;
+  }
+
+ private:
+  const TracingField& GetField(const std::string& name) {
+    for (const auto& field : fields) {
+      if (field.name == name) {
+        return field;
+      }
+    }
+    LOG(FATAL) << "Couldn't find field " << name << "in TracingFormat of "
+               << this->name;
+    return fields[0];
+  }
+};
+
+class TracingFile;
+
+class Tracing {
+ public:
+  Tracing(const std::vector<char>& data);
+  ~Tracing();
+  void Dump(size_t indent);
+  TracingFormat GetTracingFormatHavingId(uint64_t trace_event_id);
+  std::string GetTracingEventNameHavingId(uint64_t trace_event_id);
+  const std::string& GetKallsyms() const;
+  uint32_t GetPageSize() const;
+
+ private:
+  TracingFile* tracing_file_;
+  std::vector<TracingFormat> tracing_formats_;
+};
+
+bool GetTracingData(const std::vector<EventTypeAndModifier>& event_types,
+                    std::vector<char>* data);
+
+#endif  // SIMPLE_PERF_TRACING_H_
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index 5fb940e..2df15e6 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -310,3 +310,26 @@
   }
   return false;
 }
+
+size_t GetPageSize() {
+#if defined(__linux__)
+  return sysconf(_SC_PAGE_SIZE);
+#else
+  return 4096;
+#endif
+}
+
+uint64_t ConvertBytesToValue(const char* bytes, uint32_t size) {
+  switch (size) {
+    case 1:
+      return *reinterpret_cast<const uint8_t*>(bytes);
+    case 2:
+      return *reinterpret_cast<const uint16_t*>(bytes);
+    case 4:
+      return *reinterpret_cast<const uint32_t*>(bytes);
+    case 8:
+      return *reinterpret_cast<const uint64_t*>(bytes);
+  }
+  LOG(FATAL) << "unexpected size " << size << " in ConvertBytesToValue";
+  return 0;
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index a797a32..ef7d386 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -136,4 +136,8 @@
 bool ProcessKernelSymbols(std::string& symbol_data,
                           std::function<bool(const KernelSymbol&)> callback);
 
+size_t GetPageSize();
+
+uint64_t ConvertBytesToValue(const char* bytes, uint32_t size);
+
 #endif  // SIMPLE_PERF_UTILS_H_