Merge "ui: Fix thread and process names being null"
diff --git a/Android.bp b/Android.bp
index 391cbec..6a66249 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3730,6 +3730,7 @@
         "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/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",
@@ -8145,6 +8146,7 @@
     name: "perfetto_src_trace_processor_importers_common_common",
     srcs: [
         "src/trace_processor/importers/common/args_tracker.cc",
+        "src/trace_processor/importers/common/args_translation_table.cc",
         "src/trace_processor/importers/common/clock_tracker.cc",
         "src/trace_processor/importers/common/event_tracker.cc",
         "src/trace_processor/importers/common/flow_tracker.cc",
@@ -8160,6 +8162,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_common_unittests",
     srcs: [
+        "src/trace_processor/importers/common/args_translation_table_unittest.cc",
         "src/trace_processor/importers/common/clock_tracker_unittest.cc",
         "src/trace_processor/importers/common/event_tracker_unittest.cc",
         "src/trace_processor/importers/common/flow_tracker_unittest.cc",
@@ -8390,6 +8393,7 @@
         "src/trace_processor/metrics/sql/chrome/touch_jank.sql",
         "src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql",
         "src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql",
+        "src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql",
         "src/trace_processor/metrics/sql/experimental/frame_times.sql",
         "src/trace_processor/metrics/sql/experimental/media_metric.sql",
         "src/trace_processor/metrics/sql/experimental/reported_by_page.sql",
@@ -8541,6 +8545,7 @@
         "src/trace_processor/importers/proto/track_event_parser.cc",
         "src/trace_processor/importers/proto/track_event_tokenizer.cc",
         "src/trace_processor/importers/proto/track_event_tracker.cc",
+        "src/trace_processor/importers/proto/translation_table_module.cc",
         "src/trace_processor/trace_blob.cc",
         "src/trace_processor/trace_processor_context.cc",
         "src/trace_processor/trace_processor_storage.cc",
diff --git a/BUILD b/BUILD
index dcdc2fc..3260a6e 100644
--- a/BUILD
+++ b/BUILD
@@ -971,6 +971,8 @@
     srcs = [
         "src/trace_processor/importers/common/args_tracker.cc",
         "src/trace_processor/importers/common/args_tracker.h",
+        "src/trace_processor/importers/common/args_translation_table.cc",
+        "src/trace_processor/importers/common/args_translation_table.h",
         "src/trace_processor/importers/common/chunked_trace_reader.h",
         "src/trace_processor/importers/common/clock_tracker.cc",
         "src/trace_processor/importers/common/clock_tracker.h",
@@ -1130,6 +1132,7 @@
         "src/trace_processor/metrics/sql/chrome/touch_jank.sql",
         "src/trace_processor/metrics/sql/experimental/blink_gc_metric.sql",
         "src/trace_processor/metrics/sql/experimental/chrome_dropped_frames.sql",
+        "src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql",
         "src/trace_processor/metrics/sql/experimental/frame_times.sql",
         "src/trace_processor/metrics/sql/experimental/media_metric.sql",
         "src/trace_processor/metrics/sql/experimental/reported_by_page.sql",
@@ -1529,6 +1532,8 @@
         "src/trace_processor/importers/proto/track_event_tokenizer.h",
         "src/trace_processor/importers/proto/track_event_tracker.cc",
         "src/trace_processor/importers/proto/track_event_tracker.h",
+        "src/trace_processor/importers/proto/translation_table_module.cc",
+        "src/trace_processor/importers/proto/translation_table_module.h",
         "src/trace_processor/importers/syscalls/syscall_tracker.h",
         "src/trace_processor/importers/systrace/systrace_line.h",
         "src/trace_processor/timestamped_trace_piece.h",
@@ -2716,6 +2721,7 @@
         "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/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",
diff --git a/include/perfetto/tracing/track.h b/include/perfetto/tracing/track.h
index f288e81..3a84364 100644
--- a/include/perfetto/tracing/track.h
+++ b/include/perfetto/tracing/track.h
@@ -115,6 +115,15 @@
                  parent);
   }
 
+  // Construct a track using |ptr| as identifier within thread-scope.
+  // Shorthand for `Track::FromPointer(ptr, ThreadTrack::Current())`
+  // Usage: TRACE_EVENT_BEGIN("...", "...", perfetto::Track::ThreadScoped(this))
+  static Track ThreadScoped(
+      const void* ptr,
+      Track parent = MakeThreadTrack(base::GetThreadId())) {
+    return Track::FromPointer(ptr, parent);
+  }
+
  protected:
   constexpr Track(uint64_t uuid_, uint64_t parent_uuid_)
       : uuid(uuid_), parent_uuid(parent_uuid_) {}
diff --git a/protos/perfetto/metrics/chrome/BUILD.gn b/protos/perfetto/metrics/chrome/BUILD.gn
index e686114..9e88247 100644
--- a/protos/perfetto/metrics/chrome/BUILD.gn
+++ b/protos/perfetto/metrics/chrome/BUILD.gn
@@ -25,6 +25,7 @@
     "blink_gc_metric.proto",
     "dropped_frames.proto",
     "frame_times.proto",
+    "long_latency.proto",
     "media_metric.proto",
     "reported_by_page.proto",
     "scroll_jank.proto",
diff --git a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
index 2632405..4a24580 100644
--- a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
+++ b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
@@ -22,6 +22,7 @@
 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/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";
@@ -38,4 +39,5 @@
   optional MediaMetric media_metric = 1006;
   optional TouchJank touch_jank = 1007;
   optional ChromeDroppedFrames chrome_dropped_frames = 1008;
+  optional ChromeLongLatency chrome_long_latency = 1009;
 }
diff --git a/protos/perfetto/metrics/chrome/long_latency.proto b/protos/perfetto/metrics/chrome/long_latency.proto
new file mode 100644
index 0000000..2c5fbaf
--- /dev/null
+++ b/protos/perfetto/metrics/chrome/long_latency.proto
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+// Information of a frame associated with long latency input events.
+// The latency info is extracted from EventLatency slices.
+message ChromeLongLatency {
+  message LongLatency {
+    // The ending timestamp of the input event, i.e. the presentation time of
+    // the frame that contains updates triggered by the input event.
+    optional int64 ts = 1;
+
+    // The type of the input event as reported in the EventLatency slice,
+    // e.g. 'GestureScrollUpdate'.
+    optional string event_type = 2;
+
+    // The name and pid of the process where the EventLatency slice
+    // originates from.
+    optional string process_name = 3;
+    optional int64 pid = 4;
+  }
+
+  repeated LongLatency long_latency = 1;
+}
\ No newline at end of file
diff --git a/src/protozero/test/example_proto/test_messages.proto b/src/protozero/test/example_proto/test_messages.proto
index 8e710b4..ba3e5d3 100644
--- a/src/protozero/test/example_proto/test_messages.proto
+++ b/src/protozero/test/example_proto/test_messages.proto
@@ -101,8 +101,26 @@
 
 message PackedRepeatedFields {
   repeated int32 field_int32 = 1 [packed = true];
+  repeated int64 field_int64 = 4 [packed = true];
+  repeated uint32 field_uint32 = 5 [packed = true];
+  repeated uint64 field_uint64 = 6 [packed = true];
+  // Repeated packed Zig-zag fields are currently unsupported by our protoc
+  // plugin.
+  // repeated sint32 field_sint32 = 7 [packed = true];
+  // repeated sint64 field_sint64 = 8 [packed = true];
   repeated fixed32 field_fixed32 = 2 [packed = true];
+  repeated fixed64 field_fixed64 = 9 [packed = true];
+  repeated sfixed32 field_sfixed32 = 10 [packed = true];
   repeated sfixed64 field_sfixed64 = 3 [packed = true];
+  repeated float field_float = 11 [packed = true];
+  repeated double field_double = 12 [packed = true];
+  // Repeated (even non-packed) bool fields are currently unsupported by our
+  // protoc plugin.
+  // repeated bool field_bool = 13 [packed = true];
+  // Repeated packed enum fields are currently unsupported by our protoc plugin.
+  // repeated SmallEnum small_enum = 51 [packed = true];
+  // repeated SignedEnum signed_enum = 52 [packed = true];
+  // repeated BigEnum big_enum = 53 [packed = true];
 }
 
 // The following two messages are for testing that unknown fields being
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 3cbfca5..45e9774 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -131,6 +131,8 @@
     "importers/proto/track_event_tokenizer.h",
     "importers/proto/track_event_tracker.cc",
     "importers/proto/track_event_tracker.h",
