Merge "Apply fixes to overview panel"
diff --git a/Android.bp b/Android.bp
index d891e49..2061821 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1730,6 +1730,7 @@
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_trace_processor_analysis_analysis",
     ":perfetto_src_trace_processor_containers_containers",
+    ":perfetto_src_trace_processor_containers_containers_headers",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_export_json",
     ":perfetto_src_trace_processor_ftrace_descriptors",
@@ -7518,6 +7519,11 @@
   ],
 }
 
+// GN: //src/trace_processor/containers:containers_headers
+filegroup {
+  name: "perfetto_src_trace_processor_containers_containers_headers",
+}
+
 // GN: //src/trace_processor/containers:unittests
 filegroup {
   name: "perfetto_src_trace_processor_containers_unittests",
@@ -8997,6 +9003,7 @@
     ":perfetto_src_protozero_unittests",
     ":perfetto_src_trace_processor_analysis_analysis",
     ":perfetto_src_trace_processor_containers_containers",
+    ":perfetto_src_trace_processor_containers_containers_headers",
     ":perfetto_src_trace_processor_containers_unittests",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_db_unittests",
@@ -9293,6 +9300,7 @@
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_trace_processor_analysis_analysis",
     ":perfetto_src_trace_processor_containers_containers",
+    ":perfetto_src_trace_processor_containers_containers_headers",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_export_json",
     ":perfetto_src_trace_processor_ftrace_descriptors",
@@ -9441,6 +9449,7 @@
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_trace_processor_analysis_analysis",
     ":perfetto_src_trace_processor_containers_containers",
+    ":perfetto_src_trace_processor_containers_containers_headers",
     ":perfetto_src_trace_processor_db_lib",
     ":perfetto_src_trace_processor_export_json",
     ":perfetto_src_trace_processor_ftrace_descriptors",
diff --git a/BUILD b/BUILD
index 37ac3de..b48c5ec 100644
--- a/BUILD
+++ b/BUILD
@@ -780,19 +780,35 @@
 )
 
 # GN target: //src/trace_processor/containers:containers
-filegroup(
+perfetto_cc_library(
     name = "src_trace_processor_containers_containers",
     srcs = [
+        ":src_trace_processor_containers_containers_headers",
         "src/trace_processor/containers/bit_vector.cc",
-        "src/trace_processor/containers/bit_vector.h",
         "src/trace_processor/containers/bit_vector_iterators.cc",
+        "src/trace_processor/containers/nullable_vector.cc",
+        "src/trace_processor/containers/row_map.cc",
+        "src/trace_processor/containers/string_pool.cc",
+    ],
+    hdrs = [
+        ":include_perfetto_base_base",
+        ":include_perfetto_protozero_protozero",
+    ],
+    deps = [
+        ":src_base_base",
+    ],
+    linkstatic = True,
+)
+
+# GN target: //src/trace_processor/containers:containers_headers
+filegroup(
+    name = "src_trace_processor_containers_containers_headers",
+    srcs = [
+        "src/trace_processor/containers/bit_vector.h",
         "src/trace_processor/containers/bit_vector_iterators.h",
         "src/trace_processor/containers/null_term_string_view.h",
-        "src/trace_processor/containers/nullable_vector.cc",
         "src/trace_processor/containers/nullable_vector.h",
-        "src/trace_processor/containers/row_map.cc",
         "src/trace_processor/containers/row_map.h",
-        "src/trace_processor/containers/string_pool.cc",
         "src/trace_processor/containers/string_pool.h",
     ],
 )
@@ -3419,7 +3435,7 @@
     name = "trace_processor",
     srcs = [
         ":src_trace_processor_analysis_analysis",
-        ":src_trace_processor_containers_containers",
+        ":src_trace_processor_containers_containers_headers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_export_json",
         ":src_trace_processor_ftrace_descriptors",
@@ -3444,7 +3460,6 @@
         ":include_perfetto_ext_trace_processor_export_json",
         ":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
         ":include_perfetto_ext_traced_sys_stats_counters",
-        ":include_perfetto_protozero_protozero",
         ":include_perfetto_trace_processor_basic_types",
         ":include_perfetto_trace_processor_storage",
         ":include_perfetto_trace_processor_trace_processor",
@@ -3484,6 +3499,7 @@
                ":protos_perfetto_trace_track_event_zero",
                ":protozero",
                ":src_base_base",
+               ":src_trace_processor_containers_containers",
                ":src_trace_processor_importers_gen_cc_chrome_track_event_descriptor",
                ":src_trace_processor_importers_gen_cc_config_descriptor",
                ":src_trace_processor_importers_gen_cc_track_event_descriptor",
@@ -3515,7 +3531,7 @@
         ":src_profiling_symbolizer_symbolize_database",
         ":src_profiling_symbolizer_symbolizer",
         ":src_trace_processor_analysis_analysis",
-        ":src_trace_processor_containers_containers",
+        ":src_trace_processor_containers_containers_headers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_export_json",
         ":src_trace_processor_ftrace_descriptors",
@@ -3575,6 +3591,7 @@
                ":protozero",
                ":src_base_base",
                ":src_base_unix_socket",
+               ":src_trace_processor_containers_containers",
                ":src_trace_processor_importers_gen_cc_chrome_track_event_descriptor",
                ":src_trace_processor_importers_gen_cc_config_descriptor",
                ":src_trace_processor_importers_gen_cc_track_event_descriptor",
@@ -3627,6 +3644,7 @@
         ":src_profiling_deobfuscator",
         ":src_profiling_symbolizer_symbolize_database",
         ":src_profiling_symbolizer_symbolizer",
+        ":src_trace_processor_containers_containers_headers",
         ":tools_trace_to_text_pprofbuilder",
         ":tools_trace_to_text_utils",
     ],
@@ -3672,6 +3690,7 @@
         ":protos_perfetto_trace_track_event_zero",
         ":protos_third_party_pprof_zero",
         ":protozero",
+        ":src_trace_processor_containers_containers",
     ] + PERFETTO_CONFIG.deps.zlib,
     linkstatic = True,
 )
@@ -3694,7 +3713,7 @@
         ":src_profiling_symbolizer_symbolize_database",
         ":src_profiling_symbolizer_symbolizer",
         ":src_trace_processor_analysis_analysis",
-        ":src_trace_processor_containers_containers",
+        ":src_trace_processor_containers_containers_headers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_export_json",
         ":src_trace_processor_ftrace_descriptors",
@@ -3753,6 +3772,7 @@
                ":protos_third_party_pprof_zero",
                ":protozero",
                ":src_base_base",
+               ":src_trace_processor_containers_containers",
                ":src_trace_processor_importers_gen_cc_chrome_track_event_descriptor",
                ":src_trace_processor_importers_gen_cc_config_descriptor",
                ":src_trace_processor_importers_gen_cc_track_event_descriptor",
diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h
index 20b9d2e..df18c5a 100644
--- a/include/perfetto/profiling/pprof_builder.h
+++ b/include/perfetto/profiling/pprof_builder.h
@@ -17,7 +17,6 @@
 #ifndef INCLUDE_PERFETTO_PROFILING_PPROF_BUILDER_H_
 #define INCLUDE_PERFETTO_PROFILING_PPROF_BUILDER_H_
 
-#include <iostream>
 #include <string>
 #include <vector>
 
@@ -48,9 +47,19 @@
 
 enum class ConversionMode { kHeapProfile, kPerfProfile };
 
+enum class ConversionFlags : uint64_t {
+  kNone = 0,
+  // Suffix frame names with additional information. Current annotations are
+  // specific to apps running within the Android runtime, and include
+  // information such as whether the given frame was interpreted / executed
+  // under JIT / etc.
+  kAnnotateFrames = 1
+};
+
 bool TraceToPprof(trace_processor::TraceProcessor* tp,
                   std::vector<SerializedProfile>* output,
                   ConversionMode mode = ConversionMode::kHeapProfile,
+                  uint64_t flags = 0,
                   uint64_t pid = 0,
                   const std::vector<uint64_t>& timestamps = {});
 
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 2dba9e1..28a4cb9 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -27,7 +27,7 @@
 if (enable_perfetto_trace_processor_sqlite) {
   static_library("trace_processor") {
     complete_static_lib = true
-    deps = [ ":lib" ]
+    public_deps = [ ":lib" ]
   }
 }
 
@@ -150,6 +150,7 @@
     "../base",
     "../protozero",
     "containers",
+    "containers:containers_headers",
     "importers:common",
     "importers:gen_cc_chrome_track_event_descriptor",
     "importers:gen_cc_track_event_descriptor",
diff --git a/src/trace_processor/containers/BUILD.gn b/src/trace_processor/containers/BUILD.gn
index 836ab22..40e3ae3 100644
--- a/src/trace_processor/containers/BUILD.gn
+++ b/src/trace_processor/containers/BUILD.gn
@@ -12,27 +12,46 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("../../../gn/perfetto_component.gni")
 import("../../../gn/test.gni")
 
-source_set("containers") {
+# These headers are outside of include/, but used by
+# tools/trace_to_text/libpprofbuilder. In Bazel builds with strict header
+# checking, this gets converted to a filegroup of the headers, and therefore
+# allows their inclusion (as if they were directly part of the dependent
+# targets).
+# TODO(rsavitski): The more correct approach would require the use of
+# cc_library with 'hdrs' section (likely derived from 'public' in gn).
+# Otherwise we have to depend on both :containers and :containers_headers
+# everywhere for the sake of strict header checking post-bazel conversion, as
+# the filegroup isn't acting as a 'hdrs'.
+source_set("containers_headers") {
   sources = [
-    "bit_vector.cc",
     "bit_vector.h",
-    "bit_vector_iterators.cc",
     "bit_vector_iterators.h",
     "null_term_string_view.h",
-    "nullable_vector.cc",
     "nullable_vector.h",
-    "row_map.cc",
     "row_map.h",
-    "string_pool.cc",
     "string_pool.h",
   ]
+}
+
+# perfetto_component to turn this into a cc_library in Bazel, to satisfy the
+# hand-rolled ODR checking for targets including two static libs that include
+# this (unclear whether it is actually a problem).
+perfetto_component("containers") {
+  sources = [
+    "bit_vector.cc",
+    "bit_vector_iterators.cc",
+    "nullable_vector.cc",
+    "row_map.cc",
+    "string_pool.cc",
+  ]
+  public_deps = [ ":containers_headers" ]
   deps = [
     "../../../gn:default_deps",
-    "../../../include/perfetto/base",
-    "../../../include/perfetto/ext/base",
     "../../../include/perfetto/protozero",
+    "../../base",
   ]
 }
 
@@ -47,6 +66,7 @@
   ]
   deps = [
     ":containers",
+    ":containers_headers",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
   ]
@@ -57,6 +77,7 @@
     testonly = true
     deps = [
       ":containers",
+      ":containers_headers",
       "../../../gn:benchmark",
       "../../../gn:default_deps",
     ]
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index f1a959f..5c61b1f 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -30,6 +30,7 @@
     "../../../include/perfetto/ext/base",
     "../../../include/perfetto/trace_processor",
     "../containers",
+    "../containers:containers_headers",
   ]
 }
 
diff --git a/src/trace_processor/metrics/android/power_drain_in_watts.sql b/src/trace_processor/metrics/android/power_drain_in_watts.sql
index d866bb0..1fbfec3 100644
--- a/src/trace_processor/metrics/android/power_drain_in_watts.sql
+++ b/src/trace_processor/metrics/android/power_drain_in_watts.sql
@@ -31,7 +31,23 @@
   ('power.PPVAR_VPH_PWR_WLAN_uws', 'wifi'),
   ('power.PPVAR_VPH_PWR_OLED_uws', 'display'),
   ('power.PPVAR_VPH_PWR_QTM525_uws', 'cellular'),
-  ('power.PPVAR_VPH_PWR_RF_uws', 'cellular');
+  ('power.PPVAR_VPH_PWR_RF_uws', 'cellular'),
+  ('power.rails.aoc.logic', 'aoc'),
+  ('power.rails.aoc.memory', 'aoc'),
+  ('power.rails.cpu.big', 'cpu_big'),
+  ('power.rails.cpu.little', 'cpu_little'),
+  ('power.rails.cpu.mid', 'cpu_mid'),
+  ('power.rails.ddr.a', 'mem'),
+  ('power.rails.ddr.b', 'mem'),
+  ('power.rails.ddr.c', 'mem'),
+  ('power.rails.gpu', 'gpu'),
+  ('power.rails.display', 'display'),
+  ('power.rails.gps', 'gps'),
+  ('power.rails.memory.interface', 'mem'),
+  ('power.rails.modem', 'cellular'),
+  ('power.rails.radio.frontend', 'cellular'),
+  ('power.rails.system.fabric', 'soc'),
+  ('power.rails.wifi.bt', 'wifi');
 
 -- Convert power counter data into table of events, where each event has
 -- start timestamp, duration and the average power drain during its duration
@@ -72,3 +88,4 @@
   JOIN counter_track ON (counter.track_id = counter_track.id)
 WHERE counter_track.type = 'counter_track'
   AND name LIKE "power.%";
