Merge "ui: Make thread and process name columns Optional"
diff --git a/Android.bp b/Android.bp
index 6a66249..5bf2da2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1586,6 +1586,11 @@
     name: "perfetto_include_perfetto_ext_ipc_ipc",
 }
 
+// GN: //include/perfetto/ext/trace_processor:demangle
+filegroup {
+    name: "perfetto_include_perfetto_ext_trace_processor_demangle",
+}
+
 // GN: //include/perfetto/ext/trace_processor:export_json
 filegroup {
     name: "perfetto_include_perfetto_ext_trace_processor_export_json",
@@ -1855,6 +1860,7 @@
         "libgmock",
         "libgtest",
         "libperfetto_client_experimental",
+        "perfetto_src_trace_processor_demangle",
     ],
     whole_static_libs: [
         "perfetto_gtest_logcat_printer",
@@ -3730,12 +3736,14 @@
         "protos/perfetto/metrics/chrome/blink_gc_metric.proto",
         "protos/perfetto/metrics/chrome/dropped_frames.proto",
         "protos/perfetto/metrics/chrome/frame_times.proto",
+        "protos/perfetto/metrics/chrome/histogram_hashes.proto",
         "protos/perfetto/metrics/chrome/long_latency.proto",
         "protos/perfetto/metrics/chrome/media_metric.proto",
         "protos/perfetto/metrics/chrome/reported_by_page.proto",
         "protos/perfetto/metrics/chrome/scroll_jank.proto",
         "protos/perfetto/metrics/chrome/test_chrome_metric.proto",
         "protos/perfetto/metrics/chrome/touch_jank.proto",
+        "protos/perfetto/metrics/chrome/user_event_hashes.proto",
         "protos/perfetto/metrics/custom_options.proto",
         "protos/perfetto/metrics/metrics.proto",
     ],
@@ -8110,6 +8118,21 @@
     ],
 }
 
+// GN: //src/trace_processor:demangle
+cc_library_static {
+    name: "perfetto_src_trace_processor_demangle",
+    srcs: [
+        ":perfetto_include_perfetto_base_base",
+        ":perfetto_include_perfetto_ext_base_base",
+        ":perfetto_include_perfetto_ext_trace_processor_demangle",
+        "src/trace_processor/demangle.cc",
+    ],
+    host_supported: true,
+    defaults: [
+        "perfetto_defaults",
+    ],
+}
+
 // GN: //src/trace_processor:export_json
 filegroup {
     name: "perfetto_src_trace_processor_export_json",
@@ -8367,8 +8390,10 @@
         "src/trace_processor/metrics/sql/chrome/actual_power_by_category.sql",
         "src/trace_processor/metrics/sql/chrome/actual_power_by_rail_mode.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_event_metadata.sql",
+        "src/trace_processor/metrics/sql/chrome/chrome_histogram_hashes.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_processes.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_thread_slice.sql",
+        "src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql",
         "src/trace_processor/metrics/sql/chrome/cpu_time_by_category.sql",
         "src/trace_processor/metrics/sql/chrome/cpu_time_by_rail_mode.sql",
         "src/trace_processor/metrics/sql/chrome/estimated_power_by_category.sql",
@@ -9776,6 +9801,7 @@
     static_libs: [
         "libgmock",
         "libgtest",
+        "perfetto_src_trace_processor_demangle",
     ],
     whole_static_libs: [
         "perfetto_gtest_logcat_printer",
@@ -10013,6 +10039,9 @@
         "src/trace_processor/trace_processor_shell.cc",
         "src/trace_processor/util/proto_to_json.cc",
     ],
+    static_libs: [
+        "perfetto_src_trace_processor_demangle",
+    ],
     host_supported: true,
     generated_headers: [
         "perfetto_protos_perfetto_common_zero_gen_headers",
@@ -10172,6 +10201,7 @@
     static_libs: [
         "libsqlite",
         "libz",
+        "perfetto_src_trace_processor_demangle",
     ],
     generated_headers: [
         "perfetto_protos_perfetto_common_zero_gen_headers",
diff --git a/BUILD b/BUILD
index 3260a6e..8870bbd 100644
--- a/BUILD
+++ b/BUILD
@@ -423,6 +423,14 @@
     ],
 )
 
+# GN target: //include/perfetto/ext/trace_processor:demangle
+perfetto_filegroup(
+    name = "include_perfetto_ext_trace_processor_demangle",
+    srcs = [
+        "include/perfetto/ext/trace_processor/demangle.h",
+    ],
+)
+
 # GN target: //include/perfetto/ext/trace_processor:export_json
 perfetto_filegroup(
     name = "include_perfetto_ext_trace_processor_export_json",
@@ -1106,8 +1114,10 @@
         "src/trace_processor/metrics/sql/chrome/actual_power_by_category.sql",
         "src/trace_processor/metrics/sql/chrome/actual_power_by_rail_mode.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_event_metadata.sql",
+        "src/trace_processor/metrics/sql/chrome/chrome_histogram_hashes.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_processes.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_thread_slice.sql",
+        "src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql",
         "src/trace_processor/metrics/sql/chrome/cpu_time_by_category.sql",
         "src/trace_processor/metrics/sql/chrome/cpu_time_by_rail_mode.sql",
         "src/trace_processor/metrics/sql/chrome/estimated_power_by_category.sql",
@@ -1332,6 +1342,20 @@
     ],
 )
 
+# GN target: //src/trace_processor:demangle
+perfetto_cc_library(
+    name = "src_trace_processor_demangle",
+    srcs = [
+        "src/trace_processor/demangle.cc",
+    ],
+    hdrs = [
+        ":include_perfetto_base_base",
+        ":include_perfetto_ext_base_base",
+        ":include_perfetto_ext_trace_processor_demangle",
+    ],
+    linkstatic = True,
+)
+
 # GN target: //src/trace_processor:export_json
 perfetto_filegroup(
     name = "src_trace_processor_export_json",
@@ -2721,12 +2745,14 @@
         "protos/perfetto/metrics/chrome/blink_gc_metric.proto",
         "protos/perfetto/metrics/chrome/dropped_frames.proto",
         "protos/perfetto/metrics/chrome/frame_times.proto",
+        "protos/perfetto/metrics/chrome/histogram_hashes.proto",
         "protos/perfetto/metrics/chrome/long_latency.proto",
         "protos/perfetto/metrics/chrome/media_metric.proto",
         "protos/perfetto/metrics/chrome/reported_by_page.proto",
         "protos/perfetto/metrics/chrome/scroll_jank.proto",
         "protos/perfetto/metrics/chrome/test_chrome_metric.proto",
         "protos/perfetto/metrics/chrome/touch_jank.proto",
+        "protos/perfetto/metrics/chrome/user_event_hashes.proto",
     ],
     visibility = [
         PERFETTO_CONFIG.proto_library_visibility,
@@ -3692,7 +3718,8 @@
            PERFETTO_CONFIG.deps.sqlite_ext_percentile +
            PERFETTO_CONFIG.deps.zlib + [
         ":cc_amalgamated_sql_metrics",
-    ],
+    ] +
+           PERFETTO_CONFIG.deps.demangle_wrapper,
     linkstatic = True,
 )
 
@@ -3790,7 +3817,8 @@
            PERFETTO_CONFIG.deps.sqlite_ext_percentile +
            PERFETTO_CONFIG.deps.zlib + [
         ":cc_amalgamated_sql_metrics",
-    ],
+    ] +
+           PERFETTO_CONFIG.deps.demangle_wrapper,
 )
 
 # GN target: //src/traced/probes:traced_probes
@@ -3974,7 +4002,8 @@
            PERFETTO_CONFIG.deps.sqlite_ext_percentile +
            PERFETTO_CONFIG.deps.zlib + [
         ":cc_amalgamated_sql_metrics",
-    ],
+    ] +
+           PERFETTO_CONFIG.deps.demangle_wrapper,
 )
 
 # Content from BUILD.extras
diff --git a/CHANGELOG b/CHANGELOG
index 8f2d129..955527a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,7 +8,11 @@
       message.
   Trace Processor:
     * Added prebuilts for mac-arm64.
-    *
+    * Added an optional dependency from trace processor onto a subset of
+      sources from llvm-project for function name demangling. Bazel embedders
+      might need to update their PERFETTO_CONFIG in perfetto_cfg.bzl to opt in
+      or out of the new dependency. See
+      perfetto/bazel/standalone/perfetto_cfg.bzl for details.
   UI:
     * Added flow arrows between binder transaction pairs (request/reply
       and async send/async recv).
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
index 9d01d11..23fb787 100644
--- a/bazel/deps.bzl
+++ b/bazel/deps.bzl
@@ -74,6 +74,15 @@
         shallow_since = "1557160162 -0700",
     )
 
