Merge "ui: Add initial plugin architecture"
diff --git a/src/trace_processor/importers/common/args_tracker.cc b/src/trace_processor/importers/common/args_tracker.cc
index 50c03d1..5faa718 100644
--- a/src/trace_processor/importers/common/args_tracker.cc
+++ b/src/trace_processor/importers/common/args_tracker.cc
@@ -121,16 +121,5 @@
 
 ArgsTracker::BoundInserter::~BoundInserter() = default;
 
-ArgsTracker::BoundInserter& ArgsTracker::BoundInserter::TranslateAndAddArgs(
-    const ArgsTranslationTable& table,
-    const CompactArgSet& set) {
-  for (const auto& arg : set) {
-    if (table.TranslateArg(arg.key, arg.value, *this))
-      continue;
-    AddArg(arg.key, arg.value, arg.update_policy);
-  }
-  return *this;
-}
-
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index e7021fc..43324a1 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -65,11 +65,6 @@
       return *this;
     }
 
-    // Translates any args in |set| using |table| and then adds them
-    // to this BoundInserter.
-    BoundInserter& TranslateAndAddArgs(const ArgsTranslationTable& table,
-                                       const CompactArgSet& set);
-
     // IncrementArrayEntryIndex() and GetNextArrayEntryIndex() provide a way to
     // track the next array index for an array under a specific key.
     size_t GetNextArrayEntryIndex(StringId key) {
diff --git a/src/trace_processor/importers/common/args_translation_table.cc b/src/trace_processor/importers/common/args_translation_table.cc
index 60c7369..cc285c3 100644
--- a/src/trace_processor/importers/common/args_translation_table.cc
+++ b/src/trace_processor/importers/common/args_translation_table.cc
@@ -31,6 +31,10 @@
 constexpr char ArgsTranslationTable::kChromePerformanceMarkMarkHashKey[];
 constexpr char ArgsTranslationTable::kChromePerformanceMarkMarkKey[];
 
+constexpr char ArgsTranslationTable::kMojoMethodMappingIdKey[];
+constexpr char ArgsTranslationTable::kMojoMethodRelPcKey[];
+constexpr char ArgsTranslationTable::kMojoMethodNameKey[];
+
 ArgsTranslationTable::ArgsTranslationTable(TraceStorage* storage)
     : storage_(storage),
       interned_chrome_histogram_hash_key_(
@@ -48,68 +52,88 @@
       interned_chrome_performance_mark_mark_hash_key_(
           storage->InternString(kChromePerformanceMarkMarkHashKey)),
       interned_chrome_performance_mark_mark_key_(
-          storage->InternString(kChromePerformanceMarkMarkKey)) {}
+          storage->InternString(kChromePerformanceMarkMarkKey)),
+      interned_mojo_method_mapping_id_(
+          storage->InternString(kMojoMethodMappingIdKey)),
+      interned_mojo_method_rel_pc_(storage->InternString(kMojoMethodRelPcKey)),
+      interned_mojo_method_name_(storage->InternString(kMojoMethodNameKey)) {}
 
 bool ArgsTranslationTable::NeedsTranslation(StringId key_id,
                                             Variadic::Type type) const {
   return KeyIdAndTypeToEnum(key_id, type).has_value();
 }
 
-bool ArgsTranslationTable::TranslateArg(
-    StringId key_id,
-    Variadic value,
+void ArgsTranslationTable::TranslateArgs(
+    const ArgsTracker::CompactArgSet& arg_set,
     ArgsTracker::BoundInserter& inserter) const {
-  const auto key_type = KeyIdAndTypeToEnum(key_id, value.type);
-  if (!key_type.has_value()) {
-    return false;
-  }
-  switch (*key_type) {
-    case KeyType::kChromeHistogramHash: {
-      inserter.AddArg(interned_chrome_histogram_hash_key_, value);
-      const base::Optional<base::StringView> translated_value =
-          TranslateChromeHistogramHash(value.uint_value);
-      if (translated_value) {
-        inserter.AddArg(
-            interned_chrome_histogram_name_key_,
-            Variadic::String(storage_->InternString(*translated_value)));
-      }
-      return true;
+  base::Optional<uint64_t> mapping_id;
+  base::Optional<uint64_t> rel_pc;
+
+  for (const auto& arg : arg_set) {
+    const auto key_type = KeyIdAndTypeToEnum(arg.key, arg.value.type);
+    if (!key_type.has_value()) {
+      inserter.AddArg(arg.key, arg.value, arg.update_policy);
+      continue;
     }
-    case KeyType::kChromeUserEventHash: {
-      inserter.AddArg(interned_chrome_user_event_hash_key_, value);
-      const base::Optional<base::StringView> translated_value =
-          TranslateChromeUserEventHash(value.uint_value);
-      if (translated_value) {
-        inserter.AddArg(
-            interned_chrome_user_event_action_key_,
-            Variadic::String(storage_->InternString(*translated_value)));
+
+    switch (*key_type) {
+      case KeyType::kChromeHistogramHash: {
+        inserter.AddArg(interned_chrome_histogram_hash_key_, arg.value);
+        const base::Optional<base::StringView> translated_value =
+            TranslateChromeHistogramHash(arg.value.uint_value);
+        if (translated_value) {
+          inserter.AddArg(
+              interned_chrome_histogram_name_key_,
+              Variadic::String(storage_->InternString(*translated_value)));
+        }
+        break;
       }
-      return true;
-    }
-    case KeyType::kChromePerformanceMarkMarkHash: {
-      inserter.AddArg(interned_chrome_performance_mark_mark_hash_key_, value);
-      const base::Optional<base::StringView> translated_value =
-          TranslateChromePerformanceMarkMarkHash(value.uint_value);
-      if (translated_value) {
-        inserter.AddArg(
-            interned_chrome_performance_mark_mark_key_,
-            Variadic::String(storage_->InternString(*translated_value)));
+      case KeyType::kChromeUserEventHash: {
+        inserter.AddArg(interned_chrome_user_event_hash_key_, arg.value);
+        const base::Optional<base::StringView> translated_value =
+            TranslateChromeUserEventHash(arg.value.uint_value);
+        if (translated_value) {
+          inserter.AddArg(
+              interned_chrome_user_event_action_key_,
+              Variadic::String(storage_->InternString(*translated_value)));
+        }
+        break;
       }
-      return true;
-    }
-    case KeyType::kChromePerformanceMarkSiteHash: {
-      inserter.AddArg(interned_chrome_performance_mark_site_hash_key_, value);
-      const base::Optional<base::StringView> translated_value =
-          TranslateChromePerformanceMarkSiteHash(value.uint_value);
-      if (translated_value) {
-        inserter.AddArg(
-            interned_chrome_performance_mark_site_key_,
-            Variadic::String(storage_->InternString(*translated_value)));
+      case KeyType::kChromePerformanceMarkMarkHash: {
+        inserter.AddArg(interned_chrome_performance_mark_mark_hash_key_,
+                        arg.value);
+        const base::Optional<base::StringView> translated_value =
+            TranslateChromePerformanceMarkMarkHash(arg.value.uint_value);
+        if (translated_value) {
+          inserter.AddArg(
+              interned_chrome_performance_mark_mark_key_,
+              Variadic::String(storage_->InternString(*translated_value)));
+        }
+        break;
       }
-      return true;
+      case KeyType::kChromePerformanceMarkSiteHash: {
+        inserter.AddArg(interned_chrome_performance_mark_site_hash_key_,
+                        arg.value);
+        const base::Optional<base::StringView> translated_value =
+            TranslateChromePerformanceMarkSiteHash(arg.value.uint_value);
+        if (translated_value) {
+          inserter.AddArg(
+              interned_chrome_performance_mark_site_key_,
+              Variadic::String(storage_->InternString(*translated_value)));
+        }
+        break;
+      }
+      case KeyType::kMojoMethodMappingId: {
+        mapping_id = arg.value.uint_value;
+        break;
+      }
+      case KeyType::kMojoMethodRelPc: {
+        rel_pc = arg.value.uint_value;
+        break;
+      }
     }
   }
-  return false;
+  EmitMojoMethodLocation(mapping_id, rel_pc, inserter);
 }
 
 base::Optional<ArgsTranslationTable::KeyType>
@@ -130,6 +154,12 @@
   if (key_id == interned_chrome_performance_mark_site_hash_key_) {
     return KeyType::kChromePerformanceMarkSiteHash;
   }
+  if (key_id == interned_mojo_method_mapping_id_) {
+    return KeyType::kMojoMethodMappingId;
+  }
+  if (key_id == interned_mojo_method_rel_pc_) {
+    return KeyType::kMojoMethodRelPc;
+  }
   return base::nullopt;
 }
 
@@ -171,5 +201,39 @@
   return base::StringView(*value);
 }
 
+base::Optional<ArgsTranslationTable::SourceLocation>
+ArgsTranslationTable::TranslateNativeSymbol(MappingId mapping_id,
+                                            uint64_t rel_pc) const {
+  auto loc =
+      native_symbol_to_location_.Find(std::make_pair(mapping_id, rel_pc));
+  if (!loc) {
+    return base::nullopt;
+  }
+  return *loc;
+}
+
+void ArgsTranslationTable::EmitMojoMethodLocation(
+    base::Optional<uint64_t> mapping_id,
+    base::Optional<uint64_t> rel_pc,
+    ArgsTracker::BoundInserter& inserter) const {
+  if (!mapping_id || !rel_pc) {
+    return;
+  }
+  const MappingId row_id(static_cast<uint32_t>(*mapping_id));
+  const auto loc = TranslateNativeSymbol(row_id, *rel_pc);
+  if (loc) {
+    inserter.AddArg(interned_mojo_method_name_,
+                    Variadic::String(storage_->InternString(
+                        base::StringView(loc->function_name))));
+  } else {
+    // Could not find the corresponding source location. Let's emit raw arg
+    // values so that the data doesn't silently go missing.
+    inserter.AddArg(interned_mojo_method_mapping_id_,
+                    Variadic::UnsignedInteger(*mapping_id));
+    inserter.AddArg(interned_mojo_method_rel_pc_,
+                    Variadic::UnsignedInteger(*rel_pc));
+  }
+}
+
 }  // 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
index c27f02d..0f5af79 100644
--- a/src/trace_processor/importers/common/args_translation_table.h
+++ b/src/trace_processor/importers/common/args_translation_table.h
@@ -26,6 +26,15 @@
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/util/proto_to_args_parser.h"
 
+template <>
+struct std::hash<std::pair<perfetto::trace_processor::MappingId, uint64_t>> {
+  size_t operator()(const std::pair<perfetto::trace_processor::MappingId,
+                                    uint64_t>& p) const {
+    return std::hash<perfetto::trace_processor::MappingId>{}(p.first) ^
+           std::hash<uint64_t>{}(p.second);
+  }
+};
+
 namespace perfetto {
 namespace trace_processor {
 
@@ -34,18 +43,20 @@
 class ArgsTranslationTable {
  public:
   using Key = util::ProtoToArgsParser::Key;
+  using NativeSymbolKey = std::pair<MappingId, uint64_t>;
+  struct SourceLocation {
+    std::string file_name;
+    std::string function_name;
+    uint32_t line_number;
+  };
 
   explicit ArgsTranslationTable(TraceStorage* storage);
 
   // Returns true if an arg with the given key and type requires translation.
   bool NeedsTranslation(StringId key_id, Variadic::Type type) const;
 
-  // 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 TranslateArg(StringId key_id,
-                    Variadic value,
-                    ArgsTracker::BoundInserter& inserter) const;
+  void TranslateArgs(const ArgsTracker::CompactArgSet& arg_set,
+                     ArgsTracker::BoundInserter& inserter) const;
 
   void AddChromeHistogramTranslationRule(uint64_t hash, base::StringView name) {
     chrome_histogram_hash_to_name_.Insert(hash, name.ToStdString());
@@ -62,6 +73,11 @@
                                                    base::StringView name) {
     chrome_performance_mark_mark_hash_to_name_.Insert(hash, name.ToStdString());
   }
+  void AddNativeSymbolTranslationRule(MappingId mapping_id,
+                                      uint64_t rel_pc,
+                                      const SourceLocation& loc) {
+    native_symbol_to_location_.Insert(std::make_pair(mapping_id, rel_pc), loc);
+  }
 
   base::Optional<base::StringView> TranslateChromeHistogramHashForTesting(
       uint64_t hash) const {
@@ -85,7 +101,9 @@
     kChromeHistogramHash = 0,
     kChromeUserEventHash = 1,
     kChromePerformanceMarkMarkHash = 2,
-    kChromePerformanceMarkSiteHash = 3
+    kChromePerformanceMarkSiteHash = 3,
+    kMojoMethodMappingId = 4,
+    kMojoMethodRelPc = 5,
   };
 
   static constexpr char kChromeHistogramHashKey[] =
@@ -108,6 +126,13 @@
   static constexpr char kChromePerformanceMarkMarkKey[] =
       "chrome_hashed_performance_mark.mark";
 
+  static constexpr char kMojoMethodMappingIdKey[] =
+      "chrome_mojo_event_info.mojo_interface_method.native_symbol.mapping_id";
+  static constexpr char kMojoMethodRelPcKey[] =
+      "chrome_mojo_event_info.mojo_interface_method.native_symbol.rel_pc";
+  static constexpr char kMojoMethodNameKey[] =
+      "chrome_mojo_event_info.mojo_method_name";
+
   TraceStorage* storage_;
   StringId interned_chrome_histogram_hash_key_;
   StringId interned_chrome_histogram_name_key_;
@@ -117,12 +142,18 @@
   StringId interned_chrome_performance_mark_site_key_;
   StringId interned_chrome_performance_mark_mark_hash_key_;
   StringId interned_chrome_performance_mark_mark_key_;
+
+  StringId interned_mojo_method_mapping_id_;
+  StringId interned_mojo_method_rel_pc_;
+  StringId interned_mojo_method_name_;
+
   base::FlatHashMap<uint64_t, std::string> chrome_histogram_hash_to_name_;
   base::FlatHashMap<uint64_t, std::string> chrome_user_event_hash_to_action_;
   base::FlatHashMap<uint64_t, std::string>
       chrome_performance_mark_site_hash_to_name_;
   base::FlatHashMap<uint64_t, std::string>
       chrome_performance_mark_mark_hash_to_name_;
+  base::FlatHashMap<NativeSymbolKey, SourceLocation> native_symbol_to_location_;
 
   // Returns the corresponding SupportedKey enum if the table knows how to
   // translate the argument with the given key and type, and nullopt otherwise.
@@ -137,6 +168,12 @@
       uint64_t hash) const;
   base::Optional<base::StringView> TranslateChromePerformanceMarkMarkHash(
       uint64_t hash) const;
+  base::Optional<SourceLocation> TranslateNativeSymbol(MappingId mapping_id,
+                                                       uint64_t rel_pc) const;
+
+  void EmitMojoMethodLocation(base::Optional<uint64_t> mapping_id,
+                              base::Optional<uint64_t> rel_pc,
+                              ArgsTracker::BoundInserter& inserter) const;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index f679ca9..a255502 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -18,6 +18,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/string_utils.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/process_tracker.h"
@@ -464,12 +465,17 @@
     uint32_t symbol_set_id = context_->storage->symbol_table().row_count();
 
     bool has_lines = false;
+    // Taking the last (i.e. the least interned) location if there're several.
+    ArgsTranslationTable::SourceLocation last_location;
     for (auto line_it = address_symbols.lines(); line_it; ++line_it) {
       protos::pbzero::Line::Decoder line(*line_it);
       context_->storage->mutable_symbol_table()->Insert(
           {symbol_set_id, context_->storage->InternString(line.function_name()),
            context_->storage->InternString(line.source_file_name()),
            line.line_number()});
+      last_location = ArgsTranslationTable::SourceLocation{
+          line.source_file_name().ToStdString(),
+          line.function_name().ToStdString(), line.line_number()};
       has_lines = true;
     }
     if (!has_lines) {
@@ -477,6 +483,8 @@
     }
     bool frame_found = false;
     for (MappingId mapping_id : mapping_ids) {
+      context_->args_translation_table->AddNativeSymbolTranslationRule(
+          mapping_id, address_symbols.address(), last_location);
       std::vector<FrameId> frame_ids =
           context_->global_stack_profile_tracker->FindFrameIds(
               mapping_id, address_symbols.address());
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 72ade83..7fc231c 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -31,6 +31,7 @@
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/json/json_utils.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/profile_packet_utils.h"
 #include "src/trace_processor/importers/proto/track_event_tracker.h"
 #include "src/trace_processor/util/debug_annotation_parser.h"
 #include "src/trace_processor/util/proto_to_args_parser.h"
@@ -71,12 +72,10 @@
  public:
   TrackEventArgsParser(BoundInserter& inserter,
                        TraceStorage& storage,
-                       PacketSequenceStateGeneration& sequence_state,
-                       ArgsTranslationTable& args_translation_table)
+                       PacketSequenceStateGeneration& sequence_state)
       : inserter_(inserter),
         storage_(storage),
-        sequence_state_(sequence_state),
-        args_translation_table_(args_translation_table) {}
+        sequence_state_(sequence_state) {}
 
   ~TrackEventArgsParser() override;
 
@@ -88,14 +87,9 @@
                      Variadic::Integer(value));
   }
   void AddUnsignedInteger(const Key& key, uint64_t value) final {
-    StringId flat_key_id =
-        storage_.InternString(base::StringView(key.flat_key));
-    StringId key_id = storage_.InternString(base::StringView(key.key));
-    Variadic variadic_val = Variadic::UnsignedInteger(value);
-    if (args_translation_table_.TranslateArg(key_id, variadic_val, inserter_)) {
-      return;
-    }
-    inserter_.AddArg(flat_key_id, key_id, variadic_val);
+    inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)),
+                     storage_.InternString(base::StringView(key.key)),
+                     Variadic::UnsignedInteger(value));
   }
   void AddString(const Key& key, const protozero::ConstChars& value) final {
     inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)),
