trace_to_text: add an option to convert perf profiles to pprof

Currently invoked as: "trace_to_text profile --perf ...". I'm thinking
of moving to a point where "profile ..." without any extra flags does
the right thing (figures out what the trace contains, and emits either
or both types of profiles). But that has to be saved for a follow-up CL.

Change-Id: Iad7384c64d073a89044876936e46f6b5fac9f6d0
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index ced52ee..f206f16 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -52,12 +52,7 @@
 
 namespace {
 
-struct View {
-  const char* type;
-  const char* unit;
-  const char* aggregator;
-  const char* filter;
-};
+using ::perfetto::trace_processor::Iterator;
 
 void MaybeDemangle(std::string* name) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
@@ -72,80 +67,22 @@
   }
 }
 
-const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
-const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size >= 0"};
-const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
-                             "size >= 0"};
-const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
+uint64_t ToPprofId(int64_t id) {
+  PERFETTO_DCHECK(id >= 0);
+  return static_cast<uint64_t>(id) + 1;
+}
 
-const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
-                       kSpaceView};
-
-using trace_processor::Iterator;
-
-constexpr const char* kQueryProfiles =
-    "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name "
-    "from heap_profile_allocation hpa, "
-    "process p where p.upid = hpa.upid;";
-
-int64_t GetStatsInt(trace_processor::TraceProcessor* tp,
-                    const std::string& name,
-                    uint64_t pid) {
-  auto it = tp->ExecuteQuery("SELECT value from stats where name = '" + name +
-                             "' AND idx = " + std::to_string(pid));
-  if (!it.Next()) {
-    if (!it.Status().ok()) {
-      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                              it.Status().message().c_str());
-      return -1;
+std::string AsCsvString(std::vector<uint64_t> vals) {
+  std::string ret;
+  for (size_t i = 0; i < vals.size(); i++) {
+    if (i != 0) {
+      ret += ",";
     }
-    // TODO(fmayer): Remove this case once we always get an entry in the stats
-    // table.
-    return 0;
+    ret += std::to_string(vals[i]);
   }
-  return it.Get(0).AsLong();
+  return ret;
 }
 
-bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
-  bool success = true;
-  int64_t stat = GetStatsInt(tp, "heapprofd_buffer_corrupted", pid);
-  if (stat == -1) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
-  } else if (stat > 0) {
-    success = false;
-    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                  " ended early due to a buffer corruption."
-                  " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
-                  " CLIENT MEMORY CORRUPTION.",
-                  pid);
-  }
-  stat = GetStatsInt(tp, "heapprofd_buffer_overran", pid);
-  if (stat == -1) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
-  } else if (stat > 0) {
-    success = false;
-    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                  " ended early due to a buffer overrun.",
-                  pid);
-  }
-
-  stat = GetStatsInt(tp, "heapprofd_rejected_concurrent", pid);
-  if (stat == -1) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
-  } else if (stat > 0) {
-    success = false;
-    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                  " was rejected due to a concurrent profile.",
-                  pid);
-  }
-  return success;
-}
-
-struct Callsite {
-  int64_t id;
-  int64_t frame_id;
-};
-
 // Return map from callsite_id to list of frame_ids that make up the callstack.
 std::vector<std::vector<int64_t>> GetCallsiteToFrames(
     trace_processor::TraceProcessor* tp) {
@@ -184,6 +121,17 @@
   return result;
 }
 
+base::Optional<int64_t> GetMaxSymbolId(trace_processor::TraceProcessor* tp) {
+  auto max_symbol_id_it =
+      tp->ExecuteQuery("select max(id) from stack_profile_symbol");
+  if (!max_symbol_id_it.Next()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
+                            max_symbol_id_it.Status().message().c_str());
+    return base::nullopt;
+  }
+  return base::make_optional(max_symbol_id_it.Get(0).AsLong());
+}
+
 struct Line {
   int64_t symbol_id;
   uint32_t line_number;
@@ -210,6 +158,28 @@
   return result;
 }
 