+    _add_repo_if_not_existing(
+        http_archive,
+        name = "perfetto_dep_llvm_demangle",
+        url = "https://storage.googleapis.com/perfetto/llvm-project-3b4c59c156919902c785ce3cbae0eee2ee53064d.tgz",
+        sha256 = "f4a52e7f36edd7cacc844d5ae0e5f60b6f57c5afc40683e99f295886c9ce8ff4",
+        strip_prefix = "llvm-project",
+        build_file = "//bazel:llvm_demangle.BUILD",
+    )
+
     # Without this protobuf.bzl fails. This seems a bug in protobuf_deps().
     _add_repo_if_not_existing(
         http_archive,
diff --git a/bazel/llvm_demangle.BUILD b/bazel/llvm_demangle.BUILD
new file mode 100644
index 0000000..fe406fa
--- /dev/null
+++ b/bazel/llvm_demangle.BUILD
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 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.
+
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+
+cc_library(
+    name = "llvm_demangle",
+    srcs = [
+      "llvm/lib/Demangle/DLangDemangle.cpp",
+      "llvm/lib/Demangle/Demangle.cpp",
+      "llvm/lib/Demangle/ItaniumDemangle.cpp",
+      "llvm/lib/Demangle/MicrosoftDemangle.cpp",
+      "llvm/lib/Demangle/MicrosoftDemangleNodes.cpp",
+      "llvm/lib/Demangle/RustDemangle.cpp",
+    ],
+    hdrs = [
+      "llvm/include/llvm/Demangle/Demangle.h",
+      "llvm/include/llvm/Demangle/DemangleConfig.h",
+      "llvm/include/llvm/Demangle/ItaniumDemangle.h",
+      "llvm/include/llvm/Demangle/MicrosoftDemangle.h",
+      "llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h",
+      "llvm/include/llvm/Demangle/StringView.h",
+      "llvm/include/llvm/Demangle/Utility.h",
+    ],
+    copts = ["-std=c++14"] + PERFETTO_CONFIG.deps_copts.llvm_demangle,
+    includes = ["llvm/include"],
+    visibility = ["//visibility:public"],
+)
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index c9c89f1..a68bd34 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -56,6 +56,21 @@
         protobuf_py = [],
         pandas_py = [],
         tp_vendor_py = [],
+
+        # There are multiple configurations for the function name demangling
+        # logic in trace processor:
+        # (1) The following defaults include a subset of demangling sources
+        #     from llvm-project. This is the most complete implementation.
+        # (2) You can avoid the llvm dependency by setting "llvm_demangle = []"
+        #     here and PERFETTO_LLVM_DEMANGLE to false in your
+        #     perfetto_build_flags.h. Then the implementation will use a
+        #     demangler from the c++ runtime, which will most likely handle
+        #     only itanium mangling, and is unavailable on some platforms (e.g.
+        #     Windows, where it becomes a nop).
+        # (3) You can override the whole demangle_wrapper below, and provide
+        #     your own demangling implementation.
+        demangle_wrapper = [ "//:src_trace_processor_demangle" ],
+        llvm_demangle = ["@perfetto_dep_llvm_demangle//:llvm_demangle"],
     ),
 
     # This struct allows embedders to customize the cc_opts for Perfetto
@@ -66,6 +81,7 @@
         jsoncpp = [],
         linenoise = [],
         sqlite = [],
+        llvm_demangle = [],
     ),
 
     # Allow Bazel embedders to change the visibility of "public" targets.
diff --git a/buildtools/.gitignore b/buildtools/.gitignore
index e29be60..869315f 100644
--- a/buildtools/.gitignore
+++ b/buildtools/.gitignore
@@ -27,6 +27,7 @@
 linenoise/
 linux/
 linux64/
+llvm-project/
 lzma/
 mac/
 ndk/
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index ee84087..a508a74 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -974,6 +974,12 @@
   ]
 }
 
+# Here be dragons. Used only by standalone profiler builds, which are
+# considered best effort. Since the headers use c++17 features, this source_set
+# pushes -std=c++17 flags up the dependency tree, whereas the rest of the
+# project continues to build under c++11. So the profilers end up linking
+# together code built under different standards, and rely on that to be ABI
+# compatible.
 source_set("libunwindstack") {
   visibility = _buildtools_visibility
   include_dirs = [
@@ -1033,9 +1039,7 @@
       "android-unwinding/libunwindstack/MemoryMte.cpp",
     ]
   } else {
-    sources += [
-      "android-unwinding/libunwindstack/LogAndroid.cpp",
-    ]
+    sources += [ "android-unwinding/libunwindstack/LogAndroid.cpp" ]
   }
   if (current_cpu == "x86") {
     sources += [ "android-unwinding/libunwindstack/AsmGetRegsX86.S" ]
@@ -1147,3 +1151,39 @@
     deps = [ "//gn:default_deps" ]
   }
 }
+
+config("llvm_demangle_config") {
+  visibility = _buildtools_visibility
+  include_dirs = [ "llvm-project/llvm/include" ]
+}
+
+# NB: this is built under c++14 and linked into code that is c++11 by default.
+# We rely on the ABIs being compatible for this to be sane. At the time of
+# writing, the only c++14 specific code is behind an #ifndef NDEBUG, so we
+# could keep building as c++11 in non-debug builds, but we always use c++14 for
+# consistency.
+static_library("llvm_demangle") {
+  visibility = _buildtools_visibility
+  configs -= [
+    "//gn/standalone:extra_warnings",
+    "//gn/standalone:c++11",
+  ]
+  configs += [ "//gn/standalone:c++14" ]
+  public_configs = [ ":llvm_demangle_config" ]
+  sources = [
+    "llvm-project/llvm/include/llvm/Demangle/Demangle.h",
+    "llvm-project/llvm/include/llvm/Demangle/DemangleConfig.h",
+    "llvm-project/llvm/include/llvm/Demangle/ItaniumDemangle.h",
+    "llvm-project/llvm/include/llvm/Demangle/MicrosoftDemangle.h",
+    "llvm-project/llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h",
+    "llvm-project/llvm/include/llvm/Demangle/StringView.h",
+    "llvm-project/llvm/include/llvm/Demangle/Utility.h",
+    "llvm-project/llvm/lib/Demangle/DLangDemangle.cpp",
+    "llvm-project/llvm/lib/Demangle/Demangle.cpp",
+    "llvm-project/llvm/lib/Demangle/ItaniumDemangle.cpp",
+    "llvm-project/llvm/lib/Demangle/MicrosoftDemangle.cpp",
+    "llvm-project/llvm/lib/Demangle/MicrosoftDemangleNodes.cpp",
+    "llvm-project/llvm/lib/Demangle/RustDemangle.cpp",
+  ]
+  deps = [ "//gn:default_deps" ]
+}
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index f404832..b0e61b1 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -88,6 +88,7 @@
     "PERFETTO_HEAPPROFD=$enable_perfetto_heapprofd",
     "PERFETTO_STDERR_CRASH_DUMP=$enable_perfetto_stderr_crash_dump",
     "PERFETTO_X64_CPU_OPT=$enable_perfetto_x64_cpu_opt",
+    "PERFETTO_LLVM_DEMANGLE=$enable_perfetto_llvm_demangle",
   ]
 
   rel_out_path = rebase_path(gen_header_path, "$root_build_dir")
