Merge "Add simpleperf report command."
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index b7213d7..d2c5276 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -31,6 +31,7 @@
   cmd_help.cpp \
   cmd_list.cpp \
   cmd_record.cpp \
+  cmd_report.cpp \
   cmd_stat.cpp \
   command.cpp \
   environment.cpp \
@@ -41,6 +42,7 @@
   read_elf.cpp \
   record.cpp \
   record_file.cpp \
+  sample_tree.cpp \
   utils.cpp \
   workload.cpp \
 
@@ -102,6 +104,7 @@
   cmd_dumprecord_test.cpp \
   cmd_list_test.cpp \
   cmd_record_test.cpp \
+  cmd_report_test.cpp \
   cmd_stat_test.cpp \
   command_test.cpp \
   cpu_offline_test.cpp \
@@ -109,6 +112,7 @@
   gtest_main.cpp \
   record_file_test.cpp \
   record_test.cpp \
+  sample_tree_test.cpp \
   workload_test.cpp \
 
 include $(CLEAR_VARS)
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index e4594b6..9245d18 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -185,8 +185,8 @@
       while (p < end) {
         const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
         CHECK_LE(p + header->size, end);
-        CHECK_EQ(PERF_RECORD_BUILD_ID, header->type);
         BuildIdRecord record(header);
+        record.header.type = PERF_RECORD_BUILD_ID;  // Set type explicitly as perf doesn't set it.
         record.Dump(1);
         p += header->size;
       }
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
new file mode 100644
index 0000000..7b41bf7
--- /dev/null
+++ b/simpleperf/cmd_report.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2015 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 <inttypes.h>
+#include <functional>
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+#include <base/strings.h>
+
+#include "command.h"
+#include "environment.h"
+#include "event_attr.h"
+#include "event_type.h"
+#include "record.h"
+#include "record_file.h"
+#include "sample_tree.h"
+
+typedef int (*compare_sample_entry_t)(const SampleEntry& sample1, const SampleEntry& sample2);
+typedef void (*print_sample_entry_header_t)();
+typedef void (*print_sample_entry_t)(const SampleEntry& sample);
+
+struct ReportItem {
+  compare_sample_entry_t compare_function;
+  print_sample_entry_header_t print_header_function;
+  print_sample_entry_t print_function;
+};
+
+static int ComparePid(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return sample1.process_entry->pid - sample2.process_entry->pid;
+}
+
+static void PrintHeaderPid() {
+  printf("%8s", "Pid");
+}
+
+static void PrintPid(const SampleEntry& sample) {
+  printf("%8d", sample.process_entry->pid);
+}
+
+static ReportItem report_pid_item = {
+    .compare_function = ComparePid,
+    .print_header_function = PrintHeaderPid,
+    .print_function = PrintPid,
+};
+
+static int CompareComm(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return strcmp(sample1.process_entry->comm.c_str(), sample2.process_entry->comm.c_str());
+}
+
+static void PrintHeaderComm() {
+  printf("%20s", "Command");
+}
+
+static void PrintComm(const SampleEntry& sample) {
+  printf("%20s", sample.process_entry->comm.c_str());
+}
+
+static ReportItem report_comm_item = {
+    .compare_function = CompareComm,
+    .print_header_function = PrintHeaderComm,
+    .print_function = PrintComm,
+};
+
+static int CompareDso(const SampleEntry& sample1, const SampleEntry& sample2) {
+  return strcmp(sample1.map_entry->filename.c_str(), sample2.map_entry->filename.c_str());
+}
+
+static void PrintHeaderDso() {
+  printf("%20s", "Shared Object");
+}
+
+static void PrintDso(const SampleEntry& sample) {
+  std::string filename = sample.map_entry->filename;
+  if (filename == DEFAULT_KERNEL_MMAP_NAME) {
+    filename = "[kernel.kallsyms]";
+  } else if (filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) {
+    filename = "[unknown]";
+  }
+  printf("%20s", filename.c_str());
+}
+
+static ReportItem report_dso_item = {
+    .compare_function = CompareDso,
+    .print_header_function = PrintHeaderDso,
+    .print_function = PrintDso,
+};
+
+static std::unordered_map<std::string, ReportItem*> report_item_map = {
+    {"comm", &report_comm_item}, {"pid", &report_pid_item}, {"dso", &report_dso_item},
+};
+
+class ReportCommand : public Command {
+ public:
+  ReportCommand()
+      : Command("report", "report sampling information in perf.data",
+                "Usage: simpleperf report [options]\n"
+                "    -i <file>     specify path of record file, default is perf.data\n"
+                "    --sort key1,key2,... Select the keys to sort and print the report.\n"
+                "                         Possible keys include pid, comm, dso.\n"
+                "                         Default keys are \"comm,pid,dso\"\n"),
+        record_filename_("perf.data") {
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  bool ReadEventAttrFromRecordFile();
+  void ReadSampleTreeFromRecordFile();
+  int CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2);
+  void PrintReport();
+  void PrintReportContext();
+  void PrintReportHeader();
+  void PrintReportEntry(const SampleEntry& sample);
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+  perf_event_attr event_attr_;
+  std::vector<ReportItem*> report_items_;
+  std::unique_ptr<SampleTree> sample_tree_;
+};
+
+bool ReportCommand::Run(const std::vector<std::string>& args) {
+  // 1. Parse options.
+  if (!ParseOptions(args)) {
+    return false;
+  }
+
+  // 2. Read record file and build SampleTree.
+  record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+  if (record_file_reader_ == nullptr) {
+    return false;
+  }
+  if (!ReadEventAttrFromRecordFile()) {
+    return false;
+  }
+  ReadSampleTreeFromRecordFile();
+
+  // 3. Read symbol table from elf files.
+
+  // 4. Show collected information.
+  PrintReport();
+
+  return true;
+}
+
+bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
+  for (size_t i = 0; i < args.size(); ++i) {
+    if (args[i] == "-i") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      record_filename_ = args[i];
+    } else if (args[i] == "--sort") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> sort_keys = android::base::Split(args[i], ",");
+      for (auto& key : sort_keys) {
+        auto it = report_item_map.find(key);
+        if (it != report_item_map.end()) {
+          report_items_.push_back(it->second);
+        } else {
+          LOG(ERROR) << "Unknown sort key: " << key;
+          return false;
+        }
+      }
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  if (report_items_.empty()) {
+    report_items_.push_back(report_item_map["comm"]);
+    report_items_.push_back(report_item_map["pid"]);
+    report_items_.push_back(report_item_map["dso"]);
+  }
+  return true;
+}
+
+bool ReportCommand::ReadEventAttrFromRecordFile() {
+  std::vector<const PerfFileFormat::FileAttr*> attrs = record_file_reader_->AttrSection();
+  if (attrs.size() != 1) {
+    LOG(ERROR) << "record file contains " << attrs.size() << " attrs";
+    return false;
+  }
+  event_attr_ = attrs[0]->attr;
+  return true;
+}
+
+void ReportCommand::ReadSampleTreeFromRecordFile() {
+  compare_sample_func_t compare_sample_callback = std::bind(
+      &ReportCommand::CompareSampleEntry, this, std::placeholders::_1, std::placeholders::_2);
+  sample_tree_ = std::unique_ptr<SampleTree>(new SampleTree(compare_sample_callback));
+  sample_tree_->AddProcess(0, "swapper");
+
+  std::vector<std::unique_ptr<const Record>> records = record_file_reader_->DataSection();
+  for (auto& record : records) {
+    if (record->header.type == PERF_RECORD_MMAP) {
+      const MmapRecord& r = *static_cast<const MmapRecord*>(record.get());
+      if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
+        sample_tree_->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff,
+                                   r.sample_id.time_data.time, r.filename);
+      } else {
+        sample_tree_->AddUserMap(r.data.pid, r.data.addr, r.data.len, r.data.pgoff,
+                                 r.sample_id.time_data.time, r.filename);
+      }
+    } else if (record->header.type == PERF_RECORD_SAMPLE) {
+      const SampleRecord& r = *static_cast<const SampleRecord*>(record.get());
+      sample_tree_->AddSample(r.tid_data.pid, r.tid_data.tid, r.ip_data.ip, r.time_data.time,
+                              r.period_data.period);
+    } else if (record->header.type == PERF_RECORD_COMM) {
+      const CommRecord& r = *static_cast<const CommRecord*>(record.get());
+      sample_tree_->AddProcess(r.data.pid, r.comm);
+    }
+  }
+}
+
+int ReportCommand::CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2) {
+  for (auto& item : report_items_) {
+    int result = item->compare_function(sample1, sample2);
+    if (result != 0) {
+      return result;
+    }
+  }
+  return 0;
+}
+
+void ReportCommand::PrintReport() {
+  PrintReportContext();
+  PrintReportHeader();
+  sample_tree_->VisitAllSamples(
+      std::bind(&ReportCommand::PrintReportEntry, this, std::placeholders::_1));
+  fflush(stdout);
+}
+
+void ReportCommand::PrintReportContext() {
+  const EventType* event_type =
+      EventTypeFactory::FindEventTypeByConfig(event_attr_.type, event_attr_.config);
+  std::string event_type_name;
+  if (event_type != nullptr) {
+    event_type_name = event_type->name;
+  } else {
+    event_type_name =
+        android::base::StringPrintf("(type %u, config %llu)", event_attr_.type, event_attr_.config);
+  }
+  printf("Samples: %" PRIu64 " of event '%s'\n", sample_tree_->TotalSamples(),
+         event_type_name.c_str());
+  printf("Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod());
+}
+
+void ReportCommand::PrintReportHeader() {
+  printf("%8s", "Overhead");
+  for (ReportItem* item : report_items_) {
+    printf(" ");
+    item->print_header_function();
+  }
+  printf("\n");
+}
+
+void ReportCommand::PrintReportEntry(const SampleEntry& sample) {
+  double percentage = 0.0;
+  if (sample_tree_->TotalPeriod() != 0) {
+    percentage = 100.0 * sample.period / sample_tree_->TotalPeriod();
+  }
+  printf("%7.2lf%%", percentage);
+  for (ReportItem* item : report_items_) {
+    printf(" ");
+    item->print_function(sample);
+  }
+  printf("\n");
+}
+
+__attribute__((constructor)) static void RegisterReportCommand() {
+  RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
new file mode 100644
index 0000000..34d156c
--- /dev/null
+++ b/simpleperf/cmd_report_test.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include "command.h"
+
+static std::unique_ptr<Command> RecordCmd() {
+  return CreateCommandInstance("record");
+}
+
+static std::unique_ptr<Command> ReportCmd() {
+  return CreateCommandInstance("report");
+}
+
+class ReportCommandTest : public ::testing::Test {
+ protected:
+  static void SetUpTestCase() {
+    ASSERT_TRUE(RecordCmd()->Run({"-a", "sleep", "1"}));
+    ASSERT_TRUE(RecordCmd()->Run({"-a", "-o", "perf2.data", "sleep", "1"}));
+  }
+};
+
+TEST_F(ReportCommandTest, no_options) {
+  ASSERT_TRUE(ReportCmd()->Run({}));
+}
+
+TEST_F(ReportCommandTest, input_file_option) {
+  ASSERT_TRUE(ReportCmd()->Run({"-i", "perf2.data"}));
+}
+
+TEST_F(ReportCommandTest, sort_option_pid) {
+  ASSERT_TRUE(ReportCmd()->Run({"--sort", "pid"}));
+}
+
+TEST_F(ReportCommandTest, sort_option_all) {
+  ASSERT_TRUE(ReportCmd()->Run({"--sort", "comm,pid,dso"}));
+}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index 79ed4b8..5e02215 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -100,7 +100,7 @@
     event_name = event_type->name;
   }
 
-  PrintIndented(indent, "event_attr: for event %s\n", event_name.c_str());
+  PrintIndented(indent, "event_attr: for event type %s\n", event_name.c_str());
 
   PrintIndented(indent + 1, "type %u, size %u, config %llu\n", attr.type, attr.size, attr.config);
 
diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp
new file mode 100644
index 0000000..9c792f2
--- /dev/null
+++ b/simpleperf/sample_tree.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 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 "sample_tree.h"
+
+#include <base/logging.h>
+
+bool SampleTree::MapComparator::operator()(const MapEntry& map1, const MapEntry& map2) {
+  if (map1.pid != map2.pid) {
+    return map1.pid < map2.pid;
+  }
+  if (map1.start_addr != map2.start_addr) {
+    return map1.start_addr < map2.start_addr;
+  }
+  if (map1.len != map2.len) {
+    return map1.len < map2.len;
+  }
+  if (map1.time != map2.time) {
+    return map1.time < map2.time;
+  }
+  return false;
+}
+
+void SampleTree::AddProcess(int pid, const std::string& comm) {
+  ProcessEntry process = {
+      .pid = pid, .comm = comm,
+  };
+  process_tree_[pid] = process;
+}
+
+void SampleTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
+                              const std::string& filename) {
+  MapEntry map = {
+      .pid = -1,
+      .start_addr = start_addr,
+      .len = len,
+      .pgoff = pgoff,
+      .time = time,
+      .filename = filename,
+  };
+  kernel_map_tree_.insert(map);
+}
+
+void SampleTree::AddUserMap(int pid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
+                            uint64_t time, const std::string& filename) {
+  MapEntry map = {
+      .pid = pid,
+      .start_addr = start_addr,
+      .len = len,
+      .pgoff = pgoff,
+      .time = time,
+      .filename = filename,
+  };
+  user_map_tree_.insert(map);
+}
+
+const ProcessEntry* SampleTree::FindProcessEntryOrNew(int pid) {
+  auto it = process_tree_.find(pid);
+  if (it == process_tree_.end()) {
+    ProcessEntry new_entry = {
+        .pid = pid, .comm = "unknown",
+    };
+    auto pair = process_tree_.insert(std::make_pair(pid, new_entry));
+    it = pair.first;
+  }
+  return &it->second;
+}
+
+static bool IsIpInMap(int pid, uint64_t ip, const MapEntry& map) {
+  return (pid == map.pid && map.start_addr <= ip && map.start_addr + map.len > ip);
+}
+
+const MapEntry* SampleTree::FindMapEntryOrNew(int pid, uint64_t ip) {
+  // Construct a map_entry which is strictly after the searched map_entry, based on MapComparator.
+  MapEntry find_map = {
+      .pid = pid,
+      .start_addr = ip,
+      .len = static_cast<uint64_t>(-1),
+      .time = static_cast<uint64_t>(-1),
+  };
+  auto it = user_map_tree_.upper_bound(find_map);
+  if (it != user_map_tree_.begin() && IsIpInMap(pid, ip, *--it)) {
+    return &*it;
+  }
+  find_map.pid = -1;
+  it = kernel_map_tree_.upper_bound(find_map);
+  if (it != kernel_map_tree_.begin() && IsIpInMap(-1, ip, *--it)) {
+    return &*it;
+  }
+  auto unknown_it = unknown_maps_.find(pid);
+  if (unknown_it == unknown_maps_.end()) {
+    MapEntry unknown_map = {
+        .pid = pid,
+        .start_addr = 0,
+        .len = static_cast<uint64_t>(-1),
+        .pgoff = 0,
+        .time = 0,
+        .filename = "unknown",
+    };
+    auto pair = unknown_maps_.insert(std::make_pair(pid, unknown_map));
+    unknown_it = pair.first;
+  }
+  return &unknown_it->second;
+}
+
+void SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period) {
+  const ProcessEntry* process_entry = FindProcessEntryOrNew(pid);
+  const MapEntry* map_entry = FindMapEntryOrNew(pid, ip);
+
+  SampleEntry find_sample = {
+      .tid = tid,
+      .ip = ip,
+      .time = time,
+      .period = period,
+      .sample_count = 1,
+      .process_entry = process_entry,
+      .map_entry = map_entry,
+  };
+  auto it = sample_tree_.find(find_sample);
+  if (it == sample_tree_.end()) {
+    sample_tree_.insert(find_sample);
+  } else {
+    SampleEntry* sample_entry = const_cast<SampleEntry*>(&*it);
+    sample_entry->period += period;
+    sample_entry->sample_count++;
+  }
+  total_samples_++;
+  total_period_ += period;
+}
+
+void SampleTree::VisitAllSamples(std::function<void(const SampleEntry&)> callback) {
+  if (sorted_sample_tree_.size() != sample_tree_.size()) {
+    sorted_sample_tree_.clear();
+    for (auto& sample : sample_tree_) {
+      sorted_sample_tree_.insert(sample);
+    }
+  }
+  for (auto& sample : sorted_sample_tree_) {
+    callback(sample);
+  }
+}
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
new file mode 100644
index 0000000..ba144ba
--- /dev/null
+++ b/simpleperf/sample_tree.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 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_SAMPLE_TREE_H_
+#define SIMPLE_PERF_SAMPLE_TREE_H_
+
+#include <functional>
+#include <set>
+#include <string>
+#include <unordered_map>
+
+struct ProcessEntry {
+  int pid;
+  std::string comm;
+};
+
+struct MapEntry {
+  int pid;  // pid = -1 for kernel map entries.
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+  uint64_t time;  // Map creation time.
+  std::string filename;
+};
+
+struct SampleEntry {
+  int tid;
+  uint64_t ip;
+  uint64_t time;
+  uint64_t period;
+  uint64_t sample_count;
+  const ProcessEntry* process_entry;
+  const MapEntry* map_entry;
+};
+
+typedef std::function<int(const SampleEntry&, const SampleEntry&)> compare_sample_func_t;
+
+class SampleTree {
+ public:
+  SampleTree(compare_sample_func_t sample_compare_function)
+      : sample_comparator_(sample_compare_function),
+        sample_tree_(sample_comparator_),
+        sorted_sample_comparator_(sample_compare_function),
+        sorted_sample_tree_(sorted_sample_comparator_),
+        total_samples_(0),
+        total_period_(0) {
+  }
+
+  void AddProcess(int pid, const std::string& comm);
+  void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
+                    const std::string& filename);
+  void AddUserMap(int pid, uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
+                  const std::string& filename);
+  void AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period);
+  void VisitAllSamples(std::function<void(const SampleEntry&)> callback);
+
+  uint64_t TotalSamples() const {
+    return total_samples_;
+  }
+
+  uint64_t TotalPeriod() const {
+    return total_period_;
+  }
+
+ private:
+  const ProcessEntry* FindProcessEntryOrNew(int pid);
+  const MapEntry* FindMapEntryOrNew(int pid, uint64_t ip);
+
+  struct MapComparator {
+    bool operator()(const MapEntry& map1, const MapEntry& map2);
+  };
+
+  struct SampleComparator {
+    bool operator()(const SampleEntry& sample1, const SampleEntry& sample2) {
+      return compare_function(sample1, sample2) < 0;
+    }
+    SampleComparator(compare_sample_func_t compare_function) : compare_function(compare_function) {
+    }
+
+    compare_sample_func_t compare_function;
+  };
+
+  struct SortedSampleComparator {
+    bool operator()(const SampleEntry& sample1, const SampleEntry& sample2) {
+      if (sample1.period != sample2.period) {
+        return sample1.period > sample2.period;
+      }
+      return compare_function(sample1, sample2) < 0;
+    }
+    SortedSampleComparator(compare_sample_func_t compare_function)
+        : compare_function(compare_function) {
+    }
+
+    compare_sample_func_t compare_function;
+  };
+
+  std::unordered_map<int, ProcessEntry> process_tree_;
+
+  std::set<MapEntry, MapComparator> kernel_map_tree_;
+  std::set<MapEntry, MapComparator> user_map_tree_;
+  std::unordered_map<int, MapEntry> unknown_maps_;
+
+  SampleComparator sample_comparator_;
+  std::set<SampleEntry, SampleComparator> sample_tree_;
+  SortedSampleComparator sorted_sample_comparator_;
+  std::set<SampleEntry, SortedSampleComparator> sorted_sample_tree_;
+
+  uint64_t total_samples_;
+  uint64_t total_period_;
+};
+
+#endif  // SIMPLE_PERF_SAMPLE_TREE_H_
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
new file mode 100644
index 0000000..09a7a2c
--- /dev/null
+++ b/simpleperf/sample_tree_test.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 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 "sample_tree.h"
+
+#include <gtest/gtest.h>
+
+struct ExpectedSampleInMap {
+  int pid;
+  int tid;
+  int map_pid;
+  uint64_t map_start_addr;
+  size_t sample_count;
+};
+
+static void SampleMatchExpectation(const SampleEntry& sample, const ExpectedSampleInMap& expected,
+                                   bool* has_error) {
+  *has_error = true;
+  ASSERT_TRUE(sample.process_entry != nullptr);
+  ASSERT_EQ(expected.pid, sample.process_entry->pid);
+  ASSERT_EQ(expected.tid, sample.tid);
+  ASSERT_TRUE(sample.map_entry != nullptr);
+  ASSERT_EQ(expected.map_pid, sample.map_entry->pid);
+  ASSERT_EQ(expected.map_start_addr, sample.map_entry->start_addr);
+  ASSERT_EQ(expected.sample_count, sample.sample_count);
+  *has_error = false;
+}
+
+static void CheckSampleCallback(const SampleEntry& sample,
+                                std::vector<ExpectedSampleInMap>& expected_samples, size_t* pos) {
+  ASSERT_LT(*pos, expected_samples.size());
+  bool has_error;
+  SampleMatchExpectation(sample, expected_samples[*pos], &has_error);
+  ASSERT_FALSE(has_error) << "Error matching sample at pos " << *pos;
+  ++*pos;
+}
+
+static int CompareSampleFunction(const SampleEntry& sample1, const SampleEntry& sample2) {
+  if (sample1.process_entry->pid != sample2.process_entry->pid) {
+    return sample1.process_entry->pid - sample2.process_entry->pid;
+  }
+  if (sample1.tid != sample2.tid) {
+    return sample1.tid - sample2.tid;
+  }
+  if (sample1.map_entry->pid != sample2.map_entry->pid) {
+    return sample1.map_entry->pid - sample2.map_entry->pid;
+  }
+  if (sample1.map_entry->start_addr != sample2.map_entry->start_addr) {
+    return sample1.map_entry->start_addr - sample2.map_entry->start_addr;
+  }
+  return 0;
+}
+
+class SampleTreeTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    sample_tree = std::unique_ptr<SampleTree>(new SampleTree(CompareSampleFunction));
+    sample_tree->AddUserMap(1, 1, 10, 0, 0, "");
+    sample_tree->AddUserMap(1, 11, 10, 0, 0, "");
+    sample_tree->AddUserMap(2, 1, 20, 0, 0, "");
+    sample_tree->AddKernelMap(11, 20, 0, 0, "");
+  }
+
+  void VisitSampleTree(std::vector<ExpectedSampleInMap>& expected_samples) {
+    size_t pos = 0;
+    sample_tree->VisitAllSamples(
+        std::bind(&CheckSampleCallback, std::placeholders::_1, expected_samples, &pos));
+    ASSERT_EQ(expected_samples.size(), pos);
+  }
+
+  std::unique_ptr<SampleTree> sample_tree;
+};
+
+TEST_F(SampleTreeTest, ip_in_map) {
+  sample_tree->AddSample(1, 1, 1, 0, 0);
+  sample_tree->AddSample(1, 1, 5, 0, 0);
+  sample_tree->AddSample(1, 1, 10, 0, 0);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, 1, 1, 3},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_pid) {
+  sample_tree->AddSample(1, 1, 1, 0, 0);
+  sample_tree->AddSample(2, 2, 1, 0, 0);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, 1, 1, 1}, {2, 2, 2, 1, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_tid) {
+  sample_tree->AddSample(1, 1, 1, 0, 0);
+  sample_tree->AddSample(1, 11, 1, 0, 0);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, 1, 1, 1}, {1, 11, 1, 1, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, different_map) {
+  sample_tree->AddSample(1, 1, 1, 0, 0);
+  sample_tree->AddSample(1, 1, 11, 0, 0);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, 1, 1, 1}, {1, 1, 1, 11, 1},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, unmapped_sample) {
+  sample_tree->AddSample(1, 1, 0, 0, 0);
+  sample_tree->AddSample(1, 1, 31, 0, 0);
+  sample_tree->AddSample(1, 1, 70, 0, 0);
+  // Match the unknown map.
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, 1, 0, 3},
+  };
+  VisitSampleTree(expected_samples);
+}
+
+TEST_F(SampleTreeTest, map_kernel) {
+  sample_tree->AddSample(1, 1, 11, 0, 0);
+  sample_tree->AddSample(1, 1, 21, 0, 0);
+  std::vector<ExpectedSampleInMap> expected_samples = {
+      {1, 1, -1, 11, 1}, {1, 1, 1, 11, 1},
+  };
+  VisitSampleTree(expected_samples);
+}