+base::Optional<int64_t> GetStatsEntry(
+    trace_processor::TraceProcessor* tp,
+    const std::string& name,
+    base::Optional<uint64_t> idx = base::nullopt) {
+  std::string query = "select value from stats where name == '" + name + "'";
+  if (idx.has_value())
+    query += " and idx == " + std::to_string(idx.value());
+
+  auto it = tp->ExecuteQuery(query);
+  if (!it.Next()) {
+    if (!it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                              it.Status().message().c_str());
+      return base::nullopt;
+    }
+    // some stats are not present unless non-zero
+    return base::make_optional(0);
+  }
+  return base::make_optional(it.Get(0).AsLong());
+}
+
+// Helper for constructing |perftools.profiles.Profile| protos.
 class GProfileBuilder {
  public:
   GProfileBuilder(
@@ -225,78 +195,57 @@
     PERFETTO_CHECK(empty_id == 0);
   }
 
-  std::vector<Iterator> BuildViewIterators(trace_processor::TraceProcessor* tp,
-                                           uint64_t upid,
-                                           uint64_t ts,
-                                           const char* heap_name) {
-    std::vector<Iterator> view_its;
-    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-      const View& v = kViews[i];
-      std::string query = "SELECT hpa.callsite_id ";
-      query += ", " + std::string(v.aggregator) +
-               " FROM heap_profile_allocation hpa ";
-      // TODO(fmayer): Figure out where negative callsite_id comes from.
-      query += "WHERE hpa.callsite_id >= 0 ";
-      query += "AND hpa.upid = " + std::to_string(upid) + " ";
-      query += "AND hpa.ts <= " + std::to_string(ts) + " ";
-      query += "AND hpa.heap_name = '" + std::string(heap_name) + "' ";
-      if (v.filter)
-        query += "AND " + std::string(v.filter) + " ";
-      query += "GROUP BY hpa.callsite_id;";
-      view_its.emplace_back(tp->ExecuteQuery(query));
+  void WriteSampleTypes(
+      const std::vector<std::pair<std::string, std::string>>& sample_types) {
+    // The interner might eagerly append to the profile proto, prevent it from
+    // breaking up other messages by making a separate pass.
+    for (const auto& st : sample_types) {
+      Intern(st.first);
+      Intern(st.second);
     }
-    return view_its;
+    for (const auto& st : sample_types) {
+      auto* sample_type = result_->add_sample_type();
+      sample_type->set_type(Intern(st.first));
+      sample_type->set_unit(Intern(st.second));
+    }
   }
 
-  bool WriteAllocations(std::vector<Iterator>* view_its,
-                        std::set<int64_t>* seen_frames) {
-    for (;;) {
-      bool all_next = true;
-      bool any_next = false;
-      for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-        Iterator& it = (*view_its)[i];
-        bool next = it.Next();
-        if (!it.Status().ok()) {
-          PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
-                                  it.Status().message().c_str());
-          return false;
-        }
-        all_next = all_next && next;
-        any_next = any_next || next;
-      }
-
-      if (!all_next) {
-        PERFETTO_CHECK(!any_next);
-        break;
-      }
-
-      auto* gsample = result_->add_sample();
-      protozero::PackedVarInt sample_values;
-      int64_t callstack_id = -1;
-      for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-        if (i == 0) {
-          callstack_id = (*view_its)[i].Get(0).AsLong();
-          auto frames = FramesForCallstack(callstack_id);
-          if (frames.empty())
-            return false;
-          protozero::PackedVarInt location_ids;
-          for (int64_t frame : frames)
-            location_ids.Append(ToPprofId(frame));
-          gsample->set_location_id(location_ids);
-          seen_frames->insert(frames.cbegin(), frames.cend());
-        } else {
-          if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
-            PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
-            return false;
-          }
-        }
-        sample_values.Append((*view_its)[i].Get(1).AsLong());
-      }
-      gsample->set_value(sample_values);
+  bool AddSample(const protozero::PackedVarInt& values, int64_t callstack_id) {
+    const auto& frames = FramesForCallstack(callstack_id);
+    if (frames.empty()) {
+      PERFETTO_DFATAL_OR_ELOG(
+          "Failed to find frames for callstack id %" PRIi64 "", callstack_id);
+      return false;
     }
+    protozero::PackedVarInt location_ids;
+    for (int64_t frame : frames)
+      location_ids.Append(ToPprofId(frame));
+
+    auto* gsample = result_->add_sample();
+    gsample->set_value(values);
+    gsample->set_location_id(location_ids);
+
+    // remember frames to be emitted
+    seen_frames_.insert(frames.cbegin(), frames.cend());
+
     return true;
   }
 
