Introduce DebugAnnotationParser

Refactor the debug annotation parsing logic into a standalone class,
which later will be used to parse debug annotations for console
interceptor.

R=eseckler@google.com,lalitm@google.com

Change-Id: I6b53706c4b63d455f4df1600b30029b02cc92139
diff --git a/Android.bp b/Android.bp
index 58fadf2..a340617 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1745,8 +1745,10 @@
     ":perfetto_src_trace_processor_tables_tables",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_interned_message_view",
     ":perfetto_src_trace_processor_util_proto_to_args_parser",
     ":perfetto_src_trace_processor_util_protozero_to_text",
+    ":perfetto_src_trace_processor_util_trace_blob_view",
     ":perfetto_src_trace_processor_util_util",
     ":perfetto_src_traced_probes_android_log_android_log",
     ":perfetto_src_traced_probes_common_common",
@@ -8032,10 +8034,16 @@
   ],
 }
 
+// GN: //src/trace_processor/util:interned_message_view
+filegroup {
+  name: "perfetto_src_trace_processor_util_interned_message_view",
+}
+
 // GN: //src/trace_processor/util:proto_to_args_parser
 filegroup {
   name: "perfetto_src_trace_processor_util_proto_to_args_parser",
   srcs: [
+    "src/trace_processor/util/debug_annotation_parser.cc",
     "src/trace_processor/util/proto_to_args_parser.cc",
   ],
 }
@@ -8048,10 +8056,16 @@
   ],
 }
 
+// GN: //src/trace_processor/util:trace_blob_view
+filegroup {
+  name: "perfetto_src_trace_processor_util_trace_blob_view",
+}
+
 // GN: //src/trace_processor/util:unittests
 filegroup {
   name: "perfetto_src_trace_processor_util_unittests",
   srcs: [
+    "src/trace_processor/util/debug_annotation_parser_unittest.cc",
     "src/trace_processor/util/proto_to_args_parser_unittest.cc",
     "src/trace_processor/util/protozero_to_text_unittests.cc",
   ],
@@ -9034,8 +9048,10 @@
     ":perfetto_src_trace_processor_types_unittests",
     ":perfetto_src_trace_processor_unittests",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_interned_message_view",
     ":perfetto_src_trace_processor_util_proto_to_args_parser",
     ":perfetto_src_trace_processor_util_protozero_to_text",
+    ":perfetto_src_trace_processor_util_trace_blob_view",
     ":perfetto_src_trace_processor_util_unittests",
     ":perfetto_src_trace_processor_util_util",
     ":perfetto_src_traced_probes_android_log_android_log",
@@ -9321,8 +9337,10 @@
     ":perfetto_src_trace_processor_tables_tables",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_interned_message_view",
     ":perfetto_src_trace_processor_util_proto_to_args_parser",
     ":perfetto_src_trace_processor_util_protozero_to_text",
+    ":perfetto_src_trace_processor_util_trace_blob_view",
     ":perfetto_src_trace_processor_util_util",
     "src/trace_processor/trace_processor_shell.cc",
     "src/trace_processor/util/proto_to_json.cc",
@@ -9470,8 +9488,10 @@
     ":perfetto_src_trace_processor_tables_tables",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_interned_message_view",
     ":perfetto_src_trace_processor_util_proto_to_args_parser",
     ":perfetto_src_trace_processor_util_protozero_to_text",
+    ":perfetto_src_trace_processor_util_trace_blob_view",
     ":perfetto_src_trace_processor_util_util",
     ":perfetto_tools_trace_to_text_common",
     ":perfetto_tools_trace_to_text_full",
diff --git a/BUILD b/BUILD
index 7c960f8..30bea96 100644
--- a/BUILD
+++ b/BUILD
@@ -843,7 +843,6 @@
         "src/trace_processor/importers/common/slice_tracker.h",
         "src/trace_processor/importers/common/system_info_tracker.cc",
         "src/trace_processor/importers/common/system_info_tracker.h",
-        "src/trace_processor/importers/common/trace_blob_view.h",
         "src/trace_processor/importers/common/trace_parser.h",
         "src/trace_processor/importers/common/track_tracker.cc",
         "src/trace_processor/importers/common/track_tracker.h",
@@ -1113,10 +1112,20 @@
     ],
 )
 