+    "importers/proto/translation_table_module.cc",
+    "importers/proto/translation_table_module.h",
     "importers/syscalls/syscall_tracker.h",
     "importers/systrace/systrace_line.h",
     "timestamped_trace_piece.h",
@@ -177,6 +179,7 @@
     "../../protos/perfetto/trace/sys_stats:zero",
     "../../protos/perfetto/trace/system_info:zero",
     "../../protos/perfetto/trace/track_event:zero",
+    "../../protos/perfetto/trace/translation:zero",
   ]
 
   # json_utils optionally depends on jsoncpp.
diff --git a/src/trace_processor/importers/additional_modules.cc b/src/trace_processor/importers/additional_modules.cc
index 1627893..5368ed9 100644
--- a/src/trace_processor/importers/additional_modules.cc
+++ b/src/trace_processor/importers/additional_modules.cc
@@ -20,6 +20,7 @@
 #include "src/trace_processor/importers/proto/graphics_event_module.h"
 #include "src/trace_processor/importers/proto/heap_graph_module.h"
 #include "src/trace_processor/importers/proto/system_probes_module.h"
+#include "src/trace_processor/importers/proto/translation_table_module.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -29,6 +30,7 @@
   context->modules.emplace_back(new GraphicsEventModule(context));
   context->modules.emplace_back(new HeapGraphModule(context));
   context->modules.emplace_back(new SystemProbesModule(context));