+  std::string CompleteProfile(trace_processor::TraceProcessor* tp) {
+    std::set<int64_t> seen_mappings;
+    std::set<int64_t> seen_symbol_ids;
+
+    // Write the location info for frames referenced by the added samples.
+    if (!WriteFrames(tp, &seen_mappings, &seen_symbol_ids))
+      return {};
+    if (!WriteMappings(tp, seen_mappings))
+      return {};
+    if (!WriteSymbols(tp, seen_symbol_ids))
+      return {};
+    return result_.SerializeAsString();
+  }
+
+ private:
   bool WriteMappings(trace_processor::TraceProcessor* tp,
                      const std::set<int64_t>& seen_mappings) {
     Iterator mapping_it = tp->ExecuteQuery(
@@ -371,7 +320,6 @@
   }
 
   bool WriteFrames(trace_processor::TraceProcessor* tp,
-                   const std::set<int64_t>& seen_frames,
                    std::set<int64_t>* seen_mappings,
                    std::set<int64_t>* seen_symbol_ids) {
     Iterator frame_it = tp->ExecuteQuery(
@@ -381,7 +329,7 @@
     size_t frames_no = 0;
     while (frame_it.Next()) {
       int64_t frame_id = frame_it.Get(0).AsLong();
-      if (seen_frames.find(frame_id) == seen_frames.end())
+      if (seen_frames_.find(frame_id) == seen_frames_.end())
         continue;
       frames_no++;
       std::string frame_name = frame_it.Get(1).AsString();
@@ -429,54 +377,13 @@
                               frame_it.Status().message().c_str());
       return false;
     }
-    if (frames_no != seen_frames.size()) {
+    if (frames_no != seen_frames_.size()) {
       PERFETTO_DFATAL_OR_ELOG("Missing frames.");
       return false;
     }
     return true;
   }
 
-  uint64_t ToPprofId(int64_t id) {
-    PERFETTO_DCHECK(id >= 0);
-    return static_cast<uint64_t>(id) + 1;
-  }
-
-  void WriteSampleTypes() {
-    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-      Intern(kViews[i].type);
-      Intern(kViews[i].unit);
-    }
-
-    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-      auto* sample_type = result_->add_sample_type();
-      sample_type->set_type(Intern(kViews[i].type));
-      sample_type->set_unit(Intern(kViews[i].unit));
-    }
-  }
-
-  std::string GenerateGProfile(trace_processor::TraceProcessor* tp,
-                               uint64_t upid,
-                               uint64_t ts,
-                               const char* heap_name) {
-    std::set<int64_t> seen_frames;
-    std::set<int64_t> seen_mappings;
-    std::set<int64_t> seen_symbol_ids;
-
-    std::vector<Iterator> view_its =
-        BuildViewIterators(tp, upid, ts, heap_name);
-
-    WriteSampleTypes();
-    if (!WriteAllocations(&view_its, &seen_frames))
-      return {};
-    if (!WriteFrames(tp, seen_frames, &seen_mappings, &seen_symbol_ids))
-      return {};
-    if (!WriteMappings(tp, seen_mappings))
-      return {};
-    if (!WriteSymbols(tp, seen_symbol_ids))
-      return {};
-    return result_.SerializeAsString();
-  }
-
   const std::vector<int64_t>& FramesForCallstack(int64_t callstack_id) {
     size_t callsite_idx = static_cast<size_t>(callstack_id);
     PERFETTO_CHECK(callstack_id >= 0 &&
@@ -501,7 +408,6 @@
     return it->second;
   }
 
- private:
   protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
       result_;
   std::map<std::string, int64_t> string_table_;
@@ -509,54 +415,183 @@
   const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines_;
   const std::vector<Line> empty_line_vector_;
   int64_t max_symbol_id_;
+
+  std::set<int64_t> seen_frames_;
 };
 
 }  // namespace
 