@@ -104,7 +105,8 @@
 # All targets should depend on this target to inherit the right flags and
 # include directories.
 group("default_deps") {
-  visibility = [ "../*" ]  # Prevent chromium targets from depending on this (breaks component).
+  visibility = [ "../*" ]  # Prevent chromium targets from depending on this
+                           # (breaks component).
   public_configs = [ ":default_config" ]
   deps = [ ":gen_buildflags" ]
   if (perfetto_build_standalone) {
@@ -124,7 +126,8 @@
 # embedders that depend on perfetto (e.g. chrome). :public_config (see below) is
 # used for that.
 config("default_config") {
-  visibility = [ "../*" ]  # Prevent chromium targets from depending on this (breaks component).
+  visibility = [ "../*" ]  # Prevent chromium targets from depending on this
+                           # (breaks component).
   configs = [ ":public_config" ]
   defines = [ "PERFETTO_IMPLEMENTATION" ]
   include_dirs = [
@@ -393,6 +396,12 @@
   }
 }
 
+if (enable_perfetto_llvm_demangle) {
+  group("llvm_demangle") {
+    public_deps = [ "//buildtools:llvm_demangle" ]
+  }
+}
+
 # Used by fuzzers.
 if (enable_perfetto_fuzzers && use_libfuzzer) {
   group("libfuzzer") {
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index a755b8c..fe1fa11 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -100,7 +100,7 @@
 
 # Only relevant for GN builds. Sets the path where perfetto lives. This is //
 # for standalone builds and //third_party/perfetto/ in embedders. The embedder
-# can ovverride it in its GN files.
+# can override it in its GN files.
 if (perfetto_build_standalone || is_perfetto_build_generator) {
   perfetto_root_path = "//"
   import("//gn/standalone/android.gni")  # For android_api_level
@@ -291,6 +291,12 @@
   enable_perfetto_zlib =
       (enable_perfetto_trace_processor && !build_with_chromium) ||
       enable_perfetto_platform_services
+
+  # Enables function name demangling using sources from llvm. Otherwise
+  # trace_processor falls back onto using the c++ runtime demangler, which
+  # typically handles only itanium mangling.
+  enable_perfetto_llvm_demangle =
+      enable_perfetto_trace_processor && perfetto_build_standalone
 }
 
 declare_args() {
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index bd33114..ab21e58 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -116,7 +116,16 @@
   }
 }
 
-# This is needed to compile libunwindstack.
+# Used in buildtools dependencies for standalone builds.
+config("c++14") {
+  if (is_win) {
+    cflags_cc = [ "/std:c++14" ]
+  } else {
+    cflags_cc = [ "-std=c++14" ]
+  }
+}
+
+# Used in buildtools dependencies for standalone builds.
 config("c++17") {
   if (is_win) {
     cflags_cc = [ "/std:c++17" ]
diff --git a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
index a02f467..502e9bf 100644
--- a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
@@ -44,6 +44,7 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_HEAPPROFD() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STDERR_CRASH_DUMP() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_X64_CPU_OPT() (0)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LLVM_DEMANGLE() (0)
 
 // clang-format on
 #endif  // GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
diff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
index 531445c..53c423e 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -44,6 +44,7 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_HEAPPROFD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STDERR_CRASH_DUMP() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_X64_CPU_OPT() (0)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LLVM_DEMANGLE() (0)
 
 // clang-format on
 #endif  // GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
diff --git a/include/perfetto/ext/trace_processor/BUILD.gn b/include/perfetto/ext/trace_processor/BUILD.gn
index a2fd7fa..6df0cca 100644
--- a/include/perfetto/ext/trace_processor/BUILD.gn
+++ b/include/perfetto/ext/trace_processor/BUILD.gn
@@ -21,3 +21,7 @@
     sources += [ "export_json.h" ]
   }
 }
+
+source_set("demangle") {
+  sources = [ "demangle.h" ]
+}
diff --git a/include/perfetto/ext/trace_processor/demangle.h b/include/perfetto/ext/trace_processor/demangle.h
new file mode 100644
index 0000000..f3c337f
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/demangle.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_DEMANGLE_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_DEMANGLE_H_
+
+#include "perfetto/ext/base/utils.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace demangle {
+
+// Returns a |malloc|-allocated C string with the demangled name.
+// Returns an empty pointer if demangling was unsuccessful.
+std::unique_ptr<char, base::FreeDeleter> Demangle(const char* mangled_name);
+
+}  // namespace demangle
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_DEMANGLE_H_
diff --git a/protos/perfetto/metrics/chrome/BUILD.gn b/protos/perfetto/metrics/chrome/BUILD.gn
index 9e88247..ad53980 100644
--- a/protos/perfetto/metrics/chrome/BUILD.gn
+++ b/protos/perfetto/metrics/chrome/BUILD.gn
@@ -25,12 +25,14 @@
     "blink_gc_metric.proto",
     "dropped_frames.proto",
     "frame_times.proto",
+    "histogram_hashes.proto",
     "long_latency.proto",
     "media_metric.proto",
     "reported_by_page.proto",
     "scroll_jank.proto",
     "test_chrome_metric.proto",
     "touch_jank.proto",
+    "user_event_hashes.proto",
   ]
 }
 
diff --git a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
index 4a24580..5513b52 100644
--- a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
+++ b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
@@ -22,12 +22,14 @@
 import "protos/perfetto/metrics/chrome/blink_gc_metric.proto";
 import "protos/perfetto/metrics/chrome/dropped_frames.proto";
 import "protos/perfetto/metrics/chrome/frame_times.proto";
+import "protos/perfetto/metrics/chrome/histogram_hashes.proto";
 import "protos/perfetto/metrics/chrome/long_latency.proto";
 import "protos/perfetto/metrics/chrome/media_metric.proto";
 import "protos/perfetto/metrics/chrome/reported_by_page.proto";
 import "protos/perfetto/metrics/chrome/scroll_jank.proto";
 import "protos/perfetto/metrics/chrome/test_chrome_metric.proto";
 import "protos/perfetto/metrics/chrome/touch_jank.proto";
+import "protos/perfetto/metrics/chrome/user_event_hashes.proto";
 
 // TODO(lalitm): rename metrics below to include a "chrome_" prefix.
 extend TraceMetrics {
@@ -40,4 +42,6 @@
   optional TouchJank touch_jank = 1007;
   optional ChromeDroppedFrames chrome_dropped_frames = 1008;
   optional ChromeLongLatency chrome_long_latency = 1009;
+  optional ChromeHistogramHashes chrome_histogram_hashes = 1010;
+  optional ChromeUserEventHashes chrome_user_event_hashes = 1011;
 }
diff --git a/protos/perfetto/metrics/chrome/histogram_hashes.proto b/protos/perfetto/metrics/chrome/histogram_hashes.proto
new file mode 100644
index 0000000..8f7323e
--- /dev/null
+++ b/protos/perfetto/metrics/chrome/histogram_hashes.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// The list of Chrome Histogram hashes in trace track events.
+// Use cases include translating histogram hashes to histogram
+// names by getting this list, and prepending a translation table to the trace.
+message ChromeHistogramHashes {
+  repeated int64 hash = 1;
+}
diff --git a/protos/perfetto/metrics/chrome/user_event_hashes.proto b/protos/perfetto/metrics/chrome/user_event_hashes.proto
new file mode 100644
index 0000000..e50e6bd
--- /dev/null
+++ b/protos/perfetto/metrics/chrome/user_event_hashes.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// The list of Chrome user event hashes in trace track events.
+// Use cases include translating user event hashes to action
+// names by getting this list, and prepending a translation table to the trace.
+message ChromeUserEventHashes {
+  repeated int64 action_hash = 1;
+}
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 08f37a7..c30f4cd 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -5,6 +5,7 @@
 syntax = "proto2";
 
 import public "protos/perfetto/trace/track_event/track_event.proto";
+import public "protos/perfetto/trace/track_event/debug_annotation.proto";
 
 package perfetto.protos;
 
@@ -152,6 +153,11 @@
   optional bool has_speculative_render_frame_host = 3;
 
   optional RenderFrameHost current_frame_host = 4;
+  optional RenderFrameHost speculative_frame_host = 5;
+
+  // Additional untyped debug information associated with this
+  // FrameTreeNode, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message ChromeHashedPerformanceMark {
@@ -204,6 +210,10 @@
 
   // Details about the associated browser context.
   optional ChromeBrowserContext browser_context = 4;
+
+  // Additional untyped debug information associated with this
+  // RenderProcessHost, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message RenderProcessHostListener {
@@ -276,6 +286,10 @@
 
   // The SiteInstanceGroup this SiteInstance belongs to.
   optional SiteInstanceGroup site_instance_group = 7;
+
+  // Additional untyped debug information associated with this
+  // SiteInstance, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message SiteInstanceGroup {
@@ -287,8 +301,13 @@
 
   // The process ID of the SiteInstanceGroup.
   optional RenderProcessHost process = 3;