@@ -152,11 +146,12 @@
     return sequence_state_.GetInternedMessageView(field_id, iid);
   }
 
+  PacketSequenceStateGeneration* seq_state() final { return &sequence_state_; }
+
  private:
   BoundInserter& inserter_;
   TraceStorage& storage_;
   PacketSequenceStateGeneration& sequence_state_;
-  ArgsTranslationTable& args_translation_table_;
 };
 
 TrackEventArgsParser::~TrackEventArgsParser() = default;
@@ -173,6 +168,36 @@
   return result;
 }
 
+base::Optional<base::Status> MaybeParseUnsymbolizedSourceLocation(
+    std::string prefix,
+    const protozero::Field& field,
+    util::ProtoToArgsParser::Delegate& delegate) {
+  auto* decoder = delegate.GetInternedMessage(
+      protos::pbzero::InternedData::kUnsymbolizedSourceLocations,
+      field.as_uint64());
+  if (!decoder) {
+    // Lookup failed fall back on default behaviour which will just put
+    // the iid into the args table.
+    return base::nullopt;
+  }
+  // Interned mapping_id loses it's meaning when the sequence ends. So we need
+  // to get an id from stack_profile_mapping table.
+  ProfilePacketInternLookup intern_lookup(delegate.seq_state());
+  auto mapping_id =
+      delegate.seq_state()
+          ->state()
+          ->sequence_stack_profile_tracker()
+          .FindOrInsertMapping(decoder->mapping_id(), &intern_lookup);
+  if (!mapping_id) {
+    return base::nullopt;
+  }
+  delegate.AddUnsignedInteger(
+      util::ProtoToArgsParser::Key(prefix + ".mapping_id"), mapping_id->value);
+  delegate.AddUnsignedInteger(util::ProtoToArgsParser::Key(prefix + ".rel_pc"),
+                              decoder->rel_pc());
+  return base::OkStatus();
+}
+
 base::Optional<base::Status> MaybeParseSourceLocation(
     std::string prefix,
     const protozero::Field& field,
@@ -694,7 +719,6 @@
       return util::ErrStatus(
           "TrackEvent with phase E without thread association");
     }