-bool TraceToPprof(trace_processor::TraceProcessor* tp,
-                  std::vector<SerializedProfile>* output,
-                  uint64_t pid,
-                  const std::vector<uint64_t>& timestamps) {
-  auto max_symbol_id_it =
-      tp->ExecuteQuery("SELECT MAX(id) from stack_profile_symbol");
-  if (!max_symbol_id_it.Next()) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
-                            max_symbol_id_it.Status().message().c_str());
-    return false;
+namespace heap_profile {
+struct View {
+  const char* type;
+  const char* unit;
+  const char* aggregator;
+  const char* filter;
+};
+const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
+const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size >= 0"};
+const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
+                             "size >= 0"};
+const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
+
+const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
+                       kSpaceView};
+
+static bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
+  bool success = true;
+  base::Optional<int64_t> stat =
+      GetStatsEntry(tp, "heapprofd_buffer_corrupted", base::make_optional(pid));
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
+  } else if (stat.value() > 0) {
+    success = false;
+    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
+                  " ended early due to a buffer corruption."
+                  " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
+                  " CLIENT MEMORY CORRUPTION.",
+                  pid);
+  }
+  stat =
+      GetStatsEntry(tp, "heapprofd_buffer_overran", base::make_optional(pid));
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
+  } else if (stat.value() > 0) {
+    success = false;
+    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
+                  " ended early due to a buffer overrun.",
+                  pid);
   }
 
-  int64_t max_symbol_id = max_symbol_id_it.Get(0).AsLong();
+  stat = GetStatsEntry(tp, "heapprofd_rejected_concurrent", pid);
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
+  } else if (stat.value() > 0) {
+    success = false;
+    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
+                  " was rejected due to a concurrent profile.",
+                  pid);
+  }
+  return success;
+}
+
+static std::vector<Iterator> BuildViewIterators(
+    trace_processor::TraceProcessor* tp,
+    uint64_t upid,
+    uint64_t ts,
+    const char* heap_name) {
+  std::vector<Iterator> view_its;
+  for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+    const View& v = kViews[i];
+    std::string query = "SELECT hpa.callsite_id ";
+    query +=
+        ", " + std::string(v.aggregator) + " FROM heap_profile_allocation hpa ";
+    // TODO(fmayer): Figure out where negative callsite_id comes from.
+    query += "WHERE hpa.callsite_id >= 0 ";
+    query += "AND hpa.upid = " + std::to_string(upid) + " ";
+    query += "AND hpa.ts <= " + std::to_string(ts) + " ";
+    query += "AND hpa.heap_name = '" + std::string(heap_name) + "' ";
+    if (v.filter)
+      query += "AND " + std::string(v.filter) + " ";
+    query += "GROUP BY hpa.callsite_id;";
+    view_its.emplace_back(tp->ExecuteQuery(query));
+  }
+  return view_its;
+}
+
+static bool WriteAllocations(GProfileBuilder* builder,
+                             std::vector<Iterator>* view_its) {
+  for (;;) {
+    bool all_next = true;
+    bool any_next = false;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      Iterator& it = (*view_its)[i];
+      bool next = it.Next();
+      if (!it.Status().ok()) {
+        PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
+                                it.Status().message().c_str());
+        return false;
+      }
+      all_next = all_next && next;
+      any_next = any_next || next;
+    }
+
+    if (!all_next) {
+      PERFETTO_CHECK(!any_next);
+      break;
+    }
+
+    protozero::PackedVarInt sample_values;
+    int64_t callstack_id = -1;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      if (i == 0) {
+        callstack_id = (*view_its)[i].Get(0).AsLong();
+      } else if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
+        PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
+        return false;
+      }
+      sample_values.Append((*view_its)[i].Get(1).AsLong());
+    }
+
+    if (!builder->AddSample(sample_values, callstack_id))
+      return false;
+  }
+  return true;
+}
+
+static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp,
+                             std::vector<SerializedProfile>* output,
+                             uint64_t target_pid,
+                             const std::vector<uint64_t>& target_timestamps) {
   const auto callsite_to_frames = GetCallsiteToFrames(tp);
   const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
+  base::Optional<int64_t> max_symbol_id = GetMaxSymbolId(tp);
+  if (!max_symbol_id.has_value())
+    return false;
 
   bool any_fail = false;
-  Iterator it = tp->ExecuteQuery(kQueryProfiles);
+  Iterator it = tp->ExecuteQuery(
+      "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name "
+      "from heap_profile_allocation hpa, "
+      "process p where p.upid = hpa.upid;");
   while (it.Next()) {
     GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
-                            max_symbol_id);
+                            max_symbol_id.value());
     uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
     uint64_t ts = static_cast<uint64_t>(it.Get(1).AsLong());
     uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).AsLong());
     const char* heap_name = it.Get(3).AsString();
