trace_processor: migrate metadata to macro tables

While doing this, we can also refactor a bunch of code in TraceStorage
into a dedicated metadata tracker.

Context: go/perfetto-tp-refactor
Bug: 135177627
Change-Id: Id612bc66ed62fd39b5dd156d7ab87e41d4fa064c
diff --git a/Android.bp b/Android.bp
index c29530e..17b6886 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5724,7 +5724,6 @@
     "src/trace_processor/args_table.cc",
     "src/trace_processor/filtered_row_index.cc",
     "src/trace_processor/gfp_flags.cc",
-    "src/trace_processor/metadata_table.cc",
     "src/trace_processor/process_table.cc",
     "src/trace_processor/raw_table.cc",
     "src/trace_processor/read_trace.cc",
@@ -5841,6 +5840,7 @@
     "src/trace_processor/importers/proto/track_event_module.cc",
     "src/trace_processor/importers/proto/track_event_parser.cc",
     "src/trace_processor/importers/proto/track_event_tokenizer.cc",
+    "src/trace_processor/metadata_tracker.cc",
     "src/trace_processor/process_tracker.cc",
     "src/trace_processor/slice_tracker.cc",
     "src/trace_processor/stack_profile_tracker.cc",
@@ -5883,7 +5883,6 @@
     "src/trace_processor/importers/proto/heap_graph_walker_unittest.cc",
     "src/trace_processor/importers/proto/proto_trace_parser_unittest.cc",
     "src/trace_processor/importers/systrace/systrace_parser_unittest.cc",
-    "src/trace_processor/metadata_table_unittest.cc",
     "src/trace_processor/process_table_unittest.cc",
     "src/trace_processor/process_tracker_unittest.cc",
     "src/trace_processor/protozero_to_text_unittests.cc",
diff --git a/BUILD b/BUILD
index 5699b8b..18a633e 100644
--- a/BUILD
+++ b/BUILD
@@ -755,6 +755,7 @@
         "src/trace_processor/tables/counter_tables.h",
         "src/trace_processor/tables/macros.h",
         "src/trace_processor/tables/macros_internal.h",
+        "src/trace_processor/tables/metadata_tables.h",
         "src/trace_processor/tables/profiler_tables.h",
         "src/trace_processor/tables/slice_tables.h",
         "src/trace_processor/tables/track_tables.h",
@@ -789,8 +790,6 @@
         "src/trace_processor/filtered_row_index.h",
         "src/trace_processor/gfp_flags.cc",
         "src/trace_processor/gfp_flags.h",
-        "src/trace_processor/metadata_table.cc",
-        "src/trace_processor/metadata_table.h",
         "src/trace_processor/process_table.cc",
         "src/trace_processor/process_table.h",
         "src/trace_processor/raw_table.cc",
@@ -930,6 +929,8 @@
         "src/trace_processor/importers/proto/track_event_tokenizer.cc",
         "src/trace_processor/importers/proto/track_event_tokenizer.h",
         "src/trace_processor/metadata.h",
+        "src/trace_processor/metadata_tracker.cc",
+        "src/trace_processor/metadata_tracker.h",
         "src/trace_processor/process_tracker.cc",
         "src/trace_processor/process_tracker.h",
         "src/trace_processor/slice_tracker.cc",
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index b794b92..c999174 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -4072,6 +4072,75 @@
 
 // End of protos/perfetto/trace/track_event/chrome_legacy_ipc.proto
 