+# GN target: //src/trace_processor/util:interned_message_view
+filegroup(
+    name = "src_trace_processor_util_interned_message_view",
+    srcs = [
+        "src/trace_processor/util/interned_message_view.h",
+    ],
+)
+
 # GN target: //src/trace_processor/util:proto_to_args_parser
 filegroup(
     name = "src_trace_processor_util_proto_to_args_parser",
     srcs = [
+        "src/trace_processor/util/debug_annotation_parser.cc",
+        "src/trace_processor/util/debug_annotation_parser.h",
         "src/trace_processor/util/proto_to_args_parser.cc",
         "src/trace_processor/util/proto_to_args_parser.h",
     ],
@@ -1131,6 +1140,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/util:trace_blob_view
+filegroup(
+    name = "src_trace_processor_util_trace_blob_view",
+    srcs = [
+        "src/trace_processor/util/trace_blob_view.h",
+    ],
+)
+
 # GN target: //src/trace_processor/util:util
 filegroup(
     name = "src_trace_processor_util_util",
@@ -3453,8 +3470,10 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_text",
+        ":src_trace_processor_util_trace_blob_view",
         ":src_trace_processor_util_util",
     ],
     hdrs = [
@@ -3551,8 +3570,10 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_text",
+        ":src_trace_processor_util_trace_blob_view",
         ":src_trace_processor_util_util",
         "src/trace_processor/trace_processor_shell.cc",
         "src/trace_processor/util/proto_to_json.cc",
@@ -3730,8 +3751,10 @@
         ":src_trace_processor_tables_tables",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_text",
+        ":src_trace_processor_util_trace_blob_view",
         ":src_trace_processor_util_util",
         ":tools_trace_to_text_common",
         ":tools_trace_to_text_full",
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 8384f51..f07f3fb 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -154,6 +154,7 @@
     "types",
     "util",
     "util:descriptors",
+    "util:interned_message_view",
     "util:proto_to_args_parser",
   ]
   public_deps = [
@@ -172,6 +173,7 @@
     "../../protos/perfetto/trace/sys_stats:zero",
     "../../protos/perfetto/trace/system_info:zero",
     "../../protos/perfetto/trace/track_event:zero",
+    "util:trace_blob_view",
   ]
 
   # json_utils optionally depends on jsoncpp.
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index e187260..1ffb40a 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -33,7 +33,6 @@
     "slice_tracker.h",
     "system_info_tracker.cc",
     "system_info_tracker.h",
-    "trace_blob_view.h",
     "trace_parser.h",
     "track_tracker.cc",
     "track_tracker.h",
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 4c2c943..f891537 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -64,18 +64,19 @@
 
     // IncrementArrayEntryIndex() and GetNextArrayEntryIndex() provide a way to
     // track the next array index for an array under a specific key.
-    void IncrementArrayEntryIndex(StringId key) {
-      // Zero-initializes |key| in the map if it doesn't exist yet.
-      args_tracker_
-          ->array_indexes_[std::make_tuple(arg_set_id_column_, row_, key)]++;
-    }
-
     size_t GetNextArrayEntryIndex(StringId key) {
       // Zero-initializes |key| in the map if it doesn't exist yet.
       return args_tracker_
           ->array_indexes_[std::make_tuple(arg_set_id_column_, row_, key)];
     }
 
+    // Returns the next available array index after increment.
+    size_t IncrementArrayEntryIndex(StringId key) {
+      // Zero-initializes |key| in the map if it doesn't exist yet.
+      return ++args_tracker_->array_indexes_[std::make_tuple(arg_set_id_column_,
+                                                             row_, key)];
+    }
+
    protected:
     BoundInserter(ArgsTracker* args_tracker,
                   Column* arg_set_id_column,
diff --git a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
index f86bdc5..7464db5 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
@@ -16,10 +16,10 @@
 
 #include "src/trace_processor/importers/ftrace/ftrace_module_impl.h"
 #include "perfetto/base/build_config.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/ftrace/ftrace_parser.h"
 #include "src/trace_processor/importers/ftrace/ftrace_tokenizer.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_module_impl.h b/src/trace_processor/importers/ftrace/ftrace_module_impl.h
index 2ba4d6b..488c2eb 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module_impl.h
+++ b/src/trace_processor/importers/ftrace/ftrace_module_impl.h
@@ -18,12 +18,12 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_MODULE_IMPL_H_
 
 #include "perfetto/base/build_config.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/ftrace/ftrace_parser.h"
 #include "src/trace_processor/importers/ftrace/ftrace_tokenizer.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index f0ef2b9..79e1c65 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -19,12 +19,12 @@
 
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
 #include "src/trace_processor/importers/ftrace/rss_stat_tracker.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
index f4f5416..8f95fca 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
@@ -18,9 +18,9 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_FTRACE_TOKENIZER_H_
 
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_record.h b/src/trace_processor/importers/fuchsia/fuchsia_record.h
index 9518c35..a5063ae 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_record.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_record.h
@@ -17,9 +17,9 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_RECORD_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_RECORD_H_
 
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 #include <vector>
 
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
index 0881fd5..57c4718 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
@@ -18,9 +18,9 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FUCHSIA_FUCHSIA_TRACE_TOKENIZER_H_
 
 #include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
index f6f99d0..73d4f0d 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
@@ -22,8 +22,8 @@
 #include <functional>
 
 #include "perfetto/ext/base/string_view.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.cc b/src/trace_processor/importers/json/json_trace_tokenizer.cc
index cdf7020..695e692 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.cc
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.cc
@@ -21,12 +21,12 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/ext/base/string_utils.h"
 
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/json/json_tracker.h"
 #include "src/trace_processor/importers/json/json_utils.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
index fc5288c..a999f9a 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -24,10 +24,11 @@
 
 #include "perfetto/base/compiler.h"
 #include "perfetto/protozero/proto_decoder.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/interned_message_view.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 #include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
@@ -35,114 +36,6 @@
 namespace perfetto {
 namespace trace_processor {
 
-#if PERFETTO_DCHECK_IS_ON()
-// When called from GetOrCreateDecoder(), should include the stringified name of
-// the MessageType.
-#define PERFETTO_TYPE_IDENTIFIER PERFETTO_DEBUG_FUNCTION_IDENTIFIER()
-#else  // PERFETTO_DCHECK_IS_ON()
-#define PERFETTO_TYPE_IDENTIFIER nullptr
-#endif  // PERFETTO_DCHECK_IS_ON()
-
-// Entry in an interning index, refers to the interned message.
-class InternedMessageView {
- public:
-  InternedMessageView(TraceBlobView msg) : message_(std::move(msg)) {}
-
-  InternedMessageView(InternedMessageView&&) = default;
-  InternedMessageView& operator=(InternedMessageView&&) = default;
-
-  // Allow copy by cloning the TraceBlobView. This is required for
-  // UpdateTracePacketDefaults().
-  InternedMessageView(const InternedMessageView& view)
-      : message_(view.message_.slice(0, view.message_.length())) {}
-  InternedMessageView& operator=(const InternedMessageView& view) {
-    this->message_ = view.message_.slice(0, view.message_.length());
-    this->decoder_ = nullptr;
-    this->decoder_type_ = nullptr;
-    this->submessages_.clear();
-    return *this;
-  }
-
-  // Lazily initializes and returns the decoder object for the message. The
-  // decoder is stored in the InternedMessageView to avoid having to parse the
-  // message multiple times.
-  template <typename MessageType>
-  typename MessageType::Decoder* GetOrCreateDecoder() {
-    if (!decoder_) {
-      // Lazy init the decoder and save it away, so that we don't have to
-      // reparse the message every time we access the interning entry.
-      decoder_ = std::unique_ptr<void, std::function<void(void*)>>(
-          new typename MessageType::Decoder(message_.data(), message_.length()),
-          [](void* obj) {
-            delete reinterpret_cast<typename MessageType::Decoder*>(obj);
-          });
-      decoder_type_ = PERFETTO_TYPE_IDENTIFIER;
-    }
-    // Verify that the type of the decoder didn't change.
-    if (PERFETTO_TYPE_IDENTIFIER &&
-        strcmp(decoder_type_,
-               // GCC complains if this arg can be null.
-               PERFETTO_TYPE_IDENTIFIER ? PERFETTO_TYPE_IDENTIFIER : "") != 0) {
-      PERFETTO_FATAL(
-          "Interning entry accessed under different types! previous type: "
-          "%s. new type: %s.",
-          decoder_type_, PERFETTO_DEBUG_FUNCTION_IDENTIFIER());
-    }
-    return reinterpret_cast<typename MessageType::Decoder*>(decoder_.get());
-  }
-
-  // Lookup a submessage of the interned message, which is then itself stored
-  // as InternedMessageView, so that we only need to parse it once. Returns
-  // nullptr if the field isn't set.
-  // TODO(eseckler): Support repeated fields.
-  template <typename MessageType, uint32_t FieldId>
-  InternedMessageView* GetOrCreateSubmessageView() {
-    auto it = submessages_.find(FieldId);
-    if (it != submessages_.end())
-      return it->second.get();
-    auto* decoder = GetOrCreateDecoder<MessageType>();
-    // Calls the at() template method on the decoder.
-    auto field = decoder->template at<FieldId>().as_bytes();
-    if (!field.data)
-      return nullptr;
-    const size_t offset = message_.offset_of(field.data);
-    TraceBlobView submessage = message_.slice(offset, field.size);
-    InternedMessageView* submessage_view =
-        new InternedMessageView(std::move(submessage));
-    submessages_.emplace_hint(
-        it, FieldId, std::unique_ptr<InternedMessageView>(submessage_view));
-    return submessage_view;
-  }
-
-  const TraceBlobView& message() { return message_; }
-
- private:
-  using SubMessageViewMap =
-      std::unordered_map<uint32_t /*field_id*/,
-                         std::unique_ptr<InternedMessageView>>;
-
-  TraceBlobView message_;
-
-  // Stores the decoder for the message_, so that the message does not have to
-  // be re-decoded every time the interned message is looked up. Lazily
-  // initialized in GetOrCreateDecoder(). Since we don't know the type of the
-  // decoder until GetOrCreateDecoder() is called, we store the decoder as a
-  // void* unique_pointer with a destructor function that's supplied in
-  // GetOrCreateDecoder() when the decoder is created.
-  std::unique_ptr<void, std::function<void(void*)>> decoder_;
-
-  // Type identifier for the decoder. Only valid in debug builds and on
-  // supported platforms. Used to verify that GetOrCreateDecoder() is always
-  // called with the same template argument.
-  const char* decoder_type_ = nullptr;
-
-  // Views of submessages of the interned message. Submessages are lazily
-  // added by GetOrCreateSubmessageView(). By storing submessages and their
-  // decoders, we avoid having to decode submessages multiple times if they
-  // looked up often.
-  SubMessageViewMap submessages_;
-};
-
 using InternedMessageMap =
     std::unordered_map<uint64_t /*iid*/, InternedMessageView>;
 using InternedFieldMap =
diff --git a/src/trace_processor/importers/proto/proto_importer_module.h b/src/trace_processor/importers/proto/proto_importer_module.h
index d0a023a..9a1dd5b 100644
--- a/src/trace_processor/importers/proto/proto_importer_module.h
+++ b/src/trace_processor/importers/proto/proto_importer_module.h
@@ -19,7 +19,7 @@
 
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/trace_processor/status.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.h b/src/trace_processor/importers/proto/proto_trace_parser.h
index 53a6099..f33bc5f 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser.h
@@ -25,10 +25,10 @@
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/field.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h
index b2f4069..9f7a403 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.h
+++ b/src/trace_processor/importers/proto/proto_trace_reader.h
@@ -22,9 +22,9 @@
 #include <memory>
 
 #include "src/trace_processor/importers/common/chunked_trace_reader.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/proto/proto_incremental_state.h"
 #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace protozero {
 struct ConstBytes;
diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.h b/src/trace_processor/importers/proto/proto_trace_tokenizer.h
index 76a0f42..484c674 100644
--- a/src/trace_processor/importers/proto/proto_trace_tokenizer.h
+++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.h
@@ -21,9 +21,9 @@
 
 #include "perfetto/protozero/proto_utils.h"
 #include "perfetto/trace_processor/status.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/gzip/gzip_utils.h"
 #include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 8ad8177..8b93efd 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/json/json_utils.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.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"
 #include "src/trace_processor/util/status_macros.h"
 
@@ -107,10 +108,21 @@
                      storage_.InternString(base::StringView(key.key)),
                      Variadic::Boolean(value));
   }
-  void AddJson(const Key& key, const protozero::ConstChars& value) final {
-    inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)),
-                     storage_.InternString(base::StringView(key.key)),
-                     Variadic::Json(storage_.InternString(value)));
+  bool AddJson(const Key& key, const protozero::ConstChars& value) final {
+    auto json_value = json::ParseJsonString(value);
+    return json::AddJsonValueToArgs(*json_value, base::StringView(key.flat_key),
+                                    base::StringView(key.key), &storage_,
+                                    &inserter_);
+  }
+
+  size_t GetArrayEntryIndex(const std::string& array_key) final {
+    return inserter_.GetNextArrayEntryIndex(
+        storage_.InternString(base::StringView(array_key)));
+  }
+
+  size_t IncrementArrayEntryIndex(const std::string& array_key) final {
+    return inserter_.IncrementArrayEntryIndex(
+        storage_.InternString(base::StringView(array_key)));
   }
 
   InternedMessageView* GetInternedMessageView(uint32_t field_id,
@@ -148,13 +160,6 @@
   return base::OkStatus();
 }
 