-    if ((pid > 0 && profile_pid != pid) ||
-        (!timestamps.empty() && std::find(timestamps.begin(), timestamps.end(),
-                                          ts) == timestamps.end())) {
+    if ((target_pid > 0 && profile_pid != target_pid) ||
+        (!target_timestamps.empty() &&
+         std::find(target_timestamps.begin(), target_timestamps.end(), ts) ==
+             target_timestamps.end())) {
       continue;
     }
 
-    if (!VerifyPIDStats(tp, pid))
+    if (!VerifyPIDStats(tp, profile_pid))
       any_fail = true;
 
-    std::string pid_query = "select pid from process where upid = ";
-    pid_query += std::to_string(upid) + ";";
-    Iterator pid_it = tp->ExecuteQuery(pid_query);
-    PERFETTO_CHECK(pid_it.Next());
+    std::vector<std::pair<std::string, std::string>> sample_types;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      sample_types.emplace_back(std::string(kViews[i].type),
+                                std::string(kViews[i].unit));
+    }
+    builder.WriteSampleTypes(sample_types);
 
-    std::string profile_proto =
-        builder.GenerateGProfile(tp, upid, ts, heap_name);
+    std::vector<Iterator> view_its =
+        BuildViewIterators(tp, upid, ts, heap_name);
+    std::string profile_proto;
+    if (WriteAllocations(&builder, &view_its)) {
+      profile_proto = builder.CompleteProfile(tp);
+    }
     output->emplace_back(
-        SerializedProfile{static_cast<uint64_t>(pid_it.Get(0).AsLong()),
-                          heap_name, profile_proto});
+        SerializedProfile{ProfileType::kHeapProfile, profile_pid,
+                          std::move(profile_proto), heap_name});
+  }
+
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return false;
   }
   if (any_fail) {
     PERFETTO_ELOG(
@@ -564,13 +599,149 @@
         "https://perfetto.dev/docs/data-sources/"
         "native-heap-profiler#troubleshooting");
   }