+
diff --git a/src/trace_processor/storage/BUILD.gn b/src/trace_processor/storage/BUILD.gn
index 47e1195..30f50d8 100644
--- a/src/trace_processor/storage/BUILD.gn
+++ b/src/trace_processor/storage/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (C) 20 The Android Open Source Project
+# Copyright (C) 2021 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.
@@ -26,6 +26,7 @@
     "../../../include/perfetto/ext/base",
     "../../../include/perfetto/trace_processor",
     "../containers",
+    "../containers:containers_headers",
     "../tables",
     "../types",
   ]
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
index e93c0b4..d793059 100644
--- a/src/trace_processor/tables/profiler_tables.h
+++ b/src/trace_processor/tables/profiler_tables.h
@@ -203,7 +203,7 @@
 PERFETTO_TP_TABLE(PERFETTO_TP_PERF_SAMPLE_DEF);
 
 // Symbolization data for a frame. Rows with the same symbol_set_id describe
-// one frame, with the bottom-most inlined frame having id == symbol_set_id.
+// one callframe, with the most-inlined symbol having id == symbol_set_id.
 //
 // For instance, if the function foo has an inlined call to the function bar,
 // which has an inlined call to baz, the stack_profile_symbol table would look
@@ -212,9 +212,9 @@
 // ```
 // |id|symbol_set_id|name         |source_file|line_number|
 // |--|-------------|-------------|-----------|-----------|
-// |1 |      1      |foo          |foo.cc     | 60        |
+// |1 |      1      |baz          |foo.cc     | 36        |
 // |2 |      1      |bar          |foo.cc     | 30        |
-// |3 |      1      |baz          |foo.cc     | 36        |
+// |3 |      1      |foo          |foo.cc     | 60        |
 // ```
 // @param name name of the function.
 // @param source_file name of the source file containing the function.
diff --git a/src/trace_processor/types/BUILD.gn b/src/trace_processor/types/BUILD.gn
index 46d16b9..70d44b9 100644
--- a/src/trace_processor/types/BUILD.gn
+++ b/src/trace_processor/types/BUILD.gn
@@ -31,6 +31,7 @@
     "../../../include/perfetto/ext/base",
     "../../../include/perfetto/trace_processor",
     "../containers",
+    "../containers:containers_headers",
   ]
 }
 
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index c1674d8..e2c5960 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -71,6 +71,8 @@
     "../../protos/third_party/pprof:zero",
     "../../src/profiling/symbolizer",
     "../../src/profiling/symbolizer:symbolize_database",
+    "../../src/trace_processor/containers:containers",
+    "../../src/trace_processor/containers:containers_headers",
   ]
   sources = [ "pprof_builder.cc" ]
 }
@@ -78,7 +80,7 @@
 # Exposed in bazel builds.
 static_library("libpprofbuilder") {
   complete_static_lib = true
-  deps = [ ":pprofbuilder" ]
+  public_deps = [ ":pprofbuilder" ]
 }
 
 # The core source files that are used both by the "full" version (the host
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 8384b7a..e4a9f1d 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -32,7 +32,6 @@
 #include "tools/trace_to_text/trace_to_systrace.h"
 #include "tools/trace_to_text/trace_to_text.h"
 
-
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <unistd.h>
 #endif
@@ -43,17 +42,19 @@
 
 int Usage(const char* argv0) {
   fprintf(stderr,
-          "Usage: %s systrace|json|ctrace|text|profile [--pid PID] "
-          "[--timestamps TIMESTAMP1,TIMESTAMP2,...] "
-          "[--truncate start|end] "
-          "[--full-sort] "
-          "[trace.pb] "
-          "[trace.txt]\n"
-          "\nProfile mode only:\n"
-          "\t--perf generate a perf profile\n"
-          "\t--timestamps TIMESTAMP1,TIMESTAMP2,... generate profiles "
-          "only for these timestamps\n"
-          "\t--pid PID generate profiles only for this process id\n",
+          "Usage: %s MODE [OPTIONS] [input file] [output file]\n"
+          "modes:\n"
+          "  systrace|json|ctrace|text|profile|hprof|symbolize|deobfuscate\n"
+          "options:\n"
+          "  [--truncate start|end]\n"
+          "  [--full-sort]\n"
+          "\"profile\" mode options:\n"
+          "  [--perf] generate a perf profile instead of a heap profile\n"
+          "  [--no-annotations] do not suffix frame names with derived "
+          "annotations\n"
+          "  [--timestamps TIMESTAMP1,TIMESTAMP2,...] generate profiles "
+          "only for these *specific* timestamps\n"
+          "  [--pid PID] generate profiles only for this process id\n",
           argv0);
   return 1;
 }
@@ -75,6 +76,7 @@
   std::vector<uint64_t> timestamps;
   bool full_sort = false;
   bool perf_profile = false;