+  context->modules.emplace_back(new TranslationTableModule(context));
   context->modules.emplace_back(new FtraceModuleImpl(context));
   // Ftrace module is special, because it has one extra method for parsing
   // ftrace packets. So we need to store a pointer to it separately.
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index aa7f57c..534c2bb 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -18,6 +18,8 @@
   sources = [
     "args_tracker.cc",
     "args_tracker.h",
+    "args_translation_table.cc",
+    "args_translation_table.h",
     "chunked_trace_reader.h",
     "clock_tracker.cc",
     "clock_tracker.h",
@@ -56,6 +58,7 @@
 
 source_set("unittests") {
   sources = [
+    "args_translation_table_unittest.cc",
     "clock_tracker_unittest.cc",
     "event_tracker_unittest.cc",
     "flow_tracker_unittest.cc",
diff --git a/src/trace_processor/importers/common/args_translation_table.cc b/src/trace_processor/importers/common/args_translation_table.cc
new file mode 100644
index 0000000..ee91b30
--- /dev/null
+++ b/src/trace_processor/importers/common/args_translation_table.cc
@@ -0,0 +1,38 @@
+/*
+ * 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 "src/trace_processor/importers/common/args_translation_table.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+base::Optional<base::StringView>
+ArgsTranslationTable::TranslateChromeHistogramHash(uint64_t hash) const {
+  auto* value = chrome_histogram_hash_to_name_.Find(hash);
+  if (!value) {
+    return base::nullopt;
+  }
+  return base::StringView(*value);
+}
+
+void ArgsTranslationTable::AddChromeHistogramTranslationRule(
+    uint64_t hash,
+    base::StringView name) {
+  chrome_histogram_hash_to_name_.Insert(hash, name.ToStdString());
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/args_translation_table.h b/src/trace_processor/importers/common/args_translation_table.h
new file mode 100644
index 0000000..0e34e2c
--- /dev/null
+++ b/src/trace_processor/importers/common/args_translation_table.h
@@ -0,0 +1,50 @@
+/*
+ * 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 SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ARGS_TRANSLATION_TABLE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ARGS_TRANSLATION_TABLE_H_
+
+#include <cstdint>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Tracks and stores args translation rules. It allows Trace Processor
+// to map for example hashes to their names.
+class ArgsTranslationTable {
+ public:
+  static constexpr char kChromeHistogramHashKey[] =
+      "chrome_histogram_sample.name_hash";
+  static constexpr char kChromeHistogramNameKey[] =
+      "chrome_histogram_sample.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
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ARGS_TRANSLATION_TABLE_H_
diff --git a/src/trace_processor/importers/common/args_translation_table_unittest.cc b/src/trace_processor/importers/common/args_translation_table_unittest.cc
new file mode 100644
index 0000000..15dddff
--- /dev/null
+++ b/src/trace_processor/importers/common/args_translation_table_unittest.cc
@@ -0,0 +1,42 @@
+/*
+ * 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 "src/trace_processor/importers/common/args_translation_table.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+TEST(ArgsTranslationTable, EmptyTableByDefault) {
+  ArgsTranslationTable table;
+  EXPECT_EQ(table.TranslateChromeHistogramHash(1), base::nullopt);
+}
+
+TEST(ArgsTranslationTable, TranslatesHashes) {
+  ArgsTranslationTable table;
+  table.AddChromeHistogramTranslationRule(1, "hash1");
+  table.AddChromeHistogramTranslationRule(10, "hash2");
+  EXPECT_EQ(table.TranslateChromeHistogramHash(1),
+            base::Optional<base::StringView>("hash1"));
+  EXPECT_EQ(table.TranslateChromeHistogramHash(10),
+            base::Optional<base::StringView>("hash2"));
+  EXPECT_EQ(table.TranslateChromeHistogramHash(2), base::nullopt);
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/translation_table_module.cc b/src/trace_processor/importers/proto/translation_table_module.cc
new file mode 100644
index 0000000..cf523d9
--- /dev/null
+++ b/src/trace_processor/importers/proto/translation_table_module.cc
@@ -0,0 +1,61 @@
+/*
+ * 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 "src/trace_processor/importers/proto/translation_table_module.h"
+
+#include "src/trace_processor/importers/common/args_translation_table.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/translation/translation_table.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+TranslationTableModule::TranslationTableModule(TraceProcessorContext* context)
+    : context_(context) {
+  RegisterForField(TracePacket::kTranslationTableFieldNumber, context);
+}
+
+TranslationTableModule::~TranslationTableModule() = default;
+
+void TranslationTableModule::ParsePacket(const TracePacket::Decoder& decoder,
+                                         const TimestampedTracePiece& /*ttp*/,
+                                         uint32_t field_id) {
+  if (field_id != TracePacket::kTranslationTableFieldNumber) {
+    return;
+  }
+  const auto translation_table =
+      protos::pbzero::TranslationTable::Decoder(decoder.translation_table());
+  if (translation_table.has_chrome_histogram()) {
+    ParseChromeHistogramRules(translation_table.chrome_histogram());
+  }
+}
+
+void TranslationTableModule::ParseChromeHistogramRules(
+    protozero::ConstBytes bytes) {
+  const auto chrome_histogram =
+      protos::pbzero::ChromeHistorgramTranslationTable::Decoder(bytes);
+  for (auto it = chrome_histogram.hash_to_name(); it; ++it) {
+    protos::pbzero::ChromeHistorgramTranslationTable::HashToNameEntry::Decoder
+        entry(*it);
+    context_->args_translation_table->AddChromeHistogramTranslationRule(
+        entry.key(), entry.value());
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/translation_table_module.h b/src/trace_processor/importers/proto/translation_table_module.h
new file mode 100644
index 0000000..c6c61a6
--- /dev/null
+++ b/src/trace_processor/importers/proto/translation_table_module.h
@@ -0,0 +1,49 @@
+/*
+ * 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 SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRANSLATION_TABLE_MODULE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRANSLATION_TABLE_MODULE_H_
+
+#include <cstdint>
+
+#include "perfetto/ext/base/optional.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TranslationTableModule : public ProtoImporterModule {
+ public:
+  explicit TranslationTableModule(TraceProcessorContext* context);
+
+  ~TranslationTableModule() override;
+
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
+                   const TimestampedTracePiece& ttp,
+                   uint32_t field_id) override;
+
+ private:
+  void ParseChromeHistogramRules(protozero::ConstBytes bytes);
+
+  TraceProcessorContext* context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRANSLATION_TABLE_MODULE_H_
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index f8de2ad..fbbb41c 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -110,6 +110,7 @@
   "chrome/touch_jank.sql",
   "experimental/blink_gc_metric.sql",
   "experimental/chrome_dropped_frames.sql",
+  "experimental/chrome_long_latency.sql",
   "experimental/frame_times.sql",
   "experimental/media_metric.sql",
   "experimental/reported_by_page.sql",
diff --git a/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql b/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql
new file mode 100644
index 0000000..26fc2b5
--- /dev/null
+++ b/src/trace_processor/metrics/sql/experimental/chrome_long_latency.sql
@@ -0,0 +1,104 @@
+--
+-- 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.
+
+-- Find all long EventLatency slices > 100ms and also get the
+-- type of the event stored as 'debug.event' argument.
+-- In order to group all events
+-- Note that a long latency event is represented by the the ending time
+-- of an EventLatency slice, i.e. the timestamp of the frame presentation
+-- that reflects the event.
+DROP VIEW IF EXISTS long_eventlatency_slice;
+CREATE VIEW long_eventlatency_slice AS
+SELECT
+  ts + dur AS ts,
+  dur,
+  id,
+  track_id,
+  EXTRACT_ARG(arg_set_id, 'debug.event') AS event_type
+FROM slice WHERE name = 'EventLatency' AND dur > 100000000;
+
+-- Find the upid of the proccesses where the long latency occur.
+DROP VIEW IF EXISTS long_latency_with_upid;
+CREATE VIEW long_latency_with_upid AS
+SELECT
+  long_eventlatency_slice.ts,
+  long_eventlatency_slice.event_type,
+  process_track.upid
+FROM long_eventlatency_slice
+INNER JOIN process_track
+ON long_eventlatency_slice.track_id = process_track.id;
+
+-- Find the name and pid of the processes.
+-- Long latency events with the same timestamp and from the same process
+-- are considered one single long latency occurrence.
+DROP VIEW IF EXISTS long_latency_with_process_info;
+CREATE VIEW long_latency_with_process_info AS
+SELECT
+  long_latency_with_upid.ts,
+  long_latency_with_upid.event_type,
+  process.name AS process_name,
+  process.pid AS process_id
+FROM long_latency_with_upid
+INNER JOIN process
+ON long_latency_with_upid.upid = process.upid
+GROUP BY ts, process.pid;
+
+-- Create the derived event track for long latency.
+-- All tracks generated from chrome_long_latency_event are
+-- placed under a track group named 'Long Latency', whose summary
+-- track is the first track ('All Processes') in chrome_long_latency_event.
+-- Note that the 'All Processes' track is generated only when there are more
+-- than one source of long latency events.
+DROP VIEW IF EXISTS chrome_long_latency_event;
+CREATE VIEW chrome_long_latency_event AS
+SELECT
+  'slice' AS track_type,
+  'All Processes' AS track_name,
+  ts,
+  0 AS dur,
+  event_type AS slice_name,
+  'Long Latency' AS group_name
+FROM long_latency_with_process_info
+WHERE (SELECT COUNT(DISTINCT process_id)
+       FROM long_latency_with_process_info) > 1
+GROUP BY ts
+UNION ALL
+SELECT
+  'slice' AS track_type,
+  process_name || ' ' || process_id AS track_name,
+  ts,
+  0 AS dur,
+  event_type AS slice_name,
+  'Long Latency' AS group_name
+FROM long_latency_with_process_info
+GROUP BY ts;
+
+-- Create the long latency metric output.
+DROP VIEW IF EXISTS chrome_long_latency_output;
+CREATE VIEW chrome_long_latency_output AS
+SELECT ChromeLongLatency(
+  'long_latency', (
+    SELECT RepeatedField(
+      ChromeLongLatency_LongLatency(
+        'ts', ts,
+        'event_type', event_type,
+        'process_name', process_name,
+        'pid', process_id
+      )
+    )
+    FROM long_latency_with_process_info
+    ORDER BY ts
+  )
+);
\ No newline at end of file
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 412b63a..75800f3 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -18,6 +18,7 @@
 
 #include "src/trace_processor/forwarding_trace_parser.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/chunked_trace_reader.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 53bc802..cdd671e 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -21,6 +21,7 @@
 #include "src/trace_processor/forwarding_trace_parser.h"
 #include "src/trace_processor/importers/chrome_track_event.descriptor.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/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
@@ -49,6 +50,7 @@
   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_.slice_tracker.reset(new SliceTracker(&context_));
   context_.flow_tracker.reset(new FlowTracker(&context_));
   context_.event_tracker.reset(new EventTracker(&context_));
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index 4bc0e2b..221c62b 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -27,6 +27,7 @@
 namespace trace_processor {
 
 class ArgsTracker;
+class ArgsTranslationTable;
 class AsyncTrackSetTracker;
 class AndroidProbesTracker;
 class ChunkedTraceReader;
@@ -67,6 +68,7 @@
   // trackers, as they may own ArgsTrackers themselves.
   std::unique_ptr<GlobalArgsTracker> global_args_tracker;
   std::unique_ptr<ArgsTracker> args_tracker;
+  std::unique_ptr<ArgsTranslationTable> args_translation_table;
 
   std::unique_ptr<TrackTracker> track_tracker;
   std::unique_ptr<AsyncTrackSetTracker> async_track_set_tracker;
diff --git a/src/trace_processor/util/protozero_to_text.cc b/src/trace_processor/util/protozero_to_text.cc
index cdb60d1..274f504 100644
--- a/src/trace_processor/util/protozero_to_text.cc
+++ b/src/trace_processor/util/protozero_to_text.cc
@@ -16,19 +16,8 @@
 
 namespace {
 
-std::string BytesToHexEncodedString(const std::string& bytes) {
-  // Each byte becomes four chars 'A' -> "\x41" + 1 for trailing null.
-  std::string value(4 * bytes.size() + 1, 'Z');
-  for (size_t i = 0; i < bytes.size(); ++i) {
-    // snprintf prints 5 characters: '\x', then two hex digits, and finally a
-    // null byte. As we write left to right, we keep overwriting the null
-    // byte, except for the last call to snprintf.
-    snprintf(&(value[4 * i]), 5, "\\x%02hhx", bytes[i]);
-  }
-  // Trim trailing null.
-  value.resize(4 * bytes.size());
-  return value;
-}
+using protozero::proto_utils::ProtoWireType;
+using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
 
 // This function matches the implementation of TextFormatEscaper.escapeBytes
 // from the Java protobuf library.
@@ -87,118 +76,16 @@
   return '"' + ret + '"';
 }
 
-// Recursively determine the size of all the string like things passed in the
-// parameter pack |rest|.
-size_t SizeOfStr() {
-  return 0;
-}
-template <typename T, typename... Rest>
-size_t SizeOfStr(const T& first, Rest... rest) {
-  return base::StringView(first).size() + SizeOfStr(rest...);
-}
-
-// Append |to_add| which is something string like to |out|.
-template <typename T>
-void StrAppendInternal(std::string* out, const T& to_add) {
-  out->append(to_add);
-}
-
-template <typename T, typename... strings>
-void StrAppendInternal(std::string* out, const T& first, strings... values) {
-  StrAppendInternal(out, first);
-  StrAppendInternal(out, values...);
-}
-
 // Append |to_add| which is something string like to |out|.
 template <typename T>
 void StrAppend(std::string* out, const T& to_add) {
-  out->reserve(out->size() + base::StringView(to_add).size());
   out->append(to_add);
 }
 
 template <typename T, typename... strings>
 void StrAppend(std::string* out, const T& first, strings... values) {
-  out->reserve(out->size() + SizeOfStr(values...));
-  StrAppendInternal(out, first);
-  StrAppendInternal(out, values...);
-}
-
-void ConvertProtoTypeToFieldAndValueString(const FieldDescriptor& fd,
-                                           const protozero::Field& field,
-                                           const std::string& separator,
-                                           const std::string& indent,
-                                           const DescriptorPool& pool,
-                                           std::string* out) {
-  using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
-  switch (fd.type()) {
-    case FieldDescriptorProto::TYPE_INT32:
-    case FieldDescriptorProto::TYPE_SFIXED32:
-    case FieldDescriptorProto::TYPE_FIXED32:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_int32()));
-      return;
-    case FieldDescriptorProto::TYPE_SINT32:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_sint32()));
-      return;
-    case FieldDescriptorProto::TYPE_INT64:
-    case FieldDescriptorProto::TYPE_SFIXED64:
-    case FieldDescriptorProto::TYPE_FIXED64:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_int64()));
-      return;
-    case FieldDescriptorProto::TYPE_SINT64:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_sint64()));
-      return;
-    case FieldDescriptorProto::TYPE_UINT32:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_uint32()));
-      return;
-    case FieldDescriptorProto::TYPE_UINT64:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_uint64()));
-      return;
-    case FieldDescriptorProto::TYPE_BOOL:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                field.as_bool() ? "true" : "false");
-      return;
-    case FieldDescriptorProto::TYPE_DOUBLE:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_double()));
-      return;
-    case FieldDescriptorProto::TYPE_FLOAT:
-      StrAppend(out, separator, indent, fd.name(), ": ",
-                std::to_string(field.as_float()));
-      return;
-    case FieldDescriptorProto::TYPE_STRING: {
-      auto s = QuoteAndEscapeTextProtoString(field.as_std_string());
-      StrAppend(out, separator, indent, fd.name(), ": ", s);
-      return;
-    }
-    case FieldDescriptorProto::TYPE_BYTES: {
-      std::string value = BytesToHexEncodedString(field.as_std_string());
-      StrAppend(out, separator, indent, fd.name(), ": \"", value, "\"");
-      return;
-    }
-    case FieldDescriptorProto::TYPE_ENUM: {
-      auto opt_enum_descriptor_idx =
-          pool.FindDescriptorIdx(fd.resolved_type_name());
-      PERFETTO_DCHECK(opt_enum_descriptor_idx);
-      auto opt_enum_string =
-          pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
-              field.as_int32());
-      PERFETTO_DCHECK(opt_enum_string);
-      StrAppend(out, separator, indent, fd.name(), ": ", *opt_enum_string);
-      return;
-    }
-    default: {
-      PERFETTO_FATAL(
-          "Tried to write value of type field %s (in proto type "
-          "%s) which has type enum %d",
-          fd.name().c_str(), fd.resolved_type_name().c_str(), fd.type());
-    }
-  }
+  StrAppend(out, first);
+  StrAppend(out, values...);
 }
 
 void IncreaseIndents(std::string* out) {
@@ -224,9 +111,232 @@
   }
 }
 
+void PrintVarIntField(const FieldDescriptor* fd,
+                      const protozero::Field& field,
+                      const DescriptorPool& pool,
+                      std::string* out) {
+  uint32_t type = fd ? fd->type() : 0;
+  switch (type) {
+    case FieldDescriptorProto::TYPE_INT32:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
+      return;
+    case FieldDescriptorProto::TYPE_SINT32:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint32()));
+      return;
+    case FieldDescriptorProto::TYPE_UINT32:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
+      return;
+    case FieldDescriptorProto::TYPE_INT64:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
+      return;
+    case FieldDescriptorProto::TYPE_SINT64:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint64()));
+      return;
+    case FieldDescriptorProto::TYPE_UINT64:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
+      return;
+    case FieldDescriptorProto::TYPE_BOOL:
+      StrAppend(out, fd->name(), ": ", field.as_bool() ? "true" : "false");
+      return;
+    case FieldDescriptorProto::TYPE_ENUM: {
+      // If the enum value is unknown, treat it like a completely unknown field.
+      auto opt_enum_descriptor_idx =
+          pool.FindDescriptorIdx(fd->resolved_type_name());
+      if (!opt_enum_descriptor_idx)
+        break;
+      auto opt_enum_string =
+          pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
+              field.as_int32());
+      if (!opt_enum_string)
+        break;
+      StrAppend(out, fd->name(), ": ", *opt_enum_string);
+      return;
+    }
+    case 0:
+    default:
+      break;
+  }
+  StrAppend(out, std::to_string(field.id()), ": ",
+            std::to_string(field.as_uint64()));
+}
+
+void PrintFixed32Field(const FieldDescriptor* fd,
+                       const protozero::Field& field,
+                       std::string* out) {
+  uint32_t type = fd ? fd->type() : 0;
+  switch (type) {
+    case FieldDescriptorProto::TYPE_SFIXED32:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
+      break;
+    case FieldDescriptorProto::TYPE_FIXED32:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
+      break;
+    case FieldDescriptorProto::TYPE_FLOAT:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_float()));
+      break;
+    case 0:
+    default:
+      base::StackString<12> padded_hex("0x%08" PRIx32, field.as_uint32());
+      StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
+      break;
+  }
+}
+
+void PrintFixed64Field(const FieldDescriptor* fd,
+                       const protozero::Field& field,
+                       std::string* out) {
+  uint32_t type = fd ? fd->type() : 0;
+  switch (type) {
+    case FieldDescriptorProto::TYPE_SFIXED64:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
+      break;
+    case FieldDescriptorProto::TYPE_FIXED64:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
+      break;
+    case FieldDescriptorProto::TYPE_DOUBLE:
+      StrAppend(out, fd->name(), ": ", std::to_string(field.as_double()));
+      break;
+    case 0:
+    default:
+      base::StackString<20> padded_hex("0x%016" PRIx64, field.as_uint64());
+      StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
+      break;
+  }
+}
+
+void ProtozeroToTextInternal(const std::string& type,
+                             protozero::ConstBytes protobytes,
+                             NewLinesMode new_lines_mode,
+                             const DescriptorPool& pool,
+                             std::string* indents,
+                             std::string* output);
+
+template <protozero::proto_utils::ProtoWireType wire_type, typename T>
+void PrintPackedField(const FieldDescriptor& fd,
+                      const protozero::Field& field,
+                      NewLinesMode new_lines_mode,
+                      const std::string& indents,
+                      std::string* out) {
+  const bool include_new_lines = new_lines_mode == kIncludeNewLines;
+  bool err = false;
+  bool first_output = true;
+  for (protozero::PackedRepeatedFieldIterator<wire_type, T> it(
+           field.data(), field.size(), &err);
+       it; it++) {
+    T value = *it;
+    if (!first_output) {
+      if (include_new_lines) {
+        StrAppend(out, "\n", indents);
+      } else {
+        StrAppend(out, " ");
+      }
+    }
+    StrAppend(out, fd.name(), ": ", std::to_string(value));
+    first_output = false;
+  }
+
+  if (err) {
+    if (!first_output) {
+      if (include_new_lines) {
+        StrAppend(out, "\n", indents);
+      } else {
+        StrAppend(out, " ");
+      }
+    }
+    StrAppend(out, "# Packed decoding failure for field ", fd.name(), "\n");
+  }
+}
+
+void PrintLengthDelimitedField(const FieldDescriptor* fd,
+                               const protozero::Field& field,
+                               NewLinesMode new_lines_mode,
+                               std::string* indents,
+                               const DescriptorPool& pool,
+                               std::string* out) {
+  const bool include_new_lines = new_lines_mode == kIncludeNewLines;
+  uint32_t type = fd ? fd->type() : 0;
+  switch (type) {
+    case FieldDescriptorProto::TYPE_BYTES:
+    case FieldDescriptorProto::TYPE_STRING: {
+      std::string value = QuoteAndEscapeTextProtoString(field.as_std_string());
+      StrAppend(out, fd->name(), ": ", value);
+      return;
+    }
+    case FieldDescriptorProto::TYPE_MESSAGE:
+      StrAppend(out, FormattedFieldDescriptorName(*fd), ": {");
+      if (include_new_lines) {
+        IncreaseIndents(indents);
+      }
+      ProtozeroToTextInternal(fd->resolved_type_name(), field.as_bytes(),
+                              new_lines_mode, pool, indents, out);
+      if (include_new_lines) {
+        DecreaseIndents(indents);
+        StrAppend(out, "\n", *indents, "}");
+      } else {
+        StrAppend(out, " }");
+      }
+      return;
+    case FieldDescriptorProto::TYPE_DOUBLE:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, double>(
+          *fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_FLOAT:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, float>(
+          *fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_INT64:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int64_t>(
+          *fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_UINT64:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
+                       uint64_t>(*fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_INT32:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
+          *fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_FIXED64:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
+                       uint64_t>(*fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_FIXED32:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
+                       uint32_t>(*fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_UINT32:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
+                       uint32_t>(*fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_SFIXED32:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
+                       int32_t>(*fd, field, new_lines_mode, *indents, out);
+      return;
+    case FieldDescriptorProto::TYPE_SFIXED64:
+      PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
+                       int64_t>(*fd, field, new_lines_mode, *indents, out);
+      return;
+    // Our protoc plugin cannot generate code for packed repeated fields with
+    // these types. Output a comment and then fall back to the raw field_id:
+    // string representation.
+    case FieldDescriptorProto::TYPE_BOOL:
+    case FieldDescriptorProto::TYPE_ENUM:
+    case FieldDescriptorProto::TYPE_SINT32:
+    case FieldDescriptorProto::TYPE_SINT64:
+      StrAppend(out, "# Packed type ", std::to_string(type),
+                " not supported. Printing raw string.", "\n", *indents);
+      break;
+    case 0:
+    default:
+      break;
+  }
+  std::string value = QuoteAndEscapeTextProtoString(field.as_std_string());
+  StrAppend(out, std::to_string(field.id()), ": ", value);
+}
+
 // Recursive case function, Will parse |protobytes| assuming it is a proto of
 // |type| and will use |pool| to look up the |type|. All output will be placed
-// in |output| and between fields |separator| will be placed. When called for
+// in |output|, using |new_lines_mode| to separate fields. When called for
 // |indents| will be increased by 2 spaces to improve readability.
 void ProtozeroToTextInternal(const std::string& type,
                              protozero::ConstBytes protobytes,
@@ -237,44 +347,35 @@
   auto opt_proto_descriptor_idx = pool.FindDescriptorIdx(type);
   PERFETTO_DCHECK(opt_proto_descriptor_idx);
   auto& proto_descriptor = pool.descriptors()[*opt_proto_descriptor_idx];
-  bool include_new_lines = new_lines_mode == kIncludeNewLines;
+  const bool include_new_lines = new_lines_mode == kIncludeNewLines;
 
   protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
   for (auto field = decoder.ReadField(); field.valid();
        field = decoder.ReadField()) {
-    auto opt_field_descriptor = proto_descriptor.FindFieldByTag(field.id());
-    if (!opt_field_descriptor) {
-      StrAppend(
-          output, output->empty() ? "" : "\n", *indents,
-          "# Ignoring unknown field with id: ", std::to_string(field.id()));
-      continue;
-    }
-    const auto& field_descriptor = *opt_field_descriptor;
-
-    if (field_descriptor.type() ==
-        protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) {
+    if (!output->empty()) {
       if (include_new_lines) {
-        StrAppend(output, output->empty() ? "" : "\n", *indents,
-                  FormattedFieldDescriptorName(field_descriptor), ": {");
-        IncreaseIndents(indents);
+        StrAppend(output, "\n", *indents);
       } else {
-        StrAppend(output, output->empty() ? "" : " ",
-                  FormattedFieldDescriptorName(field_descriptor), ": {");
-      }
-      ProtozeroToTextInternal(field_descriptor.resolved_type_name(),
-                              field.as_bytes(), new_lines_mode, pool, indents,
-                              output);
-      if (include_new_lines) {
-        DecreaseIndents(indents);
-        StrAppend(output, "\n", *indents, "}");
-      } else {
-        StrAppend(output, " }");
+        StrAppend(output, " ", *indents);
       }
     } else {
-      ConvertProtoTypeToFieldAndValueString(
-          field_descriptor, field,
-          output->empty() ? "" : include_new_lines ? "\n" : " ", *indents, pool,
-          output);
+      StrAppend(output, *indents);
+    }
+    auto* opt_field_descriptor = proto_descriptor.FindFieldByTag(field.id());
+    switch (field.type()) {
+      case ProtoWireType::kVarInt:
+        PrintVarIntField(opt_field_descriptor, field, pool, output);
+        break;
+      case ProtoWireType::kLengthDelimited:
+        PrintLengthDelimitedField(opt_field_descriptor, field, new_lines_mode,
+                                  indents, pool, output);
+        break;
+      case ProtoWireType::kFixed32:
+        PrintFixed32Field(opt_field_descriptor, field, output);
+        break;
+      case ProtoWireType::kFixed64:
+        PrintFixed64Field(opt_field_descriptor, field, output);
+        break;
     }
   }
   PERFETTO_DCHECK(decoder.bytes_left() == 0);
@@ -341,10 +442,6 @@
       new_lines_mode);
 }
 
-std::string BytesToHexEncodedStringForTesting(const std::string& s) {
-  return BytesToHexEncodedString(s);
-}
-
 }  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/util/protozero_to_text.h b/src/trace_processor/util/protozero_to_text.h
index c7b793b..9a0acf1 100644
--- a/src/trace_processor/util/protozero_to_text.h
+++ b/src/trace_processor/util/protozero_to_text.h
@@ -71,8 +71,6 @@
   return ProtozeroEnumToText(type, static_cast<int32_t>(enum_value));
 }
 
-std::string BytesToHexEncodedStringForTesting(const std::string&);
-
 }  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/util/protozero_to_text_unittests.cc b/src/trace_processor/util/protozero_to_text_unittests.cc
index bd92298..6b664cd 100644
--- a/src/trace_processor/util/protozero_to_text_unittests.cc
+++ b/src/trace_processor/util/protozero_to_text_unittests.cc
@@ -180,10 +180,6 @@
                                 TrackEvent::TYPE_SLICE_END));
 }
 
-TEST(ProtozeroToTextTest, BytesToHexEncodedString) {
-  EXPECT_EQ(BytesToHexEncodedStringForTesting("abc"), R"(\x61\x62\x63)");
-}
-
 // Sets up a descriptor pool with all the messages from
 // "src/protozero/test/example_proto/test_messages.proto"
 class ProtozeroToTextTestMessageTest : public testing::Test {
@@ -278,8 +274,7 @@
             "big_enum: END");
 }
 
-// TODO(b/224800278): Fix ProtozeroToText() crash and reenable.
-TEST_F(ProtozeroToTextTestMessageTest, DISABLED_FieldVarIntEnumUnknown) {
+TEST_F(ProtozeroToTextTestMessageTest, FieldVarIntEnumUnknown) {
   protozero::HeapBuffered<EveryField> msg;
   msg->AppendVarInt(EveryField::kSmallEnumFieldNumber, 42);
   ASSERT_EQ(EveryField::kSmallEnumFieldNumber, 51);
@@ -293,15 +288,12 @@
   protozero::HeapBuffered<EveryField> msg;
   msg->AppendVarInt(/*field_id=*/9999, /*value=*/42);
 
-  // TODO(b/224800278): "protoc --decode" also prints the varint value, which is
-  // more useful.
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
-            "# Ignoring unknown field with id: 9999");
+            "9999: 42");
 }
 