-std::string SanitizeDebugAnnotationName(const std::string& raw_name) {
-  std::string result = raw_name;
-  std::replace(result.begin(), result.end(), '.', '_');
-  std::replace(result.begin(), result.end(), '[', '_');
-  std::replace(result.begin(), result.end(), ']', '_');
-  return result;
-}
 }  // namespace
 
 class TrackEventParser::EventImporter {
@@ -1092,10 +1097,6 @@
       PERFETTO_DLOG("ParseTrackEventArgs error: %s", status.c_message());
     };
 
-    for (auto it = event_.debug_annotations(); it; ++it) {
-      log_errors(ParseDebugAnnotation(*it, inserter));
-    }
-
     if (event_.has_source_location_iid()) {
       log_errors(AddSourceLocationArgs(event_.source_location_iid(), inserter));
     }
@@ -1116,6 +1117,14 @@
         blob_, ".perfetto.protos.TrackEvent", &parser_->reflect_fields_,
         args_writer));
 
+    {
+      auto key = parser_->args_parser_.EnterDictionary("debug");
+      util::DebugAnnotationParser parser(parser_->args_parser_);
+      for (auto it = event_.debug_annotations(); it; ++it) {
+        log_errors(parser.Parse(*it, args_writer));
+      }
+    }
+
     if (legacy_passthrough_utid_) {
       inserter->AddArg(parser_->legacy_event_passthrough_utid_id_,
                        Variadic::UnsignedInteger(*legacy_passthrough_utid_),
@@ -1123,170 +1132,6 @@
     }
   }
 