-
     auto opt_slice_id_and_args_tracker =
         context_->slice_tracker->EndMaybePreservingArgs(
             ts_, track_id_, category_id_, name_id_,
@@ -1165,8 +1189,7 @@
           ParseHistogramName(event_.chrome_histogram_sample(), inserter));
     }
 
-    TrackEventArgsParser args_writer(*inserter, *storage_, *sequence_state_,
-                                     *context_->args_translation_table);
+    TrackEventArgsParser args_writer(*inserter, *storage_, *sequence_state_);
     int unknown_extensions = 0;
     log_errors(parser_->args_parser_.ParseMessage(
         blob_, ".perfetto.protos.TrackEvent", &parser_->reflect_fields_,
@@ -1436,6 +1459,14 @@
       counter_unit_ids_{{kNullStringId, context_->storage->InternString("ns"),
                          context_->storage->InternString("count"),
                          context_->storage->InternString("bytes")}} {
+  args_parser_.AddParsingOverrideForField(
+      "chrome_mojo_event_info.mojo_interface_method_iid",
+      [](const protozero::Field& field,
+         util::ProtoToArgsParser::Delegate& delegate) {
+        return MaybeParseUnsymbolizedSourceLocation(
+            "chrome_mojo_event_info.mojo_interface_method.native_symbol", field,
+            delegate);
+      });
   // Switch |source_location_iid| into its interned data variant.
   args_parser_.AddParsingOverrideForField(
       "begin_impl_frame_args.current_args.source_location_iid",
diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc
index 4792ac2..681458f 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.cc
+++ b/src/trace_processor/importers/proto/track_event_tracker.cc
@@ -557,8 +557,8 @@
   for (const auto& translatable_arg : translatable_args_) {
     auto bound_inserter =
         context_->args_tracker->AddArgsTo(translatable_arg.slice_id);
-    bound_inserter.TranslateAndAddArgs(*context_->args_translation_table,
-                                       translatable_arg.compact_arg_set);
+    context_->args_translation_table->TranslateArgs(
+        translatable_arg.compact_arg_set, bound_inserter);
   }
   translatable_args_.clear();
 }
diff --git a/src/trace_processor/util/debug_annotation_parser_unittest.cc b/src/trace_processor/util/debug_annotation_parser_unittest.cc
index 522a25d..7244d02 100644
--- a/src/trace_processor/util/debug_annotation_parser_unittest.cc
+++ b/src/trace_processor/util/debug_annotation_parser_unittest.cc
@@ -124,6 +124,8 @@
     return nullptr;
   }
 
+  PacketSequenceStateGeneration* seq_state() final { return nullptr; }
+
   std::vector<std::string> args_;
   std::map<std::string, size_t> array_indices_;
 };
diff --git a/src/trace_processor/util/proto_to_args_parser.h b/src/trace_processor/util/proto_to_args_parser.h
index 9035a68..86fbcd1 100644
--- a/src/trace_processor/util/proto_to_args_parser.h
+++ b/src/trace_processor/util/proto_to_args_parser.h
@@ -27,6 +27,7 @@
 
 // TODO(altimin): Move InternedMessageView into trace_processor/util.
 class InternedMessageView;
+class PacketSequenceStateGeneration;
 
 namespace util {
 
@@ -92,6 +93,8 @@
     virtual size_t GetArrayEntryIndex(const std::string& array_key) = 0;
     virtual size_t IncrementArrayEntryIndex(const std::string& array_key) = 0;
 
+    virtual PacketSequenceStateGeneration* seq_state() = 0;
+
     template <typename FieldMetadata>
     typename FieldMetadata::cpp_field_type::Decoder* GetInternedMessage(
         protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
diff --git a/src/trace_processor/util/proto_to_args_parser_unittest.cc b/src/trace_processor/util/proto_to_args_parser_unittest.cc
index 54289e6..22291ab 100644
--- a/src/trace_processor/util/proto_to_args_parser_unittest.cc
+++ b/src/trace_processor/util/proto_to_args_parser_unittest.cc
@@ -123,6 +123,8 @@
     return interned_source_locations_.at(iid).get();
   }
 
+  PacketSequenceStateGeneration* seq_state() final { return nullptr; }
+
   std::vector<std::string> args_;
   std::map<uint64_t, std::unique_ptr<InternedMessageView>>
       interned_source_locations_;
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index cbb201f..4a84344 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-042585a9e56ed1ffe0031ddda7d31a10066808fb068c22e5a3a71e325282d9e1
\ No newline at end of file
+442cab0047fff46d2dc89d6596a4c5f268aee6f62a849b13b4cff720fbe1556e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index 9e7d61e..9aa9076 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-c8da89a90294ee87493dc68d7dae008a55af18b4a9beb3105f9ab3ae1e6dcaef
\ No newline at end of file
+c23a38df0d6ec8c58174b6d219a975f91fb9a2411cb31a2e3c15a744ff86174a
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index b4eb4f7..172ba3c 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-77ad6a3f90b414957bd1fd78d39ceb380b28f384b4d4a0a3ed1e11e394f635be
\ No newline at end of file
+8df8ee8d373da6ae8f0b5af63902fe965d012296aba884434d084cd838c6f7d3
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index 537a332..36fc390 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-ff27d5e8347291b7e5f86b70b30e2e137ad6ed857cee241f27b87312fcbf6049
\ No newline at end of file
+a2b31d331511482c4e9e223ab73678fc6a26f6e7ed4162e69a66c8243788a775
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index 537a332..36fc390 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-ff27d5e8347291b7e5f86b70b30e2e137ad6ed857cee241f27b87312fcbf6049
\ No newline at end of file
+a2b31d331511482c4e9e223ab73678fc6a26f6e7ed4162e69a66c8243788a775
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index e099b0c..e29e047 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-2b9fc3c8ee79a81f66d92cf412e4bf64d61fbdd1f4898ea065111343dcc0e408
\ No newline at end of file
+521aefa595992105dba9107409337c91569859c0c268be3f08ea6c04ef6cdcd5
\ No newline at end of file
diff --git a/test/trace_processor/translation/chrome_histogram.out b/test/trace_processor/translation/chrome_histogram.out
index e0c4dec..484b008 100644
--- a/test/trace_processor/translation/chrome_histogram.out
+++ b/test/trace_processor/translation/chrome_histogram.out
@@ -1,7 +1,7 @@
 "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]"
+"source_id","source_id",12345,"[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]"
diff --git a/test/trace_processor/translation/chrome_histogram.textproto b/test/trace_processor/translation/chrome_histogram.textproto
index fb6a04e..131bf45 100644
--- a/test/trace_processor/translation/chrome_histogram.textproto
+++ b/test/trace_processor/translation/chrome_histogram.textproto
@@ -7,15 +7,32 @@
     }
   }
 }