-// TODO(b/224800278): Fix ProtozeroToText() crash and reenable.
-TEST_F(ProtozeroToTextTestMessageTest, DISABLED_FieldVarIntMismatch) {
+TEST_F(ProtozeroToTextTestMessageTest, FieldVarIntMismatch) {
   protozero::HeapBuffered<EveryField> msg;
   ASSERT_EQ(EveryField::kFieldStringFieldNumber, 500);
   msg->AppendVarInt(EveryField::kFieldStringFieldNumber, 42);
@@ -336,11 +328,9 @@
   protozero::HeapBuffered<EveryField> msg;
   msg->set_field_fixed32(3000000000);
 
-  // TODO(b/224800278): This is a bug. fixed32 is supposed to be unsigned, but
-  // the code prints it as signed.
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
-            "field_fixed32: -1294967296");
+            "field_fixed32: 3000000000");
 }
 
 TEST_F(ProtozeroToTextTestMessageTest, FieldFixed32Float) {
@@ -356,15 +346,12 @@
   protozero::HeapBuffered<EveryField> msg;
   msg->AppendFixed<uint32_t>(/*field_id=*/9999, /*value=*/0x1);
 
-  // TODO(b/224800278): "protoc --decode" also prints the 32-bit value, which is
-  // more useful.
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
-            "# Ignoring unknown field with id: 9999");
+            "9999: 0x00000001");
 }
 