+// Begin of protos/perfetto/trace/track_event/chrome_process_descriptor.proto
+
+// Describes the attributes for a Chrome process. Must be paired with a
+// ProcessDescriptor in the same TrackDescriptor.
+//
+// Next id: 4.
+message ChromeProcessDescriptor {
+  // See chromium's content::ProcessType.
+  enum ChromeProcessType {
+    PROCESS_UNSPECIFIED = 0;
+    PROCESS_BROWSER = 1;
+    PROCESS_RENDERER = 2;
+    PROCESS_UTILITY = 3;
+    PROCESS_ZYGOTE = 4;
+    PROCESS_SANDBOX_HELPER = 5;
+    PROCESS_GPU = 6;
+    PROCESS_PPAPI_PLUGIN = 7;
+    PROCESS_PPAPI_BROKER = 8;
+  }
+  optional ChromeProcessType process_type = 1;
+  optional int32 process_priority = 2;
+
+  // To support old UI. New UI should determine default sorting by process_type.
+  optional int32 legacy_sort_index = 3;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_process_descriptor.proto
+
+// Begin of protos/perfetto/trace/track_event/chrome_thread_descriptor.proto
+
+// Describes a Chrome thread's attributes. Emitted as part of a TrackDescriptor,
+// usually by the thread's trace writer. Must be paired with a ThreadDescriptor
+// in the same TrackDescriptor.
+//
+// Next id: 3.
+message ChromeThreadDescriptor {
+  enum ThreadType {
+    THREAD_UNSPECIFIED = 0;
+
+    THREAD_MAIN = 1;
+    THREAD_IO = 2;
+
+    // Scheduler:
+    THREAD_POOL_BG_WORKER = 3;
+    THREAD_POOL_FG_WORKER = 4;
+    THREAD_POOL_FB_BLOCKING = 5;
+    THREAD_POOL_BG_BLOCKING = 6;
+    THREAD_POOL_SERVICE = 7;
+
+    // Compositor:
+    THREAD_COMPOSITOR = 8;
+    THREAD_VIZ_COMPOSITOR = 9;
+    THREAD_COMPOSITOR_WORKER = 10;
+
+    // Renderer:
+    THREAD_SERVICE_WORKER = 11;
+
+    // Tracing related threads:
+    THREAD_MEMORY_INFRA = 50;
+    THREAD_SAMPLING_PROFILER = 51;
+  };
+  optional ThreadType thread_type = 1;
+
+  // To support old UI. New UI should determine default sorting by thread_type.
+  optional int32 legacy_sort_index = 2;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_thread_descriptor.proto
+
 // Begin of protos/perfetto/trace/track_event/chrome_user_event.proto
 
 // Details about a UI interaction initiated by the user, such as opening or
@@ -4165,11 +4234,17 @@
 // Describes a process's attributes. Emitted as part of a TrackDescriptor,
 // usually by the process's main thread.
 //
-// Next id: 5.
+// Next id: 6.
 message ProcessDescriptor {
   optional int32 pid = 1;
   repeated string cmdline = 2;
 
+  optional int32 process_priority = 5;
+
+  // ---------------------------------------------------------------------------
+  // Deprecated / legacy fields, which will be removed in the future:
+  // ---------------------------------------------------------------------------
+
   // See chromium's content::ProcessType.
   enum ChromeProcessType {
     PROCESS_UNSPECIFIED = 0;
@@ -4183,7 +4258,6 @@
     PROCESS_PPAPI_BROKER = 8;
   }
   optional ChromeProcessType chrome_process_type = 4;
-  optional int32 process_priority = 5;
 
   // To support old UI. New UI should determine default sorting by process_type.
   optional int32 legacy_sort_index = 3;
@@ -4229,6 +4303,10 @@
 
   optional string thread_name = 5;
 
+  // ---------------------------------------------------------------------------
+  // Deprecated / legacy fields, which will be removed in the future:
+  // ---------------------------------------------------------------------------
+
   enum ChromeThreadType {
     CHROME_THREAD_UNSPECIFIED = 0;
 
@@ -4256,10 +4334,6 @@
   };
   optional ChromeThreadType chrome_thread_type = 4;
 
-  // ---------------------------------------------------------------------------
-  // Deprecated / legacy fields, which will be removed in the future:
-  // ---------------------------------------------------------------------------
-
   // Deprecated. Use ClockSnapshot in combination with TracePacket's timestamp
   // and timestamp_clock_id fields instead.
   optional int64 reference_timestamp_us = 6;
@@ -4280,7 +4354,9 @@
 // Begin of protos/perfetto/trace/track_event/track_descriptor.proto
 
 // Defines a track for TrackEvents. Slices and instant events on the same track
-// will be nested based on their timestamps, see TrackEvent::Type.
+// will be nested based on their timestamps, see TrackEvent::Type. Additionally
+// events on tracks which represent the same thread (i.e., matching pid and tid
+// in ThreadDescriptor) will be merged onto one sequential timeline.
 //
 // A TrackDescriptor only needs to be emitted by one trace writer / producer and
 // is valid for the entirety of the trace. To ensure the descriptor isn't lost
@@ -4292,7 +4368,7 @@
 // |TrackEvent::track_uuid|. It is possible but not necessary to emit a
 // TrackDescriptor for this implicit track.
 //
-// Next id: 1.
+// Next id: 8.
 message TrackDescriptor {
   // Unique ID that identifies this track. This ID is global to the whole trace.
   // Producers should ensure that it is unlikely to clash with IDs emitted by
@@ -4302,16 +4378,20 @@
   // event id + id_scope, pid, and/or tid to compute this ID.
   optional uint64 uuid = 1;
 
-  // TODO(eseckler): Support track hierarchies.
-  // uint64 parent_uuid = X;
+  // A parent track reference can be used to describe relationships between
+  // tracks. For example, to define an asynchronous track which is scoped to a
+  // specific process, specify the uuid for that process's process track here.
+  optional uint64 parent_uuid = 5;
 
   // Name of the track.
   optional string name = 2;
 
-  // Associate the track with a process or thread - the UI will draw this track
-  // in the context of the process/thread.
+  // Associate the track with a process or thread - the UI will merge all tracks
+  // for the same process / thread into a single timeline view.
   optional ProcessDescriptor process = 3;
+  optional ChromeProcessDescriptor chrome_process = 6;
   optional ThreadDescriptor thread = 4;
+  optional ChromeThreadDescriptor chrome_thread = 7;
 }
 
 // End of protos/perfetto/trace/track_event/track_descriptor.proto
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 6a05dfa..a6fd002 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -112,6 +112,8 @@
     "importers/proto/track_event_tokenizer.cc",
     "importers/proto/track_event_tokenizer.h",
     "metadata.h",
+    "metadata_tracker.cc",
+    "metadata_tracker.h",
     "process_tracker.cc",
     "process_tracker.h",
     "slice_tracker.cc",
@@ -286,8 +288,6 @@
     "filtered_row_index.h",
     "gfp_flags.cc",
     "gfp_flags.h",
-    "metadata_table.cc",
-    "metadata_table.h",
     "process_table.cc",
     "process_table.h",
     "raw_table.cc",
@@ -379,7 +379,6 @@
     "importers/proto/heap_graph_walker_unittest.cc",
     "importers/proto/proto_trace_parser_unittest.cc",
     "importers/systrace/systrace_parser_unittest.cc",
-    "metadata_table_unittest.cc",
     "process_table_unittest.cc",
     "process_tracker_unittest.cc",
     "protozero_to_text_unittests.cc",
diff --git a/src/trace_processor/args_tracker.cc b/src/trace_processor/args_tracker.cc
index cfd9f83..4f143c5 100644
--- a/src/trace_processor/args_tracker.cc
+++ b/src/trace_processor/args_tracker.cc
@@ -87,8 +87,8 @@
         break;
       // Special case: overwrites the metadata table row.
       case TableId::kMetadataTable:
-        storage->mutable_metadata()->OverwriteMetadata(
-            row, Variadic::Integer(set_id));
+        storage->mutable_metadata_table()->mutable_int_value()->Set(row,
+                                                                    set_id);
         break;
       case TableId::kTrack:
         storage->mutable_track_table()->mutable_source_arg_set_id()->Set(
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 93e9d01..ee12912 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -309,6 +309,8 @@
     PERFETTO_FATAL("Invalid type");
   }
 
+  const StringPool& string_pool() const { return *string_pool_; }
+
  private:
   enum class ColumnType {
     // Standard primitive types.
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index 99355d7..a7183f6 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -62,6 +62,11 @@
   // Inserts the value at the end of the column.
   void Append(T v) { mutable_sparse_vector<T>()->Append(v); }
 
+  // Returns the row containing the given value in the Column.
+  base::Optional<uint32_t> IndexOf(T v) const {
+    return Column::IndexOf(NumericToSqlValue(v));
+  }
+
   // Implements equality between two items of type |T|.
   static bool Equals(T a, T b) {
     // We need to use equal_to here as it could be T == double and because we
@@ -147,6 +152,16 @@
     mutable_sparse_vector<StringPool::Id>()->Append(v);
   }
 
+  // Returns the row containing the given value in the Column.
+  base::Optional<uint32_t> IndexOf(StringPool::Id v) const {
+    return Column::IndexOf(SqlValue::String(string_pool().Get(v).c_str()));
+  }
+
+  // Returns the row containing the given value in the Column.
+  base::Optional<uint32_t> IndexOf(NullTermStringView v) const {
+    return Column::IndexOf(SqlValue::String(v.c_str()));
+  }
+
   // Implements equality between two items of type |T|.
   static bool Equals(StringPool::Id a, StringPool::Id b) { return a == b; }
 
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index e10da4e..4aa62b9 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -952,64 +952,69 @@
 
 util::Status ExportMetadata(const TraceStorage* storage,
                             TraceFormatWriter* writer) {
-  const auto& trace_metadata = storage->metadata();
-  const auto& keys = trace_metadata.keys();
-  const auto& values = trace_metadata.values();
-  for (size_t pos = 0; pos < keys.size(); pos++) {
+  const auto& trace_metadata = storage->metadata_table();
+  const auto& keys = trace_metadata.name();
+  const auto& int_values = trace_metadata.int_value();
+  const auto& str_values = trace_metadata.str_value();
+
+  // Create a mapping from key string ids to keys.
+  std::unordered_map<StringId, metadata::KeyIDs> key_map;
+  for (uint32_t i = 0; i < metadata::kNumKeys; ++i) {
+    auto id = *storage->string_pool().GetId(metadata::kNames[i]);
+    key_map[id] = static_cast<metadata::KeyIDs>(i);
+  }
+
+  for (uint32_t pos = 0; pos < trace_metadata.row_count(); pos++) {
     // Cast away from enum type, as otherwise -Wswitch-enum will demand an
     // exhaustive list of cases, even if there's a default case.
-    switch (static_cast<size_t>(keys[pos])) {
+    metadata::KeyIDs key = key_map[keys[pos]];
+    switch (static_cast<size_t>(key)) {
       case metadata::benchmark_description:
         writer->AppendTelemetryMetadataString(
             "benchmarkDescriptions",
-            GetNonNullString(storage, values[pos].string_value));
+            GetNonNullString(storage, str_values[pos]));
         break;
 
       case metadata::benchmark_name:
         writer->AppendTelemetryMetadataString(
-            "benchmarks", GetNonNullString(storage, values[pos].string_value));
+            "benchmarks", GetNonNullString(storage, str_values[pos]));
         break;
 
       case metadata::benchmark_start_time_us:
 
         writer->SetTelemetryMetadataTimestamp("benchmarkStart",
-                                              values[pos].int_value);
+                                              *int_values[pos]);
         break;
 
       case metadata::benchmark_had_failures:
-        if (pos < values.size())
-          writer->AppendTelemetryMetadataBool("hadFailures",
-                                              values[pos].int_value);
+        writer->AppendTelemetryMetadataBool("hadFailures", *int_values[pos]);
         break;
 
       case metadata::benchmark_label:
         writer->AppendTelemetryMetadataString(
-            "labels", GetNonNullString(storage, values[pos].string_value));
+            "labels", GetNonNullString(storage, str_values[pos]));
         break;
 
       case metadata::benchmark_story_name:
         writer->AppendTelemetryMetadataString(
-            "stories", GetNonNullString(storage, values[pos].string_value));
+            "stories", GetNonNullString(storage, str_values[pos]));
         break;
 
       case metadata::benchmark_story_run_index:
-        writer->AppendTelemetryMetadataInt("storysetRepeats",
-                                           values[pos].int_value);
+        writer->AppendTelemetryMetadataInt("storysetRepeats", *int_values[pos]);
         break;
 
       case metadata::benchmark_story_run_time_us:
-        writer->SetTelemetryMetadataTimestamp("traceStart",
-                                              values[pos].int_value);
+        writer->SetTelemetryMetadataTimestamp("traceStart", *int_values[pos]);
         break;
 
       case metadata::benchmark_story_tags:  // repeated
         writer->AppendTelemetryMetadataString(
-            "storyTags", GetNonNullString(storage, values[pos].string_value));
+            "storyTags", GetNonNullString(storage, str_values[pos]));
         break;
 
       default:
-        PERFETTO_DLOG("Ignoring metadata key %zu",
-                      static_cast<size_t>(keys[pos]));
+        PERFETTO_DLOG("Ignoring metadata key %zu", static_cast<size_t>(key));
         break;
     }
   }
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index cfcb6f0..7cac7cd 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -26,6 +26,7 @@
 
 #include "perfetto/ext/base/temp_file.h"
 #include "src/trace_processor/args_tracker.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
 #include "src/trace_processor/track_tracker.h"
 
@@ -67,6 +68,7 @@
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.storage.reset(new TraceStorage());
     context_.track_tracker.reset(new TrackTracker(&context_));
+    context_.metadata_tracker.reset(new MetadataTracker(&context_));
   }
 
   std::string ToJson(ArgumentFilterPredicate argument_filter = nullptr,
@@ -254,17 +256,20 @@
   StringId desc_id =
       context_.storage->InternString(base::StringView(kDescription));
   Variadic description = Variadic::String(desc_id);
-  context_.storage->SetMetadata(metadata::benchmark_description, description);
+  context_.metadata_tracker->SetMetadata(metadata::benchmark_description,
+                                         description);
 
   StringId benchmark_name_id =
       context_.storage->InternString(base::StringView(kBenchmarkName));
   Variadic benchmark_name = Variadic::String(benchmark_name_id);
-  context_.storage->SetMetadata(metadata::benchmark_name, benchmark_name);
+  context_.metadata_tracker->SetMetadata(metadata::benchmark_name,
+                                         benchmark_name);
 
   StringId story_name_id =
       context_.storage->InternString(base::StringView(kStoryName));
   Variadic story_name = Variadic::String(story_name_id);
-  context_.storage->SetMetadata(metadata::benchmark_story_name, story_name);
+  context_.metadata_tracker->SetMetadata(metadata::benchmark_story_name,
+                                         story_name);
 
   StringId tag1_id =
       context_.storage->InternString(base::StringView(kStoryTag1));
@@ -272,19 +277,22 @@
       context_.storage->InternString(base::StringView(kStoryTag2));
   Variadic tag1 = Variadic::String(tag1_id);
   Variadic tag2 = Variadic::String(tag2_id);
-  context_.storage->AppendMetadata(metadata::benchmark_story_tags, tag1);
-  context_.storage->AppendMetadata(metadata::benchmark_story_tags, tag2);
+  context_.metadata_tracker->AppendMetadata(metadata::benchmark_story_tags,
+                                            tag1);
+  context_.metadata_tracker->AppendMetadata(metadata::benchmark_story_tags,
+                                            tag2);
 
   Variadic benchmark_start = Variadic::Integer(kBenchmarkStart);
-  context_.storage->SetMetadata(metadata::benchmark_start_time_us,
-                                benchmark_start);
+  context_.metadata_tracker->SetMetadata(metadata::benchmark_start_time_us,
+                                         benchmark_start);
 
   Variadic story_start = Variadic::Integer(kStoryStart);
-  context_.storage->SetMetadata(metadata::benchmark_story_run_time_us,
-                                story_start);
+  context_.metadata_tracker->SetMetadata(metadata::benchmark_story_run_time_us,
+                                         story_start);
 
   Variadic had_failures = Variadic::Integer(kHadFailures);
-  context_.storage->SetMetadata(metadata::benchmark_had_failures, had_failures);
+  context_.metadata_tracker->SetMetadata(metadata::benchmark_had_failures,
+                                         had_failures);
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
diff --git a/src/trace_processor/importers/proto/android_probes_parser.cc b/src/trace_processor/importers/proto/android_probes_parser.cc
index 5370e57..178be15 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.cc
+++ b/src/trace_processor/importers/proto/android_probes_parser.cc
@@ -21,6 +21,7 @@
 #include "src/trace_processor/args_tracker.h"
 #include "src/trace_processor/clock_tracker.h"
 #include "src/trace_processor/event_tracker.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/syscall_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
@@ -207,7 +208,7 @@
   protos::pbzero::TraceConfig::StatsdMetadata::Decoder metadata(blob.data,
                                                                 blob.size);
   if (metadata.has_triggering_subscription_id()) {
-    context_->storage->SetMetadata(
+    context_->metadata_tracker->SetMetadata(
         metadata::statsd_triggering_subscription_id,
         Variadic::Integer(metadata.triggering_subscription_id()));
   }
@@ -226,9 +227,9 @@
   for (auto it = pkg_list.packages(); it; ++it) {
     // Insert a placeholder metadata entry, which will be overwritten by the
     // arg_set_id when the arg tracker is flushed.
-    uint32_t row = context_->storage->AppendMetadata(
+    auto id = context_->metadata_tracker->AppendMetadata(
         metadata::android_packages_list, Variadic::Integer(0));
-
+    uint32_t row = *context_->storage->metadata_table().id().IndexOf(id);
     auto add_arg = [this, row](base::StringView name, Variadic value) {
       StringId key_id = context_->storage->InternString(name);
       context_->args_tracker->AddArg(TableId::kMetadataTable, row, key_id,
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index 76dffe8..9fe74c8 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -36,6 +36,7 @@
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/metadata.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/stack_profile_tracker.h"
@@ -462,47 +463,50 @@
 
 void ProtoTraceParser::ParseChromeBenchmarkMetadata(ConstBytes blob) {
   TraceStorage* storage = context_->storage.get();
+  MetadataTracker* metadata = context_->metadata_tracker.get();
+
   protos::pbzero::ChromeBenchmarkMetadata::Decoder packet(blob.data, blob.size);
   if (packet.has_benchmark_name()) {
     auto benchmark_name_id = storage->InternString(packet.benchmark_name());
-    storage->SetMetadata(metadata::benchmark_name,
-                         Variadic::String(benchmark_name_id));
+    metadata->SetMetadata(metadata::benchmark_name,
+                          Variadic::String(benchmark_name_id));
   }
   if (packet.has_benchmark_description()) {
     auto benchmark_description_id =
         storage->InternString(packet.benchmark_description());
-    storage->SetMetadata(metadata::benchmark_description,
-                         Variadic::String(benchmark_description_id));
+    metadata->SetMetadata(metadata::benchmark_description,
+                          Variadic::String(benchmark_description_id));
   }
   if (packet.has_label()) {
     auto label_id = storage->InternString(packet.label());
-    storage->SetMetadata(metadata::benchmark_label, Variadic::String(label_id));
+    metadata->SetMetadata(metadata::benchmark_label,
+                          Variadic::String(label_id));
   }
   if (packet.has_story_name()) {
     auto story_name_id = storage->InternString(packet.story_name());
-    storage->SetMetadata(metadata::benchmark_story_name,
-                         Variadic::String(story_name_id));
+    metadata->SetMetadata(metadata::benchmark_story_name,
+                          Variadic::String(story_name_id));
   }
   for (auto it = packet.story_tags(); it; ++it) {
     auto story_tag_id = storage->InternString(*it);
-    storage->AppendMetadata(metadata::benchmark_story_tags,
-                            Variadic::String(story_tag_id));
+    metadata->AppendMetadata(metadata::benchmark_story_tags,
+                             Variadic::String(story_tag_id));
   }
   if (packet.has_benchmark_start_time_us()) {
-    storage->SetMetadata(metadata::benchmark_start_time_us,
-                         Variadic::Integer(packet.benchmark_start_time_us()));
+    metadata->SetMetadata(metadata::benchmark_start_time_us,
+                          Variadic::Integer(packet.benchmark_start_time_us()));
   }
   if (packet.has_story_run_time_us()) {
-    storage->SetMetadata(metadata::benchmark_story_run_time_us,
-                         Variadic::Integer(packet.story_run_time_us()));
+    metadata->SetMetadata(metadata::benchmark_story_run_time_us,
+                          Variadic::Integer(packet.story_run_time_us()));
   }
   if (packet.has_story_run_index()) {
-    storage->SetMetadata(metadata::benchmark_story_run_index,
-                         Variadic::Integer(packet.story_run_index()));
+    metadata->SetMetadata(metadata::benchmark_story_run_index,
+                          Variadic::Integer(packet.story_run_index()));
   }
   if (packet.has_had_failures()) {
-    storage->SetMetadata(metadata::benchmark_had_failures,
-                         Variadic::Integer(packet.had_failures()));
+    metadata->SetMetadata(metadata::benchmark_had_failures,
+                          Variadic::Integer(packet.had_failures()));
   }
 }
 
@@ -615,7 +619,8 @@
     base::Uuid uuid(uuid_lsb, uuid_msb);
     std::string str = uuid.ToPrettyString();
     StringId id = context_->storage->InternString(base::StringView(str));
-    context_->storage->SetMetadata(metadata::trace_uuid, Variadic::String(id));
+    context_->metadata_tracker->SetMetadata(metadata::trace_uuid,
+                                            Variadic::String(id));
   }
 }
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 9e7b9cd..f90418d 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -28,6 +28,7 @@
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
 #include "src/trace_processor/metadata.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/register_additional_modules.h"
 #include "src/trace_processor/slice_tracker.h"
@@ -218,6 +219,7 @@
     context_.storage.reset(storage_);
     context_.track_tracker.reset(new TrackTracker(&context_));
     context_.args_tracker.reset(new ArgsTracker(&context_));
+    context_.metadata_tracker.reset(new MetadataTracker(&context_));
     event_ = new MockEventTracker(&context_);
     context_.event_tracker.reset(event_);
     sched_ = new MockSchedEventTracker(&context_);
@@ -2373,22 +2375,25 @@
   EXPECT_CALL(*storage_, InternString(base::StringView(kTag2)))
       .WillOnce(Return(3));
 
+  StringId benchmark_id = *storage_->string_pool().GetId(
+      metadata::kNames[metadata::benchmark_name]);
+  StringId tags_id = *storage_->string_pool().GetId(
+      metadata::kNames[metadata::benchmark_story_tags]);
+
   context_.sorter->ExtractEventsForced();
 
-  const auto& meta_keys = storage_->metadata().keys();
-  const auto& meta_values = storage_->metadata().values();
-  EXPECT_EQ(meta_keys.size(), 3u);
-  std::vector<std::pair<metadata::KeyIDs, Variadic>> meta_entries;
-  for (size_t i = 0; i < meta_keys.size(); i++) {
+  const auto& meta_keys = storage_->metadata_table().name();
+  const auto& meta_values = storage_->metadata_table().str_value();
+  EXPECT_EQ(storage_->metadata_table().row_count(), 3u);
+
+  std::vector<std::pair<StringId, StringId>> meta_entries;
+  for (uint32_t i = 0; i < storage_->metadata_table().row_count(); i++) {
     meta_entries.emplace_back(std::make_pair(meta_keys[i], meta_values[i]));
   }
-  EXPECT_THAT(
-      meta_entries,
-      UnorderedElementsAreArray(
-          {std::make_pair(metadata::benchmark_name, Variadic::String(1)),
-           std::make_pair(metadata::benchmark_story_tags, Variadic::String(2)),
-           std::make_pair(metadata::benchmark_story_tags,
-                          Variadic::String(3))}));
+  EXPECT_THAT(meta_entries,
+              UnorderedElementsAreArray({std::make_pair(benchmark_id, 1),
+                                         std::make_pair(tags_id, 2),
+                                         std::make_pair(tags_id, 3)}));
 }
 
 TEST_F(ProtoTraceParserTest, AndroidPackagesList) {
@@ -2426,25 +2431,15 @@
   // structure, make an assumption that metadata storage is filled in in the
   // FIFO order of seen packages.
   const auto& args = context_.storage->args();
-  const auto& metadata = context_.storage->metadata();
-  const auto& meta_keys = metadata.keys();
-  const auto& meta_values = metadata.values();
+  const auto& metadata = context_.storage->metadata_table();
 
-  ASSERT_TRUE(std::count(meta_keys.cbegin(), meta_keys.cend(),
-                         metadata::android_packages_list) == 2);
+  Table package_list = metadata.Filter(
+      {metadata.name().eq(metadata::kNames[metadata::android_packages_list])});
+  ASSERT_EQ(package_list.row_count(), 2u);
 
-  auto first_meta_idx = std::distance(
-      meta_keys.cbegin(), std::find(meta_keys.cbegin(), meta_keys.cend(),
-                                    metadata::android_packages_list));
-  auto second_meta_idx = std::distance(
-      meta_keys.cbegin(),
-      std::find(meta_keys.cbegin() + first_meta_idx + 1, meta_keys.cend(),
-                metadata::android_packages_list));
-
-  uint32_t first_set_id = static_cast<uint32_t>(
-      meta_values[static_cast<size_t>(first_meta_idx)].int_value);
-  uint32_t second_set_id = static_cast<uint32_t>(
-      meta_values[static_cast<size_t>(second_meta_idx)].int_value);
+  const Column* int_value = package_list.GetColumnByName("int_value");
+  uint32_t first_set_id = static_cast<uint32_t>(int_value->Get(0).long_value);
+  uint32_t second_set_id = static_cast<uint32_t>(int_value->Get(1).long_value);
 
   // helper to look up arg values
   auto find_arg = [&args, this](ArgSetId set_id, const char* arg_name) {
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 5beed38..dd143dd 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -21,6 +21,7 @@
 #include "perfetto/protozero/proto_decoder.h"
 #include "src/trace_processor/event_tracker.h"
 #include "src/trace_processor/metadata.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/syscall_tracker.h"
 #include "src/trace_processor/trace_processor_context.h"
@@ -308,18 +309,18 @@
     StringPool::Id machine_id =
         context_->storage->InternString(utsname.machine());
 
-    context_->storage->SetMetadata(metadata::system_name,
-                                   Variadic::String(sysname_id));
-    context_->storage->SetMetadata(metadata::system_version,
-                                   Variadic::String(version_id));
-    context_->storage->SetMetadata(metadata::system_release,
-                                   Variadic::String(release_id));
-    context_->storage->SetMetadata(metadata::system_machine,
-                                   Variadic::String(machine_id));
+    MetadataTracker* metadata = context_->metadata_tracker.get();
+    metadata->SetMetadata(metadata::system_name, Variadic::String(sysname_id));
+    metadata->SetMetadata(metadata::system_version,
+                          Variadic::String(version_id));
+    metadata->SetMetadata(metadata::system_release,
+                          Variadic::String(release_id));
+    metadata->SetMetadata(metadata::system_machine,
+                          Variadic::String(machine_id));
   }
 
   if (packet.has_android_build_fingerprint()) {
-    context_->storage->SetMetadata(
+    context_->metadata_tracker->SetMetadata(
         metadata::android_build_fingerprint,
         Variadic::String(context_->storage->InternString(
             packet.android_build_fingerprint())));
diff --git a/src/trace_processor/metadata.h b/src/trace_processor/metadata.h
index aad028f..e7edcd8 100644
--- a/src/trace_processor/metadata.h
+++ b/src/trace_processor/metadata.h
@@ -28,41 +28,58 @@
 
 // Compile time list of metadata items.
 // clang-format off
-#define PERFETTO_TP_METADATA(F)                                        \
-  F(benchmark_description,               kSingle,  Variadic::kString), \
-  F(benchmark_name,                      kSingle,  Variadic::kString), \
-  F(benchmark_start_time_us,             kSingle,  Variadic::kInt),    \
-  F(benchmark_had_failures,              kSingle,  Variadic::kInt),    \
-  F(benchmark_label,                     kSingle,  Variadic::kString), \
-  F(benchmark_story_name,                kSingle,  Variadic::kString), \
-  F(benchmark_story_run_index,           kSingle,  Variadic::kInt),    \
-  F(benchmark_story_run_time_us,         kSingle,  Variadic::kInt),    \
-  F(benchmark_story_tags,                kMulti,   Variadic::kString), \
-  F(android_packages_list,               kMulti,   Variadic::kInt),    \
-  F(statsd_triggering_subscription_id,   kSingle,  Variadic::kInt),    \
-  F(trace_uuid,                          kSingle,  Variadic::kString), \
-  F(system_name,                         kSingle,  Variadic::kString), \
-  F(system_version,                      kSingle,  Variadic::kString), \
-  F(system_release,                      kSingle,  Variadic::kString), \
-  F(system_machine,                      kSingle,  Variadic::kString), \
-  F(android_build_fingerprint,           kSingle,  Variadic::kString)
+#define PERFETTO_TP_METADATA(F)                                               \
+  F(benchmark_description,             KeyType::kSingle,  Variadic::kString), \
+  F(benchmark_name,                    KeyType::kSingle,  Variadic::kString), \
+  F(benchmark_start_time_us,           KeyType::kSingle,  Variadic::kInt),    \
+  F(benchmark_had_failures,            KeyType::kSingle,  Variadic::kInt),    \
+  F(benchmark_label,                   KeyType::kSingle,  Variadic::kString), \
+  F(benchmark_story_name,              KeyType::kSingle,  Variadic::kString), \
+  F(benchmark_story_run_index,         KeyType::kSingle,  Variadic::kInt),    \
+  F(benchmark_story_run_time_us,       KeyType::kSingle,  Variadic::kInt),    \
+  F(benchmark_story_tags,              KeyType::kMulti,   Variadic::kString), \
+  F(android_packages_list,             KeyType::kMulti,   Variadic::kInt),    \
+  F(statsd_triggering_subscription_id, KeyType::kSingle,  Variadic::kInt),    \
+  F(trace_uuid,                        KeyType::kSingle,  Variadic::kString), \
+  F(system_name,                       KeyType::kSingle,  Variadic::kString), \
+  F(system_version,                    KeyType::kSingle,  Variadic::kString), \
+  F(system_release,                    KeyType::kSingle,  Variadic::kString), \
+  F(system_machine,                    KeyType::kSingle,  Variadic::kString), \
+  F(android_build_fingerprint,         KeyType::kSingle,  Variadic::kString)
 // clang-format on
 
-enum KeyType {
-  kSingle,  // One value per key.
-  kMulti    // Multiple values per key.
+// Compile time list of metadata items.
+// clang-format off
+#define PERFETTO_TP_METADATA_KEY_TYPES(F) \
+  F(kSingle, "single"),                   \
+  F(kMulti,  "multi")
+// clang-format
+
+#define PERFETTO_TP_META_TYPE_ENUM(varname, ...) varname
+enum class KeyType : size_t {
+  PERFETTO_TP_METADATA_KEY_TYPES(PERFETTO_TP_META_TYPE_ENUM),
+  kNumKeyTypes,
+};
+
+#define PERFETTO_TP_META_TYPE_NAME(_, name, ...) name
+constexpr char const* kKeyTypeNames[] = {
+  PERFETTO_TP_METADATA_KEY_TYPES(PERFETTO_TP_META_TYPE_NAME)
 };
 
 // Declares an enum of literals (one for each item). The enum values of each
 // literal corresponds to the string index in the arrays below.
 #define PERFETTO_TP_META_ENUM(name, ...) name
-enum KeyIDs : size_t { PERFETTO_TP_METADATA(PERFETTO_TP_META_ENUM), kNumKeys };
+enum KeyIDs : size_t {
+  PERFETTO_TP_METADATA(PERFETTO_TP_META_ENUM),
+  kNumKeys
+};
 
 // The code below declares an array for each property:
 // name, key type, value type.
 
 #define PERFETTO_TP_META_NAME(name, ...) #name
-constexpr char const* kNames[] = {PERFETTO_TP_METADATA(PERFETTO_TP_META_NAME)};
+constexpr char const* kNames[] = {
+  PERFETTO_TP_METADATA(PERFETTO_TP_META_NAME)};
 
 #define PERFETTO_TP_META_KEYTYPE(_, type, ...) type
 constexpr KeyType kKeyTypes[] = {
diff --git a/src/trace_processor/metadata_table.cc b/src/trace_processor/metadata_table.cc
deleted file mode 100644
index a31a7c0..0000000
--- a/src/trace_processor/metadata_table.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2019 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/metadata_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/storage_columns.h"
-#include "src/trace_processor/storage_schema.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-MetadataTable::MetadataTable(sqlite3*, const TraceStorage* storage)
-    : storage_(storage) {}
-
-void MetadataTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
-  SqliteTable::Register<MetadataTable>(db, storage, "metadata");
-}
-
-StorageSchema MetadataTable::CreateStorageSchema() {
-  return StorageSchema::Builder()
-      .AddColumn<StringColumn<MetadataKeyNameAccessor>>(
-          "name", &storage_->metadata().keys())
-      .AddColumn<StringColumn<MetadataKeyTypeAccessor>>(
-          "key_type", &storage_->metadata().keys())
-      .AddColumn<ValueColumn>("int_value", Variadic::Type::kInt, storage_)
-      .AddColumn<ValueColumn>("str_value", Variadic::Type::kString, storage_)
-      .Build({"name"});
-}
-
-uint32_t MetadataTable::RowCount() {
-  return static_cast<uint32_t>(storage_->metadata().keys().size());
-}
-
-int MetadataTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
-  return SQLITE_OK;
-}
-
-MetadataTable::MetadataKeyNameAccessor::MetadataKeyNameAccessor(
-    const std::deque<metadata::KeyIDs>* keys)
-    : keys_(keys) {}
-
-MetadataTable::MetadataKeyNameAccessor::~MetadataKeyNameAccessor() = default;
-
-MetadataTable::MetadataKeyTypeAccessor::MetadataKeyTypeAccessor(
-    const std::deque<metadata::KeyIDs>* keys)
-    : keys_(keys) {}
-
-MetadataTable::MetadataKeyTypeAccessor::~MetadataKeyTypeAccessor() = default;
-
-MetadataTable::ValueColumn::ValueColumn(std::string col_name,
-                                        Variadic::Type type,
-                                        const TraceStorage* storage)
-    : StorageColumn(col_name, false /* hidden */),
-      type_(type),
-      storage_(storage) {
-  PERFETTO_CHECK(type == Variadic::Type::kInt ||
-                 type == Variadic::Type::kString);
-}
-
-void MetadataTable::ValueColumn::ReportResult(sqlite3_context* ctx,
-                                              uint32_t row) const {
-  const auto& metadata = storage_->metadata();
-  auto value_type = metadata::kValueTypes[metadata.keys()[row]];
-  if (value_type != type_) {
-    sqlite3_result_null(ctx);
-    return;
-  }
-
-  if (value_type == Variadic::Type::kInt) {
-    sqlite_utils::ReportSqliteResult(ctx, metadata.values()[row].int_value);
-    return;
-  }
-  if (value_type == Variadic::Type::kString) {
-    const char* str =
-        storage_->GetString(metadata.values()[row].string_value).c_str();
-    sqlite3_result_text(ctx, str, -1, sqlite_utils::kSqliteStatic);
-    return;
-  }
-  PERFETTO_FATAL("Unimplemented metadata value type.");
-}
-
-MetadataTable::ValueColumn::Bounds MetadataTable::ValueColumn::BoundFilter(
-    int,
-    sqlite3_value*) const {
-  return Bounds{};
-}
-
-void MetadataTable::ValueColumn::Filter(int op,
-                                        sqlite3_value* value,
-                                        FilteredRowIndex* index) const {
-  if (type_ == Variadic::Type::kInt) {
-    bool op_is_null = sqlite_utils::IsOpIsNull(op);
-    auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
-    index->FilterRows(
-        [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
-          const auto& arg = storage_->metadata().values()[row];
-          return arg.type == type_ ? predicate(arg.int_value) : op_is_null;
-        });
-    return;
-  }
-  if (type_ == Variadic::Type::kString) {
-    auto predicate = sqlite_utils::CreateStringPredicate(op, value);
-    index->FilterRows([this, &predicate](uint32_t row) PERFETTO_ALWAYS_INLINE {
-      const auto& arg = storage_->metadata().values()[row];
-      return arg.type == type_
-                 ? predicate(storage_->GetString(arg.string_value).c_str())
-                 : predicate(nullptr);
-    });
-    return;
-  }
-  PERFETTO_FATAL("Unimplemented metadata value type.");
-}
-
-MetadataTable::ValueColumn::Comparator MetadataTable::ValueColumn::Sort(
-    const QueryConstraints::OrderBy& ob) const {
-  if (ob.desc) {
-    return [this](uint32_t f, uint32_t s) { return -CompareRefsAsc(f, s); };
-  }
-  return [this](uint32_t f, uint32_t s) { return CompareRefsAsc(f, s); };
-}
-
-int MetadataTable::ValueColumn::CompareRefsAsc(uint32_t f, uint32_t s) const {
-  const auto& arg_f = storage_->metadata().values()[f];
-  const auto& arg_s = storage_->metadata().values()[s];
-
-  if (arg_f.type == type_ && arg_s.type == type_) {
-    if (type_ == Variadic::Type::kInt) {
-      return sqlite_utils::CompareValuesAsc(arg_f.int_value, arg_s.int_value);
-    }
-    if (type_ == Variadic::Type::kString) {
-      const auto& f_str = storage_->GetString(arg_f.string_value);
-      const auto& s_str = storage_->GetString(arg_s.string_value);
-      return sqlite_utils::CompareValuesAsc(f_str, s_str);
-    }
-    PERFETTO_FATAL("Unimplemented metadata value type.");
-  } else if (arg_s.type == type_) {
-    return -1;
-  } else if (arg_f.type == type_) {
-    return 1;
-  }
-  return 0;
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/metadata_table.h b/src/trace_processor/metadata_table.h
deleted file mode 100644
index c7b0dfe..0000000
--- a/src/trace_processor/metadata_table.h
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2019 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_METADATA_TABLE_H_
-#define SRC_TRACE_PROCESSOR_METADATA_TABLE_H_
-
-#include "src/trace_processor/metadata.h"
-#include "src/trace_processor/storage_table.h"
-#include "src/trace_processor/trace_storage.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class MetadataTable : public StorageTable {
- public:
-  MetadataTable(sqlite3*, const TraceStorage*);
-
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
-
-  // StorageTable implementation.
-  StorageSchema CreateStorageSchema() override;
-  uint32_t RowCount() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
-
- private:
-  // Returns the stringified key enum name from metadata::kNames.
-  class MetadataKeyNameAccessor : public Accessor<NullTermStringView> {
-   public:
-    MetadataKeyNameAccessor(const std::deque<metadata::KeyIDs>* keys);
-    ~MetadataKeyNameAccessor() override;
-
-    uint32_t Size() const override {
-      return static_cast<uint32_t>(keys_->size());
-    }
-
-    NullTermStringView Get(uint32_t idx) const override {
-      return NullTermStringView(metadata::kNames[(*keys_)[idx]]);
-    }
-
-   private:
-    const std::deque<metadata::KeyIDs>* keys_;
-  };
-
-  // Returns the stringified metadata type, "single" for scalar, "multi" for
-  // repeated.
-  class MetadataKeyTypeAccessor : public Accessor<NullTermStringView> {
-   public:
-    MetadataKeyTypeAccessor(const std::deque<metadata::KeyIDs>* keys);
-    ~MetadataKeyTypeAccessor() override;
-
-    uint32_t Size() const override {
-      return static_cast<uint32_t>(keys_->size());
-    }
-
-    NullTermStringView Get(uint32_t idx) const override {
-      switch (metadata::kKeyTypes[(*keys_)[idx]]) {
-        case metadata::KeyType::kSingle:
-          return NullTermStringView("single");
-          break;
-        case metadata::KeyType::kMulti:
-          return NullTermStringView("multi");
-          break;
-      }
-      PERFETTO_FATAL("unsupported metadata type");  // for gcc
-    }
-
-   private:
-    const std::deque<metadata::KeyIDs>* keys_;
-  };
-
-  // Returns values from Variadic storage. Only supports columns of
-  // type Variadic::Type::kInt or Variadic::Type::kString.
-  //
-  // Based on ArgsTable::ValueColumn.
-  class ValueColumn final : public StorageColumn {
-   public:
-    ValueColumn(std::string col_name,
-                Variadic::Type type,
-                const TraceStorage* storage);
-
-    void ReportResult(sqlite3_context* ctx, uint32_t row) const override;
-    Bounds BoundFilter(int op, sqlite3_value* sqlite_val) const override;
-    void Filter(int op, sqlite3_value* value, FilteredRowIndex*) const override;
-    Comparator Sort(const QueryConstraints::OrderBy& ob) const override;
-
-    bool HasOrdering() const override { return false; }
-
-    SqlValue::Type GetType() const override {
-      if (type_ == Variadic::Type::kInt)
-        return SqlValue::Type::kLong;
-      if (type_ == Variadic::Type::kString)
-        return SqlValue::Type::kString;
-      PERFETTO_FATAL("Unimplemented metadata value type.");
-    }
-
-   private:
-    int CompareRefsAsc(uint32_t f, uint32_t s) const;
-
-    Variadic::Type type_;
-    const TraceStorage* storage_ = nullptr;
-  };
-
-  const TraceStorage* const storage_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_METADATA_TABLE_H_
diff --git a/src/trace_processor/metadata_table_unittest.cc b/src/trace_processor/metadata_table_unittest.cc
deleted file mode 100644
index 9f8309c..0000000
--- a/src/trace_processor/metadata_table_unittest.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2019 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/metadata_table.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/trace_processor_context.h"
-#include "src/trace_processor/trace_storage.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-
-class MetadataTableUnittest : public ::testing::Test {
- public:
-  MetadataTableUnittest() {
-    sqlite3* db = nullptr;
-    PERFETTO_CHECK(sqlite3_initialize() == SQLITE_OK);
-    PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
-    db_.reset(db);
-
-    context_.storage.reset(new TraceStorage());
-    MetadataTable::RegisterTable(db_.get(), context_.storage.get());
-  }
-
-  void PrepareValidStatement(const std::string& sql) {
-    int size = static_cast<int>(sql.size());
-    sqlite3_stmt* stmt;
-    ASSERT_EQ(sqlite3_prepare_v2(*db_, sql.c_str(), size, &stmt, nullptr),
-              SQLITE_OK);
-    stmt_.reset(stmt);
-  }
-
-  const char* GetColumnAsText(int colId) {
-    return reinterpret_cast<const char*>(sqlite3_column_text(*stmt_, colId));
-  }
-
- protected:
-  TraceProcessorContext context_;
-  ScopedDb db_;
-  ScopedStmt stmt_;
-};
-
-TEST_F(MetadataTableUnittest, NoEntries) {
-  PrepareValidStatement("SELECT * FROM metadata");
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
-}
-
-TEST_F(MetadataTableUnittest, SingleStringValue) {
-  static const char kName[] = "benchmark";
-  Variadic value = Variadic::String(context_.storage->InternString(kName));
-  context_.storage->SetMetadata(metadata::benchmark_name, value);
-
-  PrepareValidStatement("SELECT * FROM metadata");
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_STREQ(GetColumnAsText(0), "benchmark_name");      // name
-  ASSERT_STREQ(GetColumnAsText(1), "single");              // key_type
-  ASSERT_EQ(sqlite3_column_type(*stmt_, 2), SQLITE_NULL);  // int_value
-  ASSERT_STREQ(GetColumnAsText(3), kName);                 // str_value
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
-}
-
-TEST_F(MetadataTableUnittest, SingleIntegerValue) {
-  static const int64_t kTimestamp = 1234567890;
-  Variadic value = Variadic::Integer(kTimestamp);
-  context_.storage->SetMetadata(metadata::benchmark_story_run_time_us, value);
-
-  PrepareValidStatement("SELECT * FROM metadata");
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_STREQ(GetColumnAsText(0), "benchmark_story_run_time_us");  // name
-  ASSERT_STREQ(GetColumnAsText(1), "single");                       // key_type
-  ASSERT_EQ(sqlite3_column_int64(*stmt_, 2), kTimestamp);           // int_value
-  ASSERT_EQ(sqlite3_column_type(*stmt_, 3), SQLITE_NULL);           // str_value
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
-}
-
-TEST_F(MetadataTableUnittest, MultipleStringValues) {
-  static const char kTag1[] = "foo";
-  static const char kTag2[] = "bar";
-  Variadic tag1 = Variadic::String(context_.storage->InternString(kTag1));
-  Variadic tag2 = Variadic::String(context_.storage->InternString(kTag2));
-  context_.storage->AppendMetadata(metadata::benchmark_story_tags, tag1);
-  context_.storage->AppendMetadata(metadata::benchmark_story_tags, tag2);
-
-  PrepareValidStatement("SELECT * FROM metadata");
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_STREQ(GetColumnAsText(0), "benchmark_story_tags");  // name
-  ASSERT_STREQ(GetColumnAsText(1), "multi");                 // key_type
-  ASSERT_EQ(sqlite3_column_type(*stmt_, 2), SQLITE_NULL);    // int_value
-  ASSERT_STREQ(GetColumnAsText(3), kTag1);                   // str_value
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_ROW);
-  ASSERT_STREQ(GetColumnAsText(0), "benchmark_story_tags");  // name
-  ASSERT_STREQ(GetColumnAsText(1), "multi");                 // key_type
-  ASSERT_EQ(sqlite3_column_type(*stmt_, 2), SQLITE_NULL);    // int_value
-  ASSERT_STREQ(GetColumnAsText(3), kTag2);                   // str_value
-
-  ASSERT_EQ(sqlite3_step(*stmt_), SQLITE_DONE);
-}
-
-}  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/metadata_tracker.cc b/src/trace_processor/metadata_tracker.cc
new file mode 100644
index 0000000..e105d01
--- /dev/null
+++ b/src/trace_processor/metadata_tracker.cc
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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/metadata_tracker.h"
+
+#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+MetadataTracker::MetadataTracker(TraceProcessorContext* context)
+    : context_(context) {
+  for (uint32_t i = 0; i < kNumKeys; ++i) {
+    key_ids_[i] = context->storage->InternString(metadata::kNames[i]);
+  }
+  for (uint32_t i = 0; i < kNumKeyTypes; ++i) {
+    key_type_ids_[i] =
+        context->storage->InternString(metadata::kKeyTypeNames[i]);
+  }
+}
+
+MetadataId MetadataTracker::SetMetadata(metadata::KeyIDs key, Variadic value) {
+  PERFETTO_DCHECK(metadata::kKeyTypes[key] == metadata::KeyType::kSingle);
+  PERFETTO_DCHECK(value.type == metadata::kValueTypes[key]);
+
+  auto* metadata_table = context_->storage->mutable_metadata_table();
+  uint32_t key_idx = static_cast<uint32_t>(key);
+  base::Optional<uint32_t> opt_row =
+      metadata_table->name().IndexOf(metadata::kNames[key_idx]);
+  if (opt_row) {
+    WriteValue(*opt_row, value);
+    return metadata_table->id()[*opt_row];
+  }
+
+  tables::MetadataTable::Row row;
+  row.name = key_ids_[key_idx];
+  row.key_type = key_type_ids_[static_cast<size_t>(metadata::KeyType::kSingle)];
+
+  MetadataId id = metadata_table->Insert(row);
+  WriteValue(*metadata_table->id().IndexOf(id), value);
+  return id;
+}
+
+MetadataId MetadataTracker::AppendMetadata(metadata::KeyIDs key,
+                                           Variadic value) {
+  PERFETTO_DCHECK(key < metadata::kNumKeys);
+  PERFETTO_DCHECK(metadata::kKeyTypes[key] == metadata::KeyType::kMulti);
+  PERFETTO_DCHECK(value.type == metadata::kValueTypes[key]);
+
+  uint32_t key_idx = static_cast<uint32_t>(key);
+  tables::MetadataTable::Row row;
+  row.name = key_ids_[key_idx];
+  row.key_type = key_type_ids_[static_cast<size_t>(metadata::KeyType::kMulti)];
+
+  auto* metadata_table = context_->storage->mutable_metadata_table();
+  MetadataId id = metadata_table->Insert(row);
+  WriteValue(*metadata_table->id().IndexOf(id), value);
+  return id;
+}
+
+void MetadataTracker::WriteValue(uint32_t row, Variadic value) {
+  auto* metadata_table = context_->storage->mutable_metadata_table();
+  switch (value.type) {
+    case Variadic::Type::kInt:
+      metadata_table->mutable_int_value()->Set(row, value.int_value);
+      break;
+    case Variadic::Type::kString:
+      metadata_table->mutable_str_value()->Set(row, value.string_value);
+      break;
+    case Variadic::Type::kJson:
+    case Variadic::Type::kBool:
+    case Variadic::Type::kPointer:
+    case Variadic::Type::kUint:
+    case Variadic::Type::kReal:
+      PERFETTO_FATAL("Unsupported value type");
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/metadata_tracker.h b/src/trace_processor/metadata_tracker.h
new file mode 100644
index 0000000..38685ac
--- /dev/null
+++ b/src/trace_processor/metadata_tracker.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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_METADATA_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_METADATA_TRACKER_H_
+
+#include "src/trace_processor/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+// Tracks information in the metadata table.
+class MetadataTracker {
+ public:
+  MetadataTracker(TraceProcessorContext* context);
+
+  // Example usage:
+  // SetMetadata(metadata::benchmark_name,
+  //             Variadic::String(storage->InternString("foo"));
+  // Returns the id of the new entry.
+  MetadataId SetMetadata(metadata::KeyIDs key, Variadic value);
+
+  // Example usage:
+  // AppendMetadata(metadata::benchmark_story_tags,
+  //                Variadic::String(storage->InternString("bar"));
+  // Returns the id of the new entry.
+  MetadataId AppendMetadata(metadata::KeyIDs key, Variadic value);
+
+ private:
+  static constexpr size_t kNumKeys =
+      static_cast<size_t>(metadata::KeyIDs::kNumKeys);
+  static constexpr size_t kNumKeyTypes =
+      static_cast<size_t>(metadata::KeyType::kNumKeyTypes);
+
+  void WriteValue(uint32_t row, Variadic value);
+
+  std::array<StringId, kNumKeys> key_ids_;
+  std::array<StringId, kNumKeyTypes> key_type_ids_;
+
+  TraceProcessorContext* context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_METADATA_TRACKER_H_
diff --git a/src/trace_processor/raw_table.cc b/src/trace_processor/raw_table.cc
index f657650..ac8127f 100644
--- a/src/trace_processor/raw_table.cc
+++ b/src/trace_processor/raw_table.cc
@@ -96,20 +96,22 @@
 }
 
 bool RawTable::ParseGfpFlags(Variadic value, base::StringWriter* writer) {
-  if (!storage_->metadata().MetadataExists(metadata::KeyIDs::system_name) ||
-      !storage_->metadata().MetadataExists(metadata::KeyIDs::system_release)) {
-    return false;
-  }
+  const auto& metadata_table = storage_->metadata_table();
 
-  const Variadic& name =
-      storage_->metadata().GetScalarMetadata(metadata::KeyIDs::system_name);
-  base::StringView system_name = storage_->GetString(name.string_value);
+  auto opt_name_idx = metadata_table.name().IndexOf(
+      metadata::kNames[metadata::KeyIDs::system_name]);
+  auto opt_release_idx = metadata_table.name().IndexOf(
+      metadata::kNames[metadata::KeyIDs::system_release]);
+  if (!opt_name_idx || !opt_release_idx)
+    return false;
+
+  StringId name = metadata_table.str_value()[*opt_name_idx];
+  base::StringView system_name = storage_->GetString(name);
   if (system_name != "Linux")
     return false;
 
-  const Variadic& release =
-      storage_->metadata().GetScalarMetadata(metadata::KeyIDs::system_release);
-  base::StringView system_release = storage_->GetString(release.string_value);
+  StringId release = metadata_table.str_value()[*opt_release_idx];
+  base::StringView system_release = storage_->GetString(release);
   auto version = ParseKernelReleaseVersion(system_release);
 
   WriteGfpFlag(value.uint_value, version, writer);
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index aebd76c..268e419 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -20,6 +20,7 @@
     "counter_tables.h",
     "macros.h",
     "macros_internal.h",
+    "metadata_tables.h",
     "profiler_tables.h",
     "slice_tables.h",
     "track_tables.h",
diff --git a/src/trace_processor/tables/metadata_tables.h b/src/trace_processor/tables/metadata_tables.h
new file mode 100644
index 0000000..9a2095d
--- /dev/null
+++ b/src/trace_processor/tables/metadata_tables.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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_TABLES_METADATA_TABLES_H_
+#define SRC_TRACE_PROCESSOR_TABLES_METADATA_TABLES_H_
+
+#include "src/trace_processor/tables/macros.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace tables {
+
+#define PERFETTO_TP_METADATA_TABLE_DEF(NAME, PARENT, C) \
+  NAME(MetadataTable, "metadata")                       \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                     \
+  C(StringPool::Id, name)                               \
+  C(StringPool::Id, key_type)                           \
+  C(base::Optional<int64_t>, int_value)                 \
+  C(base::Optional<StringPool::Id>, str_value)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_METADATA_TABLE_DEF);
+
+}  // namespace tables
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_TABLES_METADATA_TABLES_H_
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index dbefd62..52ec6de 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -25,6 +25,7 @@
 #include "src/trace_processor/importers/json/json_trace_parser.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/stack_profile_tracker.h"
diff --git a/src/trace_processor/trace_processor_context.h b/src/trace_processor/trace_processor_context.h
index 2fd3c15..10e6eb4 100644
--- a/src/trace_processor/trace_processor_context.h
+++ b/src/trace_processor/trace_processor_context.h
@@ -35,6 +35,7 @@
 class FtraceModule;
 class HeapGraphTracker;
 class HeapProfileTracker;
+class MetadataTracker;
 class ProcessTracker;
 class SliceTracker;
 class TraceParser;
@@ -60,6 +61,7 @@
   std::unique_ptr<TraceSorter> sorter;
   std::unique_ptr<ChunkedTraceReader> chunk_reader;
   std::unique_ptr<HeapProfileTracker> heap_profile_tracker;
+  std::unique_ptr<MetadataTracker> metadata_tracker;
 
   // These fields are stored as pointers to Destructible objects rather than
   // their actual type (a subclass of Destructible), as the concrete subclass
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 191780b..c843df7 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -25,7 +25,6 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "src/trace_processor/args_table.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
-#include "src/trace_processor/metadata_table.h"
 #include "src/trace_processor/process_table.h"
 #include "src/trace_processor/raw_table.h"
 #include "src/trace_processor/register_additional_modules.h"
@@ -379,7 +378,6 @@
   WindowOperatorTable::RegisterTable(*db_, context_.storage.get());
   StatsTable::RegisterTable(*db_, context_.storage.get());
   RawTable::RegisterTable(*db_, context_.storage.get());
-  MetadataTable::RegisterTable(*db_, context_.storage.get());
 
   // New style db-backed tables.
   const TraceStorage* storage = context_.storage.get();
@@ -451,6 +449,9 @@
   DbSqliteTable::RegisterTable(
       *db_, &storage->vulkan_memory_allocations_table(),
       storage->vulkan_memory_allocations_table().table_name());
+
+  DbSqliteTable::RegisterTable(*db_, &storage->metadata_table(),
+                               storage->metadata_table().table_name());
 }
 
 TraceProcessorImpl::~TraceProcessorImpl() {
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 9e0726d..0ad11ee 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -26,6 +26,7 @@
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
+#include "src/trace_processor/metadata_tracker.h"
 #include "src/trace_processor/process_tracker.h"
 #include "src/trace_processor/slice_tracker.h"
 #include "src/trace_processor/stack_profile_tracker.h"
@@ -46,6 +47,7 @@
   context_.process_tracker.reset(new ProcessTracker(&context_));
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.heap_profile_tracker.reset(new HeapProfileTracker(&context_));
+  context_.metadata_tracker.reset(new MetadataTracker(&context_));
 
   context_.modules.emplace_back(new FtraceModule());
   // Ftrace module is special, because it has one extra method for parsing
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 2faa5d9..83a609b 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -38,6 +38,7 @@
 #include "src/trace_processor/stats.h"
 #include "src/trace_processor/tables/android_tables.h"
 #include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/tables/metadata_tables.h"
 #include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/tables/slice_tables.h"
 #include "src/trace_processor/tables/track_tables.h"
@@ -85,6 +86,8 @@
 
 using MappingId = tables::StackProfileMappingTable::Id;
 
+using MetadataId = tables::MetadataTable::Id;
+
 // TODO(lalitm): this is a temporary hack while migrating the counters table and
 // will be removed when the migration is complete.
 static const TrackId kInvalidTrackId =
@@ -479,64 +482,6 @@
   };
   using StatsMap = std::array<Stats, stats::kNumKeys>;
 
-  class Metadata {
-   public:
-    const std::deque<metadata::KeyIDs>& keys() const { return keys_; }
-    const std::deque<Variadic>& values() const { return values_; }
-
-    uint32_t SetScalarMetadata(metadata::KeyIDs key, Variadic value) {
-      PERFETTO_DCHECK(key < metadata::kNumKeys);
-      PERFETTO_DCHECK(metadata::kKeyTypes[key] == metadata::kSingle);
-      PERFETTO_DCHECK(value.type == metadata::kValueTypes[key]);
-
-      // Already set - on release builds, overwrite the previous value.
-      auto it = scalar_indices.find(key);
-      if (it != scalar_indices.end()) {
-        PERFETTO_DFATAL("Setting a scalar metadata entry more than once.");
-        uint32_t index = static_cast<uint32_t>(it->second);
-        values_[index] = value;
-        return index;
-      }
-      // First time setting this key.
-      keys_.push_back(key);
-      values_.push_back(value);
-      uint32_t index = static_cast<uint32_t>(keys_.size() - 1);
-      scalar_indices[key] = index;
-      return index;
-    }
-
-    uint32_t AppendMetadata(metadata::KeyIDs key, Variadic value) {
-      PERFETTO_DCHECK(key < metadata::kNumKeys);
-      PERFETTO_DCHECK(metadata::kKeyTypes[key] == metadata::kMulti);
-      PERFETTO_DCHECK(value.type == metadata::kValueTypes[key]);
-
-      keys_.push_back(key);
-      values_.push_back(value);
-      return static_cast<uint32_t>(keys_.size() - 1);
-    }
-
-    const Variadic& GetScalarMetadata(metadata::KeyIDs key) const {
-      PERFETTO_DCHECK(scalar_indices.count(key) == 1);
-      return values_.at(scalar_indices.at(key));
-    }
-
-    bool MetadataExists(metadata::KeyIDs key) const {
-      return scalar_indices.count(key) >= 1;
-    }
-
-    void OverwriteMetadata(uint32_t index, Variadic value) {
-      PERFETTO_DCHECK(index < values_.size());
-      values_[index] = value;
-    }
-
-   private:
-    std::deque<metadata::KeyIDs> keys_;
-    std::deque<Variadic> values_;
-    // Extraneous state to track locations of entries that should have at most
-    // one row. Used only to maintain uniqueness during insertions.
-    std::map<metadata::KeyIDs, uint32_t> scalar_indices;
-  };
-
   UniqueTid AddEmptyThread(uint32_t tid) {
     unique_threads_.emplace_back(tid);
     return static_cast<UniqueTid>(unique_threads_.size() - 1);
@@ -592,24 +537,6 @@
     stats_[key].indexed_values[index] = value;
   }
 
-  // Example usage:
-  // SetMetadata(metadata::benchmark_name,
-  //             Variadic::String(storage->InternString("foo"));
-  // Returns the row of the new entry.
-  // Virtual for testing.
-  virtual uint32_t SetMetadata(metadata::KeyIDs key, Variadic value) {
-    return metadata_.SetScalarMetadata(key, value);
-  }
-
-  // Example usage:
-  // AppendMetadata(metadata::benchmark_story_tags,
-  //                Variadic::String(storage->InternString("bar"));
-  // Returns the row of the new entry.
-  // Virtual for testing.
-  virtual uint32_t AppendMetadata(metadata::KeyIDs key, Variadic value) {
-    return metadata_.AppendMetadata(key, value);
-  }
-
   class ScopedStatsTracer {
    public:
     ScopedStatsTracer(TraceStorage* storage, size_t key)
@@ -772,8 +699,10 @@
 
   const StatsMap& stats() const { return stats_; }
 
-  const Metadata& metadata() const { return metadata_; }
-  Metadata* mutable_metadata() { return &metadata_; }
+  const tables::MetadataTable& metadata_table() const {
+    return metadata_table_;
+  }
+  tables::MetadataTable* mutable_metadata_table() { return &metadata_table_; }
 
   const Args& args() const { return args_; }
   Args* mutable_args() { return &args_; }
@@ -925,7 +854,7 @@
   // Extra data extracted from the trace. Includes:
   // * metadata from chrome and benchmarking infrastructure
   // * descriptions of android packages
-  Metadata metadata_{};
+  tables::MetadataTable metadata_table_{&string_pool_, nullptr};
 
   // Metadata for tracks.
   tables::TrackTable track_table_{&string_pool_, nullptr};
diff --git a/tools/gen_merged_protos b/tools/gen_merged_protos
index d8a8f7f..5cfece7 100755
--- a/tools/gen_merged_protos
+++ b/tools/gen_merged_protos
@@ -99,6 +99,8 @@
     'protos/perfetto/trace/track_event/chrome_histogram_sample.proto',
     'protos/perfetto/trace/track_event/chrome_keyed_service.proto',
     'protos/perfetto/trace/track_event/chrome_legacy_ipc.proto',
+    'protos/perfetto/trace/track_event/chrome_process_descriptor.proto',
+    'protos/perfetto/trace/track_event/chrome_thread_descriptor.proto',
     'protos/perfetto/trace/track_event/chrome_user_event.proto',
     'protos/perfetto/trace/track_event/debug_annotation.proto',
     'protos/perfetto/trace/track_event/log_message.proto',