+# Track for slice begin/end events.
+packet {
+  timestamp: 0
+  trusted_packet_sequence_id: 1
+  track_descriptor {
+    uuid: 12345
+    thread {
+      pid: 123
+      tid: 345
+    }
+    parent_uuid: 0
+    chrome_thread {
+      thread_type: THREAD_POOL_FG_WORKER
+    }
+  }
+}
 # Known histogram hash, should be translated to a name
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 1
+
   track_event {
     categories: "cat1"
-    type: 3
-    name_iid: 1
+    track_uuid: 12345
+    type: 1
+    name: "slice1"
     chrome_histogram_sample {
       name_hash: 10
       sample: 100
@@ -25,12 +42,12 @@
 # Another known hash, should be translated to a name
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 2
   track_event {
     categories: "cat2"
-    type: 3
-    name_iid: 2
+    track_uuid: 12345
+    type: 1
+    name: "slice2"
     chrome_histogram_sample {
       name_hash: 20
     }
@@ -39,15 +56,49 @@
 # Unknown hash, should not be translated to any name
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 3
   track_event {
     categories: "cat3"
-    type: 3
-    name_iid: 3
+    track_uuid: 12345
+    type: 1
+    name: "slice3"
     chrome_histogram_sample {
       name_hash: 30
     }
   }
 }
 
+# Slice end events
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6000
+  track_event {
+    track_uuid: 12345
+    categories: "cat3"
+    name: "slice3"
+    type: 2
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6001
+  track_event {
+    track_uuid: 12345
+    categories: "cat2"
+    name: "slice2"
+    type: 2
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6002
+  track_event {
+    track_uuid: 12345
+    categories: "cat1"
+    name: "slice1"
+    type: 2
+  }
+}
+
diff --git a/test/trace_processor/translation/chrome_performance_mark.out b/test/trace_processor/translation/chrome_performance_mark.out
index 0231cb7..ae86237 100644
--- a/test/trace_processor/translation/chrome_performance_mark.out
+++ b/test/trace_processor/translation/chrome_performance_mark.out
@@ -1,7 +1,7 @@
 "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]"
+"source_id","source_id",12345,"[NULL]"
 "chrome_hashed_performance_mark.mark","chrome_hashed_performance_mark.mark","[NULL]","mark2"
 "chrome_hashed_performance_mark.mark_hash","chrome_hashed_performance_mark.mark_hash",20,"[NULL]"
 "chrome_hashed_performance_mark.site","chrome_hashed_performance_mark.site","[NULL]","site1"
diff --git a/test/trace_processor/translation/chrome_performance_mark.textproto b/test/trace_processor/translation/chrome_performance_mark.textproto
index 6e5234b..b3ff668 100644
--- a/test/trace_processor/translation/chrome_performance_mark.textproto
+++ b/test/trace_processor/translation/chrome_performance_mark.textproto
@@ -7,17 +7,44 @@
     }
   }
 }