-// TODO(b/224800278): Fix ProtozeroToText() crash and reenable.
-TEST_F(ProtozeroToTextTestMessageTest, DISABLED_FieldFixed32Mismatch) {
+TEST_F(ProtozeroToTextTestMessageTest, FieldFixed32Mismatch) {
   protozero::HeapBuffered<EveryField> msg;
   ASSERT_EQ(EveryField::kFieldStringFieldNumber, 500);
   msg->AppendFixed<uint32_t>(EveryField::kFieldStringFieldNumber, 0x1);
@@ -405,15 +392,12 @@
   protozero::HeapBuffered<EveryField> msg;
   msg->AppendFixed<uint64_t>(/*field_id=*/9999, /*value=*/0x1);
 
-  // TODO(b/224800278): "protoc --decode" also prints the 64-bit value, which is
-  // more useful.
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
-            "# Ignoring unknown field with id: 9999");
+            "9999: 0x0000000000000001");
 }
 
-// TODO(b/224800278): Fix ProtozeroToText() crash and reenable.
-TEST_F(ProtozeroToTextTestMessageTest, DISABLED_FieldFixed64Mismatch) {
+TEST_F(ProtozeroToTextTestMessageTest, FieldFixed64Mismatch) {
   protozero::HeapBuffered<EveryField> msg;
   ASSERT_EQ(EveryField::kFieldStringFieldNumber, 500);
   msg->AppendFixed<uint64_t>(EveryField::kFieldStringFieldNumber, 0x1);
@@ -436,35 +420,257 @@
   protozero::HeapBuffered<EveryField> msg;
   msg->set_field_bytes("Hello");
 
-  // TODO(b/224800278): "protoc --decode" always tries to print the value as
-  // ASCII.
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
-            R"(field_bytes: "\x48\x65\x6c\x6c\x6f")");
+            R"(field_bytes: "Hello")");
 }
 
 TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedUnknown) {
   protozero::HeapBuffered<EveryField> msg;
   msg->AppendString(9999, "Hello");
 
-  // TODO(b/224800278): "protoc --decode" also prints the string value, which is
-  // more useful.
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
-            "# Ignoring unknown field with id: 9999");
+            R"(9999: "Hello")");
 }
 