+
+  // Additional untyped debug information associated with this
+  // SiteInstanceGroup, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
+// Next ID: 7
 message RenderViewHost {
   // The RenderViewHostMapId for the RenderViewHost.
   optional int32 rvh_map_id = 1;
@@ -296,14 +315,21 @@
   // The routing ID for the RenderViewHost.
   optional int32 routing_id = 2;
 
-  // The process ID of the RenderViewHost.
+  // The process ID of the RenderViewHost. Deprecated in favour of |process|.
   optional int32 process_id = 3;
 
+  // Process this RenderViewHost is associated with.
+  optional RenderProcessHost process = 6;
+
   // Whether the RenderViewHost is in back/forward cache or not.
   optional bool is_in_back_forward_cache = 4;
 
   // Whether the renderer-side RenderView is created.
   optional bool renderer_view_created = 5;
+
+  // Additional untyped debug information associated with this
+  // RenderViewHost, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message RenderFrameProxyHost {
@@ -327,6 +353,10 @@
   // The SiteInstanceGroupId of the SiteInstanceGroup associated with the
   // RenderFrameProxyHost.
   optional int32 site_instance_group_id = 6;
+
+  // Additional untyped debug information associated with this
+  // RenderFrameProxyHost, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message AndroidView {
@@ -420,6 +450,10 @@
 message BrowsingContextState {
   // The ID of the BrowsingInstance that the BrowsingContextState belongs to.
   optional int32 browsing_instance_id = 1;
+
+  // Additional untyped debug information associated with this
+  // FrameTreeNode, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message RenderFrameHost {
@@ -448,6 +482,10 @@
   optional RenderFrameHost outer_document = 9;
   optional RenderFrameHost embedder = 10;
   optional BrowsingContextState browsing_context_state = 11;
+
+  // Additional untyped debug information associated with this
+  // RenderViewHost, populated via TracedProto::AddDebugAnnotations API.
+  repeated DebugAnnotation debug_annotations = 99;
 }
 
 message ChromeThreadPoolTask {
@@ -711,6 +749,19 @@
   optional RemoteHungProcessTerminateReason remote_process_terminate_reason = 2;
 }
 
+message NavigationHandle {
+  optional int64 navigation_id = 1;
+  optional bool has_committed = 2;
+  optional bool is_error_page = 3;
+  optional FrameTreeNodeInfo frame_tree_node = 4;
+  optional RenderFrameHost render_frame_host = 5;
+
+  // Additional untyped debug information associated with this
+  // NavigationHandle/Request, populated via TracedProto::AddDebugAnnotations
+  // API.
+  repeated DebugAnnotation debug_annotations = 99;
+}
+
 enum DeviceThermalState {
   DEVICE_THERMAL_STATE_UNKNOWN = 0;
   DEVICE_THERMAL_STATE_NOMINAL = 1;
@@ -801,5 +852,7 @@
     optional BrowsingContextState browsing_context_state = 1035;
 
     optional DeviceThermalState device_thermal_state = 1036;
+
+    optional NavigationHandle navigation = 1037;
   }
 }
diff --git a/src/perfetto_cmd/config.cc b/src/perfetto_cmd/config.cc
index f2b40d4..5022203 100644
--- a/src/perfetto_cmd/config.cc
+++ b/src/perfetto_cmd/config.cc
@@ -147,6 +147,7 @@
     ftrace_cfg.add_atrace_categories(cat);
   for (const auto& app : atrace_apps)
     ftrace_cfg.add_atrace_apps(app);
+  ftrace_cfg.set_symbolize_ksyms(true);
   ds_config->set_ftrace_config_raw(ftrace_cfg.SerializeAsString());
 
   auto* ps_config = config->add_data_sources()->mutable_config();
diff --git a/src/perfetto_cmd/config_unittest.cc b/src/perfetto_cmd/config_unittest.cc
index cc1db34..5435c8a 100644
--- a/src/perfetto_cmd/config_unittest.cc
+++ b/src/perfetto_cmd/config_unittest.cc
@@ -147,6 +147,7 @@
   ASSERT_TRUE(ftrace.ParseFromString(
       config.data_sources()[0].config().ftrace_config_raw()));
   EXPECT_THAT(ftrace.ftrace_events(), Contains("sched/sched_switch"));
+  EXPECT_TRUE(ftrace.symbolize_ksyms());
   EXPECT_THAT(ftrace.atrace_categories(), Contains("sw"));
   EXPECT_THAT(ftrace.atrace_apps(), Contains("com.android.chrome"));
 }
diff --git a/src/profiling/deobfuscator.cc b/src/profiling/deobfuscator.cc
index ba58dfa..378677e 100644
--- a/src/profiling/deobfuscator.cc
+++ b/src/profiling/deobfuscator.cc
@@ -159,8 +159,10 @@
 // See https://www.guardsquare.com/en/products/proguard/manual/retrace for the
 // file format we are parsing.
 base::Status ProguardParser::AddLine(std::string line) {
-  if (line.length() == 0 || line[0] == '#')
+  auto first_ch_pos = line.find_first_not_of(" \t");
+  if (first_ch_pos == std::string::npos || line[first_ch_pos] == '#')
     return base::Status();
+
   bool is_member = line[0] == ' ';
   if (is_member && !current_class_) {
     return base::Status(
diff --git a/src/profiling/deobfuscator_unittest.cc b/src/profiling/deobfuscator_unittest.cc
index 02a0e8d..11b267c 100644
--- a/src/profiling/deobfuscator_unittest.cc
+++ b/src/profiling/deobfuscator_unittest.cc
@@ -71,12 +71,11 @@
   ASSERT_TRUE(
       p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate -> b")
           .ok());
-  std::map<std::string, std::string> deobfuscated_fields{{"b", "mDelegate"}};
-  ASSERT_THAT(
+  EXPECT_THAT(
       p.ConsumeMapping(),
       ElementsAre(std::pair<std::string, ObfuscatedClass>(
           "android.arch.a.a.a", {"android.arch.core.executor.ArchTaskExecutor",
-                                 std::move(deobfuscated_fields),
+                                 {{"b", "mDelegate"}},
                                  {}})));
 }
 
@@ -224,6 +223,25 @@
           .ok());
 }
 
+TEST(ProguardParserTest, EmptyLinesAndComments) {
+  ProguardParser p;
+  const char input[] = R"(
+# comment
+
+Example$$Class -> C:
+
+    int first -> q
+    # indented comment
+    long second -> o
+)";
+
+  ASSERT_TRUE(p.AddLines(std::string(input)));
+  EXPECT_THAT(
+      p.ConsumeMapping(),
+      ElementsAre(std::pair<std::string, ObfuscatedClass>(
+          "C", {"Example$$Class", {{"q", "first"}, {"o", "second"}}, {}})));
+}
+
 }  // namespace
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 45e9774..0961c10 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -54,6 +54,19 @@
   ]
 }
 