+# Track for slice begin/end events.
+packet {
+  timestamp: 0
+  trusted_packet_sequence_id: 1
+  track_descriptor {
+    uuid: 12345
+    thread {
+      pid: 123
+      tid: 345
+    }
+    parent_uuid: 0
+    chrome_thread {
+      thread_type: THREAD_POOL_FG_WORKER
+    }
+  }
+}
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 1
   track_event {
     categories: "cat1"
-    type: 3
-    name_iid: 1
+    track_uuid: 12345
+    type: 1
+    name: "slice1"
     [perfetto.protos.ChromeTrackEvent.chrome_hashed_performance_mark] {
       site_hash: 10
       mark_hash: 20
     }
   }
 }
+# Slice end
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6000
+  track_event {
+    track_uuid: 12345
+    categories: "cat1"
+    name: "slice1"
+    type: 2
+  }
+}
diff --git a/test/trace_processor/translation/chrome_user_event.out b/test/trace_processor/translation/chrome_user_event.out
index b092f7a..2e0630b 100644
--- a/test/trace_processor/translation/chrome_user_event.out
+++ b/test/trace_processor/translation/chrome_user_event.out
@@ -1,9 +1,9 @@
 "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]"
+"source_id","source_id",12345,"[NULL]"
+"chrome_user_event.action_hash","chrome_user_event.action_hash",30,"[NULL]"
 "chrome_user_event.action","chrome_user_event.action","[NULL]","action1"
 "chrome_user_event.action_hash","chrome_user_event.action_hash",10,"[NULL]"
 "chrome_user_event.action","chrome_user_event.action","[NULL]","action2"
 "chrome_user_event.action_hash","chrome_user_event.action_hash",20,"[NULL]"