-// TODO(b/224800278): Fix ProtozeroToText() crash and reenable.
-TEST_F(ProtozeroToTextTestMessageTest, DISABLED_FieldLengthLimitedMismatch) {
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedMismatch) {
   protozero::HeapBuffered<EveryField> msg;
   ASSERT_EQ(EveryField::kFieldBoolFieldNumber, 13);
   msg->AppendString(EveryField::kFieldBoolFieldNumber, "Hello");
 
   EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
                             msg.SerializeAsArray(), kIncludeNewLines),
+            "# Packed type 8 not supported. Printing raw string.\n"
             R"(13: "Hello")");
 }
 
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedForNonPacked) {
+  // Even though repeated_int32 doesn't have [packed = true], it still accepts a
+  // packed representation.
+  protozero::HeapBuffered<EveryField> msg;
+  protozero::PackedVarInt buf;
+  buf.Append<int32_t>(-42);
+  buf.Append<int32_t>(2147483647);
+  msg->AppendBytes(EveryField::kRepeatedInt32FieldNumber, buf.data(),
+                   buf.size());
+
+  EXPECT_EQ(ProtozeroToText(pool_, ".protozero.test.protos.EveryField",
+                            msg.SerializeAsArray(), kIncludeNewLines),
+            "repeated_int32: -42\nrepeated_int32: 2147483647");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedVarIntInt32) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedVarInt buf;
+  buf.Append<int32_t>(-42);
+  buf.Append<int32_t>(2147483647);
+  msg->set_field_int32(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_int32: -42\nfield_int32: 2147483647");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedVarIntInt64) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedVarInt buf;
+  buf.Append<int64_t>(-42);
+  buf.Append<int64_t>(3000000000);
+  msg->set_field_int64(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_int64: -42\nfield_int64: 3000000000");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedVarIntUint32) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedVarInt buf;
+  buf.Append<uint32_t>(42);
+  buf.Append<uint32_t>(3000000000);
+  msg->set_field_uint32(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_uint32: 42\nfield_uint32: 3000000000");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedVarIntUint64) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedVarInt buf;
+  buf.Append<uint64_t>(42);
+  buf.Append<uint64_t>(3000000000000);
+  msg->set_field_uint64(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_uint64: 42\nfield_uint64: 3000000000000");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixed32Uint32) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<uint32_t> buf;
+  buf.Append(42);
+  buf.Append(3000000000);
+  msg->set_field_fixed32(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_fixed32: 42\nfield_fixed32: 3000000000");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixed32Int32) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<int32_t> buf;
+  buf.Append(-42);
+  buf.Append(42);
+  msg->set_field_sfixed32(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_sfixed32: -42\nfield_sfixed32: 42");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixed32Float) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<float> buf;
+  buf.Append(-42);
+  buf.Append(42.125);
+  msg->set_field_float(buf);
+
+  std::string output =
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines);
+
+  EXPECT_THAT(base::SplitString(output, "\n"),
+              ElementsAre(StartsWith("field_float: -42"),
+                          StartsWith("field_float: 42.125")));
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixed64Uint64) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<uint64_t> buf;
+  buf.Append(42);
+  buf.Append(3000000000000);
+  msg->set_field_fixed64(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_fixed64: 42\nfield_fixed64: 3000000000000");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixed64Int64) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<int64_t> buf;
+  buf.Append(-42);
+  buf.Append(3000000000000);
+  msg->set_field_sfixed64(buf);
+
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_sfixed64: -42\nfield_sfixed64: 3000000000000");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixed64Double) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<double> buf;
+  buf.Append(-42);
+  buf.Append(42.125);
+  msg->set_field_double(buf);
+
+  EXPECT_THAT(
+      base::SplitString(
+          ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                          msg.SerializeAsArray(), kIncludeNewLines),
+          "\n"),
+      ElementsAre(StartsWith("field_double: -42"),
+                  StartsWith("field_double: 42.125")));
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixedErrShort) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  std::string buf;
+  buf = "\x01";
+  // buf does not contain enough data for a fixed 64
+  msg->AppendBytes(PackedRepeatedFields::kFieldFixed64FieldNumber, buf.data(),
+                   buf.size());
+
+  // "protoc --decode", instead, returns an error on stderr and doesn't output
+  // anything at all.
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "# Packed decoding failure for field field_fixed64\n");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedFixedGarbage) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedFixedSizeInt<uint64_t> buf;
+  buf.Append(42);
+  buf.Append(3000000000000);
+  std::string buf_and_garbage(reinterpret_cast<const char*>(buf.data()),
+                              buf.size());
+  buf_and_garbage += "\x01";
+  // buf contains extra garbage
+  msg->AppendBytes(PackedRepeatedFields::kFieldFixed64FieldNumber,
+                   buf_and_garbage.data(), buf_and_garbage.size());
+
+  // "protoc --decode", instead, returns an error on stderr and doesn't output
+  // anything at all.
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "# Packed decoding failure for field field_fixed64\n");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedVarIntShort) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  std::string buf;
+  buf = "\xFF";
+  // for the varint to be valid, buf should contain another byte.
+  msg->AppendBytes(PackedRepeatedFields::kFieldInt32FieldNumber, buf.data(),
+                   buf.size());
+
+  // "protoc --decode", instead, returns an error on stderr and doesn't output
+  // anything at all.
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "# Packed decoding failure for field field_int32\n");
+}
+
+TEST_F(ProtozeroToTextTestMessageTest, FieldLengthLimitedPackedVarIntGarbage) {
+  protozero::HeapBuffered<PackedRepeatedFields> msg;
+  protozero::PackedVarInt buf;
+  buf.Append(42);
+  buf.Append(105);
+  std::string buf_and_garbage(reinterpret_cast<const char*>(buf.data()),
+                              buf.size());
+  buf_and_garbage += "\xFF";
+  // buf contains extra garbage
+  msg->AppendBytes(PackedRepeatedFields::kFieldInt32FieldNumber,
+                   buf_and_garbage.data(), buf_and_garbage.size());
+
+  // "protoc --decode", instead:
+  // * doesn't output anything.
+  // * returns an error on stderr.
+  EXPECT_EQ(
+      ProtozeroToText(pool_, ".protozero.test.protos.PackedRepeatedFields",
+                      msg.SerializeAsArray(), kIncludeNewLines),
+      "field_int32: 42\n"
+      "field_int32: 105\n"
+      "# Packed decoding failure for field field_int32\n");
+}
+
 }  // namespace
 }  // namespace protozero_to_text
 }  // namespace trace_processor
