[tp] Detect missing processes in Chromium traces

The data is exposed in a new experimental_missing_process_data SQL table.

Bug: b/244285392
Change-Id: I200d75cb3d48d96471eb3078b113e7efba058df6
diff --git a/Android.bp b/Android.bp
index 4f8a3c6..3771992 100644
--- a/Android.bp
+++ b/Android.bp
@@ -9125,6 +9125,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_proto_storage_minimal",
     srcs: [
+        "src/trace_processor/importers/proto/active_chrome_processes_tracker.cc",
         "src/trace_processor/importers/proto/heap_profile_tracker.cc",
         "src/trace_processor/importers/proto/profiler_util.cc",
         "src/trace_processor/importers/proto/stack_profile_tracker.cc",
@@ -9135,6 +9136,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_importers_proto_unittests",
     srcs: [
+        "src/trace_processor/importers/proto/active_chrome_processes_tracker_unittest.cc",
         "src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc",
         "src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc",
     ],
diff --git a/BUILD b/BUILD
index ab137e9..9c7ccec 100644
--- a/BUILD
+++ b/BUILD
@@ -1152,6 +1152,8 @@
 perfetto_filegroup(
     name = "src_trace_processor_importers_proto_storage_minimal",
     srcs = [
+        "src/trace_processor/importers/proto/active_chrome_processes_tracker.cc",
+        "src/trace_processor/importers/proto/active_chrome_processes_tracker.h",
         "src/trace_processor/importers/proto/heap_profile_tracker.cc",
         "src/trace_processor/importers/proto/heap_profile_tracker.h",
         "src/trace_processor/importers/proto/profiler_util.cc",
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 732e002..48c2f26 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -14,6 +14,8 @@
 
 source_set("storage_minimal") {
   sources = [
+    "active_chrome_processes_tracker.cc",
+    "active_chrome_processes_tracker.h",
     "heap_profile_tracker.cc",
     "heap_profile_tracker.h",
     "profiler_util.cc",
@@ -53,6 +55,7 @@
 source_set("unittests") {
   testonly = true
   sources = [
+    "active_chrome_processes_tracker_unittest.cc",
     "heap_graph_tracker_unittest.cc",
     "heap_profile_tracker_unittest.cc",
   ]
diff --git a/src/trace_processor/importers/proto/active_chrome_processes_tracker.cc b/src/trace_processor/importers/proto/active_chrome_processes_tracker.cc
new file mode 100644
index 0000000..4a54095
--- /dev/null
+++ b/src/trace_processor/importers/proto/active_chrome_processes_tracker.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/active_chrome_processes_tracker.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+std::vector<ProcessWithDataLoss>
+ActiveChromeProcessesTracker::GetProcessesWithDataLoss() const {
+  std::vector<ProcessWithDataLoss> processes_with_data_loss;
+  for (auto it = process_data_.GetIterator(); it; ++it) {
+    UniquePid upid = it.key();
+    const auto& process_data = it.value();
+    base::Optional<int64_t> last_loss_moment;
+    base::Optional<int64_t> next_no_loss_moment;
+    for (int64_t metadata_ts : process_data.metadata_timestamps) {
+      // Looks for a matching process descriptor in the [t - 0.2s, t + 0.2s]
+      // window. The window size is somewhat arbitrary, and can be changed in
+      // the future. It should be smaller than the incremental state reset
+      // interval, which is 5s for Chromium traces.
+      constexpr int64_t kMaxTimestampDiff = 200 * 1000 * 1000;
+      auto descriptor_it = process_data.descriptor_timestamps.lower_bound(
+          metadata_ts - kMaxTimestampDiff);
+      if (descriptor_it != process_data.descriptor_timestamps.end()) {
+        if (*descriptor_it > metadata_ts + kMaxTimestampDiff) {
+          // There's no matching descriptor, but there's a descriptor at some
+          // point in the future.
+          last_loss_moment = metadata_ts;
+          next_no_loss_moment = *descriptor_it;
+        }
+      } else {
+        // There's no matching descriptor, and there're no descriptors in the
+        // future.
+        last_loss_moment = metadata_ts;
+        next_no_loss_moment = base::nullopt;
+        break;
+      }
+    }
+    if (last_loss_moment) {
+      processes_with_data_loss.push_back({upid, next_no_loss_moment});
+    }
+  }
+  return processes_with_data_loss;
+}
+
+void ActiveChromeProcessesTracker::NotifyEndOfFile() {
+  const auto processes = GetProcessesWithDataLoss();
+  for (const auto& p : processes) {
+    tables::ExperimentalMissingChromeProcessesTable::Row row;
+    row.upid = p.upid;
+    row.reliable_from = p.reliable_from;
+    context_->storage->mutable_experimental_missing_chrome_processes_table()
+        ->Insert(row);
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/active_chrome_processes_tracker.h b/src/trace_processor/importers/proto/active_chrome_processes_tracker.h
new file mode 100644
index 0000000..ed5b83d
--- /dev/null
+++ b/src/trace_processor/importers/proto/active_chrome_processes_tracker.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ACTIVE_CHROME_PROCESSES_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ACTIVE_CHROME_PROCESSES_TRACKER_H_
+
+#include <set>
+#include <vector>
+#include "perfetto/ext/base/optional.h"
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+struct ProcessWithDataLoss {
+  UniquePid upid;
+  // If not nullopt, the process data is reliable from this point until
+  // the end of the trace.
+  base::Optional<int64_t> reliable_from;
+};
+
+// Tracks ActiveProcesses metadata packets from ChromeTrackEvent,
+// and process descriptors.
+// Computes a list of processes with missing data based on this information.
+class ActiveChromeProcessesTracker {
+ public:
+  explicit ActiveChromeProcessesTracker(TraceProcessorContext* context)
+      : context_(context) {}
+
+  void AddActiveProcessMetadata(int64_t timestamp, UniquePid upid) {
+    process_data_[upid].metadata_timestamps.insert(timestamp);
+  }
+  void AddProcessDescriptor(int64_t timestamp, UniquePid upid) {
+    process_data_[upid].descriptor_timestamps.insert(timestamp);
+  }
+  std::vector<ProcessWithDataLoss> GetProcessesWithDataLoss() const;
+  void NotifyEndOfFile();
+
+ private:
+  struct ProcessData {
+    std::set<int64_t> metadata_timestamps;
+    std::set<int64_t> descriptor_timestamps;
+  };
+
+  TraceProcessorContext* context_;
+  base::FlatHashMap<UniquePid, ProcessData> process_data_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ACTIVE_CHROME_PROCESSES_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/active_chrome_processes_tracker_unittest.cc b/src/trace_processor/importers/proto/active_chrome_processes_tracker_unittest.cc
new file mode 100644
index 0000000..6fdc1e8
--- /dev/null
+++ b/src/trace_processor/importers/proto/active_chrome_processes_tracker_unittest.cc
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/importers/proto/active_chrome_processes_tracker.h"
+
+#include "perfetto/base/logging.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+static bool operator==(const ProcessWithDataLoss& lhs,
+                       const ProcessWithDataLoss& rhs) {
+  return lhs.upid == rhs.upid && lhs.reliable_from == rhs.reliable_from;
+}
+
+namespace {
+
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAre;
+
+constexpr int64_t kNanosecondsInSecond = 1000 * 1000 * 1000;
+
+TEST(ActiveChromeProcessesTrackerTest, NoMetadataAndNoDescriptors) {
+  // No metadata and no descriptor = no processes are missing.
+  ActiveChromeProcessesTracker tracker(nullptr);
+  EXPECT_THAT(tracker.GetProcessesWithDataLoss(), IsEmpty());
+}
+
+TEST(ActiveChromeProcessesTrackerTest, NoDescriptors) {
+  ActiveChromeProcessesTracker tracker(nullptr);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/10, /*upid=*/1);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/10, /*upid=*/2);
+  EXPECT_THAT(tracker.GetProcessesWithDataLoss(),
+              UnorderedElementsAre(ProcessWithDataLoss{1, base::nullopt},
+                                   ProcessWithDataLoss{2, base::nullopt}));
+}
+
+TEST(ActiveChromeProcessesTrackerTest, InexactMatch) {
+  ActiveChromeProcessesTracker tracker(nullptr);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/10 * kNanosecondsInSecond,
+                                   /*upid=*/1);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/15 * kNanosecondsInSecond,
+                                   /*upid=*/1);
+  tracker.AddProcessDescriptor(
+      /*timestamp=*/10 * kNanosecondsInSecond - 200 * 1000 * 1000, /*upid=*/1);
+  tracker.AddProcessDescriptor(
+      /*timestamp=*/15 * kNanosecondsInSecond + 200 * 1000 * 1000, /*upid=*/1);
+  EXPECT_THAT(tracker.GetProcessesWithDataLoss(), IsEmpty());
+}
+
+TEST(ActiveChromeProcessesTrackerTest, InexactMatchTooBigDiff) {
+  ActiveChromeProcessesTracker tracker(nullptr);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/10 * kNanosecondsInSecond,
+                                   /*upid=*/1);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/15 * kNanosecondsInSecond,
+                                   /*upid=*/1);
+  tracker.AddProcessDescriptor(
+      /*timestamp=*/10 * kNanosecondsInSecond - 200 * 1000 * 1000 - 1,
+      /*upid=*/1);
+  tracker.AddProcessDescriptor(
+      /*timestamp=*/15 * kNanosecondsInSecond + 200 * 1000 * 1000 + 1,
+      /*upid=*/1);
+  EXPECT_THAT(tracker.GetProcessesWithDataLoss(),
+              UnorderedElementsAre(ProcessWithDataLoss{
+                  1, 15 * kNanosecondsInSecond + 200 * 1000 * 1000 + 1}));
+}
+
+TEST(ActiveChromeProcessesTrackerTest, ExtraDescriptor) {
+  // There're more descriptors than metadata packets - this is OK.
+  ActiveChromeProcessesTracker tracker(nullptr);
+  tracker.AddActiveProcessMetadata(/*timestamp=*/15 * kNanosecondsInSecond,
+                                   /*upid=*/1);
+  tracker.AddProcessDescriptor(/*timestamp=*/10 * kNanosecondsInSecond,
+                               /*upid=*/1);
+  tracker.AddProcessDescriptor(/*timestamp=*/15 * kNanosecondsInSecond,
+                               /*upid=*/1);
+  EXPECT_THAT(tracker.GetProcessesWithDataLoss(), IsEmpty());
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_module.cc b/src/trace_processor/importers/proto/track_event_module.cc
index f0ece26..547bf3b 100644
--- a/src/trace_processor/importers/proto/track_event_module.cc
+++ b/src/trace_processor/importers/proto/track_event_module.cc
@@ -72,17 +72,17 @@
 }
 
 void TrackEventModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
-                                            int64_t,
+                                            int64_t ts,
                                             const TracePacketData&,
                                             uint32_t field_id) {
   switch (field_id) {
     case TracePacket::kTrackDescriptorFieldNumber:
-      parser_.ParseTrackDescriptor(decoder.track_descriptor(),
+      parser_.ParseTrackDescriptor(ts, decoder.track_descriptor(),
                                    decoder.trusted_packet_sequence_id());
       break;
     case TracePacket::kProcessDescriptorFieldNumber:
       // TODO(eseckler): Remove once Chrome has switched to TrackDescriptors.
-      parser_.ParseProcessDescriptor(decoder.process_descriptor());
+      parser_.ParseProcessDescriptor(ts, decoder.process_descriptor());
       break;
     case TracePacket::kThreadDescriptorFieldNumber:
       // TODO(eseckler): Remove once Chrome has switched to TrackDescriptors.
@@ -101,5 +101,9 @@
   track_event_tracker_->OnFirstPacketOnSequence(packet_sequence_id);
 }
 
+void TrackEventModule::NotifyEndOfFile() {
+  parser_.NotifyEndOfFile();
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_module.h b/src/trace_processor/importers/proto/track_event_module.h
index 54652a1..2b68a88 100644
--- a/src/trace_processor/importers/proto/track_event_module.h
+++ b/src/trace_processor/importers/proto/track_event_module.h
@@ -52,6 +52,8 @@
                             const TracePacketData& data,
                             uint32_t field_id) override;
 
+  void NotifyEndOfFile() override;
+
  private:
   std::unique_ptr<TrackEventTracker> track_event_tracker_;
   TrackEventTokenizer tokenizer_;
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index bf6041f..cd8f7a5 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -70,10 +70,12 @@
 
 class TrackEventArgsParser : public util::ProtoToArgsParser::Delegate {
  public:
-  TrackEventArgsParser(BoundInserter& inserter,
+  TrackEventArgsParser(int64_t packet_timestamp,
+                       BoundInserter& inserter,
                        TraceStorage& storage,
                        PacketSequenceStateGeneration& sequence_state)
-      : inserter_(inserter),
+      : packet_timestamp_(packet_timestamp),
+        inserter_(inserter),
         storage_(storage),
         sequence_state_(sequence_state) {}
 
@@ -146,9 +148,12 @@
     return sequence_state_.GetInternedMessageView(field_id, iid);
   }
 
+  int64_t packet_timestamp() final { return packet_timestamp_; }
+
   PacketSequenceStateGeneration* seq_state() final { return &sequence_state_; }
 
  private:
+  int64_t packet_timestamp_;
   BoundInserter& inserter_;
   TraceStorage& storage_;
   PacketSequenceStateGeneration& sequence_state_;
@@ -1177,7 +1182,8 @@
           ParseHistogramName(event_.chrome_histogram_sample(), inserter));
     }
 
-    TrackEventArgsParser args_writer(*inserter, *storage_, *sequence_state_);
+    TrackEventArgsParser args_writer(ts_, *inserter, *storage_,
+                                     *sequence_state_);
     int unknown_extensions = 0;
     log_errors(parser_->args_parser_.ParseMessage(
         blob_, ".perfetto.protos.TrackEvent", &parser_->reflect_fields_,
@@ -1466,7 +1472,8 @@
       chrome_string_lookup_(context->storage.get()),
       counter_unit_ids_{{kNullStringId, context_->storage->InternString("ns"),
                          context_->storage->InternString("count"),
-                         context_->storage->InternString("bytes")}} {
+                         context_->storage->InternString("bytes")}},
+      active_chrome_processes_tracker_(context) {
   args_parser_.AddParsingOverrideForField(
       "chrome_mojo_event_info.mojo_interface_method_iid",
       [](const protozero::Field& field,
@@ -1518,12 +1525,24 @@
         return annotation_parser.Parse(data, delegate);
       });
 
+  args_parser_.AddParsingOverrideForField(
+      "active_processes.pid", [&](const protozero::Field& field,
+                                  util::ProtoToArgsParser::Delegate& delegate) {
+        UniquePid upid = context_->process_tracker->GetOrCreateProcess(
+            static_cast<uint32_t>(field.as_int32()));
+        active_chrome_processes_tracker_.AddActiveProcessMetadata(
+            delegate.packet_timestamp(), upid);
+        // Fallthrough so that the parser adds pid as a regular arg.
+        return base::nullopt;
+      });
+
   for (uint16_t index : kReflectFields) {
     reflect_fields_.push_back(index);
   }
 }
 
 void TrackEventParser::ParseTrackDescriptor(
+    int64_t packet_timestamp,
     protozero::ConstBytes track_descriptor,
     uint32_t packet_sequence_id) {
   protos::pbzero::TrackDescriptor::Decoder decoder(track_descriptor);
@@ -1538,7 +1557,8 @@
     if (decoder.has_chrome_thread())
       ParseChromeThreadDescriptor(utid, decoder.chrome_thread());
   } else if (decoder.has_process()) {
-    UniquePid upid = ParseProcessDescriptor(decoder.process());
+    UniquePid upid =
+        ParseProcessDescriptor(packet_timestamp, decoder.process());
     if (decoder.has_chrome_process())
       ParseChromeProcessDescriptor(upid, decoder.chrome_process());
   } else if (decoder.has_counter()) {
@@ -1554,10 +1574,12 @@
 }
 
 UniquePid TrackEventParser::ParseProcessDescriptor(
+    int64_t packet_timestamp,
     protozero::ConstBytes process_descriptor) {
   protos::pbzero::ProcessDescriptor::Decoder decoder(process_descriptor);
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
       static_cast<uint32_t>(decoder.pid()));
+  active_chrome_processes_tracker_.AddProcessDescriptor(packet_timestamp, upid);
   if (decoder.has_process_name() && decoder.process_name().size) {
     // Don't override system-provided names.
     context_->process_tracker->SetProcessNameIfUnset(
@@ -1696,5 +1718,9 @@
   }
 }
 
+void TrackEventParser::NotifyEndOfFile() {
+  active_chrome_processes_tracker_.NotifyEndOfFile();
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
index 6033b2c..72b3bf3 100644
--- a/src/trace_processor/importers/proto/track_event_parser.h
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -25,6 +25,7 @@
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/trace_parser.h"
+#include "src/trace_processor/importers/proto/active_chrome_processes_tracker.h"
 #include "src/trace_processor/importers/proto/chrome_string_lookup.h"
 #include "src/trace_processor/parser_types.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -54,8 +55,11 @@
  public:
   TrackEventParser(TraceProcessorContext*, TrackEventTracker*);
 
-  void ParseTrackDescriptor(protozero::ConstBytes, uint32_t packet_sequence_id);
-  UniquePid ParseProcessDescriptor(protozero::ConstBytes);
+  void ParseTrackDescriptor(int64_t packet_timestamp,
+                            protozero::ConstBytes,
+                            uint32_t packet_sequence_id);
+  UniquePid ParseProcessDescriptor(int64_t packet_timestamp,
+                                   protozero::ConstBytes);
   UniqueTid ParseThreadDescriptor(protozero::ConstBytes);
 
   void ParseTrackEvent(int64_t ts,
@@ -63,6 +67,8 @@
                        protozero::ConstBytes,
                        uint32_t packet_sequence_id);
 
+  void NotifyEndOfFile();
+
  private:
   class EventImporter;
 
@@ -121,6 +127,8 @@
   std::array<StringId, 4> counter_unit_ids_;
 
   std::vector<uint16_t> reflect_fields_;
+
+  ActiveChromeProcessesTracker active_chrome_processes_tracker_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 8fd886f..6b85fed 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -694,6 +694,15 @@
     return &experimental_proto_content_table_;
   }
 
+  const tables::ExperimentalMissingChromeProcessesTable&
+  experimental_missing_chrome_processes_table() const {
+    return experimental_missing_chrome_processes_table_;
+  }
+  tables::ExperimentalMissingChromeProcessesTable*
+  mutable_experimental_missing_chrome_processes_table() {
+    return &experimental_missing_chrome_processes_table_;
+  }
+
   const views::ThreadSliceView& thread_slice_view() const {
     return thread_slice_view_;
   }
@@ -927,6 +936,9 @@
   tables::ExperimentalProtoContentTable experimental_proto_content_table_{
       &string_pool_, nullptr};
 
+  tables::ExperimentalMissingChromeProcessesTable
+      experimental_missing_chrome_processes_table_{&string_pool_, nullptr};
+
   views::ThreadSliceView thread_slice_view_{&slice_table_, &thread_track_table_,
                                             &thread_table_};
 
diff --git a/src/trace_processor/tables/metadata_tables.h b/src/trace_processor/tables/metadata_tables.h
index 2654ee4..562dd84 100644
--- a/src/trace_processor/tables/metadata_tables.h
+++ b/src/trace_processor/tables/metadata_tables.h
@@ -140,6 +140,17 @@
 
 PERFETTO_TP_TABLE(PERFETTO_TP_PROCESS_TABLE_DEF);
 
+// Experimental table, subject to arbitrary breaking changes.
+#define PERFETTO_TP_EXPERIMENTAL_MISSING_CHROME_PROCESSES_TABLE_DEF(NAME,      \
+                                                                    PARENT, C) \
+  NAME(ExperimentalMissingChromeProcessesTable,                                \
+       "experimental_missing_chrome_processes")                                \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                                            \
+  C(uint32_t, upid)                                                            \
+  C(base::Optional<int64_t>, reliable_from)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_EXPERIMENTAL_MISSING_CHROME_PROCESSES_TABLE_DEF);
+
 // Contains information of processes seen during the trace
 //
 // @name cpu
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 8ee86fa..115a89e 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -43,6 +43,8 @@
 // metadata_tables.h
 RawTable::~RawTable() = default;
 ArgTable::~ArgTable() = default;
+ExperimentalMissingChromeProcessesTable::
+    ~ExperimentalMissingChromeProcessesTable() = default;
 MetadataTable::~MetadataTable() = default;
 CpuTable::~CpuTable() = default;
 CpuFreqTable::~CpuFreqTable() = default;
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9be69b7..3bbe937 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -1136,6 +1136,8 @@
   RegisterDbTable(storage->memory_snapshot_edge_table());
 
   RegisterDbTable(storage->experimental_proto_content_table());
+
+  RegisterDbTable(storage->experimental_missing_chrome_processes_table());
 }
 
 TraceProcessorImpl::~TraceProcessorImpl() = default;
diff --git a/src/trace_processor/util/proto_to_args_parser.h b/src/trace_processor/util/proto_to_args_parser.h
index 86fbcd1..5e54775 100644
--- a/src/trace_processor/util/proto_to_args_parser.h
+++ b/src/trace_processor/util/proto_to_args_parser.h
@@ -95,6 +95,8 @@
 
     virtual PacketSequenceStateGeneration* seq_state() = 0;
 
+    virtual int64_t packet_timestamp() { return 0; }
+
     template <typename FieldMetadata>
     typename FieldMetadata::cpp_field_type::Decoder* GetInternedMessage(
         protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
diff --git a/test/trace_processor/chrome/chrome_missing_processes.out b/test/trace_processor/chrome/chrome_missing_processes.out
new file mode 100644
index 0000000..d3583ae
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_missing_processes.out
@@ -0,0 +1,3 @@
+"upid","pid","reliable_from"
+2,100,1000000000
+3,1000,"[NULL]"
diff --git a/test/trace_processor/chrome/chrome_missing_processes.textproto b/test/trace_processor/chrome/chrome_missing_processes.textproto
new file mode 100644
index 0000000..5f62e09
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_missing_processes.textproto
@@ -0,0 +1,36 @@
+packet {
+  timestamp: 1
+  incremental_state_cleared: true
+  trusted_packet_sequence_id: 1
+  track_event {
+    type: TYPE_INSTANT
+    name: "ActiveProcesses"
+    [perfetto.protos.ChromeTrackEvent.active_processes]: {
+      pid: 10
+      pid: 100
+      pid: 1000
+    }
+  }
+}
+packet {
+  timestamp: 1
+  trusted_packet_sequence_id: 2
+  track_descriptor {
+    uuid: 1
+    process {
+      pid: 10
+    }
+    parent_uuid: 0
+  }
+}
+packet {
+  timestamp: 1000000000
+  trusted_packet_sequence_id: 3
+  track_descriptor {
+    uuid: 2
+    process {
+      pid: 100
+    }
+    parent_uuid: 0
+  }
+}
\ No newline at end of file
diff --git a/test/trace_processor/chrome/chrome_missing_processes_args.out b/test/trace_processor/chrome/chrome_missing_processes_args.out
new file mode 100644
index 0000000..acc7c28
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_missing_processes_args.out
@@ -0,0 +1,4 @@
+"arg_set_id","key","int_value"
+2,"active_processes.pid[0]",10
+2,"active_processes.pid[1]",100
+2,"active_processes.pid[2]",1000
diff --git a/test/trace_processor/chrome/chrome_missing_processes_args_test.sql b/test/trace_processor/chrome/chrome_missing_processes_args_test.sql
new file mode 100644
index 0000000..b404cbd
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_missing_processes_args_test.sql
@@ -0,0 +1,22 @@
+--
+-- Copyright 2022 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+SELECT arg_set_id, key, int_value
+FROM
+    slice
+JOIN
+    args
+USING(arg_set_id)
+ORDER BY arg_set_id, key;
\ No newline at end of file
diff --git a/test/trace_processor/chrome/chrome_missing_processes_default_trace.out b/test/trace_processor/chrome/chrome_missing_processes_default_trace.out
new file mode 100644
index 0000000..43e28ea
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_missing_processes_default_trace.out
@@ -0,0 +1 @@
+"upid","pid","reliable_from"
diff --git a/test/trace_processor/chrome/chrome_missing_processes_test.sql b/test/trace_processor/chrome/chrome_missing_processes_test.sql
new file mode 100644
index 0000000..30ef9e5
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_missing_processes_test.sql
@@ -0,0 +1,22 @@
+--
+-- Copyright 2022 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+SELECT upid, pid, reliable_from
+FROM
+    experimental_missing_chrome_processes
+JOIN
+    process
+USING(upid)
+ORDER BY upid;
\ No newline at end of file
diff --git a/test/trace_processor/chrome/index b/test/trace_processor/chrome/index
index 59cc592..18282b2 100644
--- a/test/trace_processor/chrome/index
+++ b/test/trace_processor/chrome/index
@@ -92,3 +92,8 @@
 # Log messages.
 chrome_log_message.textproto chrome_log_message_test.sql chrome_log_message.out
 chrome_log_message.textproto chrome_log_message_args_test.sql chrome_log_message_args.out
+
+# Missing processes.
+../../data/chrome_scroll_without_vsync.pftrace chrome_missing_processes_test.sql chrome_missing_processes_default_trace.out
+chrome_missing_processes.textproto chrome_missing_processes_test.sql chrome_missing_processes.out
+chrome_missing_processes.textproto chrome_missing_processes_args_test.sql chrome_missing_processes_args.out