+  bool profile_no_annotations = false;
   for (int i = 1; i < argc; i++) {
     if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
       printf("%s\n", base::GetVersionString());
@@ -103,6 +105,8 @@
       }
     } else if (strcmp(argv[i], "--perf") == 0) {
       perf_profile = true;
+    } else if (strcmp(argv[i], "--no-annotations") == 0) {
+      profile_no_annotations = true;
     } else if (strcmp(argv[i], "--full-sort") == 0) {
       full_sort = true;
     } else {
@@ -187,10 +191,11 @@
     return TraceToText(input_stream, output_stream);
 
   if (format == "profile") {
-    return perf_profile ? TraceToPerfProfile(input_stream, output_stream, pid,
-                                             timestamps)
-                        : TraceToHeapProfile(input_stream, output_stream, pid,
-                                             timestamps);
+    return perf_profile
+               ? TraceToPerfProfile(input_stream, output_stream, pid,
+                                    timestamps, !profile_no_annotations)
+               : TraceToHeapProfile(input_stream, output_stream, pid,
+                                    timestamps, !profile_no_annotations);
   }
 
   if (format == "hprof")
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index fdb8717..588c438 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "perfetto/profiling/pprof_builder.h"
-
 #include "perfetto/base/build_config.h"
 
+#include "perfetto/profiling/pprof_builder.h"
+
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <cxxabi.h>
 #endif
@@ -27,29 +27,120 @@
 #include <algorithm>
 #include <map>
 #include <set>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
 #include "tools/trace_to_text/utils.h"
 
 #include "perfetto/base/logging.h"
-#include "perfetto/base/time.h"
+#include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/protozero/packed_repeated_fields.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/trace_processor/trace_processor.h"
-
 #include "src/profiling/symbolizer/symbolize_database.h"
 #include "src/profiling/symbolizer/symbolizer.h"
+#include "src/trace_processor/containers/string_pool.h"
 
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/third_party/pprof/profile.pbzero.h"
 
+// Quick hint on navigating the file:
+// Conversions for both perf and heap profiles start with |TraceToPprof|.
+// Non-shared logic is in the |heap_profile| and |perf_profile| namespaces.
+//
+// To build one or more profiles, first the callstack information is queried
+// from the SQL tables, and converted into an in-memory representation by
+// |PreprocessLocations|. Then an instance of |GProfileBuilder| is used to
+// accumulate samples for that profile, and emit all additional information as a
+// serialized proto. Only the entities referenced by that particular
+// |GProfileBuilder| instance are emitted.
+//
+// See protos/third_party/pprof/profile.proto for the meaning of terms like
+// function/location/line.
+
+namespace {
+using StringId = ::perfetto::trace_processor::StringPool::Id;
+
+// In-memory representation of a Profile.Function.
+struct Function {
+  StringId name_id = StringId::Null();
+  StringId system_name_id = StringId::Null();
+  StringId filename_id = StringId::Null();
+
+  Function(StringId n, StringId s, StringId f)
+      : name_id(n), system_name_id(s), filename_id(f) {}
+
+  bool operator==(const Function& other) const {
+    return std::tie(name_id, system_name_id, filename_id) ==
+           std::tie(other.name_id, other.system_name_id, other.filename_id);
+  }
+};
+
+// In-memory representation of a Profile.Line.
+struct Line {
+  int64_t function_id = 0;  // LocationTracker's interned Function id
+  int64_t line_no = 0;
+
+  Line(int64_t func, int64_t line) : function_id(func), line_no(line) {}
+
+  bool operator==(const Line& other) const {
+    return function_id == other.function_id && line_no == other.line_no;
+  }
+};
+
+// In-memory representation of a Profile.Location.
+struct Location {
+  int64_t mapping_id = 0;  // sqlite row id
+  // Common case: location references a single function.
+  int64_t single_function_id = 0;  // interned Function id
+  // Alternatively: multiple inlined functions, recovered via offline
+  // symbolisation. Leaf-first ordering.
+  std::vector<Line> inlined_functions;
+
+  Location(int64_t map, int64_t func, std::vector<Line> inlines)
+      : mapping_id(map),
+        single_function_id(func),
+        inlined_functions(std::move(inlines)) {}
+
+  bool operator==(const Location& other) const {
+    return std::tie(mapping_id, single_function_id, inlined_functions) ==
+           std::tie(other.mapping_id, other.single_function_id,
+                    other.inlined_functions);
+  }
+};
+}  // namespace
+
+template <>
+struct std::hash<Function> {
+  size_t operator()(const Function& loc) const {
+    perfetto::base::Hash hasher;
+    hasher.Update(loc.name_id.raw_id());
+    hasher.Update(loc.system_name_id.raw_id());
+    hasher.Update(loc.filename_id.raw_id());
+    return static_cast<size_t>(hasher.digest());
+  }
+};
+
+template <>
+struct std::hash<Location> {
+  size_t operator()(const Location& loc) const {
+    perfetto::base::Hash hasher;
+    hasher.Update(loc.mapping_id);
+    hasher.Update(loc.single_function_id);
+    for (auto line : loc.inlined_functions) {
+      hasher.Update(line.function_id);
+      hasher.Update(line.line_no);
+    }
+    return static_cast<size_t>(hasher.digest());
+  }
+};
+
 namespace perfetto {
 namespace trace_to_text {
-
 namespace {
 
 using ::perfetto::trace_processor::Iterator;
@@ -83,84 +174,6 @@
   return ret;
 }
 
-// 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) {
-  Iterator count_it =
-      tp->ExecuteQuery("select count(*) from stack_profile_callsite;");
-  if (!count_it.Next()) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get number of callsites: %s",
-                            count_it.Status().message().c_str());
-    return {};
-  }
-  int64_t count = count_it.Get(0).AsLong();
-
-  Iterator it = tp->ExecuteQuery(
-      "select id, parent_id, frame_id from stack_profile_callsite order by "
-      "depth;");
-  std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
-  while (it.Next()) {
-    int64_t id = it.Get(0).AsLong();
-    int64_t frame_id = it.Get(2).AsLong();
-    std::vector<int64_t>& path = result[static_cast<size_t>(id)];
-    path.push_back(frame_id);
-
-    auto parent_id_value = it.Get(1);
-    if (!parent_id_value.is_null()) {
-      const std::vector<int64_t>& parent_path =
-          result[static_cast<size_t>(parent_id_value.AsLong())];
-      path.insert(path.end(), parent_path.begin(), parent_path.end());
-    }
-  }
-
-  if (!it.Status().ok()) {
-    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                            it.Status().message().c_str());
-    return {};
-  }
-  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;
-  }
-  auto value = max_symbol_id_it.Get(0);
-  if (value.is_null())
-    return base::nullopt;
-  return base::make_optional(value.AsLong());
-}
-
-struct Line {
-  int64_t symbol_id;
-  uint32_t line_number;
-};
-
-std::map<int64_t, std::vector<Line>> GetSymbolSetIdToLines(
-    trace_processor::TraceProcessor* tp) {
-  std::map<int64_t, std::vector<Line>> result;
-  Iterator it = tp->ExecuteQuery(
-      "SELECT symbol_set_id, id, line_number FROM stack_profile_symbol;");
-  while (it.Next()) {
-    int64_t symbol_set_id = it.Get(0).AsLong();
-    int64_t id = it.Get(1).AsLong();
-    int64_t line_number = it.Get(2).AsLong();
-    result[symbol_set_id].emplace_back(
-        Line{id, static_cast<uint32_t>(line_number)});
-  }
-
-  if (!it.Status().ok()) {
-    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                            it.Status().message().c_str());
-    return {};
-  }
-  return result;
-}
-
 base::Optional<int64_t> GetStatsEntry(
     trace_processor::TraceProcessor* tp,
     const std::string& name,
@@ -182,73 +195,373 @@
   return base::make_optional(it.Get(0).AsLong());
 }
 