diff --git a/src/traced/probes/android_log/android_log_data_source.cc b/src/traced/probes/android_log/android_log_data_source.cc
index 4d8d309..2d4d264 100644
--- a/src/traced/probes/android_log/android_log_data_source.cc
+++ b/src/traced/probes/android_log/android_log_data_source.cc
@@ -368,8 +368,6 @@
   if (!ReadAndAdvance(&buf, end, &eid))
     return false;
 
-  // TODO test events with 0 arguments. DNS.
-
   const EventFormat* fmt = GetEventFormat(eid);
   if (!fmt) {
     // We got an event which doesn't have a corresponding entry in
diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc
index d61e41c..665a350 100644
--- a/src/traced/probes/ps/process_stats_data_source.cc
+++ b/src/traced/probes/ps/process_stats_data_source.cc
@@ -23,7 +23,6 @@
 
 #include "perfetto/base/task_runner.h"
 #include "perfetto/base/time.h"
-#include "perfetto/ext/base/crash_keys.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/metatrace.h"
@@ -55,10 +54,6 @@
 // was provided in the config. The cache is trimmed if it exceeds this size.
 const size_t kThreadTimeInStateCacheSize = 10000;
 
-// TODO(b/189749310): For debugging of b/189749310. Remove by Jan 2022.
-base::CrashKey g_crash_key_proc_file("proc_file");
-base::CrashKey g_crash_key_proc_count("proc_count");
-
 int32_t ReadNextNumericDir(DIR* dirp) {
   while (struct dirent* dir_ent = readdir(dirp)) {
     if (dir_ent->d_type != DT_DIR)
@@ -344,8 +339,6 @@
 std::string ProcessStatsDataSource::ReadProcPidFile(int32_t pid,
                                                     const std::string& file) {
   base::StackString<128> path("/proc/%" PRId32 "/%s", pid, file.c_str());
-  auto scoped_key = g_crash_key_proc_file.SetScoped(path.string_view());
-  g_crash_key_proc_count.Set(g_crash_key_proc_count.int_value() + 1);
   std::string contents;
   contents.reserve(4096);
   if (!base::ReadFile(path.c_str(), &contents))
@@ -433,7 +426,6 @@
     base::WeakPtr<ProcessStatsDataSource> weak_this) {
   if (!weak_this)
     return;
-  g_crash_key_proc_count.Clear();
   ProcessStatsDataSource& thiz = *weak_this;
   uint32_t period_ms = thiz.poll_period_ms_;
   uint32_t delay_ms =
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 18f6e69..f9da779 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -53,7 +53,6 @@
 #include "perfetto/base/status.h"
 #include "perfetto/base/task_runner.h"
 #include "perfetto/ext/base/android_utils.h"
-#include "perfetto/ext/base/crash_keys.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/base/string_utils.h"
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 4a16b42..97a94f4 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -24,6 +24,7 @@
 #include <mutex>
 #include <regex>
 #include <thread>
+#include <unordered_set>
 #include <vector>
 
 // We also want to test legacy trace events.
@@ -2805,6 +2806,56 @@
                                   "]I:test.Event"));
 }
 
+TEST_P(PerfettoApiTest, TrackEventTrackFromThreadScopedPointer) {
+  // Create a new trace session.
+  auto* tracing_session = NewTraceWithCategories({"test"});
+  tracing_session->get()->StartBlocking();
+
+  int num = 2;
+  TRACE_EVENT_INSTANT("test", "Event0.1");
+  TRACE_EVENT_INSTANT("test", "Event0.2");
+  TRACE_EVENT_INSTANT("test", "Event1.1", perfetto::Track::ThreadScoped(&num));
+  TRACE_EVENT_INSTANT("test", "Event1.2", perfetto::Track::ThreadScoped(&num));
+  std::thread t1([&]() {
+    TRACE_EVENT_INSTANT("test", "Event2.1",
+                        perfetto::Track::ThreadScoped(&num));
+    TRACE_EVENT_INSTANT("test", "Event2.2",
+                        perfetto::Track::ThreadScoped(&num));
+  });
+  t1.join();
+  std::thread t2([&]() {
+    TRACE_EVENT_INSTANT("test", "Event3.1",
+                        perfetto::Track::ThreadScoped(&num));
+    TRACE_EVENT_INSTANT("test", "Event3.2",
+                        perfetto::Track::ThreadScoped(&num));
+  });
+  t2.join();
+  perfetto::TrackEvent::Flush();
+  tracing_session->get()->StopBlocking();
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace parsed_trace;
+  ASSERT_TRUE(parsed_trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  std::unordered_map<std::string, uint64_t> track_uuid_map;
+  for (auto packet : parsed_trace.packet()) {
+    if (packet.has_interned_data()) {
+      for (auto& ename : packet.interned_data().event_names()) {
+        track_uuid_map.emplace(ename.name(), packet.track_event().track_uuid());
+      }
+    }
+  }
+  EXPECT_EQ(track_uuid_map.at("Event0.1"), track_uuid_map.at("Event0.2"));
+  EXPECT_EQ(track_uuid_map.at("Event1.1"), track_uuid_map.at("Event1.2"));
+  EXPECT_EQ(track_uuid_map.at("Event2.1"), track_uuid_map.at("Event2.2"));
+  EXPECT_EQ(track_uuid_map.at("Event3.1"), track_uuid_map.at("Event3.2"));
+
+  EXPECT_EQ(4u,
+            (std::unordered_set<uint64_t>{
+                 track_uuid_map.at("Event0.1"), track_uuid_map.at("Event1.1"),
+                 track_uuid_map.at("Event2.1"), track_uuid_map.at("Event3.1")})
+                .size());
+}
+
 TEST_P(PerfettoApiTest, TrackEventDebugAnnotations) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
diff --git a/test/trace_processor/chrome/chrome_long_latency_metric.out b/test/trace_processor/chrome/chrome_long_latency_metric.out
new file mode 100644
index 0000000..9779ed9
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_long_latency_metric.out
@@ -0,0 +1,5 @@
+
+"ts","event_type","process_name","process_id"
+200111000,"GestureScrollUpdate","Renderer",1001
+200111000,"GestureScrollUpdate","Renderer",1002
+280111001,"GestureScrollUpdate","Renderer",1001
diff --git a/test/trace_processor/chrome/chrome_long_latency_metric.sql b/test/trace_processor/chrome/chrome_long_latency_metric.sql
new file mode 100644
index 0000000..13db1ff
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_long_latency_metric.sql
@@ -0,0 +1,18 @@
+--
+-- 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 RUN_METRIC('experimental/chrome_long_latency.sql') AS suppress_query_output;
+
+SELECT * FROM long_latency_with_process_info;
\ No newline at end of file
diff --git a/test/trace_processor/chrome/index b/test/trace_processor/chrome/index
index 019067a..7e6856c 100644
--- a/test/trace_processor/chrome/index
+++ b/test/trace_processor/chrome/index
@@ -17,6 +17,7 @@
 ../track_event/track_event_counters.textproto chrome_thread_slice_repeated.sql chrome_thread_slice_repeated.out
 ../../data/chrome_rendering_desktop.pftrace frame_times frame_times_metric.out
 ../../data/chrome_rendering_desktop.pftrace chrome_dropped_frames chrome_dropped_frames_metric.out
+../chrome/long_event_latency.textproto chrome_long_latency_metric.sql chrome_long_latency_metric.out
 scroll_jank_mojo_simple_watcher.py scroll_jank_mojo_simple_watcher.sql scroll_jank_mojo_simple_watcher.out
 scroll_jank_gpu_check.py scroll_jank_gpu_check.sql scroll_jank_gpu_check.out
 
diff --git a/test/trace_processor/chrome/long_event_latency.textproto b/test/trace_processor/chrome/long_event_latency.textproto
new file mode 100644
index 0000000..8b6fa8b
--- /dev/null
+++ b/test/trace_processor/chrome/long_event_latency.textproto
@@ -0,0 +1,212 @@
+# Long latency events from two renderer processes
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 10
+    process {
+      pid: 1001
+      process_name: "Renderer"
+    }
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 20
+    process {
+      pid: 1002
+      process_name: "Renderer"
+    }
+  }
+}
+
+# EventLatency 1: 200ms duration
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 101
+    parent_uuid: 10
+    name: "EventLatency"
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 111000
+  track_event {
+    track_uuid: 101
+    categories: "cat"
+    debug_annotations: {
+      name: "event"
+      string_value: "GestureScrollUpdate"
+    }
+    name: "EventLatency"
+    type: 1
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 200111000
+  track_event {
+    track_uuid: 101
+    categories: "cat"
+    name: "EventLatency"
+    type: 2
+  }
+}
+
+# EventLatency 2: 110ms ending at the same timestamp as EventLatency 1.
+# EventLatency 1 and 2 are reported as one occurrence of long latency.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 102
+    parent_uuid: 10
+    name: "EventLatency"
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 90111000
+  track_event {
+    track_uuid: 102
+    categories: "cat"
+    debug_annotations: {
+      name: "event"
+      string_value: "GestureScrollUpdate"
+    }
+    name: "EventLatency"
+    type: 1
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 200111000
+  track_event {
+    track_uuid: 102
+    categories: "cat"
+    name: "EventLatency"
+    type: 2
+  }
+}
+
+# EventLatency 3: a long latency slightly above the 100ms threshold
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 103
+    parent_uuid: 10
+    name: "EventLatency"
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 180111000
+  track_event {
+    track_uuid: 103
+    categories: "cat"
+    debug_annotations: {
+      name: "event"
+      string_value: "GestureScrollUpdate"
+    }
+    name: "EventLatency"
+    type: 1
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 280111001
+  track_event {
+    track_uuid: 103
+    categories: "cat"
+    name: "EventLatency"
+    type: 2
+  }
+}
+
+# EventLatency 4: below the 100ms threshold; not a long latency
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 104
+    parent_uuid: 10
+    name: "EventLatency"
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 250111001
+  track_event {
+    track_uuid: 104
+    categories: "cat"
+    debug_annotations: {
+      name: "event"
+      string_value: "GestureScrollUpdate"
+    }
+    name: "EventLatency"
+    type: 1
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 350111000
+  track_event {
+    track_uuid: 104
+    categories: "cat"
+    name: "EventLatency"
+    type: 2
+  }
+}
+
+# EventLatency 5: long latency from the second renderer process;
+# should be reported as a standalone long latency occurrence despite having
+# the same end time as EventLatency 1.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 201
+    parent_uuid: 20
+    name: "EventLatency"
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 111000
+  track_event {
+    track_uuid: 201
+    categories: "cat"
+    debug_annotations: {
+      name: "event"
+      string_value: "GestureScrollUpdate"
+    }
+    name: "EventLatency"
+    type: 1
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 200111000
+  track_event {
+    track_uuid: 201
+    categories: "cat"
+    name: "EventLatency"
+    type: 2
+  }
+}
\ No newline at end of file
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 18df6ce..da6809b 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -155,7 +155,10 @@
   labels?: string[];
   trackKindPriority: TrackKindPriority;
   trackGroup?: string;