+  return true;
+}
+}  // namespace heap_profile
+
+namespace perf_profile {
+struct ProcessInfo {
+  uint64_t pid;
+  std::vector<uint64_t> utids;
+};
+
+// Returns a map of upid -> {pid, utids[]} for sampled processes.
+static std::map<uint64_t, ProcessInfo> GetProcessMap(
+    trace_processor::TraceProcessor* tp) {
+  Iterator it = tp->ExecuteQuery(
+      "select distinct process.upid, process.pid, thread.utid from perf_sample "
+      "join thread using (utid) join process using (upid) order by "
+      "process.upid asc");
+  std::map<uint64_t, ProcessInfo> process_map;
+  while (it.Next()) {
+    uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
+    uint64_t pid = static_cast<uint64_t>(it.Get(1).AsLong());
+    uint64_t utid = static_cast<uint64_t>(it.Get(2).AsLong());
+    process_map[upid].pid = pid;
+    process_map[upid].utids.push_back(utid);
+  }
   if (!it.Status().ok()) {
     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
                             it.Status().message().c_str());
+    return {};
+  }
+  return process_map;
+}
+
+static void LogTracePerfEventIssues(trace_processor::TraceProcessor* tp) {
+  base::Optional<int64_t> stat = GetStatsEntry(tp, "perf_samples_skipped");
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to look up perf_samples_skipped stat");
+  } else if (stat.value() > 0) {
+    PERFETTO_ELOG(
+        "Warning: the trace recorded %" PRIi64
+        " skipped samples, which otherwise matched the tracing config. This "
+        "would cause a process to be completely absent from the trace, but "
+        "does *not* imply data loss in any of the output profiles.",
+        stat.value());
+  }
+
+  stat = GetStatsEntry(tp, "perf_samples_skipped_dataloss");
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG(
+        "Failed to look up perf_samples_skipped_dataloss stat");
+  } else if (stat.value() > 0) {
+    PERFETTO_ELOG("DATA LOSS: the trace recorded %" PRIi64
+                  " lost perf samples (within traced_perf). This means that "
+                  "the trace is missing information, but it is not known "
+                  "which profile that affected.",
+                  stat.value());
+  }
+
+  // Check if any per-cpu ringbuffers encountered dataloss (as recorded by the
+  // kernel).
+  Iterator it = tp->ExecuteQuery(
+      "select idx, value from stats where name == 'perf_cpu_lost_records' and "
+      "value > 0 order by idx asc");
+  while (it.Next()) {
+    PERFETTO_ELOG(
+        "DATA LOSS: during the trace, the per-cpu kernel ring buffer for cpu "
+        "%" PRIi64 " recorded %" PRIi64
+        " lost samples. This means that the trace is missing information, "
+        "but it is not known which profile that affected.",
+        static_cast<int64_t>(it.Get(0).AsLong()),
+        static_cast<int64_t>(it.Get(1).AsLong()));
+  }
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+  }
+}
+
+// TODO(rsavitski): decide whether errors in |AddSample| should result in an
+// empty profile (and/or whether they should make the overall conversion
+// unsuccessful). Furthermore, clarify the return value's semantics for both
+// perf and heap profiles.
+static bool TraceToPerfPprof(trace_processor::TraceProcessor* tp,
+                             std::vector<SerializedProfile>* output,
+                             uint64_t target_pid) {
+  const auto callsite_to_frames = GetCallsiteToFrames(tp);
+  const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
+  base::Optional<int64_t> max_symbol_id = GetMaxSymbolId(tp);
+  if (!max_symbol_id.has_value())
     return false;
+
+  LogTracePerfEventIssues(tp);
+
+  // Aggregate samples by upid when building profiles.
+  std::map<uint64_t, ProcessInfo> process_map = GetProcessMap(tp);
+  for (const auto& p : process_map) {
+    const ProcessInfo& process = p.second;
+
+    if (target_pid != 0 && process.pid != target_pid)
+      continue;
+
+    GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
+                            max_symbol_id.value());
+
+    builder.WriteSampleTypes({{"samples", "count"}});
+
+    std::string query = "select callsite_id from perf_sample where utid in (" +
+                        AsCsvString(process.utids) + ") order by ts asc;";
+
+    protozero::PackedVarInt single_count_value;
+    single_count_value.Append(1);
+
+    Iterator it = tp->ExecuteQuery(query);
+    while (it.Next()) {
+      int64_t callsite_id = static_cast<int64_t>(it.Get(0).AsLong());
+      builder.AddSample(single_count_value, callsite_id);
+    }
+    if (!it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Failed to iterate over samples.");
+      return false;
+    }
+
+    std::string profile_proto = builder.CompleteProfile(tp);
+    output->emplace_back(SerializedProfile{
+        ProfileType::kPerfProfile, process.pid, std::move(profile_proto), ""});
   }
   return true;
 }
+}  // namespace perf_profile
+
+bool TraceToPprof(trace_processor::TraceProcessor* tp,
+                  std::vector<SerializedProfile>* output,
+                  ConversionMode mode,
+                  uint64_t pid,
+                  const std::vector<uint64_t>& timestamps) {
+  switch (mode) {
+    case (ConversionMode::kHeapProfile):
+      return heap_profile::TraceToHeapPprof(tp, output, pid, timestamps);
+    case (ConversionMode::kPerfProfile):
+      return perf_profile::TraceToPerfPprof(tp, output, pid);
+  }
+  PERFETTO_FATAL("unknown conversion option");  // for gcc
+}
 
 }  // namespace trace_to_text
 }  // namespace perfetto