-  util::Status ParseDebugAnnotation(ConstBytes data, BoundInserter* inserter) {
-    protos::pbzero::DebugAnnotation::Decoder annotation(data);
-
-    std::string name;
-    util::Status name_parse_result = ParseDebugAnnotationName(annotation, name);
-    if (!name_parse_result.ok())
-      return name_parse_result;
-
-    return ParseDebugAnnotationValue(annotation, inserter, "debug." + name);
-  }
-
-  util::Status ParseDebugAnnotationName(
-      protos::pbzero::DebugAnnotation::Decoder& annotation,
-      std::string& result) {
-    uint64_t name_iid = annotation.name_iid();
-    if (PERFETTO_LIKELY(name_iid)) {
-      auto* decoder = sequence_state_->LookupInternedMessage<
-          protos::pbzero::InternedData::kDebugAnnotationNamesFieldNumber,
-          protos::pbzero::DebugAnnotationName>(name_iid);
-      if (!decoder)
-        return util::ErrStatus("Debug annotation with invalid name_iid");
-
-      result = SanitizeDebugAnnotationName(decoder->name().ToStdString());
-    } else if (annotation.has_name()) {
-      result = SanitizeDebugAnnotationName(annotation.name().ToStdString());
-    } else {
-      return util::ErrStatus("Debug annotation without name");
-    }
-    return util::OkStatus();
-  }
-
-  util::Status ParseDebugAnnotationValue(
-      protos::pbzero::DebugAnnotation::Decoder& annotation,
-      BoundInserter* inserter,
-      const std::string& context_name) {
-    StringId name_id = storage_->InternString(base::StringView(context_name));
-
-    if (annotation.has_bool_value()) {
-      inserter->AddArg(name_id, Variadic::Boolean(annotation.bool_value()));
-    } else if (annotation.has_uint_value()) {
-      inserter->AddArg(name_id,
-                       Variadic::UnsignedInteger(annotation.uint_value()));
-    } else if (annotation.has_int_value()) {
-      inserter->AddArg(name_id, Variadic::Integer(annotation.int_value()));
-    } else if (annotation.has_double_value()) {
-      inserter->AddArg(name_id, Variadic::Real(annotation.double_value()));
-    } else if (annotation.has_string_value()) {
-      inserter->AddArg(
-          name_id,
-          Variadic::String(storage_->InternString(annotation.string_value())));
-    } else if (annotation.has_pointer_value()) {
-      inserter->AddArg(name_id, Variadic::Pointer(annotation.pointer_value()));
-    } else if (annotation.has_dict_entries()) {
-      for (auto it = annotation.dict_entries(); it; ++it) {
-        protos::pbzero::DebugAnnotation::Decoder key_value(*it);
-        std::string key;
-        util::Status key_parse_result =
-            ParseDebugAnnotationName(key_value, key);
-        if (!key_parse_result.ok())
-          return key_parse_result;
-
-        std::string child_flat_key = context_name + "." + key;
-        util::Status value_parse_result =
-            ParseDebugAnnotationValue(key_value, inserter, child_flat_key);
-        if (!value_parse_result.ok())
-          return value_parse_result;
-      }
-    } else if (annotation.has_array_values()) {
-      size_t index = 0;
-      for (auto it = annotation.array_values(); it; ++it) {
-        protos::pbzero::DebugAnnotation::Decoder value(*it);
-
-        std::string child_flat_key =
-            context_name + "[" + std::to_string(index) + "]";
-        util::Status value_parse_result =
-            ParseDebugAnnotationValue(value, inserter, child_flat_key);
-        if (!value_parse_result.ok())
-          return value_parse_result;
-        ++index;
-      }
-    } else if (annotation.has_legacy_json_value()) {
-      if (!json::IsJsonSupported())
-        return util::ErrStatus("Ignoring legacy_json_value (no json support)");
-
-      auto value = json::ParseJsonString(annotation.legacy_json_value());
-      auto name = storage_->GetString(name_id);
-      json::AddJsonValueToArgs(*value, name, name, storage_, inserter);
-    } else if (annotation.has_nested_value()) {
-      auto name = storage_->GetString(name_id);
-      ParseNestedValueArgs(annotation.nested_value(), name, name, inserter);
-    }
-
-    return util::OkStatus();
-  }
-
-  bool ParseNestedValueArgs(ConstBytes nested_value,
-                            base::StringView flat_key,
-                            base::StringView key,
-                            BoundInserter* inserter) {
-    protos::pbzero::DebugAnnotation::NestedValue::Decoder value(nested_value);
-    switch (value.nested_type()) {
-      case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
-        auto flat_key_id = storage_->InternString(flat_key);
-        auto key_id = storage_->InternString(key);
-        // Leaf value.
-        if (value.has_bool_value()) {
-          inserter->AddArg(flat_key_id, key_id,
-                           Variadic::Boolean(value.bool_value()));
-          return true;
-        }
-        if (value.has_int_value()) {
-          inserter->AddArg(flat_key_id, key_id,
-                           Variadic::Integer(value.int_value()));
-          return true;
-        }
-        if (value.has_double_value()) {
-          inserter->AddArg(flat_key_id, key_id,
-                           Variadic::Real(value.double_value()));
-          return true;
-        }
-        if (value.has_string_value()) {
-          inserter->AddArg(
-              flat_key_id, key_id,
-              Variadic::String(storage_->InternString(value.string_value())));
-          return true;
-        }
-        return false;
-      }
-      case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
-        auto key_it = value.dict_keys();
-        auto value_it = value.dict_values();
-        bool inserted = false;
-        for (; key_it && value_it; ++key_it, ++value_it) {
-          std::string child_name =
-              SanitizeDebugAnnotationName((*key_it).ToStdString());
-          std::string child_flat_key =
-              flat_key.ToStdString() + "." + child_name;
-          std::string child_key = key.ToStdString() + "." + child_name;
-          inserted |=
-              ParseNestedValueArgs(*value_it, base::StringView(child_flat_key),
-                                   base::StringView(child_key), inserter);
-        }
-        return inserted;
-      }
-      case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
-        bool inserted_any = false;
-        std::string array_key = key.ToStdString();
-        StringId array_key_id = storage_->InternString(key);
-        for (auto value_it = value.array_values(); value_it; ++value_it) {
-          size_t array_index = inserter->GetNextArrayEntryIndex(array_key_id);
-          std::string child_key =
-              array_key + "[" + std::to_string(array_index) + "]";
-          bool inserted = ParseNestedValueArgs(
-              *value_it, flat_key, base::StringView(child_key), inserter);
-          if (inserted)
-            inserter->IncrementArrayEntryIndex(array_key_id);
-          inserted_any |= inserted;
-        }
-        return inserted_any;
-      }
-    }
-    return false;
-  }
-
   util::Status ParseTaskExecutionArgs(ConstBytes task_execution,
                                       BoundInserter* inserter) {
     protos::pbzero::TaskExecution::Decoder task(task_execution);
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index 9d28e46..056d342 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -19,7 +19,6 @@
 #include "perfetto/base/logging.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/proto_trace_reader.h"
@@ -27,6 +26,7 @@
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/trace_sorter.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
diff --git a/src/trace_processor/timestamped_trace_piece.h b/src/trace_processor/timestamped_trace_piece.h
index f775443..303e234 100644
--- a/src/trace_processor/timestamped_trace_piece.h
+++ b/src/trace_processor/timestamped_trace_piece.h
@@ -19,13 +19,13 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/fuchsia/fuchsia_record.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/systrace/systrace_line.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 // GCC can't figure out the relationship between TimestampedTracePiece's type
 // and the union, and thus thinks that we may be moving or destroying
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index cca6947..e9c0f37 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -25,7 +25,6 @@
 #include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/default_modules.h"
 #include "src/trace_processor/importers/proto/async_track_set_tracker.h"
@@ -38,6 +37,7 @@
 #include "src/trace_processor/importers/track_event.descriptor.h"
 #include "src/trace_processor/trace_sorter.h"
 #include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 614d627..f654163 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -21,9 +21,9 @@
 
 #include "perfetto/ext/base/circular_queue.h"
 #include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 
 namespace Json {
 class Value;
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 02887ac..31925e8 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -45,6 +45,23 @@
   ]
 }
 
+source_set("trace_blob_view") {
+  sources = [ "trace_blob_view.h" ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../base",
+  ]
+}
+
+source_set("interned_message_view") {
+  sources = [ "interned_message_view.h" ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../base",
+  ]
+  public_deps = [ ":trace_blob_view" ]
+}
+
 source_set("descriptors") {
   sources = [
     "descriptors.cc",
@@ -55,14 +72,16 @@
     "../../../gn:default_deps",
     "../../../protos/perfetto/common:zero",
     "../../../protos/perfetto/trace_processor:zero",
+    "../../base",
     "../../protozero",
     "../importers:gen_cc_track_event_descriptor",
   ]
-  public_deps = [ "../../base" ]
 }
 
 source_set("proto_to_args_parser") {
   sources = [
+    "debug_annotation_parser.cc",
+    "debug_annotation_parser.h",
     "proto_to_args_parser.cc",
     "proto_to_args_parser.h",
   ]
@@ -70,6 +89,7 @@
     "../../../gn:default_deps",
     "../../../protos/perfetto/common:zero",
     "../../../protos/perfetto/trace/interned_data:zero",
+    "../../../protos/perfetto/trace/track_event:zero",
     "../../../protos/perfetto/trace_processor:zero",
     "../../protozero",
     "../importers:gen_cc_track_event_descriptor",
@@ -77,17 +97,15 @@
 
   public_deps = [
     ":descriptors",
+    ":interned_message_view",
     ":util",
     "../../base",
-
-    # TODO(altimin): Move InternedMessageView and TraceBlobView here and remove
-    # this dependency.
-    "../importers/common",
   ]
 }
 
 source_set("unittests") {
   sources = [
+    "debug_annotation_parser_unittest.cc",
     "proto_to_args_parser_unittest.cc",
     "protozero_to_text_unittests.cc",
   ]
@@ -104,9 +122,5 @@
     "../../protozero",
     "../../protozero:testing_messages_zero",
     "../importers:gen_cc_track_event_descriptor",
-
-    # TODO(altimin): Move InternedMessageView and TraceBlobView here and remove
-    # this dependency.
-    "..:storage_minimal",
   ]
 }