-  config: {};
+  config: {
+    trackId?: number;
+    trackIds?: number[];
+  };
 }
 
 export interface TrackGroupState {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index d38e64f..6bf5326 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -217,13 +217,12 @@
         cpuToTrackId.set((track.config as {cpu: number}).cpu, track.id);
         continue;
       }
-      if (track.kind === 'ChromeSliceTrack') {
-        const config = (track.config as {trackId: number});
+      const config = track.config || {};
+      if (config.trackId !== undefined) {
         engineTrackIdToTrackId.set(config.trackId, track.id);
         continue;
       }
-      if (track.kind === 'AsyncSliceTrack') {
-        const config = (track.config as {trackIds: number[]});
+      if (config.trackIds !== undefined) {
         for (const trackId of config.trackIds) {
           engineTrackIdToTrackId.set(trackId, track.id);
         }
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 66c55a0..a940716 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -50,7 +50,7 @@
   processName?: string;
   uid?: number;
   packageName?: string;
-  versionCode?: string;
+  versionCode?: number;
 }
 
 // This class queries the TP for the details on a specific slice that has
@@ -539,7 +539,7 @@
     // check if the result has returned any rows.
     if (packageResult.numRows() > 0) {
       const packageDetails =
-          packageResult.firstRow({package_name: STR, version_code: STR});
+          packageResult.firstRow({package_name: STR, version_code: NUM});
       details.packageName = packageDetails.package_name;
       details.versionCode = packageDetails.version_code;
     }
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index dd5b51b..e5fa7ad 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -63,7 +63,7 @@
   processName?: string;
   uid?: number;
   packageName?: string;
-  versionCode?: string;
+  versionCode?: number;
   args?: Args;
   argsTree?: ArgsTree;
   description?: Description;
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 204fcb2..0bbb98e 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -42,8 +42,10 @@
   const selection = globals.state.currentSelection;
   const noModifiers = !(e.ctrlKey || e.metaKey || e.altKey || e.shiftKey);
   const ctrlOrMeta = (e.ctrlKey || e.metaKey) && !(e.altKey || e.shiftKey);
+  // No other modifiers other than possibly Shift.
+  const maybeShift = !(e.ctrlKey || e.metaKey || e.altKey);
 
-  if (down && 'm' === key && noModifiers) {
+  if (down && 'm' === key && maybeShift) {
     if (selection && selection.kind === 'AREA') {
       globals.dispatch(Actions.toggleMarkCurrentArea({persistent: e.shiftKey}));
     } else if (selection) {
@@ -56,10 +58,10 @@
   if (down && 'b' === key && ctrlOrMeta) {
     globals.dispatch(Actions.toggleSidebar({}));
   }
-  if (down && '?' === key && noModifiers) {
+  if (down && '?' === key && maybeShift) {
     toggleHelp();
   }
-  if (down && 'enter' === key && noModifiers) {
+  if (down && 'enter' === key && maybeShift) {
     e.preventDefault();
     executeSearch(e.shiftKey);
   }
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 85dbff1..a1b3428 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -32,7 +32,10 @@
       ['Process name', sliceInfo.processName],
       ['User ID', sliceInfo.uid ? String(sliceInfo.uid) : undefined],
       ['Package name', sliceInfo.packageName],
-      ['Version code', sliceInfo.versionCode]
+      [
+        'Version code',
+        sliceInfo.versionCode ? String(sliceInfo.versionCode) : undefined
+      ]
     ]);
   }
 }
\ No newline at end of file