-// Helper for constructing |perftools.profiles.Profile| protos.
+// Interns Locations, Lines, and Functions. Interning is done by the entity's
+// contents, and has no relation to the row ids in the SQL tables.
+// Contains all data for the trace, so can be reused when emitting multiple
+// profiles.
+//
+// TODO(rsavitski): consider moving mappings into here as well. For now, they're
+// still emitted in a single scan during profile building. Mappings should be
+// unique-enough already in the SQL tables, with only incremental state clearing
+// duplicating entries.
+class LocationTracker {
+ public:
+  int64_t InternLocation(Location loc) {
+    auto it = locations_.find(loc);
+    if (it == locations_.end()) {
+      bool inserted = false;
+      std::tie(it, inserted) = locations_.emplace(
+          std::move(loc), static_cast<int64_t>(locations_.size()));
+      PERFETTO_DCHECK(inserted);
+    }
+    return it->second;
+  }
+
+  int64_t InternFunction(Function func) {
+    auto it = functions_.find(func);
+    if (it == functions_.end()) {
+      bool inserted = false;
+      std::tie(it, inserted) =
+          functions_.emplace(func, static_cast<int64_t>(functions_.size()));
+      PERFETTO_DCHECK(inserted);
+    }
+    return it->second;
+  }
+
+  bool IsCallsiteProcessed(int64_t callstack_id) const {
+    return callsite_to_locations_.find(callstack_id) !=
+           callsite_to_locations_.end();
+  }
+
+  void MaybeSetCallsiteLocations(int64_t callstack_id,
+                                 const std::vector<int64_t>& locs) {
+    // nop if already set
+    callsite_to_locations_.emplace(callstack_id, locs);
+  }
+
+  const std::vector<int64_t>& LocationsForCallstack(
+      int64_t callstack_id) const {
+    auto it = callsite_to_locations_.find(callstack_id);
+    PERFETTO_CHECK(callstack_id >= 0 && it != callsite_to_locations_.end());
+    return it->second;
+  }
+
+  const std::unordered_map<Location, int64_t>& AllLocations() const {
+    return locations_;
+  }
+  const std::unordered_map<Function, int64_t>& AllFunctions() const {
+    return functions_;
+  }
+
+ private:
+  // Root-first location ids for a given callsite id.
+  std::unordered_map<int64_t, std::vector<int64_t>> callsite_to_locations_;
+  std::unordered_map<Location, int64_t> locations_;
+  std::unordered_map<Function, int64_t> functions_;
+};
+
+struct PreprocessedInline {
+  StringId system_name_id = StringId::Null();
+  StringId filename_id = StringId::Null();
+  int64_t line_no = 0;
+
+  PreprocessedInline(StringId s, StringId f, int64_t line)
+      : system_name_id(s), filename_id(f), line_no(line) {}
+};
+
+std::unordered_map<int64_t, std::vector<PreprocessedInline>>
+PreprocessInliningInfo(trace_processor::TraceProcessor* tp,
+                       trace_processor::StringPool* interner) {
+  std::unordered_map<int64_t, std::vector<PreprocessedInline>> inlines;
+
+  // Most-inlined function (leaf) has the lowest id within a symbol set. Query
+  // such that the per-set line vectors are built up leaf-first.
+  Iterator it = tp->ExecuteQuery(
+      "select symbol_set_id, name, source_file, line_number from "
+      "stack_profile_symbol order by symbol_set_id asc, id asc;");
+  while (it.Next()) {
+    int64_t symbol_set_id = it.Get(0).AsLong();
+    auto func_sysname = it.Get(1).is_null() ? "" : it.Get(1).AsString();
+    auto filename = it.Get(2).is_null() ? "" : it.Get(2).AsString();
+    int64_t line_no = it.Get(3).AsLong();
+
+    inlines[symbol_set_id].emplace_back(interner->InternString(func_sysname),
+                                        interner->InternString(filename),
+                                        line_no);
+  }
+
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return {};
+  }
+  return inlines;
+}
+
+// Extracts and interns the unique frames and locations (as defined by the proto
+// format) from the callstack SQL tables.
+//
+// Approach:
+//   * for each callstack (callsite ids of the leaves):
+//     * use experimental_annotated_callstack to build the full list of
+//       constituent frames
+//     * for each frame (root to leaf):
+//         * intern the location and function(s)
+//         * remember the mapping from callsite_id to the callstack so far (from
+//            the root and including the frame being considered)
+//
+// Optionally mixes in the annotations as a frame name suffix (since there's no
+// good way to attach extra info to locations in the proto format). This relies
+// on the annotations (produced by experimental_annotated_callstack) to be
+// stable for a given callsite (equivalently: dependent only on their parents).
+LocationTracker PreprocessLocations(trace_processor::TraceProcessor* tp,
+                                    trace_processor::StringPool* interner,
+                                    bool annotate_frames) {
+  LocationTracker tracker;
+
+  // Keyed by symbol_set_id, discarded once this function converts the inlines
+  // into Line and Function entries.
+  std::unordered_map<int64_t, std::vector<PreprocessedInline>> inlining_info =
+      PreprocessInliningInfo(tp, interner);
+
+  // Higher callsite ids most likely correspond to the deepest stacks, so we'll
+  // fill more of the overall callsite->location map by visiting the callsited
+  // in decreasing id order. Since processing a callstack also fills in the data
+  // for all parent callsites.
+  Iterator cid_it = tp->ExecuteQuery(
+      "select id from stack_profile_callsite order by id desc;");
+  while (cid_it.Next()) {
+    int64_t query_cid = cid_it.Get(0).AsLong();
+
+    // If the leaf has been processed, the rest of the stack is already known.
+    if (tracker.IsCallsiteProcessed(query_cid))
+      continue;
+
+    std::string annotated_query =
+        "select sp.id, sp.annotation, spf.mapping, "
+        "ifnull(spf.deobfuscated_name, spf.name), spf.symbol_set_id from "
+        "experimental_annotated_callstack(" +
+        std::to_string(query_cid) +
+        ") sp join stack_profile_frame spf on (sp.frame_id == spf.id) "
+        "order by depth asc";
+    Iterator c_it = tp->ExecuteQuery(annotated_query);
+
+    std::vector<int64_t> callstack_loc_ids;
+    while (c_it.Next()) {
+      int64_t cid = c_it.Get(0).AsLong();
+      int64_t mapping_id = c_it.Get(2).AsLong();
+      auto annotation = c_it.Get(1).is_null() ? "" : c_it.Get(1).AsString();
+      auto func_sysname = c_it.Get(3).is_null() ? "" : c_it.Get(3).AsString();
+      base::Optional<int64_t> symbol_set_id =
+          c_it.Get(4).is_null() ? base::nullopt
+                                : base::make_optional(c_it.Get(4).AsLong());
+
+      Location loc(mapping_id, /*single_function_id=*/-1, {});
+
+      auto intern_function = [interner, &tracker, annotate_frames](
+                                 StringId func_sysname_id, StringId filename_id,
+                                 const std::string& anno) {
+        std::string func_name = interner->Get(func_sysname_id).ToStdString();
+        MaybeDemangle(&func_name);
+        if (annotate_frames && !anno.empty() && !func_name.empty())
+          func_name = func_name + " [" + anno + "]";
+        StringId func_name_id =
+            interner->InternString(base::StringView(func_name));
+        Function func(func_name_id, func_sysname_id, filename_id);
+        return tracker.InternFunction(func);
+      };
+
+      // Inlining information available
+      if (symbol_set_id.has_value()) {
+        auto it = inlining_info.find(*symbol_set_id);
+        if (it == inlining_info.end()) {
+          PERFETTO_DFATAL_OR_ELOG(
+              "Failed to find stack_profile_symbol entry for symbol_set_id "
+              "%" PRIi64 "",
+              *symbol_set_id);
+          return {};
+        }
+
+        // N inlined functions
+        for (const auto& line : it->second) {
+          int64_t func_id = intern_function(line.system_name_id,
+                                            line.filename_id, annotation);
+
+          loc.inlined_functions.emplace_back(func_id, line.line_no);
+        }
+      } else {
+        // Otherwise - single function
+        int64_t func_id =
+            intern_function(interner->InternString(func_sysname),
+                            /*filename_id=*/StringId::Null(), annotation);
+        loc.single_function_id = func_id;
+      }
+
+      int64_t loc_id = tracker.InternLocation(std::move(loc));
+
+      // Update the tracker with the locations so far (for example, at depth 2,
+      // we'll have 3 root-most locations in |callstack_loc_ids|).
+      callstack_loc_ids.push_back(loc_id);
+      tracker.MaybeSetCallsiteLocations(cid, callstack_loc_ids);
+    }
+
+    if (!c_it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                              c_it.Status().message().c_str());
+      return {};
+    }
+  }
+
+  if (!cid_it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            cid_it.Status().message().c_str());
+    return {};
+  }
+
+  return tracker;
+}
+
+// Builds the |perftools.profiles.Profile| proto.
 class GProfileBuilder {
  public:
-  GProfileBuilder(
-      const std::vector<std::vector<int64_t>>& callsite_to_frames,
-      const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines,
-      int64_t max_symbol_id)
-      : callsite_to_frames_(callsite_to_frames),
-        symbol_set_id_to_lines_(symbol_set_id_to_lines),
-        max_symbol_id_(max_symbol_id) {
-    // The pprof format expects the first entry in the string table to be the
+  GProfileBuilder(const LocationTracker& locations,
+                  trace_processor::StringPool* interner)
+      : locations_(locations), interner_(interner) {
+    // The pprof format requires the first entry in the string table to be the
     // empty string.
-    int64_t empty_id = Intern("");
+    int64_t empty_id = ToStringTableId(StringId::Null());
     PERFETTO_CHECK(empty_id == 0);
   }
 
   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);
-    }
     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));