+static_library("demangle") {
+  sources = [ "demangle.cc" ]
+  deps = [
+    "../../gn:default_deps",
+    "../../include/perfetto/base",
+    "../../include/perfetto/ext/base",
+  ]
+  public_deps = [ "../../include/perfetto/ext/trace_processor:demangle" ]
+  if (enable_perfetto_llvm_demangle) {
+    deps += [ "../../gn:llvm_demangle" ]
+  }
+}
+
 source_set("ftrace_descriptors") {
   sources = [
     "importers/ftrace/ftrace_descriptors.cc",
@@ -330,6 +343,7 @@
     ]
 
     deps = [
+      ":demangle",
       ":export_json",
       ":metatrace",
       ":storage_full",
diff --git a/src/trace_processor/demangle.cc b/src/trace_processor/demangle.cc
new file mode 100644
index 0000000..c44f834
--- /dev/null
+++ b/src/trace_processor/demangle.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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 "perfetto/ext/trace_processor/demangle.h"
+
+#include <string.h>
+#include <string>
+
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_LLVM_DEMANGLE)
+#include "llvm/Demangle/Demangle.h"
+#elif !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <cxxabi.h>
+#endif
+
+namespace perfetto {
+namespace trace_processor {
+namespace demangle {
+
+// Implementation depends on platform and build config. If llvm demangling
+// sources are available, use them. That is the most portable and handles more
+// than just Itanium mangling (e.g. Rust's _R scheme). Otherwise use the c++
+// standard library demangling if it implements the appropriate ABI. This
+// excludes Windows builds, where we therefore never demangle.
+// TODO(rsavitski): consider reimplementing llvm::demangle inline as it's
+// wrapping in std::strings a set of per-scheme demangling functions that
+// operate on C strings. Right now we're introducing yet another layer that
+// undoes that conversion.
+std::unique_ptr<char, base::FreeDeleter> Demangle(const char* mangled_name) {
+#if PERFETTO_BUILDFLAG(PERFETTO_LLVM_DEMANGLE)
+  std::string input(mangled_name);
+  std::string demangled = llvm::demangle(input);
+  if (demangled == input)
+    return nullptr;  // demangling unsuccessful
+
+  std::unique_ptr<char, base::FreeDeleter> output(
+      static_cast<char*>(malloc(demangled.size() + 1)));
+  if (!output)
+    return nullptr;
+  memcpy(output.get(), demangled.c_str(), demangled.size() + 1);
+  return output;
+
+#elif !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  int ignored = 0;
+  return std::unique_ptr<char, base::FreeDeleter>(
+      abi::__cxa_demangle(mangled_name, nullptr, nullptr, &ignored));
+
+#else
+  return nullptr;
+#endif
+}
+
+}  // namespace demangle
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 534c2bb..7048b96 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -41,6 +41,7 @@
   ]
   public_deps = [
     "../:gen_cc_config_descriptor",
+    "../../util:proto_to_args_parser",
     "../../util:protozero_to_text",
   ]
   deps = [
diff --git a/src/trace_processor/importers/common/args_translation_table.cc b/src/trace_processor/importers/common/args_translation_table.cc
index ee91b30..216305d 100644
--- a/src/trace_processor/importers/common/args_translation_table.cc
+++ b/src/trace_processor/importers/common/args_translation_table.cc
@@ -19,6 +19,36 @@
 namespace perfetto {
 namespace trace_processor {
 
+constexpr char ArgsTranslationTable::kChromeHistogramHashKey[];
+constexpr char ArgsTranslationTable::kChromeHistogramNameKey[];
+
+ArgsTranslationTable::ArgsTranslationTable(TraceStorage* storage)
+    : storage_(storage),
+      interned_chrome_histogram_name_key_(
+          storage->InternString(kChromeHistogramNameKey)) {}
+
+bool ArgsTranslationTable::TranslateUnsignedIntegerArg(
+    const Key& key,
+    uint64_t value,
+    ArgsTracker::BoundInserter& inserter) {
+  if (key.key == kChromeHistogramHashKey) {
+    const base::Optional<base::StringView> translated_value =
+        TranslateChromeHistogramHash(value);
+    if (translated_value) {
+      inserter.AddArg(
+          interned_chrome_histogram_name_key_,
+          Variadic::String(storage_->InternString(*translated_value)));
+    }
+  }
+  return false;
+}
+
+base::Optional<base::StringView>
+ArgsTranslationTable::TranslateChromeHistogramHashForTesting(
+    uint64_t hash) const {
+  return TranslateChromeHistogramHash(hash);
+}
+
 base::Optional<base::StringView>
 ArgsTranslationTable::TranslateChromeHistogramHash(uint64_t hash) const {
   auto* value = chrome_histogram_hash_to_name_.Find(hash);
diff --git a/src/trace_processor/importers/common/args_translation_table.h b/src/trace_processor/importers/common/args_translation_table.h
index 0e34e2c..aac2bb6 100644
--- a/src/trace_processor/importers/common/args_translation_table.h
+++ b/src/trace_processor/importers/common/args_translation_table.h
@@ -22,6 +22,9 @@
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -30,18 +33,34 @@
 // to map for example hashes to their names.
 class ArgsTranslationTable {
  public:
+  using Key = util::ProtoToArgsParser::Key;
+
+  ArgsTranslationTable(TraceStorage* storage);
+
+  // Returns true if the translation table fully handles the arg, in which case
+  // the original arg doesn't need to be processed. This function has not added
+  // anything if returning false.
+  bool TranslateUnsignedIntegerArg(const Key& key,
+                                   uint64_t value,
+                                   ArgsTracker::BoundInserter& inserter);
+
+  void AddChromeHistogramTranslationRule(uint64_t hash, base::StringView name);
+
+  base::Optional<base::StringView> TranslateChromeHistogramHashForTesting(
+      uint64_t hash) const;
+
+ private:
   static constexpr char kChromeHistogramHashKey[] =
       "chrome_histogram_sample.name_hash";
   static constexpr char kChromeHistogramNameKey[] =
       "chrome_histogram_sample.name";
 
+  TraceStorage* storage_;
+  StringId interned_chrome_histogram_name_key_;
+  base::FlatHashMap<uint64_t, std::string> chrome_histogram_hash_to_name_;
+
   base::Optional<base::StringView> TranslateChromeHistogramHash(
       uint64_t hash) const;
-
-  void AddChromeHistogramTranslationRule(uint64_t hash, base::StringView name);
-
- private:
-  base::FlatHashMap<uint64_t, std::string> chrome_histogram_hash_to_name_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/common/args_translation_table_unittest.cc b/src/trace_processor/importers/common/args_translation_table_unittest.cc
index 15dddff..08bc458 100644
--- a/src/trace_processor/importers/common/args_translation_table_unittest.cc
+++ b/src/trace_processor/importers/common/args_translation_table_unittest.cc
@@ -22,19 +22,21 @@
 namespace {
 
 TEST(ArgsTranslationTable, EmptyTableByDefault) {
-  ArgsTranslationTable table;
-  EXPECT_EQ(table.TranslateChromeHistogramHash(1), base::nullopt);
+  TraceStorage storage;
+  ArgsTranslationTable table(&storage);
+  EXPECT_EQ(table.TranslateChromeHistogramHashForTesting(1), base::nullopt);
 }
 
 TEST(ArgsTranslationTable, TranslatesHashes) {
-  ArgsTranslationTable table;
+  TraceStorage storage;
+  ArgsTranslationTable table(&storage);
   table.AddChromeHistogramTranslationRule(1, "hash1");
   table.AddChromeHistogramTranslationRule(10, "hash2");
-  EXPECT_EQ(table.TranslateChromeHistogramHash(1),
+  EXPECT_EQ(table.TranslateChromeHistogramHashForTesting(1),
             base::Optional<base::StringView>("hash1"));
-  EXPECT_EQ(table.TranslateChromeHistogramHash(10),
+  EXPECT_EQ(table.TranslateChromeHistogramHashForTesting(10),
             base::Optional<base::StringView>("hash2"));
-  EXPECT_EQ(table.TranslateChromeHistogramHash(2), base::nullopt);
+  EXPECT_EQ(table.TranslateChromeHistogramHashForTesting(2), base::nullopt);
 }
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index f90e808..f83aede 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -167,6 +167,7 @@
       cros_ec_arg_num_id_(context->storage->InternString("ec_num")),
       cros_ec_arg_ec_id_(context->storage->InternString("ec_delta")),
       cros_ec_arg_sample_ts_id_(context->storage->InternString("sample_ts")),
+      ufs_clkgating_id_(context->storage->InternString("UFS clkgating (OFF/REQ_OFF/REQ_ON/ON)")),
       ufs_command_count_id_(context->storage->InternString("UFS Command Count")) {
   // Build the lookup table for the strings inside ftrace events (e.g. the
   // name of ftrace event fields and the names of their args).
@@ -735,6 +736,10 @@
         ParseWakeSourceDeactivate(ts, data);
         break;
       }
+      case FtraceEvent::kUfshcdClkGatingFieldNumber: {
+        ParseUfshcdClkGating(ts, data);
+        break;
+      }
       default:
         break;
     }
@@ -1986,6 +1991,31 @@
       args_inserter);
 }
 
+void FtraceParser::ParseUfshcdClkGating(int64_t timestamp,
+                                      protozero::ConstBytes blob) {
+  protos::pbzero::UfshcdClkGatingFtraceEvent::Decoder evt(blob.data, blob.size);
+  int32_t clk_state = 0;
+
+  switch (evt.state()) {
+      case 1:
+          // Change ON state to 3
+          clk_state = 3;
+          break;
+      case 2:
+          // Change REQ_OFF state to 1
+          clk_state = 1;
+          break;
+      case 3:
+          // Change REQ_ON state to 2
+          clk_state = 2;
+          break;
+  }
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      ufs_clkgating_id_);
+  context_->event_tracker->PushCounter(timestamp, static_cast<double>(clk_state),
+                                       track);
+}
+
 void FtraceParser::ParseUfshcdCommand(int64_t timestamp,
                                       protozero::ConstBytes blob) {
   protos::pbzero::UfshcdCommandFtraceEvent::Decoder evt(blob.data, blob.size);
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 723be1c..8dff094 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -170,6 +170,7 @@
   void ParseCpuFrequencyLimits(int64_t timestamp, protozero::ConstBytes);
   void ParseKfreeSkb(int64_t timestamp, protozero::ConstBytes);
   void ParseUfshcdCommand(int64_t timestamp, protozero::ConstBytes);
+  void ParseUfshcdClkGating(int64_t timestamp, protozero::ConstBytes);
 
   void ParseCrosEcSensorhubData(int64_t timestamp, protozero::ConstBytes);
   void ParseWakeSourceActivate(int64_t timestamp, protozero::ConstBytes);
@@ -225,6 +226,7 @@
   const StringId cros_ec_arg_num_id_;
   const StringId cros_ec_arg_ec_id_;
   const StringId cros_ec_arg_sample_ts_id_;
+  const StringId ufs_clkgating_id_;
   const StringId ufs_command_count_id_;
 
   struct FtraceMessageStrings {
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index f264d27..faa8261 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -24,6 +24,7 @@
 #include "perfetto/ext/base/string_writer.h"
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
@@ -70,10 +71,13 @@
  public:
   TrackEventArgsParser(BoundInserter& inserter,
                        TraceStorage& storage,
-                       PacketSequenceStateGeneration& sequence_state)
+                       PacketSequenceStateGeneration& sequence_state,
+                       ArgsTranslationTable& args_translation_table)
       : inserter_(inserter),
         storage_(storage),
-        sequence_state_(sequence_state) {}
+        sequence_state_(sequence_state),
+        args_translation_table_(args_translation_table) {}
+
   ~TrackEventArgsParser() override;
 
   using Key = util::ProtoToArgsParser::Key;
@@ -84,6 +88,10 @@
                      Variadic::Integer(value));
   }
   void AddUnsignedInteger(const Key& key, uint64_t value) final {
+    if (args_translation_table_.TranslateUnsignedIntegerArg(key, value,
+                                                            inserter_)) {
+      return;
+    }
     inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)),
                      storage_.InternString(base::StringView(key.key)),
                      Variadic::UnsignedInteger(value));
@@ -141,6 +149,7 @@
   BoundInserter& inserter_;
   TraceStorage& storage_;
   PacketSequenceStateGeneration& sequence_state_;
+  ArgsTranslationTable& args_translation_table_;
 };
 
 TrackEventArgsParser::~TrackEventArgsParser() = default;
@@ -1121,7 +1130,8 @@
           ParseHistogramName(event_.chrome_histogram_sample(), inserter));
     }
 