-"chrome_user_event.action_hash","chrome_user_event.action_hash",30,"[NULL]"
diff --git a/test/trace_processor/translation/chrome_user_event.textproto b/test/trace_processor/translation/chrome_user_event.textproto
index 9dd30c1..14cec5b 100644
--- a/test/trace_processor/translation/chrome_user_event.textproto
+++ b/test/trace_processor/translation/chrome_user_event.textproto
@@ -7,15 +7,33 @@
     }
   }
 }
+
+# Track for slice begin/end events.
+packet {
+  timestamp: 0
+  trusted_packet_sequence_id: 1
+  track_descriptor {
+    uuid: 12345
+    thread {
+      pid: 123
+      tid: 345
+    }
+    parent_uuid: 0
+    chrome_thread {
+      thread_type: THREAD_POOL_FG_WORKER
+    }
+  }
+}
+
 # Known action hash, should be translated to a name
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 1
   track_event {
     categories: "cat1"
-    type: 3
-    name_iid: 1
+    track_uuid: 12345
+    type: 1
+    name: "slice1"
     chrome_user_event {
       action_hash: 10
     }
@@ -24,12 +42,12 @@
 # Another known hash, should be translated to a name
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 2
   track_event {
     categories: "cat2"
-    type: 3
-    name_iid: 2
+    track_uuid: 12345
+    type: 1
+    name: "slice2"
     chrome_user_event {
       action_hash: 20
     }
@@ -38,15 +56,48 @@
 # Unknown hash, should not be translated to any name
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  incremental_state_cleared: true
+  timestamp: 3
   track_event {
     categories: "cat3"
+    track_uuid: 12345
     type: 3
-    name_iid: 3
+    name: "slice3"
     chrome_user_event {
       action_hash: 30
     }
   }
 }
 
+# Slice end events
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6000
+  track_event {
+    track_uuid: 12345
+    categories: "cat3"
+    name: "slice3"
+    type: 2
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6001
+  track_event {
+    track_uuid: 12345
+    categories: "cat2"
+    name: "slice2"
+    type: 2
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6002
+  track_event {
+    track_uuid: 12345
+    categories: "cat1"
+    name: "slice1"
+    type: 2
+  }
+}
diff --git a/test/trace_processor/translation/index b/test/trace_processor/translation/index
index 920499f..a4230a2 100644
--- a/test/trace_processor/translation/index
+++ b/test/trace_processor/translation/index
@@ -3,3 +3,4 @@
 chrome_performance_mark.textproto chrome_args_test.sql chrome_performance_mark.out
 slice_name.textproto slice_name_test.sql slice_name.out
 slice_name_negative_timestamp.textproto slice_name_test.sql slice_name.out
+native_symbol_arg.textproto chrome_args_test.sql native_symbol_arg.out
diff --git a/test/trace_processor/translation/native_symbol_arg.out b/test/trace_processor/translation/native_symbol_arg.out
new file mode 100644
index 0000000..8c9f259
--- /dev/null
+++ b/test/trace_processor/translation/native_symbol_arg.out
@@ -0,0 +1,5 @@
+"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",12345,"[NULL]"
+"chrome_mojo_event_info.mojo_method_name","chrome_mojo_event_info.mojo_method_name","[NULL]","symbolized_func"
diff --git a/test/trace_processor/translation/native_symbol_arg.textproto b/test/trace_processor/translation/native_symbol_arg.textproto
new file mode 100644
index 0000000..9c9da6a
--- /dev/null
+++ b/test/trace_processor/translation/native_symbol_arg.textproto
@@ -0,0 +1,84 @@
+packet {
+  incremental_state_cleared: true
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  interned_data {
+    mapping_paths {
+      iid: 1
+      str: "liblib.so"
+    }
+    build_ids {
+      iid: 2
+      str: "build-id"
+    }
+    mappings {
+        iid: 1
+        path_string_ids: 1
+        build_id: 2
+    }
+    unsymbolized_source_locations {
+        iid: 1
+        mapping_id: 1
+        rel_pc: 123
+    }
+  }
+}
+# Track for slice begin/end events.
+packet {
+  timestamp: 0
+  trusted_packet_sequence_id: 1
+  track_descriptor {
+    uuid: 12345
+    thread {
+      pid: 123
+      tid: 345
+    }
+    parent_uuid: 0
+    chrome_thread {
+      thread_type: THREAD_POOL_FG_WORKER
+    }
+  }
+}
+
+# Slice begin with mojo arg
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 10
+  track_event {
+    track_uuid: 12345
+    categories: "cat1"
+    type: 1
+    name: "slice1"
+    chrome_mojo_event_info {
+        mojo_interface_method_iid: 1
+    }
+  }
+}
+
+# Slice end
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 6000
+  track_event {
+    track_uuid: 12345
+    categories: "cat1"
+    name: "slice1"
+    type: 2
+  }
+}
+
+# Module symbols translation packet
+packet {
+  module_symbols {
+    path: "/liblib.so"
+    build_id: "build-id"
+    address_symbols {
+      address: 123
+      lines {
+        function_name: "symbolized_func"
+        source_file_name: "file.cc"
+        line_number: 33
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index d73d976..ba3e684 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -637,6 +637,16 @@
       user-select: text;
     }
 
+    .track-subtitle {
+      font-size: 0.6rem;
+      font-weight: normal;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      // Maximum width according to grid-template-columns value for .shell
+      width: calc(var(--track-shell-width) - 48px);
+    }
+
     .chip {
       background-color: #bed6ff;
       border-radius: 3px;
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index c89d1d1..0cecb6e 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -109,7 +109,7 @@
     let child = null;
     if (this.summaryTrackState.labels &&
         this.summaryTrackState.labels.length > 0) {
-      child = m('span', this.summaryTrackState.labels.join(', '));
+      child = this.summaryTrackState.labels.join(', ');
     }
 
     return m(
@@ -129,11 +129,15 @@
           m('.fold-button',
             m('i.material-icons',
               this.trackGroupState.collapsed ? EXPAND_DOWN : EXPAND_UP)),
-          m('h1.track-title',
-            {title: name},
-            name,
-            ('namespace' in this.summaryTrackState.config) &&
-                m('span.chip', 'metric')),
+          m('div',
+            m('h1.track-title',
+              {title: name},
+              name,
+              ('namespace' in this.summaryTrackState.config) &&
+                  m('span.chip', 'metric')),
+            (this.trackGroupState.collapsed && child !== null) ?
+                m('h2.track-subtitle', child) :
+                null),
           selection && selection.kind === 'AREA' ?
               m('i.material-icons.track-button',
                 {
@@ -146,10 +150,13 @@
                 checkBox) :
               ''),
 
-        this.summaryTrack ? m(TrackContent,
-                              {track: this.summaryTrack},
-                              this.trackGroupState.collapsed ? '' : child) :
-                            null);
+        this.summaryTrack ?
+            m(TrackContent,
+              {track: this.summaryTrack},
+              (!this.trackGroupState.collapsed && child !== null) ?
+                  m('span', child) :
+                  null) :
+            null);
   }
 
   oncreate(vnode: m.CVnodeDOM<Attrs>) {