+      sample_type->set_type(
+          ToStringTableId(interner_->InternString(base::StringView(st.first))));
+      sample_type->set_unit(ToStringTableId(
+          interner_->InternString(base::StringView(st.second))));
     }
   }
 
   bool AddSample(const protozero::PackedVarInt& values, int64_t callstack_id) {
-    const auto& frames = FramesForCallstack(callstack_id);
-    if (frames.empty()) {
+    const auto& location_ids = locations_.LocationsForCallstack(callstack_id);
+    if (location_ids.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));
+
+    // LocationTracker stores location lists root-first, but the pprof format
+    // requires leaf-first.
+    protozero::PackedVarInt packed_locs;
+    for (auto it = location_ids.rbegin(); it != location_ids.rend(); ++it)
+      packed_locs.Append(ToPprofId(*it));
 
     auto* gsample = result_->add_sample();
     gsample->set_value(values);
-    gsample->set_location_id(location_ids);
+    gsample->set_location_id(packed_locs);
 
-    // remember frames to be emitted
-    seen_frames_.insert(frames.cbegin(), frames.cend());
-
+    // Remember the locations s.t. we only serialize the referenced ones.
+    seen_locations_.insert(location_ids.cbegin(), location_ids.cend());
     return true;
   }
 
   std::string CompleteProfile(trace_processor::TraceProcessor* tp) {
     std::set<int64_t> seen_mappings;
-    std::set<int64_t> seen_symbol_ids;
+    std::set<int64_t> seen_functions;
 
-    // Write the location info for frames referenced by the added samples.
-    if (!WriteFrames(tp, &seen_mappings, &seen_symbol_ids))
+    if (!WriteLocations(&seen_mappings, &seen_functions))
+      return {};
+    if (!WriteFunctions(seen_functions))
       return {};
     if (!WriteMappings(tp, seen_mappings))
       return {};
-    if (!WriteSymbols(tp, seen_symbol_ids))
-      return {};
+
+    WriteStringTable();
     return result_.SerializeAsString();
   }
 
  private:
+  // Serializes the Profile.Location entries referenced by this profile.
+  bool WriteLocations(std::set<int64_t>* seen_mappings,
+                      std::set<int64_t>* seen_functions) {
+    const std::unordered_map<Location, int64_t>& locations =
+        locations_.AllLocations();
+
+    size_t written_locations = 0;
+    for (const auto& loc_and_id : locations) {
+      const auto& loc = loc_and_id.first;
+      int64_t id = loc_and_id.second;
+
+      if (seen_locations_.find(id) == seen_locations_.end())
+        continue;
+
+      written_locations += 1;
+      seen_mappings->emplace(loc.mapping_id);
+
+      auto* glocation = result_->add_location();
+      glocation->set_id(ToPprofId(id));
+      glocation->set_mapping_id(ToPprofId(loc.mapping_id));
+
+      if (!loc.inlined_functions.empty()) {
+        for (const auto& line : loc.inlined_functions) {
+          seen_functions->insert(line.function_id);
+
+          auto* gline = glocation->add_line();
+          gline->set_function_id(ToPprofId(line.function_id));
+          gline->set_line(line.line_no);
+        }
+      } else {
+        seen_functions->insert(loc.single_function_id);
+
+        glocation->add_line()->set_function_id(
+            ToPprofId(loc.single_function_id));
+      }
+    }
+
+    if (written_locations != seen_locations_.size()) {
+      PERFETTO_DFATAL_OR_ELOG(
+          "Found only %zu/%zu locations during serialization.",
+          written_locations, seen_locations_.size());
+      return false;
+    }
+    return true;
+  }
+
+  // Serializes the Profile.Function entries referenced by this profile.
+  bool WriteFunctions(const std::set<int64_t>& seen_functions) {
+    const std::unordered_map<Function, int64_t>& functions =
+        locations_.AllFunctions();
+
+    size_t written_functions = 0;
+    for (const auto& func_and_id : functions) {
+      const auto& func = func_and_id.first;
+      int64_t id = func_and_id.second;
+
+      if (seen_functions.find(id) == seen_functions.end())
+        continue;
+
+      written_functions += 1;
+
+      auto* gfunction = result_->add_function();
+      gfunction->set_id(ToPprofId(id));
+      gfunction->set_name(ToStringTableId(func.name_id));
+      gfunction->set_system_name(ToStringTableId(func.system_name_id));
+      if (!func.filename_id.is_null())
+        gfunction->set_filename(ToStringTableId(func.filename_id));
+    }
+
+    if (written_functions != seen_functions.size()) {
+      PERFETTO_DFATAL_OR_ELOG(
+          "Found only %zu/%zu functions during serialization.",
+          written_functions, seen_functions.size());
+      return false;
+    }
+    return true;
+  }
+
+  // Serializes the Profile.Mapping entries referenced by this profile.
   bool WriteMappings(trace_processor::TraceProcessor* tp,
                      const std::set<int64_t>& seen_mappings) {
     Iterator mapping_it = tp->ExecuteQuery(
@@ -260,7 +573,8 @@
       if (seen_mappings.find(id) == seen_mappings.end())
         continue;
       ++mappings_no;
-      auto interned_filename = Intern(mapping_it.Get(4).AsString());
+      auto interned_filename = ToStringTableId(
+          interner_->InternString(mapping_it.Get(4).AsString()));
       auto* gmapping = result_->add_mapping();
       gmapping->set_id(ToPprofId(id));
       // Do not set the build_id here to avoid downstream services
@@ -285,145 +599,48 @@
     return true;
   }
 
-  bool WriteSymbols(trace_processor::TraceProcessor* tp,
-                    const std::set<int64_t>& seen_symbol_ids) {
-    Iterator symbol_it = tp->ExecuteQuery(
-        "SELECT id, name, source_file FROM stack_profile_symbol");
-    size_t symbols_no = 0;
-    while (symbol_it.Next()) {
-      int64_t id = symbol_it.Get(0).AsLong();
-      if (seen_symbol_ids.find(id) == seen_symbol_ids.end())
-        continue;
-      ++symbols_no;
-      const std::string& name = symbol_it.Get(1).AsString();
-      std::string demangled_name = name;
-      MaybeDemangle(&demangled_name);
-
-      auto interned_demangled_name = Intern(demangled_name);
-      auto interned_system_name = Intern(name);
-      auto interned_filename = Intern(symbol_it.Get(2).AsString());
-      auto* gfunction = result_->add_function();
-      gfunction->set_id(ToPprofId(id));
-      gfunction->set_name(interned_demangled_name);
-      gfunction->set_system_name(interned_system_name);
-      gfunction->set_filename(interned_filename);
+  void WriteStringTable() {
+    for (StringId id : string_table_) {
+      trace_processor::NullTermStringView s = interner_->Get(id);
+      result_->add_string_table(s.data(), s.size());
     }
-
-    if (!symbol_it.Status().ok()) {
-      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                              symbol_it.Status().message().c_str());
-      return false;
-    }
-
-    if (symbols_no != seen_symbol_ids.size()) {
-      PERFETTO_DFATAL_OR_ELOG("Missing symbols.");
-      return false;
-    }
-    return true;
   }
 
-  bool WriteFrames(trace_processor::TraceProcessor* tp,
-                   std::set<int64_t>* seen_mappings,
-                   std::set<int64_t>* seen_symbol_ids) {
-    Iterator frame_it = tp->ExecuteQuery(
-        "SELECT spf.id, IFNULL(spf.deobfuscated_name, spf.name), spf.mapping, "
-        "spf.rel_pc, spf.symbol_set_id "
-        "FROM stack_profile_frame spf;");
-    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())
-        continue;
-      frames_no++;
-      std::string frame_name = frame_it.Get(1).AsString();
-      int64_t mapping_id = frame_it.Get(2).AsLong();
-      int64_t rel_pc = frame_it.Get(3).AsLong();
-      base::Optional<int64_t> symbol_set_id;
-      if (!frame_it.Get(4).is_null())
-        symbol_set_id = frame_it.Get(4).AsLong();
-
-      seen_mappings->emplace(mapping_id);
-      auto* glocation = result_->add_location();
-      glocation->set_id(ToPprofId(frame_id));
-      glocation->set_mapping_id(ToPprofId(mapping_id));
-      // TODO(fmayer): Convert to abspc.
-      // relpc + (mapping.start - (mapping.exact_offset -
-      //                           mapping.start_offset)).
-      glocation->set_address(static_cast<uint64_t>(rel_pc));
-      if (symbol_set_id) {
-        for (const Line& line : LineForSymbolSetId(*symbol_set_id)) {
-          seen_symbol_ids->emplace(line.symbol_id);
-          auto* gline = glocation->add_line();
-          gline->set_line(line.line_number);
-          gline->set_function_id(ToPprofId(line.symbol_id));
-        }
-      } else {
-        int64_t synthesized_symbol_id = ++max_symbol_id_;
-        std::string demangled_name = frame_name;
-        MaybeDemangle(&demangled_name);
-
-        auto* gline = glocation->add_line();
-        gline->set_line(0);
-        gline->set_function_id(ToPprofId(synthesized_symbol_id));
-
-        auto interned_demangled_name = Intern(demangled_name);
-        auto interned_system_name = Intern(frame_name);
-        auto* gfunction = result_->add_function();
-        gfunction->set_id(ToPprofId(synthesized_symbol_id));
-        gfunction->set_name(interned_demangled_name);
-        gfunction->set_system_name(interned_system_name);
-      }
-    }
-
-    if (!frame_it.Status().ok()) {
-      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                              frame_it.Status().message().c_str());
-      return false;
-    }
-    if (frames_no != seen_frames_.size()) {
-      PERFETTO_DFATAL_OR_ELOG("Missing frames.");
-      return false;
-    }
-    return true;
-  }
-
-  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 &&
-                   callsite_idx < callsite_to_frames_.size());
-    return callsite_to_frames_[callsite_idx];
-  }
-
-  const std::vector<Line>& LineForSymbolSetId(int64_t symbol_set_id) {
-    auto it = symbol_set_id_to_lines_.find(symbol_set_id);
-    if (it == symbol_set_id_to_lines_.end())
-      return empty_line_vector_;
-    return it->second;
-  }
-
-  int64_t Intern(const std::string& s) {
-    auto it = string_table_.find(s);
-    if (it == string_table_.end()) {
-      std::tie(it, std::ignore) =
-          string_table_.emplace(s, string_table_.size());
-      result_->add_string_table(s);
+  int64_t ToStringTableId(StringId interned_id) {
+    auto it = interning_remapper_.find(interned_id);
+    if (it == interning_remapper_.end()) {
+      int64_t table_id = static_cast<int64_t>(string_table_.size());
+      string_table_.push_back(interned_id);
+      bool inserted = false;
+      std::tie(it, inserted) =
+          interning_remapper_.emplace(interned_id, table_id);
+      PERFETTO_DCHECK(inserted);
     }
     return it->second;
   }
 
+  // Contains all locations, lines, functions (in memory):
+  const LocationTracker& locations_;
+
+  // String interner, strings referenced by LocationTracker are already
+  // interned. The new internings will come from mappings, and sample types.
+  trace_processor::StringPool* interner_;
+
+  // The profile format uses the repeated string_table field's index as an
+  // implicit id, so these structures remap the interned strings into sequential
+  // ids. Only the strings referenced by this GProfileBuilder instance will be
+  // added to the table.
+  std::unordered_map<StringId, int64_t> interning_remapper_;
+  std::vector<StringId> string_table_;
+
+  // Profile proto being serialized.
   protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
       result_;
-  std::map<std::string, int64_t> string_table_;
-  const std::vector<std::vector<int64_t>>& callsite_to_frames_;
-  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_;
+  // Set of locations referenced by the added samples.
+  std::set<int64_t> seen_locations_;
 };
 
-}  // namespace
-
 namespace heap_profile {
 struct View {
   const char* type;
@@ -543,13 +760,12 @@
 
 static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp,
                              std::vector<SerializedProfile>* output,
+                             bool annotate_frames,
                              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;
+  trace_processor::StringPool interner;
+  LocationTracker locations =
+      PreprocessLocations(tp, &interner, annotate_frames);
 
   bool any_fail = false;
   Iterator it = tp->ExecuteQuery(
@@ -557,8 +773,7 @@
       "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.value());
+    GProfileBuilder builder(locations, &interner);
     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());
@@ -686,12 +901,11 @@
 // perf and heap profiles.
 static bool TraceToPerfPprof(trace_processor::TraceProcessor* tp,
                              std::vector<SerializedProfile>* output,
+                             bool annotate_frames,
                              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;
+  trace_processor::StringPool interner;
+  LocationTracker locations =
+      PreprocessLocations(tp, &interner, annotate_frames);
 
   LogTracePerfEventIssues(tp);
 
@@ -703,9 +917,7 @@
     if (target_pid != 0 && process.pid != target_pid)
       continue;
 
-    GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
-                            max_symbol_id.value());
-
+    GProfileBuilder builder(locations, &interner);
     builder.WriteSampleTypes({{"samples", "count"}});
 
     std::string query = "select callsite_id from perf_sample where utid in (" +
@@ -733,17 +945,22 @@
   return true;
 }
 }  // namespace perf_profile