-    TrackEventArgsParser args_writer(*inserter, *storage_, *sequence_state_);
+    TrackEventArgsParser args_writer(*inserter, *storage_, *sequence_state_,
+                                     *context_->args_translation_table);
     int unknown_extensions = 0;
     log_errors(parser_->args_parser_.ParseMessage(
         blob_, ".perfetto.protos.TrackEvent", &parser_->reflect_fields_,
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index fbbb41c..5c07ca1 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -84,8 +84,10 @@
   "chrome/actual_power_by_category.sql",
   "chrome/actual_power_by_rail_mode.sql",
   "chrome/chrome_event_metadata.sql",
+  "chrome/chrome_histogram_hashes.sql",
   "chrome/chrome_processes.sql",
   "chrome/chrome_thread_slice.sql",
+  "chrome/chrome_user_event_hashes.sql",
   "chrome/cpu_time_by_category.sql",
   "chrome/cpu_time_by_rail_mode.sql",
   "chrome/estimated_power_by_category.sql",
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_histogram_hashes.sql b/src/trace_processor/metrics/sql/chrome/chrome_histogram_hashes.sql
new file mode 100644
index 0000000..ef5d918
--- /dev/null
+++ b/src/trace_processor/metrics/sql/chrome/chrome_histogram_hashes.sql
@@ -0,0 +1,26 @@
+--
+-- Copyright 2022 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
+--
+--     https://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.
+
+DROP VIEW IF EXISTS chrome_histogram_hashes_output;
+
+CREATE VIEW chrome_histogram_hashes_output AS
+SELECT ChromeHistogramHashes(
+  'hash', (
+    SELECT RepeatedField(int_value)
+    FROM args
+    WHERE key = 'chrome_histogram_sample.name_hash'
+    ORDER BY int_value
+  )
+);
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql b/src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql
new file mode 100644
index 0000000..005e2ad
--- /dev/null
+++ b/src/trace_processor/metrics/sql/chrome/chrome_user_event_hashes.sql
@@ -0,0 +1,26 @@
+--
+-- Copyright 2022 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
+--
+--     https://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.
+
+DROP VIEW IF EXISTS chrome_user_event_hashes_output;
+
+CREATE VIEW chrome_user_event_hashes_output AS
+SELECT ChromeUserEventHashes(
+  'action_hash', (
+    SELECT RepeatedField(int_value)
+    FROM args
+    WHERE key = 'chrome_user_event.action_hash'
+    ORDER BY int_value
+  )
+);
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 6a59ca6..4df80b9 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -179,7 +179,8 @@
   ASSERT_EQ(it.Get(0).long_value, static_cast<int64_t>(0xa9cb070fdc15f7a4));
 }
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if !PERFETTO_BUILDFLAG(PERFETTO_LLVM_DEMANGLE) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #define MAYBE_Demangle DISABLED_Demangle
 #else
 #define MAYBE_Demangle Demangle
@@ -187,15 +188,38 @@
 TEST_F(TraceProcessorIntegrationTest, MAYBE_Demangle) {
   auto it = Query("select DEMANGLE('_Znwm')");
   ASSERT_TRUE(it.Next());
-  ASSERT_STRCASEEQ(it.Get(0).string_value, "operator new(unsigned long)");
+  EXPECT_STRCASEEQ(it.Get(0).string_value, "operator new(unsigned long)");
 
   it = Query("select DEMANGLE('_ZN3art6Thread14CreateCallbackEPv')");
   ASSERT_TRUE(it.Next());
-  ASSERT_STRCASEEQ(it.Get(0).string_value,
+  EXPECT_STRCASEEQ(it.Get(0).string_value,
                    "art::Thread::CreateCallback(void*)");
 
   it = Query("select DEMANGLE('test')");
   ASSERT_TRUE(it.Next());
+  EXPECT_TRUE(it.Get(0).is_null());
+}
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_LLVM_DEMANGLE)
+#define MAYBE_DemangleRust DISABLED_DemangleRust
+#else
+#define MAYBE_DemangleRust DemangleRust
+#endif
+TEST_F(TraceProcessorIntegrationTest, MAYBE_DemangleRust) {
+  auto it = Query(
+      "select DEMANGLE("
+      "'_RNvNvMs0_NtNtNtCsg1Z12QU66Yk_3std3sys4unix6threadNtB7_"
+      "6Thread3new12thread_start')");
+  ASSERT_TRUE(it.Next());
+  EXPECT_STRCASEEQ(it.Get(0).string_value,
+                   "<std::sys::unix::thread::Thread>::new::thread_start");
+
+  it = Query("select DEMANGLE('_RNvCsdV139EorvfX_14keystore2_main4main')");
+  ASSERT_TRUE(it.Next());
+  ASSERT_STRCASEEQ(it.Get(0).string_value, "keystore2_main::main");
+
+  it = Query("select DEMANGLE('_R')");
+  ASSERT_TRUE(it.Next());
   ASSERT_TRUE(it.Get(0).is_null());
 }
 
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 42d1d4e..9ace9ee 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -24,6 +24,7 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/trace_processor/demangle.h"
 #include "src/trace_processor/dynamic/ancestor_generator.h"
 #include "src/trace_processor/dynamic/connected_flow_generator.h"
 #include "src/trace_processor/dynamic/descendant_generator.h"
@@ -71,9 +72,6 @@
 #include "src/trace_processor/metrics/metrics.h"
 #include "src/trace_processor/metrics/sql/amalgamated_sql_metrics.h"
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-#include <cxxabi.h>
-#endif
 
 // In Android and Chromium tree builds, we don't have the percentile module.
 // Just don't include it.
@@ -422,20 +420,16 @@
   if (sqlite3_value_type(value) != SQLITE_TEXT)
     return base::ErrStatus("Unsupported type of arg passed to DEMANGLE");
 
-  const char* ptr = reinterpret_cast<const char*>(sqlite3_value_text(value));
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  int ignored = 0;
-  // This memory was allocated by malloc and will be passed to SQLite to free.
-  char* demangled_name = abi::__cxa_demangle(ptr, nullptr, nullptr, &ignored);
-  if (!demangled_name)
+  const char* mangled =
+      reinterpret_cast<const char*>(sqlite3_value_text(value));
+
+  std::unique_ptr<char, base::FreeDeleter> demangled =
+      demangle::Demangle(mangled);
+  if (!demangled)
     return base::OkStatus();
 
   destructors.string_destructor = free;
-  out = SqlValue::String(demangled_name);
-#else
-  destructors.string_destructor = sqlite_utils::kSqliteTransient;
-  out = SqlValue::String(ptr);
-#endif
+  out = SqlValue::String(demangled.release());
   return base::OkStatus();
 }
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 2c1afb7..51c9e18 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -600,8 +600,9 @@
 
   auto dur = query_end - query_start;
   PERFETTO_ILOG(
-      "Query execution time: %lld ms",
-      std::chrono::duration_cast<std::chrono::milliseconds>(dur).count());
+      "Query execution time: %" PRIi64 " ms",
+      static_cast<int64_t>(
+          std::chrono::duration_cast<std::chrono::milliseconds>(dur).count()));
   return base::OkStatus();
 }
 
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index cdd671e..47693df 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -50,7 +50,8 @@
   context_.track_tracker.reset(new TrackTracker(&context_));
   context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
   context_.args_tracker.reset(new ArgsTracker(&context_));