diff --git a/src/trace_processor/util/debug_annotation_parser.cc b/src/trace_processor/util/debug_annotation_parser.cc
new file mode 100644
index 0000000..eeaf39f
--- /dev/null
+++ b/src/trace_processor/util/debug_annotation_parser.cc
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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/util/debug_annotation_parser.h"
+#include "perfetto/base/build_config.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+#include "src/trace_processor/util/interned_message_view.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace util {
+
+namespace {
+
+std::string SanitizeDebugAnnotationName(const std::string& raw_name) {
+  std::string result = raw_name;
+  std::replace(result.begin(), result.end(), '.', '_');
+  std::replace(result.begin(), result.end(), '[', '_');
+  std::replace(result.begin(), result.end(), ']', '_');
+  return result;
+}
+
+bool IsJsonSupported() {
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+  return true;
+#else
+  return false;
+#endif
+}
+
+}  // namespace
+
+DebugAnnotationParser::DebugAnnotationParser(ProtoToArgsParser& parser)
+    : proto_to_args_parser_(parser) {}
+
+base::Status DebugAnnotationParser::ParseDebugAnnotationName(
+    protos::pbzero::DebugAnnotation::Decoder& annotation,
+    ProtoToArgsParser::Delegate& delegate,
+    std::string& result) {
+  uint64_t name_iid = annotation.name_iid();
+  if (PERFETTO_LIKELY(name_iid)) {
+    auto* decoder = delegate.GetInternedMessage(
+        protos::pbzero::InternedData::kDebugAnnotationNames, name_iid);
+    if (!decoder)
+      return base::ErrStatus("Debug annotation with invalid name_iid");
+
+    result = SanitizeDebugAnnotationName(decoder->name().ToStdString());
+  } else if (annotation.has_name()) {
+    result = SanitizeDebugAnnotationName(annotation.name().ToStdString());
+  } else {
+    return base::ErrStatus("Debug annotation without name");
+  }
+  return base::OkStatus();
+}
+
+DebugAnnotationParser::ParseResult
+DebugAnnotationParser::ParseDebugAnnotationValue(
+    protos::pbzero::DebugAnnotation::Decoder& annotation,
+    ProtoToArgsParser::Delegate& delegate,
+    const ProtoToArgsParser::Key& context_name) {
+  if (annotation.has_bool_value()) {
+    delegate.AddBoolean(context_name, annotation.bool_value());
+  } else if (annotation.has_uint_value()) {
+    delegate.AddUnsignedInteger(context_name, annotation.uint_value());
+  } else if (annotation.has_int_value()) {
+    delegate.AddInteger(context_name, annotation.int_value());
+  } else if (annotation.has_double_value()) {
+    delegate.AddDouble(context_name, annotation.double_value());
+  } else if (annotation.has_string_value()) {
+    delegate.AddString(context_name, annotation.string_value());
+  } else if (annotation.has_pointer_value()) {
+    delegate.AddPointer(context_name, reinterpret_cast<const void*>(
+                                          annotation.pointer_value()));
+  } else if (annotation.has_dict_entries()) {
+    bool added_entry = false;
+    for (auto it = annotation.dict_entries(); it; ++it) {
+      protos::pbzero::DebugAnnotation::Decoder key_value(*it);
+      std::string key;
+      base::Status key_parse_result =
+          ParseDebugAnnotationName(key_value, delegate, key);
+      if (!key_parse_result.ok())
+        return {key_parse_result, added_entry};
+
+      auto nested_key = proto_to_args_parser_.EnterDictionary(key);
+      ParseResult value_parse_result =
+          ParseDebugAnnotationValue(key_value, delegate, nested_key.key());
+      added_entry |= value_parse_result.added_entry;
+      if (!value_parse_result.status.ok())
+        return {value_parse_result.status, added_entry};
+    }
+  } else if (annotation.has_array_values()) {
+    size_t index = delegate.GetArrayEntryIndex(context_name.key);
+    bool added_entry = false;
+    for (auto it = annotation.array_values(); it; ++it) {
+      protos::pbzero::DebugAnnotation::Decoder value(*it);
+
+      auto nested_key = proto_to_args_parser_.EnterArray(index);
+      ParseResult value_parse_result =
+          ParseDebugAnnotationValue(value, delegate, nested_key.key());
+
+      // Reset the key here to ensure that we have the correct array key to
+      // increment.
+      nested_key.Reset();
+
+      if (value_parse_result.added_entry) {
+        index = delegate.IncrementArrayEntryIndex(context_name.key);
+        added_entry = true;
+      }
+      if (!value_parse_result.status.ok())
+        return {value_parse_result.status, added_entry};
+    }
+  } else if (annotation.has_legacy_json_value()) {
+    if (!IsJsonSupported())
+      return {base::ErrStatus("Ignoring legacy_json_value (no json support)"),
+              false};
+
+    bool added_entry =
+        delegate.AddJson(context_name, annotation.legacy_json_value());
+    return {base::OkStatus(), added_entry};
+  } else if (annotation.has_nested_value()) {
+    return ParseNestedValueArgs(annotation.nested_value(), context_name,
+                                delegate);
+  } else {
+    return {base::OkStatus(), /*added_entry=*/false};
+  }
+
+  return {base::OkStatus(), /*added_entry=*/true};
+}
+
+// static
+base::Status DebugAnnotationParser::Parse(
+    protozero::ConstBytes data,
+    ProtoToArgsParser::Delegate& delegate) {
+  protos::pbzero::DebugAnnotation::Decoder annotation(data);
+
+  std::string name;
+  base::Status name_parse_result =
+      ParseDebugAnnotationName(annotation, delegate, name);
+  if (!name_parse_result.ok())
+    return name_parse_result;
+
+  auto context = proto_to_args_parser_.EnterDictionary(name);
+
+  return ParseDebugAnnotationValue(annotation, delegate, context.key()).status;
+}
+
+DebugAnnotationParser::ParseResult DebugAnnotationParser::ParseNestedValueArgs(
+    protozero::ConstBytes nested_value,
+    const ProtoToArgsParser::Key& context_name,
+    ProtoToArgsParser::Delegate& delegate) {
+  protos::pbzero::DebugAnnotation::NestedValue::Decoder value(nested_value);
+  switch (value.nested_type()) {
+    case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
+      // Leaf value.
+      if (value.has_bool_value()) {
+        delegate.AddBoolean(context_name, value.bool_value());
+        return {base::OkStatus(), true};
+      }
+      if (value.has_int_value()) {
+        delegate.AddInteger(context_name, value.int_value());
+        return {base::OkStatus(), true};
+      }
+      if (value.has_double_value()) {
+        delegate.AddDouble(context_name, value.double_value());
+        return {base::OkStatus(), true};
+      }
+      if (value.has_string_value()) {
+        delegate.AddString(context_name, value.string_value());
+        return {base::OkStatus(), true};
+      }
+      return {base::OkStatus(), false};
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
+      bool added_entry = false;
+      auto key_it = value.dict_keys();
+      auto value_it = value.dict_values();
+      for (; key_it && value_it; ++key_it, ++value_it) {
+        std::string child_name =
+            SanitizeDebugAnnotationName((*key_it).ToStdString());
+        auto nested_key = proto_to_args_parser_.EnterDictionary(child_name);
+        ParseResult result =
+            ParseNestedValueArgs(*value_it, nested_key.key(), delegate);
+        added_entry |= result.added_entry;
+        if (!result.status.ok())
+          return {result.status, added_entry};
+      }
+      return {base::OkStatus(), true};
+    }
+
+    case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
+      std::string array_key = context_name.key;
+      size_t array_index = delegate.GetArrayEntryIndex(context_name.key);
+      bool added_entry = false;
+
+      for (auto value_it = value.array_values(); value_it; ++value_it) {
+        auto nested_key = proto_to_args_parser_.EnterArray(array_index);
+        ParseResult result =
+            ParseNestedValueArgs(*value_it, nested_key.key(), delegate);
+
+        if (result.added_entry) {
+          ++array_index;
+          delegate.IncrementArrayEntryIndex(array_key);
+          added_entry = true;
+        }
+        if (!result.status.ok())
+          return {result.status, added_entry};
+      }
+      return {base::OkStatus(), added_entry};
+    }
+  }
+  return {base::OkStatus(), false};
+}
+
+}  // namespace util
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/util/debug_annotation_parser.h b/src/trace_processor/util/debug_annotation_parser.h
new file mode 100644
index 0000000..0e68eec
--- /dev/null
+++ b/src/trace_processor/util/debug_annotation_parser.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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_UTIL_DEBUG_ANNOTATION_PARSER_H_
+#define SRC_TRACE_PROCESSOR_UTIL_DEBUG_ANNOTATION_PARSER_H_
+
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace util {
+
+// |DebugAnnotationParser| is responsible for parsing DebugAnnotation protos
+// and turning it into key-value arg pairs.
+// |DebugAnnotationParser| is a logical extension of |ProtoToArgsParser|:
+// it uses |ProtoToArgsParser::Delegate| for writing the results and uses
+// |ProtoToArgsParser| to parse arbitrary protos nested inside DebugAnnotation.
+class DebugAnnotationParser {
+ public:
+  explicit DebugAnnotationParser(ProtoToArgsParser& proto_to_args_parser);
+
+  base::Status Parse(protozero::ConstBytes data,
+                     ProtoToArgsParser::Delegate& delegate);
+
+ private:
+  struct ParseResult {
+    base::Status status;
+    // True if parsing of the annotation added at least one entry to the
+    // |delegate|.
+    bool added_entry;
+  };
+
+  base::Status ParseDebugAnnotationName(
+      protos::pbzero::DebugAnnotation::Decoder& annotation,
+      ProtoToArgsParser::Delegate& delegate,
+      std::string& result);
+  ParseResult ParseDebugAnnotationValue(
+      protos::pbzero::DebugAnnotation::Decoder& annotation,
+      ProtoToArgsParser::Delegate& delegate,
+      const ProtoToArgsParser::Key& context_name);
+  ParseResult ParseNestedValueArgs(protozero::ConstBytes nested_value,
+                                   const ProtoToArgsParser::Key& context_name,
+                                   ProtoToArgsParser::Delegate& delegate);
+
+  ProtoToArgsParser& proto_to_args_parser_;
+};
+
+}  // namespace util
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_UTIL_DEBUG_ANNOTATION_PARSER_H_
diff --git a/src/trace_processor/util/debug_annotation_parser_unittest.cc b/src/trace_processor/util/debug_annotation_parser_unittest.cc
new file mode 100644
index 0000000..e3f3380
--- /dev/null
+++ b/src/trace_processor/util/debug_annotation_parser_unittest.cc
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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/util/debug_annotation_parser.h"
+
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/common/descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
+#include "src/protozero/test/example_proto/test_messages.pbzero.h"
+#include "src/trace_processor/test_messages.descriptor.h"
+#include "src/trace_processor/util/interned_message_view.h"
+#include "src/trace_processor/util/proto_to_args_parser.h"
+#include "src/trace_processor/util/trace_blob_view.h"
+#include "test/gtest_and_gmock.h"
+
+#include <sstream>
+
+namespace perfetto {
+namespace trace_processor {
+namespace util {
+namespace {
+
+base::Status ParseDebugAnnotation(
+    DebugAnnotationParser& parser,
+    protozero::HeapBuffered<protos::pbzero::DebugAnnotation>& msg,
+    ProtoToArgsParser::Delegate& delegate) {
+  std::vector<uint8_t> data = msg.SerializeAsArray();
+  return parser.Parse(protozero::ConstBytes{data.data(), data.size()},
+                      delegate);
+}
+
+class DebugAnnotationParserTest : public ::testing::Test,
+                                  public ProtoToArgsParser::Delegate {
+ protected:
+  DebugAnnotationParserTest() {}
+
+  const std::vector<std::string>& args() const { return args_; }
+
+ private:
+  using Key = ProtoToArgsParser::Key;
+
+  void AddInteger(const Key& key, int64_t value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << value;
+    args_.push_back(ss.str());
+  }
+
+  void AddUnsignedInteger(const Key& key, uint64_t value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << value;
+    args_.push_back(ss.str());
+  }
+
+  void AddString(const Key& key, const protozero::ConstChars& value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << value.ToStdString();
+    args_.push_back(ss.str());
+  }
+
+  void AddDouble(const Key& key, double value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << value;
+    args_.push_back(ss.str());
+  }
+
+  void AddPointer(const Key& key, const void* value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << std::hex
+       << reinterpret_cast<uintptr_t>(value) << std::dec;
+    args_.push_back(ss.str());
+  }
+
+  void AddBoolean(const Key& key, bool value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << (value ? "true" : "false");
+    args_.push_back(ss.str());
+  }
+
+  bool AddJson(const Key& key, const protozero::ConstChars& value) override {
+    std::stringstream ss;
+    ss << key.flat_key << " " << key.key << " " << std::hex
+       << value.ToStdString() << std::dec;
+    args_.push_back(ss.str());
+    return true;
+  }
+
+  size_t GetArrayEntryIndex(const std::string& array_key) final {
+    return array_indices_[array_key];
+  }
+
+  size_t IncrementArrayEntryIndex(const std::string& array_key) final {
+    return ++array_indices_[array_key];
+  }
+
+  InternedMessageView* GetInternedMessageView(uint32_t, uint64_t) override {
+    return nullptr;
+  }
+
+  std::vector<std::string> args_;
+  std::map<std::string, size_t> array_indices_;
+};
+
+// This test checks that in when an array is nested inside a dict which is
+// nested inside an array which is nested inside a dict, flat keys and non-flat
+// keys are parsed correctly.
+TEST_F(DebugAnnotationParserTest, DeeplyNestedDictsAndArrays) {
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> msg;
+
+  msg->set_name("root");
+  auto* dict1 = msg->add_dict_entries();
+  dict1->set_name("k1");
+  auto* array1 = dict1->add_array_values();
+  auto* dict2 = array1->add_dict_entries();
+  dict2->set_name("k2");
+  auto* array2 = dict2->add_array_values();
+  array2->set_int_value(42);
+
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTestMessagesDescriptor.data(),
+                                              kTestMessagesDescriptor.size());
+  EXPECT_TRUE(status.ok()) << "Failed to parse kTestMessagesDescriptor: "
+                           << status.message();
+
+  ProtoToArgsParser args_parser(pool);
+  DebugAnnotationParser parser(args_parser);
+
+  status = ParseDebugAnnotation(parser, msg, *this);
+  EXPECT_TRUE(status.ok()) << "DebugAnnotationParser::Parse failed with error: "
+                           << status.message();
+
+  EXPECT_THAT(args(), testing::ElementsAre("root.k1.k2 root.k1[0].k2[0] 42"));
+}
+
+// This test checks that array indexes are correctly merged across messages.
+TEST_F(DebugAnnotationParserTest, MergeArrays) {
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> msg1;
+  msg1->set_name("root");
+  auto* item1 = msg1->add_array_values();
+  item1->set_int_value(1);
+
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> msg2;
+  msg2->set_name("root");
+  auto* item2 = msg1->add_array_values();
+  item2->set_int_value(2);
+
+  DescriptorPool pool;
+  ProtoToArgsParser args_parser(pool);
+  DebugAnnotationParser parser(args_parser);
+
+  base::Status status = ParseDebugAnnotation(parser, msg1, *this);
+  EXPECT_TRUE(status.ok()) << "DebugAnnotationParser::Parse failed with error: "
+                           << status.message();
+
+  status = ParseDebugAnnotation(parser, msg2, *this);
+  EXPECT_TRUE(status.ok()) << "DebugAnnotationParser::Parse failed with error: "
+                           << status.message();
+
+  EXPECT_THAT(args(), testing::ElementsAre("root root[0] 1", "root root[1] 2"));
+}
+
+// This test checks that nested empty dictionaries / arrays do not cause array
+// index to be incremented.
+TEST_F(DebugAnnotationParserTest, EmptyArrayIndexIsSkipped) {
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> msg;
+  msg->set_name("root");
+
+  msg->add_array_values()->set_int_value(1);
+
+  // Empty item.
+  msg->add_array_values();
+
+  msg->add_array_values()->set_int_value(3);
+
+  // Empty dict.
+  msg->add_array_values()->add_dict_entries()->set_name("key1");
+
+  auto* nested_dict_entry = msg->add_array_values()->add_dict_entries();
+  nested_dict_entry->set_name("key2");
+  nested_dict_entry->set_string_value("value");
+
+  msg->add_array_values()->set_int_value(5);
+
+  DescriptorPool pool;
+  ProtoToArgsParser args_parser(pool);
+  DebugAnnotationParser parser(args_parser);
+
+  base::Status status = ParseDebugAnnotation(parser, msg, *this);
+  EXPECT_TRUE(status.ok()) << "DebugAnnotationParser::Parse failed with error: "
+                           << status.message();
+
+  EXPECT_THAT(args(), testing::ElementsAre("root root[0] 1", "root root[1] 3",
+                                           "root.key2 root[3].key2 value",
+                                           "root root[4] 5"));
+}
+
+TEST_F(DebugAnnotationParserTest, NestedArrays) {
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> msg;
+  msg->set_name("root");
+  auto* item1 = msg->add_array_values();
+  item1->add_array_values()->set_int_value(1);
+  item1->add_array_values()->set_int_value(2);
+  auto* item2 = msg->add_array_values();
+  item2->add_array_values()->set_int_value(3);
+  item2->add_array_values()->set_int_value(4);
+
+  DescriptorPool pool;
+  ProtoToArgsParser args_parser(pool);
+  DebugAnnotationParser parser(args_parser);
+
+  base::Status status = ParseDebugAnnotation(parser, msg, *this);
+  EXPECT_TRUE(status.ok()) << "DebugAnnotationParser::Parse failed with error: "
+                           << status.message();
+
+  EXPECT_THAT(args(),
+              testing::ElementsAre("root root[0][0] 1", "root root[0][1] 2",
+                                   "root root[1][0] 3", "root root[1][1] 4"));
+}
+
+}  // namespace
+}  // namespace util
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/util/interned_message_view.h b/src/trace_processor/util/interned_message_view.h
new file mode 100644
index 0000000..7341aea
--- /dev/null
+++ b/src/trace_processor/util/interned_message_view.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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_UTIL_INTERNED_MESSAGE_VIEW_H_
+#define SRC_TRACE_PROCESSOR_UTIL_INTERNED_MESSAGE_VIEW_H_
+
+#include "src/trace_processor/util/trace_blob_view.h"
+
+#include <unordered_map>
+
+namespace perfetto {
+namespace trace_processor {
+
+#if PERFETTO_DCHECK_IS_ON()
+// When called from GetOrCreateDecoder(), should include the stringified name of
+// the MessageType.
+#define PERFETTO_TYPE_IDENTIFIER PERFETTO_DEBUG_FUNCTION_IDENTIFIER()
+#else  // PERFETTO_DCHECK_IS_ON()
+#define PERFETTO_TYPE_IDENTIFIER nullptr
+#endif  // PERFETTO_DCHECK_IS_ON()
+
+// Entry in an interning index, refers to the interned message.
+class InternedMessageView {
+ public:
+  InternedMessageView(TraceBlobView msg) : message_(std::move(msg)) {}
+
+  InternedMessageView(InternedMessageView&&) = default;
+  InternedMessageView& operator=(InternedMessageView&&) = default;
+
+  // Allow copy by cloning the TraceBlobView. This is required for
+  // UpdateTracePacketDefaults().
+  InternedMessageView(const InternedMessageView& view)
+      : message_(view.message_.slice(0, view.message_.length())) {}
+  InternedMessageView& operator=(const InternedMessageView& view) {
+    this->message_ = view.message_.slice(0, view.message_.length());
+    this->decoder_ = nullptr;
+    this->decoder_type_ = nullptr;
+    this->submessages_.clear();
+    return *this;
+  }
+
+  // Lazily initializes and returns the decoder object for the message. The
+  // decoder is stored in the InternedMessageView to avoid having to parse the
+  // message multiple times.
+  template <typename MessageType>
+  typename MessageType::Decoder* GetOrCreateDecoder() {
+    if (!decoder_) {
+      // Lazy init the decoder and save it away, so that we don't have to
+      // reparse the message every time we access the interning entry.
+      decoder_ = std::unique_ptr<void, std::function<void(void*)>>(
+          new typename MessageType::Decoder(message_.data(), message_.length()),
+          [](void* obj) {
+            delete reinterpret_cast<typename MessageType::Decoder*>(obj);
+          });
+      decoder_type_ = PERFETTO_TYPE_IDENTIFIER;
+    }
+    // Verify that the type of the decoder didn't change.
+    if (PERFETTO_TYPE_IDENTIFIER &&
+        strcmp(decoder_type_,
+               // GCC complains if this arg can be null.
+               PERFETTO_TYPE_IDENTIFIER ? PERFETTO_TYPE_IDENTIFIER : "") != 0) {
+      PERFETTO_FATAL(
+          "Interning entry accessed under different types! previous type: "
+          "%s. new type: %s.",
+          decoder_type_, PERFETTO_DEBUG_FUNCTION_IDENTIFIER());
+    }
+    return reinterpret_cast<typename MessageType::Decoder*>(decoder_.get());
+  }
+
+  // Lookup a submessage of the interned message, which is then itself stored
+  // as InternedMessageView, so that we only need to parse it once. Returns
+  // nullptr if the field isn't set.
+  // TODO(eseckler): Support repeated fields.
+  template <typename MessageType, uint32_t FieldId>
+  InternedMessageView* GetOrCreateSubmessageView() {
+    auto it = submessages_.find(FieldId);
+    if (it != submessages_.end())
+      return it->second.get();
+    auto* decoder = GetOrCreateDecoder<MessageType>();
+    // Calls the at() template method on the decoder.
+    auto field = decoder->template at<FieldId>().as_bytes();
+    if (!field.data)
+      return nullptr;
+    const size_t offset = message_.offset_of(field.data);
+    TraceBlobView submessage = message_.slice(offset, field.size);
+    InternedMessageView* submessage_view =
+        new InternedMessageView(std::move(submessage));
+    submessages_.emplace_hint(
+        it, FieldId, std::unique_ptr<InternedMessageView>(submessage_view));
+    return submessage_view;
+  }
+
+  const TraceBlobView& message() { return message_; }
+
+ private:
+  using SubMessageViewMap =
+      std::unordered_map<uint32_t /*field_id*/,
+                         std::unique_ptr<InternedMessageView>>;
+
+  TraceBlobView message_;
+
+  // Stores the decoder for the message_, so that the message does not have to
+  // be re-decoded every time the interned message is looked up. Lazily
+  // initialized in GetOrCreateDecoder(). Since we don't know the type of the
+  // decoder until GetOrCreateDecoder() is called, we store the decoder as a
+  // void* unique_pointer with a destructor function that's supplied in
+  // GetOrCreateDecoder() when the decoder is created.
+  std::unique_ptr<void, std::function<void(void*)>> decoder_;
+
+  // Type identifier for the decoder. Only valid in debug builds and on
+  // supported platforms. Used to verify that GetOrCreateDecoder() is always
+  // called with the same template argument.
+  const char* decoder_type_ = nullptr;
+
+  // Views of submessages of the interned message. Submessages are lazily
+  // added by GetOrCreateSubmessageView(). By storing submessages and their
+  // decoders, we avoid having to decode submessages multiple times if they
+  // looked up often.
+  SubMessageViewMap submessages_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_UTIL_INTERNED_MESSAGE_VIEW_H_
diff --git a/src/trace_processor/util/proto_to_args_parser.cc b/src/trace_processor/util/proto_to_args_parser.cc
index 6fec35e..8e4eb22 100644
--- a/src/trace_processor/util/proto_to_args_parser.cc
+++ b/src/trace_processor/util/proto_to_args_parser.cc
@@ -63,6 +63,33 @@
     : flat_key(fk), key(k) {}
 ProtoToArgsParser::Key::~Key() = default;
 
+ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext(Key& key)
+    : key_(key),
+      old_flat_key_length_(key.flat_key.length()),
+      old_key_length_(key.key.length()) {}
+
+ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext(
+    ProtoToArgsParser::ScopedNestedKeyContext&& other)
+    : key_(other.key_),
+      old_flat_key_length_(other.old_flat_key_length_),
+      old_key_length_(other.old_key_length_) {
+  other.old_flat_key_length_ = base::nullopt;
+  other.old_key_length_ = base::nullopt;
+}
+
+ProtoToArgsParser::ScopedNestedKeyContext::~ScopedNestedKeyContext() {
+  Reset();
+}
+
+void ProtoToArgsParser::ScopedNestedKeyContext::Reset() {
+  if (old_flat_key_length_)
+    key_.flat_key.resize(old_flat_key_length_.value());
+  if (old_key_length_)
+    key_.key.resize(old_key_length_.value());
+  old_flat_key_length_ = base::nullopt;
+  old_key_length_ = base::nullopt;
+}
+
 ProtoToArgsParser::Delegate::~Delegate() = default;
 
 ProtoToArgsParser::ProtoToArgsParser(const DescriptorPool& pool) : pool_(pool) {
@@ -238,6 +265,25 @@
   }
 }
 
+ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterArray(
+    size_t index) {
+  auto context = ScopedNestedKeyContext(key_prefix_);
+  key_prefix_.key += "[" + std::to_string(index) + "]";
+  return context;
+}
+
+ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterDictionary(
+    const std::string& name) {
+  auto context = ScopedNestedKeyContext(key_prefix_);
+  if (!key_prefix_.key.empty())
+    key_prefix_.key += '.';
+  key_prefix_.key += name;
+  if (!key_prefix_.flat_key.empty())
+    key_prefix_.flat_key += '.';
+  key_prefix_.flat_key += name;
+  return context;
+}
+
 }  // namespace util
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/util/proto_to_args_parser.h b/src/trace_processor/util/proto_to_args_parser.h
index f99179c..9110e91 100644
--- a/src/trace_processor/util/proto_to_args_parser.h
+++ b/src/trace_processor/util/proto_to_args_parser.h
@@ -83,9 +83,13 @@
     virtual void AddDouble(const Key& key, double value) = 0;
     virtual void AddPointer(const Key& key, const void* value) = 0;
     virtual void AddBoolean(const Key& key, bool value) = 0;
-    virtual void AddJson(const Key& key,
+    // Returns whether an entry was added or not.
+    virtual bool AddJson(const Key& key,
                          const protozero::ConstChars& value) = 0;
 
+    virtual size_t GetArrayEntryIndex(const std::string& array_key) = 0;
+    virtual size_t IncrementArrayEntryIndex(const std::string& array_key) = 0;
+
     template <typename FieldMetadata>
     typename FieldMetadata::cpp_field_type::Decoder* GetInternedMessage(
         protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
@@ -159,6 +163,37 @@
                             const std::vector<uint16_t>* allowed_fields,
                             Delegate& delegate);
 
+  struct ScopedNestedKeyContext {
+   public:
+    ~ScopedNestedKeyContext();
+    ScopedNestedKeyContext(ScopedNestedKeyContext&&);
+    ScopedNestedKeyContext(const ScopedNestedKeyContext&) = delete;
+    ScopedNestedKeyContext& operator=(const ScopedNestedKeyContext&) = delete;
+
+    const Key& key() const { return key_; }
+
+    // Reset this context, which sets |key_| to the state before the nested
+    // context was created.
+    void Reset();
+
+   private:
+    friend class ProtoToArgsParser;
+
+    ScopedNestedKeyContext(Key& old_value);
+
+    struct ScopedStringAppender;
+
+    Key& key_;
+    base::Optional<size_t> old_flat_key_length_ = base::nullopt;
+    base::Optional<size_t> old_key_length_ = base::nullopt;
+  };
+
+  // These methods can be called from parsing overrides to enter nested
+  // contexts. The contexts are left when the returned scope is destroyed or
+  // reset.
+  ScopedNestedKeyContext EnterDictionary(const std::string& key);
+  ScopedNestedKeyContext EnterArray(size_t index);
+
  private:
   base::Status ParseField(const FieldDescriptor& field_descriptor,
                           int repeated_field_number,
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 89ee945..a8d17f6 100644
--- a/src/trace_processor/util/proto_to_args_parser_unittest.cc
+++ b/src/trace_processor/util/proto_to_args_parser_unittest.cc
@@ -21,9 +21,9 @@
 #include "protos/perfetto/common/descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/source_location.pbzero.h"
 #include "src/protozero/test/example_proto/test_messages.pbzero.h"
-#include "src/trace_processor/importers/common/trace_blob_view.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/test_messages.descriptor.h"
+#include "src/trace_processor/util/interned_message_view.h"
+#include "src/trace_processor/util/trace_blob_view.h"
 #include "test/gtest_and_gmock.h"
 
 #include <sstream>
@@ -92,13 +92,18 @@
     args_.push_back(ss.str());
   }
 
-  void AddJson(const Key& key, const protozero::ConstChars& value) override {
+  bool AddJson(const Key& key, const protozero::ConstChars& value) override {
     std::stringstream ss;
     ss << key.flat_key << " " << key.key << " " << std::hex
        << value.ToStdString() << std::dec;
     args_.push_back(ss.str());
+    return true;
   }
 
+  size_t GetArrayEntryIndex(const std::string&) final { return 0; }
+
+  size_t IncrementArrayEntryIndex(const std::string&) final { return 0; }
+
   InternedMessageView* GetInternedMessageView(uint32_t field_id,
                                               uint64_t iid) override {
     if (field_id != protos::pbzero::InternedData::kSourceLocationsFieldNumber)
diff --git a/src/trace_processor/importers/common/trace_blob_view.h b/src/trace_processor/util/trace_blob_view.h
similarity index 95%
rename from src/trace_processor/importers/common/trace_blob_view.h
rename to src/trace_processor/util/trace_blob_view.h
index a94a994..7d9c1a4 100644
--- a/src/trace_processor/importers/common/trace_blob_view.h
+++ b/src/trace_processor/util/trace_blob_view.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_BLOB_VIEW_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_BLOB_VIEW_H_
+#ifndef SRC_TRACE_PROCESSOR_UTIL_TRACE_BLOB_VIEW_H_
+#define SRC_TRACE_PROCESSOR_UTIL_TRACE_BLOB_VIEW_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -145,4 +145,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACE_BLOB_VIEW_H_
+#endif  // SRC_TRACE_PROCESSOR_UTIL_TRACE_BLOB_VIEW_H_
diff --git a/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out b/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out
index b0c0529..8cecb1d 100644
--- a/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out
+++ b/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out
@@ -22,6 +22,6 @@
 "debug.debug2.key4","debug.debug2.key4",40,"[NULL]"
 "debug.debug3","debug.debug3",32,"[NULL]"
 "debug.debug4.key1","debug.debug4.key1",10,"[NULL]"
-"debug.debug4.key2[0]","debug.debug4.key2[0]",20,"[NULL]"
-"debug.debug4.key2[1]","debug.debug4.key2[1]",21,"[NULL]"
+"debug.debug4.key2","debug.debug4.key2[0]",20,"[NULL]"
+"debug.debug4.key2","debug.debug4.key2[1]",21,"[NULL]"
 "legacy_event.passthrough_utid","legacy_event.passthrough_utid",1,"[NULL]"