+}  // namespace
 
 bool TraceToPprof(trace_processor::TraceProcessor* tp,
                   std::vector<SerializedProfile>* output,
                   ConversionMode mode,
+                  uint64_t flags,
                   uint64_t pid,
                   const std::vector<uint64_t>& timestamps) {
+  bool annotate_frames =
+      flags & static_cast<uint64_t>(ConversionFlags::kAnnotateFrames);
   switch (mode) {
     case (ConversionMode::kHeapProfile):
-      return heap_profile::TraceToHeapPprof(tp, output, pid, timestamps);
+      return heap_profile::TraceToHeapPprof(tp, output, annotate_frames, pid,
+                                            timestamps);
     case (ConversionMode::kPerfProfile):
-      return perf_profile::TraceToPerfPprof(tp, output, pid);
+      return perf_profile::TraceToPerfPprof(tp, output, annotate_frames, pid);
   }
   PERFETTO_FATAL("unknown conversion option");  // for gcc
 }
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index a394e2b..c71a827 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -50,6 +50,12 @@
 namespace trace_to_text {
 namespace {
 
+uint64_t ToConversionFlags(bool annotate_frames) {
+  return static_cast<uint64_t>(annotate_frames
+                                   ? ConversionFlags::kAnnotateFrames
+                                   : ConversionFlags::kNone);
+}
+
 std::string GetRandomString(size_t n) {
   std::random_device r;
   auto rng = std::default_random_engine(r());
@@ -92,6 +98,7 @@
     uint64_t pid,
     std::vector<uint64_t> timestamps,
     ConversionMode conversion_mode,
+    uint64_t conversion_flags,
     std::string dirname_prefix,
     std::function<std::string(const SerializedProfile&)> filename_fn) {
   std::vector<SerializedProfile> profiles;
@@ -106,7 +113,8 @@
   MaybeSymbolize(tp.get());
   MaybeDeobfuscate(tp.get());
 
-  TraceToPprof(tp.get(), &profiles, conversion_mode, pid, timestamps);
+  TraceToPprof(tp.get(), &profiles, conversion_mode, conversion_flags, pid,
+               timestamps);
   if (profiles.empty()) {
     return 0;
   }
@@ -132,31 +140,33 @@
 int TraceToHeapProfile(std::istream* input,
                        std::ostream* output,
                        uint64_t pid,
-                       std::vector<uint64_t> timestamps) {
+                       std::vector<uint64_t> timestamps,
+                       bool annotate_frames) {
   int file_idx = 0;
   auto filename_fn = [&file_idx](const SerializedProfile& profile) {
     return "heap_dump." + std::to_string(++file_idx) + "." +
            std::to_string(profile.pid) + "." + profile.heap_name + ".pb";
   };
 
-  return TraceToProfile(input, output, pid, timestamps,
-                        ConversionMode::kHeapProfile, "heap_profile-",
-                        filename_fn);
+  return TraceToProfile(
+      input, output, pid, timestamps, ConversionMode::kHeapProfile,
+      ToConversionFlags(annotate_frames), "heap_profile-", filename_fn);
 }
 
 int TraceToPerfProfile(std::istream* input,
                        std::ostream* output,
                        uint64_t pid,
-                       std::vector<uint64_t> timestamps) {
+                       std::vector<uint64_t> timestamps,
+                       bool annotate_frames) {
   int file_idx = 0;
   auto filename_fn = [&file_idx](const SerializedProfile& profile) {
     return "profile." + std::to_string(++file_idx) + ".pid." +
            std::to_string(profile.pid) + ".pb";
   };
 
-  return TraceToProfile(input, output, pid, timestamps,
-                        ConversionMode::kPerfProfile, "perf_profile-",
-                        filename_fn);
+  return TraceToProfile(
+      input, output, pid, timestamps, ConversionMode::kPerfProfile,
+      ToConversionFlags(annotate_frames), "perf_profile-", filename_fn);
 }
 
 }  // namespace trace_to_text
diff --git a/tools/trace_to_text/trace_to_profile.h b/tools/trace_to_text/trace_to_profile.h
index 1c41aad..313b32b 100644
--- a/tools/trace_to_text/trace_to_profile.h
+++ b/tools/trace_to_text/trace_to_profile.h
@@ -27,13 +27,15 @@
 int TraceToHeapProfile(std::istream* input,
                        std::ostream* output,
                        uint64_t pid,
-                       std::vector<uint64_t> timestamps);
+                       std::vector<uint64_t> timestamps,
+                       bool annotate_frames);
 
 // 0: success
 int TraceToPerfProfile(std::istream* input,
                        std::ostream* output,
                        uint64_t pid,
-                       std::vector<uint64_t> timestamps);
+                       std::vector<uint64_t> timestamps,
+                       bool annotate_frames);
 
 }  // namespace trace_to_text
 }  // namespace perfetto