-  context_.args_translation_table.reset(new ArgsTranslationTable());
+  context_.args_translation_table.reset(
+      new ArgsTranslationTable(context_.storage.get()));
   context_.slice_tracker.reset(new SliceTracker(&context_));
   context_.flow_tracker.reset(new FlowTracker(&context_));
   context_.event_tracker.reset(new EventTracker(&context_));
diff --git a/test/trace_processor/chrome/chrome_histogram_hashes.out b/test/trace_processor/chrome/chrome_histogram_hashes.out
new file mode 100644
index 0000000..4cd61a5
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_histogram_hashes.out
@@ -0,0 +1,4 @@
+[perfetto.protos.chrome_histogram_hashes]: {
+  hash: 10
+  hash: 20
+}
diff --git a/test/trace_processor/chrome/chrome_histogram_hashes.textproto b/test/trace_processor/chrome/chrome_histogram_hashes.textproto
new file mode 100644
index 0000000..dfc1260
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_histogram_hashes.textproto
@@ -0,0 +1,27 @@
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat1"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 10
+      sample: 100
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat2"
+    type: 3
+    name_iid: 2
+    chrome_histogram_sample {
+      name_hash: 20
+    }
+  }
+}
diff --git a/test/trace_processor/chrome/chrome_user_event_hashes.out b/test/trace_processor/chrome/chrome_user_event_hashes.out
new file mode 100644
index 0000000..69ca70f
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_user_event_hashes.out
@@ -0,0 +1,5 @@
+[perfetto.protos.chrome_user_event_hashes]: {
+  action_hash: 10
+  action_hash: 20
+}
+
diff --git a/test/trace_processor/chrome/chrome_user_event_hashes.textproto b/test/trace_processor/chrome/chrome_user_event_hashes.textproto
new file mode 100644
index 0000000..41b21b9
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_user_event_hashes.textproto
@@ -0,0 +1,27 @@
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat1"
+    type: 3
+    name_iid: 1
+    chrome_user_event {
+      action_hash: 10
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat2"
+    type: 3
+    name_iid: 2
+    chrome_user_event {
+      action_hash: 20
+    }
+  }
+}
+
diff --git a/test/trace_processor/chrome/index b/test/trace_processor/chrome/index
index 7e6856c..9bdeb89 100644
--- a/test/trace_processor/chrome/index
+++ b/test/trace_processor/chrome/index
@@ -54,3 +54,9 @@
 ../../data/chrome_android_systrace.pftrace chrome_processes.sql chrome_processes_android_systrace.out
 ../../data/chrome_scroll_without_vsync.pftrace chrome_threads.sql chrome_threads.out
 ../../data/chrome_android_systrace.pftrace chrome_threads.sql chrome_threads_android_systrace.out
+
+# Chrome histogram hashes
+chrome_histogram_hashes.textproto chrome_histogram_hashes chrome_histogram_hashes.out
+
+# Chrome user events
+chrome_user_event_hashes.textproto chrome_user_event_hashes chrome_user_event_hashes.out
diff --git a/test/trace_processor/include_index b/test/trace_processor/include_index
index 4152371..593e338 100644
--- a/test/trace_processor/include_index
+++ b/test/trace_processor/include_index
@@ -1,11 +1,14 @@
 camera/index
-cros/index
 chrome/index
+cros/index
 dynamic/index
 fuchsia/index
 graphics/index
+io/index
 memory/index
+network/index
 parsing/index
+performance/index
 power/index
 process_tracking/index
 profiling/index
@@ -14,6 +17,4 @@
 startup/index
 tables/index
 track_event/index
-network/index
-performance/index
-io/index
+translation/index
diff --git a/test/trace_processor/translation/chrome_histogram.out b/test/trace_processor/translation/chrome_histogram.out
new file mode 100644
index 0000000..e0c4dec
--- /dev/null
+++ b/test/trace_processor/translation/chrome_histogram.out
@@ -0,0 +1,10 @@
+"flat_key","key","int_value","string_value"
+"is_root_in_scope","is_root_in_scope",1,"[NULL]"
+"source","source","[NULL]","descriptor"
+"source_id","source_id",0,"[NULL]"
+"chrome_histogram_sample.name","chrome_histogram_sample.name","[NULL]","histogram_name1"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",10,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",100,"[NULL]"
+"chrome_histogram_sample.name","chrome_histogram_sample.name","[NULL]","histogram_name2"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",20,"[NULL]"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",30,"[NULL]"
diff --git a/test/trace_processor/translation/chrome_histogram.sql b/test/trace_processor/translation/chrome_histogram.sql
new file mode 100644
index 0000000..63c2492
--- /dev/null
+++ b/test/trace_processor/translation/chrome_histogram.sql
@@ -0,0 +1,16 @@
+--
+-- Copyright 2022 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
+--
+--     https://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.
+--
+select flat_key, key, int_value, string_value from args order by arg_set_id, key asc;
diff --git a/test/trace_processor/translation/chrome_histogram.textproto b/test/trace_processor/translation/chrome_histogram.textproto
new file mode 100644
index 0000000..fb6a04e
--- /dev/null
+++ b/test/trace_processor/translation/chrome_histogram.textproto
@@ -0,0 +1,53 @@
+# Chrome histogram hashes translation rules
+packet {
+  translation_table {
+    chrome_histogram {
+      hash_to_name { key: 10 value: "histogram_name1" }
+      hash_to_name { key: 20 value: "histogram_name2" }
+    }
+  }
+}
+# Known histogram hash, should be translated to a name
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat1"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 10
+      sample: 100
+    }
+  }
+}
+# Another known hash, should be translated to a name
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat2"
+    type: 3
+    name_iid: 2
+    chrome_histogram_sample {
+      name_hash: 20
+    }
+  }
+}
+# Unknown hash, should not be translated to any name
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "cat3"
+    type: 3
+    name_iid: 3
+    chrome_histogram_sample {
+      name_hash: 30
+    }
+  }
+}
+
diff --git a/test/trace_processor/translation/index b/test/trace_processor/translation/index
new file mode 100644
index 0000000..77fd3d4
--- /dev/null
+++ b/test/trace_processor/translation/index
@@ -0,0 +1 @@
+chrome_histogram.textproto chrome_histogram.sql chrome_histogram.out
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 0345604..a3e829f 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -96,6 +96,7 @@
     '//:libperfetto',
     '//:libperfetto_client_experimental',
     '//protos/perfetto/trace:perfetto_trace_protos',
+    '//src/trace_processor:demangle',
     '//src/trace_processor:trace_processor_shell',
 ]
 
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 79bd4e8..c911be4 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -46,6 +46,7 @@
     'enable_perfetto_heapprofd=false',
     'enable_perfetto_traced_perf=false',
     'perfetto_force_dcheck="off"',
+    'enable_perfetto_llvm_demangle=false',
 ])
 
 # Default targets to translate to the blueprint file.
@@ -113,6 +114,10 @@
         'PERFETTO_CONFIG.deps.sqlite_ext_percentile'
     ],
     '//gn:zlib': ['PERFETTO_CONFIG.deps.zlib'],
+    '//gn:llvm_demangle': ['PERFETTO_CONFIG.deps.llvm_demangle'],
+    '//src/trace_processor:demangle': [
+        'PERFETTO_CONFIG.deps.demangle_wrapper'
+    ],
     '//src/trace_processor/metrics/sql:gen_amalgamated_sql_metrics': [[
         ':cc_amalgamated_sql_metrics'
     ]],
diff --git a/tools/install-build-deps b/tools/install-build-deps
index bd9100c..96a2f65 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -211,6 +211,17 @@
         'all',
         'all'),
 
+    # Archive with only the demangling sources from llvm-project.
+    # See tools/repackage_llvm_demangler.sh on how to update this.
+    # File suffix is the git reference to the commit at which we rearchived the
+    # sources, as hosted on https://llvm.googlesource.com/llvm-project.
+    # If updating the version, also update bazel/deps.bzl.
+    Dependency(
+        'buildtools/llvm-project.tgz',
+        'https://storage.googleapis.com/perfetto/llvm-project-3b4c59c156919902c785ce3cbae0eee2ee53064d.tgz',
+        'f4a52e7f36edd7cacc844d5ae0e5f60b6f57c5afc40683e99f295886c9ce8ff4',
+        'all', 'all'),
+
     # These dependencies are for libunwindstack, which is used by src/profiling.
     Dependency('buildtools/android-core',
                'https://android.googlesource.com/platform/system/core.git',
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 1540749..156f53f 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
     },
     {
       "name": "canary",
-      "rev": "433c9fe808b23a28f3bcfaaf026830d908be4104"
+      "rev": "6c78846fe39842faac064421b1e1c745a14c516a"
     },
     {
       "name": "autopush",
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index 58a8e42..a6ff835 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -396,6 +396,7 @@
   display: grid;
   grid-template-rows: auto 1fr;
   font-family: 'Roboto Condensed', sans-serif;
+  user-select: text;
 
   header {
     position: sticky;
diff --git a/ui/src/assets/modal.scss b/ui/src/assets/modal.scss
index f65961e..20a80a8 100644
--- a/ui/src/assets/modal.scss
+++ b/ui/src/assets/modal.scss
@@ -147,7 +147,8 @@
   animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
 }
 
-.micromodal-slide[aria-hidden="false"] .modal-container {
+.micromodal-slide[aria-hidden="false"] .modal-container,
+.micromodal-slide[aria-hidden="false"] .partial-modal-container {
   animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
 }
 
@@ -155,11 +156,13 @@
   animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
 }
 
-.micromodal-slide[aria-hidden="true"] .modal-container {
+.micromodal-slide[aria-hidden="true"] .modal-container,
+.micromodal-slide[aria-hidden="true"] .partial-modal-container {
   animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
 }
 
 .micromodal-slide .modal-container,
+.micromodal-slide .partial-modal-container,
 .micromodal-slide .modal-overlay {
   will-change: transform;
 }
@@ -215,3 +218,32 @@
 .modal-small {
   font-size: 11px;
 }
+
+.partial-modal-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0,0,0,0.6);
+  display: flex;
+  justify-content: center;
+  z-index: 999;
+}
+
+.partial-modal-container {
+  background-color: #fff;
+  margin-top: 1vh;
+  padding: 30px 30px 20px 30px;
+  max-width: 90vw;
+  height: fit-content;
+  border-radius: 4px;
+  overflow-y: auto;
+  box-sizing: border-box;
+}
+
+.partial-modal-header {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index bd914e0..8366f65 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -1006,6 +1006,10 @@
   setPivotStateReduxState(
       state: StateDraft, args: {pivotTableState: PivotTableReduxState}) {
     state.pivotTableRedux = args.pivotTableState;
+  },
+
+  dismissFlamegraphModal(state: StateDraft, _: {}) {
+    state.flamegraphModalDismissed = true;
   }
 };
 
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index cc57df5..ac287d2 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -97,6 +97,7 @@
     recordingInProgress: false,
     recordingCancelled: false,
     extensionInstalled: false,
+    flamegraphModalDismissed: false,
     recordingTarget: recordTargetStore.getValidTarget(),
     availableAdbDevices: [],
 
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index da6809b..ef7edcd 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -79,7 +79,8 @@
 // typed key/value because a `Map` does not preserve type during
 // serialisation+deserialisation.
 // 15: Added state for Pivot Table V2
-export const STATE_VERSION = 15;
+// 16: Added boolean tracking if the flamegraph modal was dismissed
+export const STATE_VERSION = 16;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -466,6 +467,7 @@
   recordingInProgress: boolean;
   recordingCancelled: boolean;
   extensionInstalled: boolean;
+  flamegraphModalDismissed: boolean;
   recordingTarget: RecordingTarget;
   availableAdbDevices: AdbRecordingTarget[];
   lastRecordingError?: string;
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index cb7ea7e..cb74e41 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -101,7 +101,7 @@
     if (hasAreaChanged) {
       const upids = [];
       if (!area) {
-        publishFlamegraphDetails(
+        this.checkCompletionAndPublishFlamegraph(
             {...frontendGlobals.flamegraphDetails, isInAreaSelection: false});
         return;
       }
@@ -114,7 +114,7 @@
         upids.push((trackState.config as PerfSampleConfig).upid);
       }
       if (upids.length === 0) {
-        publishFlamegraphDetails(
+        this.checkCompletionAndPublishFlamegraph(
             {...frontendGlobals.flamegraphDetails, isInAreaSelection: false});
         return;
       }
@@ -228,7 +228,17 @@
     this.flamegraphDetails.expandedCallsite = expandedCallsite;
     this.flamegraphDetails.viewingOption = viewingOption;
     this.flamegraphDetails.isInAreaSelection = hasAreaChanged;
-    publishFlamegraphDetails(this.flamegraphDetails);
+    this.checkCompletionAndPublishFlamegraph(this.flamegraphDetails);
+  }
+
+  private async checkCompletionAndPublishFlamegraph(flamegraphDetails:
+                                                        FlamegraphDetails) {
+    flamegraphDetails.graphIncomplete =
+        (await this.args.engine.query(`select value from stats
+       where severity = 'error' and name = 'heap_graph_non_finalized_graph'`))
+            .firstRow({value: NUM})
+            .value > 0;
+    publishFlamegraphDetails(flamegraphDetails);
   }
 
   async getFlamegraphData(
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index 35a4d9f..368ab32 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -29,8 +29,10 @@
 import {PerfettoMouseEvent} from './events';
 import {Flamegraph, NodeRendering} from './flamegraph';
 import {globals} from './globals';
+import {showPartialModal} from './modal';
 import {Panel, PanelSize} from './panel';
 import {debounce} from './rate_limiters';
+import {Router} from './router';
 import {getCurrentTrace} from './sidebar';
 import {convertTraceToPprofAndDownload} from './trace_converter';
 
@@ -119,6 +121,7 @@
               }
             }
           },
+          this.maybeShowModal(flamegraphDetails.graphIncomplete),
           m('.details-panel-heading.flamegraph-profile',
             {onclick: (e: MouseEvent) => e.stopPropagation()},
             [
@@ -165,6 +168,39 @@
     }
   }
 
+
+  private maybeShowModal(graphIncomplete?: boolean): m.Vnode|undefined {
+    if (!graphIncomplete || globals.state.flamegraphModalDismissed) {
+      return undefined;
+    }
+    return showPartialModal({
+      title: 'The flamegraph is incomplete',
+      content:
+          m('div',
+            m('div',
+              'The current trace does not have a fully formed flamegraph.')),
+      buttons: [
+        {
+          text: 'Show the errors',
+          primary: true,
+          id: 'incomplete_graph_show',
+          action: () => {
+            Router.navigate('#!/info');
+          }
+        },
+        {
+          text: 'Skip',
+          primary: false,
+          id: 'incomplete_graph_skip',
+          action: () => {
+            globals.dispatch(Actions.dismissFlamegraphModal({}));
+            globals.rafScheduler.scheduleFullRedraw();
+          }
+        }
+      ],
+    });
+  }
+
   private getTitle(): string {
     switch (this.profileType!) {
       case ProfileType.NATIVE_HEAP_PROFILE:
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index e5fa7ad..473a945 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -124,6 +124,9 @@
   // isInAreaSelection is true if a flamegraph is part of the current area
   // selection.
   isInAreaSelection?: boolean;
+  // When heap_graph_non_finalized_graph has a count >0, we mark the graph
+  // as incomplete.
+  graphIncomplete?: boolean;
 }
 
 export interface CpuProfileDetails {
diff --git a/ui/src/frontend/modal.ts b/ui/src/frontend/modal.ts
index 69482e3..40badd9 100644
--- a/ui/src/frontend/modal.ts
+++ b/ui/src/frontend/modal.ts
@@ -84,3 +84,13 @@
   });
   return buttons;
 }
+
+export function showPartialModal(attrs: ModalDefinition): m.Vnode {
+  return m(
+      '.partial-modal-overlay',
+      {tabindex: -1},
+      m('.partial-modal-container',
+        m('header.partial-modal-header', m('h2.modal-title', attrs.title)),
+        m('main.modal-content', attrs.content),
+        m('footer.modal-footer', ...makeButtons(attrs.buttons))));
+}