Merge changes Icbf2f808,I973cd662 into main

* changes:
  Trace Redaction - Use Three Stage Pattern In Collect Primitive
  Trace Redaction - Clean up "find package uid" primitive
diff --git a/Android.bp b/Android.bp
index 43d2027..40484c4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12380,6 +12380,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/input.sql",
diff --git a/BUILD b/BUILD
index 2b53c16..4e316ed 100644
--- a/BUILD
+++ b/BUILD
@@ -2392,6 +2392,7 @@
     srcs = [
         "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql",
     ],
 )
 
diff --git a/protos/perfetto/metrics/android/java_heap_stats.proto b/protos/perfetto/metrics/android/java_heap_stats.proto
index 10a0b8b..5163f41 100644
--- a/protos/perfetto/metrics/android/java_heap_stats.proto
+++ b/protos/perfetto/metrics/android/java_heap_stats.proto
@@ -26,7 +26,7 @@
     optional int64 obj_count = 3;
   }
 
-  // Next id: 11
+  // Next id: 12
   message Sample {
     optional int64 ts = 1;
     // Size of the Java heap in bytes
@@ -47,6 +47,8 @@
     repeated HeapRoots roots = 7;
     // OOM adjustment score
     optional int64 oom_score_adj = 10;
+    // Process uptime in millis at the time of the heap dump
+    optional int64 process_uptime_ms = 11;
   }
 
   // Heap stats per process. One sample per dump (can be > 1 if continuous
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 14a2847..6b7a611 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -1417,7 +1417,7 @@
     optional int64 obj_count = 3;
   }
 
-  // Next id: 11
+  // Next id: 12
   message Sample {
     optional int64 ts = 1;
     // Size of the Java heap in bytes
@@ -1438,6 +1438,8 @@
     repeated HeapRoots roots = 7;
     // OOM adjustment score
     optional int64 oom_score_adj = 10;
+    // Process uptime in millis at the time of the heap dump
+    optional int64 process_uptime_ms = 11;
   }
 
   // Heap stats per process. One sample per dump (can be > 1 if continuous
diff --git a/python/generators/diff_tests/runner.py b/python/generators/diff_tests/runner.py
index b3c9fe9..7c77d54 100644
--- a/python/generators/diff_tests/runner.py
+++ b/python/generators/diff_tests/runner.py
@@ -28,7 +28,8 @@
 from python.generators.diff_tests.testing import TestCase, TestType, BinaryProto
 from python.generators.diff_tests.utils import (
     ColorFormatter, create_message_factory, get_env, get_trace_descriptor_path,
-    read_all_tests, serialize_python_trace, serialize_textproto_trace)
+    read_all_tests, serialize_python_trace, serialize_textproto_trace,
+    modify_trace)
 
 ROOT_DIR = os.path.dirname(
     os.path.dirname(
@@ -327,6 +328,19 @@
       with open(gen_trace_file.name, 'w') as trace_file:
         trace_file.write(self.test.blueprint.trace.contents)
 
+    if self.test.blueprint.trace_modifier is not None:
+      if gen_trace_file:
+        # Overwrite |gen_trace_file|.
+        modify_trace(self.trace_descriptor_path, extension_descriptor_paths,
+                     gen_trace_file.name, gen_trace_file.name,
+                     self.test.blueprint.trace_modifier)
+      else:
+        # Create |gen_trace_file| to save the modified trace.
+        gen_trace_file = tempfile.NamedTemporaryFile(delete=False)
+        modify_trace(self.trace_descriptor_path, extension_descriptor_paths,
+                     self.test.trace_path, gen_trace_file.name,
+                     self.test.blueprint.trace_modifier)
+
     if gen_trace_file:
       trace_path = os.path.realpath(gen_trace_file.name)
     else:
diff --git a/python/generators/diff_tests/testing.py b/python/generators/diff_tests/testing.py
index 8a4f45e..515f956 100644
--- a/python/generators/diff_tests/testing.py
+++ b/python/generators/diff_tests/testing.py
@@ -16,7 +16,7 @@
 import inspect
 import os
 from dataclasses import dataclass
-from typing import List, Union, Callable
+from typing import Any, Dict, List, Union, Callable
 from enum import Enum
 import re
 
@@ -72,6 +72,42 @@
   contents: str
 
 
+class TraceInjector:
+  '''Injects fields into trace packets before test starts.
+
+  TraceInjector can be used within a DiffTestBlueprint to selectively inject
+  fields to trace packets containing specific data types. For example:
+
+    DiffTestBlueprint(
+        trace=...,
+        trace_modifier=TraceInjector('ftrace_events',
+                                     'sys_stats',
+                                     'process_tree',
+                                     {'machine_id': 1001},
+                                     trusted_uid=123)
+        query=...,
+        out=...)
+
+  packet_data_types: Data types to target for injection ('ftrace_events',
+  'sys_stats', 'process_tree')
+  injected_fields: Fields and their values to inject into matching packets
+  ({'machine_id': 1001}, trusted_uid=123).
+  '''
+
+  def __init__(self, packet_data_types: List[str], injected_fields: Dict[str,
+                                                                         Any]):
+    self.packet_data_types = packet_data_types
+    self.injected_fields = injected_fields
+
+  def inject(self, proto):
+    for p in proto.packet:
+      for f in self.packet_data_types:
+        if p.HasField(f):
+          for k, v, in self.injected_fields.items():
+            setattr(p, k, v)
+          continue
+
+
 class TestType(Enum):
   QUERY = 1
   METRIC = 2
@@ -86,6 +122,7 @@
   trace: Union[Path, DataPath, Json, Systrace, TextProto]
   query: Union[str, Path, DataPath, Metric]
   out: Union[Path, DataPath, Json, Csv, TextProto, BinaryProto]
+  trace_modifier: Union[TraceInjector, None] = None
 
   def is_trace_file(self):
     return isinstance(self.trace, Path)
diff --git a/python/generators/diff_tests/utils.py b/python/generators/diff_tests/utils.py
index cb447fd..3101637 100644
--- a/python/generators/diff_tests/utils.py
+++ b/python/generators/diff_tests/utils.py
@@ -149,6 +149,23 @@
   return trace_descriptor_path
 
 
+def modify_trace(trace_descriptor_path, extension_descriptor_paths,
+                 in_trace_path, out_trace_path, modifier):
+  trace_proto = create_message_factory([trace_descriptor_path] +
+                                       extension_descriptor_paths,
+                                       'perfetto.protos.Trace')()
+
+  with open(in_trace_path, "rb") as f:
+    # This may raise DecodeError when |in_trace_path| isn't protobuf.
+    trace_proto.ParseFromString(f.read())
+    # Modify the trace proto object with the provided modifier function.
+    modifier.inject(trace_proto)
+
+  with open(out_trace_path, "wb") as f:
+    f.write(trace_proto.SerializeToString())
+    f.flush()
+
+
 def read_all_tests(name_filter: str, root_dir: str) -> List[testing.TestCase]:
   # Import
   INCLUDE_PATH = os.path.join(root_dir, 'test', 'trace_processor', 'diff_tests')
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 6a137a9..cc4850f 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/perf/perf_data_parser.cc b/src/trace_processor/importers/perf/perf_data_parser.cc
index 71912f2..83f8800 100644
--- a/src/trace_processor/importers/perf/perf_data_parser.cc
+++ b/src/trace_processor/importers/perf/perf_data_parser.cc
@@ -43,7 +43,7 @@
 
 base::StatusOr<PerfDataTracker::PerfSample> PerfDataParser::ParseSample(
     TraceBlobView tbv) {
-  perf_importer::Reader reader(std::move(tbv));
+  perf_importer::PerfDataReader reader(std::move(tbv));
   return tracker_->ParseSample(reader);
 }
 
diff --git a/src/trace_processor/importers/perf/perf_data_reader.cc b/src/trace_processor/importers/perf/perf_data_reader.cc
index a2c7691..e061826 100644
--- a/src/trace_processor/importers/perf/perf_data_reader.cc
+++ b/src/trace_processor/importers/perf/perf_data_reader.cc
@@ -25,7 +25,7 @@
 namespace perfetto {
 namespace trace_processor {
 namespace perf_importer {
-void Reader::SkipSlow(size_t bytes_to_skip) {
+void PerfDataReader::SkipSlow(size_t bytes_to_skip) {
   size_t bytes_in_buffer = BytesInBuffer();
 
   // Size fits in buffer.
@@ -40,7 +40,7 @@
   blob_offset_ += bytes_to_skip - bytes_in_buffer;
 }
 
-void Reader::PeekSlow(uint8_t* obj_data, size_t size) const {
+void PerfDataReader::PeekSlow(uint8_t* obj_data, size_t size) const {
   size_t bytes_in_buffer = BytesInBuffer();
 
   // Read from buffer.
@@ -55,7 +55,7 @@
          size - bytes_in_buffer);
 }
 
-TraceBlobView Reader::PeekTraceBlobViewSlow(size_t size) const {
+TraceBlobView PerfDataReader::PeekTraceBlobViewSlow(size_t size) const {
   auto blob = TraceBlob::Allocate(size);
   size_t bytes_in_buffer = BytesInBuffer();
 
diff --git a/src/trace_processor/importers/perf/perf_data_reader.h b/src/trace_processor/importers/perf/perf_data_reader.h
index 86f1c6b..acf07d1 100644
--- a/src/trace_processor/importers/perf/perf_data_reader.h
+++ b/src/trace_processor/importers/perf/perf_data_reader.h
@@ -35,10 +35,10 @@
 // it's design is not related to perf. Responsible for hiding away the
 // complexity of reading values from TraceBlobView and glueing the tbvs together
 // in case there is data between many of them.
-class Reader {
+class PerfDataReader {
  public:
-  Reader() = default;
-  explicit Reader(TraceBlobView tbv) : tbv_(std::move(tbv)) {}
+  PerfDataReader() = default;
+  explicit PerfDataReader(TraceBlobView tbv) : tbv_(std::move(tbv)) {}
 
   // Updates old TraceBlobView with new one. If there is data left in the old
   // one, it will be saved in the buffer.
diff --git a/src/trace_processor/importers/perf/perf_data_reader_unittest.cc b/src/trace_processor/importers/perf/perf_data_reader_unittest.cc
index 61c866c..5bf3081 100644
--- a/src/trace_processor/importers/perf/perf_data_reader_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_data_reader_unittest.cc
@@ -37,7 +37,7 @@
 
 TEST(PerfDataReaderUnittest, AppendToEmpty) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{1, 2, 3});
-  Reader reader;
+  PerfDataReader reader;
   EXPECT_FALSE(reader.CanReadSize(1));
   reader.Append(std::move(tbv));
   EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 2));
@@ -45,7 +45,7 @@
 
 TEST(PerfDataReaderUnittest, Append) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{1, 2, 3});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
 
   EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 3));
   EXPECT_FALSE(reader.CanReadSize(sizeof(uint64_t) * 3 + 1));
@@ -56,7 +56,7 @@
 
 TEST(PerfDataReaderUnittest, Read) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   uint64_t val;
   reader.Read(val);
   EXPECT_EQ(val, 2u);
@@ -64,7 +64,7 @@
 
 TEST(PerfDataReaderUnittest, ReadFromBuffer) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 6});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3}));
 
   // Now the first vector should be in the buffer.
@@ -75,7 +75,7 @@
 
 TEST(PerfDataReaderUnittest, ReadBetweenBufferAndBlob) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
 
   struct Nums {
@@ -94,7 +94,7 @@
 
 TEST(PerfDataReaderUnittest, ReadOptional) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   std::optional<uint64_t> val;
   reader.ReadOptional(val);
   EXPECT_EQ(val, 2u);
@@ -103,7 +103,7 @@
 TEST(PerfDataReaderUnittest, ReadVector) {
   TraceBlobView tbv =
       TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8, 16, 32});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
 
   std::vector<uint64_t> res(3);
   reader.ReadVector(res);
@@ -114,7 +114,7 @@
 
 TEST(PerfDataReaderUnittest, Skip) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
 
   reader.Skip<uint64_t>();
 
@@ -125,7 +125,7 @@
 
 TEST(PerfDataReaderUnittest, SkipInBuffer) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
 
   reader.Skip<uint64_t>();
@@ -134,7 +134,7 @@
 
 TEST(PerfDataReaderUnittest, SkipBetweenBufferAndBlob) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
 
   struct Nums {
@@ -149,7 +149,7 @@
 
 TEST(PerfDataReaderUnittest, Peek) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
 
   uint64_t peek_val;
   reader.Peek(peek_val);
@@ -161,7 +161,7 @@
 
 TEST(PerfDataReaderUnittest, PeekFromBuffer) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 6});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3}));
 
   uint64_t val;
@@ -171,7 +171,7 @@
 
 TEST(PerfDataReaderUnittest, PeekBetweenBufferAndBlob) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
 
   struct Nums {
@@ -190,40 +190,40 @@
 
 TEST(PerfDataReaderUnittest, GetTraceBlobView) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   EXPECT_TRUE(reader.CanReadSize(sizeof(uint64_t) * 3));
 
   TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 2);
-  Reader new_reader(std::move(new_tbv));
+  PerfDataReader new_reader(std::move(new_tbv));
   EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 2));
   EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 3));
 }
 
 TEST(PerfDataReaderUnittest, GetTraceBlobViewFromBuffer) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
 
   TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 2);
-  Reader new_reader(std::move(new_tbv));
+  PerfDataReader new_reader(std::move(new_tbv));
   EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 2));
   EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 3));
 }
 
 TEST(PerfDataReaderUnittest, GetTraceBlobViewFromBetweenBufferAndBlob) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   reader.Append(TraceBlobViewFromVector(std::vector<uint64_t>{1, 3, 5}));
 
   TraceBlobView new_tbv = reader.PeekTraceBlobView(sizeof(uint64_t) * 3);
-  Reader new_reader(std::move(new_tbv));
+  PerfDataReader new_reader(std::move(new_tbv));
   EXPECT_TRUE(new_reader.CanReadSize(sizeof(uint64_t) * 3));
   EXPECT_FALSE(new_reader.CanReadSize(sizeof(uint64_t) * 4));
 }
 
 TEST(PerfDataReaderUnittest, CanAccessFileRange) {
   TraceBlobView tbv = TraceBlobViewFromVector(std::vector<uint64_t>{2, 4, 8});
-  Reader reader(std::move(tbv));
+  PerfDataReader reader(std::move(tbv));
   EXPECT_TRUE(reader.CanAccessFileRange(2, sizeof(uint64_t) * 3));
   EXPECT_FALSE(reader.CanAccessFileRange(2, sizeof(uint64_t) * 3 + 10));
 }
diff --git a/src/trace_processor/importers/perf/perf_data_tokenizer.h b/src/trace_processor/importers/perf/perf_data_tokenizer.h
index 1fa4277..7a54088 100644
--- a/src/trace_processor/importers/perf/perf_data_tokenizer.h
+++ b/src/trace_processor/importers/perf/perf_data_tokenizer.h
@@ -100,7 +100,7 @@
   uint64_t ids_end_ = 0;
   std::vector<uint8_t> after_header_buffer_;
 
-  perf_importer::Reader reader_;
+  perf_importer::PerfDataReader reader_;
 };
 
 }  // namespace perf_importer
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.cc b/src/trace_processor/importers/perf/perf_data_tracker.cc
index c670258..8b3bc47 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker.cc
@@ -101,7 +101,7 @@
 }
 
 base::StatusOr<PerfDataTracker::PerfSample> PerfDataTracker::ParseSample(
-    perfetto::trace_processor::perf_importer::Reader& reader) {
+    perfetto::trace_processor::perf_importer::PerfDataReader& reader) {
   uint64_t sample_type = common_sample_type();
   PerfDataTracker::PerfSample sample;
 
diff --git a/src/trace_processor/importers/perf/perf_data_tracker.h b/src/trace_processor/importers/perf/perf_data_tracker.h
index 11258ed..c96b08d 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker.h
+++ b/src/trace_processor/importers/perf/perf_data_tracker.h
@@ -98,7 +98,7 @@
   uint64_t common_sample_type() { return common_sample_type_; }
 
   base::StatusOr<PerfSample> ParseSample(
-      perfetto::trace_processor::perf_importer::Reader&);
+      perfetto::trace_processor::perf_importer::PerfDataReader&);
 
  private:
   const perf_event_attr* FindAttrWithId(uint64_t id) const;
diff --git a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
index 129271c..923473f 100644
--- a/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
+++ b/src/trace_processor/importers/perf/perf_data_tracker_unittest.cc
@@ -121,7 +121,7 @@
 
   TraceBlob blob =
       TraceBlob::CopyFrom(static_cast<const void*>(&ts), sizeof(uint64_t));
-  Reader reader(TraceBlobView(std::move(blob)));
+  PerfDataReader reader(TraceBlobView(std::move(blob)));
 
   auto parsed_sample = tracker->ParseSample(reader);
   EXPECT_TRUE(parsed_sample.ok());
@@ -149,7 +149,7 @@
   memcpy(blob.data(), &sample.callchain_size, sizeof(uint64_t));
   memcpy(blob.data() + sizeof(uint64_t), sample.callchain.data(),
          sizeof(uint64_t) * 3);
-  Reader reader(TraceBlobView(std::move(blob)));
+  PerfDataReader reader(TraceBlobView(std::move(blob)));
 
   auto parsed_sample = tracker->ParseSample(reader);
   EXPECT_TRUE(parsed_sample.ok());
@@ -185,7 +185,7 @@
   memcpy(blob.data(), &sample, sizeof(Sample));
   memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3);
 
-  Reader reader(TraceBlobView(std::move(blob)));
+  PerfDataReader reader(TraceBlobView(std::move(blob)));
   EXPECT_TRUE(reader.CanReadSize(sizeof(Sample)));
 
   auto parsed_sample = tracker->ParseSample(reader);
@@ -229,7 +229,7 @@
   memcpy(blob.data(), &sample, sizeof(Sample));
   memcpy(blob.data() + sizeof(Sample), callchain.data(), sizeof(uint64_t) * 3);
 
-  Reader reader(TraceBlobView(std::move(blob)));
+  PerfDataReader reader(TraceBlobView(std::move(blob)));
 
   auto parsed_sample = tracker->ParseSample(reader);
   EXPECT_TRUE(parsed_sample.ok());
diff --git a/src/trace_processor/metrics/sql/android/java_heap_stats.sql b/src/trace_processor/metrics/sql/android/java_heap_stats.sql
index dd2b7b6..1a4d534 100644
--- a/src/trace_processor/metrics/sql/android/java_heap_stats.sql
+++ b/src/trace_processor/metrics/sql/android/java_heap_stats.sql
@@ -109,6 +109,11 @@
     base_stats.upid,
     RepeatedField(JavaHeapStats_Sample(
       'ts', graph_sample_ts,
+      'process_uptime_ms',
+        CASE WHEN process.start_ts IS NOT NULL
+        THEN (graph_sample_ts - process.start_ts) / 1000000
+        ELSE NULL
+        END,
       'heap_size', total_size,
       'heap_native_size', total_native_size,
       'obj_count', total_obj_count,
@@ -120,6 +125,7 @@
       'oom_score_adj', closest_anon_swap_oom.oom_score_val
     )) AS sample_protos
   FROM base_stats
+  JOIN process USING (upid)
   LEFT JOIN closest_anon_swap_oom USING (upid, graph_sample_ts)
   GROUP BY 1
 )
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn
index bc434d5..306758a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/BUILD.gn
@@ -18,5 +18,6 @@
   sources = [
     "per_frame_metrics.sql",
     "timeline.sql",
+    "timeline_maxsdk28.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
index b492bb3..3d29281 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql
@@ -13,6 +13,9 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+INCLUDE PERFETTO MODULE slices.with_context;
+INCLUDE PERFETTO MODULE android.frames.timeline_maxsdk28;
+
 -- Parses the slice name to fetch `frame_id` from `slice` table.
 -- Use with caution. Slice names are a flaky source of ids and the resulting
 -- table might require some further operations.
@@ -23,13 +26,16 @@
     -- `slice.id` of the frame slice.
     id INT,
     -- Parsed frame id.
-    frame_id INT
+    frame_id INT,
+    -- Utid.
+    utid INT
 ) AS
 WITH all_found AS (
     SELECT
         id,
-        cast_int!(STR_SPLIT(name, ' ', 1)) AS frame_id
-    FROM slice
+        cast_int!(STR_SPLIT(name, ' ', 1)) AS frame_id,
+        utid
+    FROM thread_slice
     WHERE name GLOB $glob_str
 )
 SELECT *
@@ -47,11 +53,10 @@
     ui_thread_utid INT
 ) AS
 SELECT
-    c.*,
-    thread_track.utid AS ui_thread_utid
-FROM _get_frame_table_with_id('Choreographer#doFrame*') c
-JOIN slice USING (id)
-JOIN thread_track ON (thread_track.id = slice.track_id);
+    id,
+    frame_id,
+    utid AS ui_thread_utid
+FROM _get_frame_table_with_id('Choreographer#doFrame*');
 
 -- All of the `DrawFrame` slices with their frame id and render thread.
 -- There might be multiple DrawFrames slices for a single vsync (frame id).
@@ -66,11 +71,10 @@
     render_thread_utid INT
 ) AS
 SELECT
-    d.*,
-    thread_track.utid AS render_thread_utid
-FROM _get_frame_table_with_id('DrawFrame*') d
-JOIN slice USING (id)
-JOIN thread_track ON (thread_track.id = slice.track_id);
+    id,
+    frame_id,
+    utid AS render_thread_utid
+FROM _get_frame_table_with_id('DrawFrame*');
 
 -- `actual_frame_timeline_slice` returns the same slice on different tracks.
 -- We are getting the first slice with one frame id.
@@ -119,21 +123,47 @@
     -- `utid` of the UI thread.
     ui_thread_utid INT
 ) AS
+WITH frames_sdk_after_28 AS (
 SELECT
     frame_id,
     ts,
     dur,
     do_frame.id AS do_frame_id,
     draw_frame.id AS draw_frame_id,
-    act.id AS actual_frame_timeline_id,
-    exp.id AS expected_frame_timeline_id,
     draw_frame.render_thread_utid,
-    do_frame.ui_thread_utid
+    do_frame.ui_thread_utid,
+    "after_28" AS sdk,
+    act.id AS actual_frame_timeline_id,
+    exp.id AS expected_frame_timeline_id
 FROM android_frames_choreographer_do_frame do_frame
 JOIN android_frames_draw_frame draw_frame USING (frame_id)
 JOIN _distinct_from_actual_timeline_slice act USING (frame_id)
 JOIN _distinct_from_expected_timeline_slice exp USING (frame_id)
-ORDER BY frame_id;
+ORDER BY frame_id
+),
+all_frames AS (
+    SELECT * FROM frames_sdk_after_28
+    UNION
+    SELECT
+        *,
+        NULL AS actual_frame_timeline_id,
+        NULL AS expected_frame_timeline_id
+    FROM _frames_maxsdk_28
+)
+SELECT
+    frame_id,
+    ts,
+    dur,
+    do_frame_id,
+    draw_frame_id,
+    actual_frame_timeline_id,
+    expected_frame_timeline_id,
+    render_thread_utid,
+    ui_thread_utid
+FROM all_frames
+WHERE sdk = IIF(
+    (SELECT COUNT(1) FROM actual_frame_timeline_slice) > 0,
+    "after_28", "maxsdk28");
 
 -- Returns first frame after the provided timestamp. The returning table has at
 -- most one row.
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql
new file mode 100644
index 0000000..b8fad07
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/frames/timeline_maxsdk28.sql
@@ -0,0 +1,76 @@
+--
+-- Copyright 2024 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.
+
+INCLUDE PERFETTO MODULE slices.with_context;
+
+-- All slices related to one frame for max SDK 28. Aggregates
+-- "Choreographer#doFrame" and "DrawFrame". Tries to guess the `ts` and `dur`
+-- of the frame by first guessing which "DrawFrame" slices are related to which
+-- "Choreographer#doSlice".
+CREATE PERFETTO TABLE _frames_maxsdk_28(
+    -- Frame id. Created manually starting from 0.
+    frame_id INT,
+    -- Timestamp of the frame. Start of "Choreographer#doFrame" slice.
+    ts INT,
+    -- Duration of the frame, defined as the duration until the last
+    -- "DrawFrame" of this frame finishes.
+    dur INT,
+    -- `slice.id` of "Choreographer#doFrame" slice.
+    do_frame_id INT,
+    -- `slice.id` of "DrawFrame" slice. Fetched as one of the "DrawFrame"
+    -- slices that happen for the same process as "Choreographer#doFrame" slice
+    -- and start after it started and before the next "doFrame" started.
+    draw_frame_id INT,
+    -- `utid` of the render thread.
+    render_thread_utid INT,
+    -- `utid` of the UI thread.
+    ui_thread_utid INT,
+    -- "maxsdk28"
+    sdk STRING
+) AS
+WITH do_frames AS (
+    SELECT
+        id,
+        ts,
+        LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY upid ORDER BY ts) AS next_do_frame,
+        utid,
+        upid
+    FROM thread_slice
+    WHERE name = 'Choreographer#doFrame' AND is_main_thread = 1
+    ORDER BY ts
+),
+draw_frames AS (
+    SELECT
+        id,
+        ts,
+        dur,
+        ts + dur AS ts_end,
+        utid,
+        upid
+    FROM thread_slice
+    WHERE name = 'DrawFrame'
+)
+SELECT
+  ROW_NUMBER() OVER () AS frame_id,
+  do.ts,
+  MAX(draw.ts_end) OVER (PARTITION BY do.id) - do.ts AS dur,
+  do.id AS do_frame_id,
+  draw.id AS draw_frame_id,
+  draw.utid AS render_thread_utid,
+  do.utid AS ui_thread_utid,
+  "maxsdk28" AS sdk
+FROM do_frames do
+JOIN draw_frames draw ON (do.upid = draw.upid AND draw.ts >= do.ts AND draw.ts < next_do_frame)
+ORDER BY do.ts;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql
index 0a7fab2..5188b63 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups_maxsdk28.sql
@@ -13,17 +13,51 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+INCLUDE PERFETTO MODULE slices.with_context;
 INCLUDE PERFETTO MODULE android.startup.startup_events;
+INCLUDE PERFETTO MODULE android.frames.timeline;
 
 CREATE PERFETTO TABLE _startups_maxsdk28 AS
+-- Warm and cold starts only are based on the launching slice
+WITH warm_and_cold AS (
+  SELECT
+    le.ts,
+    le.ts_end AS ts_end,
+    package_name AS package
+  FROM _startup_events le
+),
+-- Hot starts don’t have a launching slice so we use activityResume as a
+-- proxy.
+--
+-- Note that this implementation will also count warm and cold starts but
+-- we will remove those below.
+maybe_hot AS (
+  SELECT
+    sl.ts,
+    rs.ts + rs.dur AS ts_end,
+    -- We use the process name as the package as we have no better option.
+    process_name AS package
+  FROM thread_slice sl
+  JOIN android_first_frame_after(sl.ts) rs
+  WHERE name = 'activityResume'
+  -- Remove any launches here where the activityResume slices happens during
+  -- a warm/cold startup.
+  AND sl.ts NOT IN (SELECT ts FROM warm_and_cold)
+),
+cold_warm_hot AS (
+  SELECT * FROM warm_and_cold
+  UNION ALL
+  SELECT * FROM maybe_hot
+
+)
 SELECT
-  "maxsdk28" as sdk,
+  "maxsdk28" AS sdk,
   ROW_NUMBER() OVER(ORDER BY ts) AS startup_id,
-  le.ts,
-  le.ts_end AS ts_end,
-  le.ts_end - le.ts AS dur,
-  package_name AS package,
+  ts,
+  ts_end,
+  ts_end - ts AS dur,
+  package,
   NULL AS startup_type
-FROM _startup_events le
-ORDER BY ts;
+FROM cold_warm_hot;
+
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql b/src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql
index 6e9ba0d..9430bd9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/slices/with_context.sql
@@ -36,8 +36,10 @@
   thread_name STRING,
   -- Alias for `thread.utid`.
   utid INT,
-  -- Alias for `thread.tid`
+  -- Alias for `thread.tid`.
   tid INT,
+  -- Alias for `thread.is_main_thread`.
+  is_main_thread BOOL,
   -- Alias for `process.name`.
   process_name STRING,
   -- Alias for `process.upid`.
@@ -67,6 +69,7 @@
   thread.name AS thread_name,
   thread.utid,
   thread.tid,
+  thread.is_main_thread,
   process.name AS process_name,
   process.upid,
   process.pid,
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index cfd73dc..fcd68e8 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/sqlite/sqlite_engine.h"
 
+#include <sqlite3.h>
 #include <cstdint>
 #include <optional>
 #include <string>
@@ -47,7 +48,15 @@
   // sqlite3_initialize isn't actually thread-safe in standalone builds because
   // we build with SQLITE_THREADSAFE=0. Ensure it's only called from a single
   // thread.
-  static bool init_once = [] { return sqlite3_initialize() == SQLITE_OK; }();
+  static bool init_once = [] {
+    // Enabling memstatus causes a lock to be taken on every malloc/free in
+    // SQLite to update the memory statistics. This can cause massive contention
+    // in trace processor when multiple instances are used in parallel.
+    // Fix this by disabling the memstatus API which we don't make use of in
+    // any case. See b/335019324 for more info on this.
+    PERFETTO_CHECK(sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0) == SQLITE_OK);
+    return sqlite3_initialize() == SQLITE_OK;
+  }();
   PERFETTO_CHECK(init_once);
 }
 
diff --git a/src/traced/probes/ftrace/atrace_wrapper.cc b/src/traced/probes/ftrace/atrace_wrapper.cc
index 1a64544..38f0200 100644
--- a/src/traced/probes/ftrace/atrace_wrapper.cc
+++ b/src/traced/probes/ftrace/atrace_wrapper.cc
@@ -187,18 +187,18 @@
 #endif
 }
 
-bool AtraceWrapperImpl::IsOldAtrace() {
+bool AtraceWrapperImpl::SupportsUserspaceOnly() {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \
     !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
   // Sideloaded case. We could be sideloaded on a modern device or an older one.
   std::string str_value = base::GetAndroidProp("ro.build.version.sdk");
   if (str_value.empty())
-    return false;
+    return true;
   auto opt_value = base::CStringToUInt32(str_value.c_str());
-  return opt_value.has_value() && *opt_value < 28;  // 28 == Android P.
+  return !opt_value.has_value() || *opt_value >= 28;  // 28 == Android P.
 #else
   // In in-tree builds we know that atrace is current, no runtime checks needed.
-  return false;
+  return true;
 #endif
 }
 
diff --git a/src/traced/probes/ftrace/atrace_wrapper.h b/src/traced/probes/ftrace/atrace_wrapper.h
index 5c6a148..18c3d2d 100644
--- a/src/traced/probes/ftrace/atrace_wrapper.h
+++ b/src/traced/probes/ftrace/atrace_wrapper.h
@@ -30,7 +30,7 @@
   // - Just use atrace --async_start/stop, which will cause atrace to also
   //   poke at ftrace.
   // - Suppress the checks for "somebody else enabled ftrace unexpectedly".
-  virtual bool IsOldAtrace() = 0;
+  virtual bool SupportsUserspaceOnly() = 0;
   virtual bool RunAtrace(const std::vector<std::string>& args,
                          std::string* atrace_errors) = 0;
 };
@@ -38,7 +38,7 @@
 class AtraceWrapperImpl : public AtraceWrapper {
  public:
   ~AtraceWrapperImpl() override;
-  bool IsOldAtrace() override;
+  bool SupportsUserspaceOnly() override;
   bool RunAtrace(const std::vector<std::string>& args,
                  std::string* atrace_errors) override;
 };
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index e4d8a19..f8a0eff 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -656,7 +656,7 @@
           "atrace_apps options as they affect global state");
       return false;
     }
-    if (atrace_wrapper_->IsOldAtrace() && !ds_configs_.empty()) {
+    if (!atrace_wrapper_->SupportsUserspaceOnly() && !ds_configs_.empty()) {
       PERFETTO_ELOG(
           "Concurrent atrace sessions are not supported before Android P, "
           "bailing out.");
@@ -1050,7 +1050,7 @@
   std::vector<std::string> args;
   args.push_back("atrace");  // argv0 for exec()
   args.push_back("--async_start");
-  if (!atrace_wrapper_->IsOldAtrace())
+  if (atrace_wrapper_->SupportsUserspaceOnly())
     args.push_back("--only_userspace");
 
   for (const auto& category : categories)
@@ -1078,7 +1078,7 @@
   PERFETTO_DLOG("Stop atrace...");
 
   std::vector<std::string> args{"atrace", "--async_stop"};
-  if (!atrace_wrapper_->IsOldAtrace())
+  if (atrace_wrapper_->SupportsUserspaceOnly())
     args.push_back("--only_userspace");
   if (atrace_wrapper_->RunAtrace(args, /*atrace_errors=*/nullptr)) {
     current_state_.atrace_categories.clear();
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index 513d851..9e9ca5e 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -96,7 +96,7 @@
 class MockAtraceWrapper : public AtraceWrapper {
  public:
   MOCK_METHOD(bool, RunAtrace, (const std::vector<std::string>&, std::string*));
-  MOCK_METHOD(bool, IsOldAtrace, ());
+  MOCK_METHOD(bool, SupportsUserspaceOnly, ());
 };
 
 class MockProtoTranslationTable : public ProtoTranslationTable {
@@ -173,7 +173,7 @@
  protected:
   FtraceConfigMuxerTest() {
     ON_CALL(atrace_wrapper_, RunAtrace).WillByDefault(Return(true));
-    ON_CALL(atrace_wrapper_, IsOldAtrace).WillByDefault(Return(false));
+    ON_CALL(atrace_wrapper_, SupportsUserspaceOnly).WillByDefault(Return(true));
   }
 
   std::unique_ptr<MockProtoTranslationTable> GetMockTable() {
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index 1a3538e..bfcb516 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -205,7 +205,7 @@
 class MockAtraceWrapper : public AtraceWrapper {
  public:
   MOCK_METHOD(bool, RunAtrace, (const std::vector<std::string>&, std::string*));
-  MOCK_METHOD(bool, IsOldAtrace, ());
+  MOCK_METHOD(bool, SupportsUserspaceOnly, ());
 };
 
 }  // namespace
diff --git a/test/data/api24_startup_cold.perfetto-trace.sha256 b/test/data/api24_startup_cold.perfetto-trace.sha256
new file mode 100644
index 0000000..dbc4e62
--- /dev/null
+++ b/test/data/api24_startup_cold.perfetto-trace.sha256
@@ -0,0 +1 @@
+ac006a3ec74fac70feb58c2cfad840c552af0ebd25f03bdfbf14d5f77148764b
\ No newline at end of file
diff --git a/test/data/api24_startup_hot.perfetto-trace.sha256 b/test/data/api24_startup_hot.perfetto-trace.sha256
new file mode 100644
index 0000000..6fa48c6
--- /dev/null
+++ b/test/data/api24_startup_hot.perfetto-trace.sha256
@@ -0,0 +1 @@
+0ec527c6392adf16e8580ba0fa3eaccf69eccba36c863377eca5c5618f00f3ea
\ No newline at end of file
diff --git a/test/data/api24_startup_warm.perfetto-trace.sha256 b/test/data/api24_startup_warm.perfetto-trace.sha256
new file mode 100644
index 0000000..04eca80
--- /dev/null
+++ b/test/data/api24_startup_warm.perfetto-trace.sha256
@@ -0,0 +1 @@
+59d1e84ddf6a492c10d7b6ecebf7b80813e6a0a2b3bef7d854d5ef3cf7210137
\ No newline at end of file
diff --git a/test/data/api31_startup_hot.perfetto-trace.sha256 b/test/data/api31_startup_hot.perfetto-trace.sha256
new file mode 100644
index 0000000..8cac597
--- /dev/null
+++ b/test/data/api31_startup_hot.perfetto-trace.sha256
@@ -0,0 +1 @@
+072802c0adf6968d0eb9c56fa13b33cad32f583398d019dc8a798981164333c5
\ No newline at end of file
diff --git a/test/data/api32_startup_warm.perfetto-trace.sha256 b/test/data/api32_startup_warm.perfetto-trace.sha256
new file mode 100644
index 0000000..f83dd34
--- /dev/null
+++ b/test/data/api32_startup_warm.perfetto-trace.sha256
@@ -0,0 +1 @@
+776122b5660c5d6e738950031fdb4992a64e3224e9e82bbaed474a0a281ed7e3
\ No newline at end of file
diff --git a/test/data/api34_startup_cold.perfetto-trace.sha256 b/test/data/api34_startup_cold.perfetto-trace.sha256
new file mode 100644
index 0000000..2a7a044
--- /dev/null
+++ b/test/data/api34_startup_cold.perfetto-trace.sha256
@@ -0,0 +1 @@
+1958521dc5128cd4eadd1df281e19987aded718750e6883f82ffd3b5eb529bd6
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 1f711b8..cb82c55 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -92,6 +92,7 @@
 from diff_tests.parser.translated_args.tests import TranslatedArgs
 from diff_tests.parser.ufs.tests import Ufs
 from diff_tests.stdlib.android.frames_tests import Frames
+from diff_tests.stdlib.android.startups_tests import Startups
 from diff_tests.stdlib.android.tests import AndroidStdlib
 from diff_tests.stdlib.chrome.chrome_stdlib_testsuites import CHROME_STDLIB_TESTSUITES
 from diff_tests.stdlib.common.tests import StdlibCommon
@@ -282,6 +283,7 @@
                        'StdlibIntervals').fetch(),
       *IntervalsIntersect(index_path, 'stdlib/intervals',
                           'StdlibIntervalsIntersect').fetch(),
+      *Startups(index_path, 'stdlib/android', 'Startups').fetch(),
       *Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
       *WattsonStdlib(index_path, 'stdlib/wattson', 'WattsonStdlib').fetch(),
   ] + chrome_stdlib_tests
diff --git a/test/trace_processor/diff_tests/parser/android/tests.py b/test/trace_processor/diff_tests/parser/android/tests.py
index 9fca28e..6a20f59 100644
--- a/test/trace_processor/diff_tests/parser/android/tests.py
+++ b/test/trace_processor/diff_tests/parser/android/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric, Systrace
 from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 from python.generators.diff_tests.testing import PrintProfileProto
 
@@ -203,3 +203,54 @@
       0
       0
       """))
+
+  # Tests when counter_tack.machine_id is not null.
+  def test_android_system_property_counter_machine_id(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 1000
+          android_system_property {
+            values {
+              name: "debug.tracing.screen_state"
+              value: "2"
+            }
+            values {
+              name: "debug.tracing.device_state"
+              value: "some_state_from_sysprops"
+            }
+          }
+          machine_id: 1001
+        }
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 2000
+              pid: 1
+              print {
+                buf: "C|1000|ScreenState|1\n"
+              }
+            }
+            event {
+              timestamp: 3000
+              pid: 1
+              print {
+                buf: "N|1000|DeviceStateChanged|some_state_from_atrace\n"
+              }
+            }
+          }
+          machine_id: 1001
+        }
+        """),
+        query="""
+        SELECT t.type, t.name, c.id, c.ts, c.type, c.value
+        FROM counter_track t JOIN counter c ON t.id = c.track_id
+        WHERE name = 'ScreenState'
+          AND t.machine_id IS NOT NULL;
+        """,
+        out=Csv("""
+        "type","name","id","ts","type","value"
+        "counter_track","ScreenState",0,1000,"counter",2.000000
+        "counter_track","ScreenState",1,2000,"counter",1.000000
+        """))
diff --git a/test/trace_processor/diff_tests/parser/graphics/tests.py b/test/trace_processor/diff_tests/parser/graphics/tests.py
index 7fe4af1..0d5d8b5 100644
--- a/test/trace_processor/diff_tests/parser/graphics/tests.py
+++ b/test/trace_processor/diff_tests/parser/graphics/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -296,3 +296,20 @@
   #      751796307210,4313965,"mali_KCPU_FENCE_WAIT"
   #      751800638997,0,"mali_KCPU_FENCE_SIGNAL"
   #      """))
+
+  # Tests gpu_track with machine_id ID.
+  def test_graphics_frame_events_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('graphics_frame_events.py'),
+        trace_modifier=TraceInjector(['graphics_frame_event'],
+                                     {'machine_id': 1001}),
+        query="""
+        SELECT ts, gpu_track.name AS track_name, dur, frame_slice.name AS slice_name,
+          frame_number, layer_name
+        FROM gpu_track
+        LEFT JOIN frame_slice ON gpu_track.id = frame_slice.track_id
+        WHERE scope = 'graphics_frame_event'
+          AND gpu_track.machine_id IS NOT NULL
+        ORDER BY ts;
+        """,
+        out=Path('graphics_frame_events.out'))
diff --git a/test/trace_processor/diff_tests/parser/parsing/tests.py b/test/trace_processor/diff_tests/parser/parsing/tests.py
index 8619bc3..505bdf9 100644
--- a/test/trace_processor/diff_tests/parser/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parser/parsing/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -1354,3 +1354,58 @@
         "name","severity","value"
         "ftrace_abi_errors_skipped_zero_data_length","info",1
         """))
+
+  # CPU info
+  def test_cpu_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('cpu_info.textproto'),
+        trace_modifier=TraceInjector(['cpu_info'], {'machine_id': 1001}),
+        query="""
+        SELECT
+          id,
+          cluster_id,
+          processor
+        FROM cpu
+        WHERE machine_id is not NULL;
+        """,
+        out=Csv("""
+        "id","cluster_id","processor"
+        0,0,"AArch64 Processor rev 13 (aarch64)"
+        1,0,"AArch64 Processor rev 13 (aarch64)"
+        2,0,"AArch64 Processor rev 13 (aarch64)"
+        3,0,"AArch64 Processor rev 13 (aarch64)"
+        4,0,"AArch64 Processor rev 13 (aarch64)"
+        5,0,"AArch64 Processor rev 13 (aarch64)"
+        6,1,"AArch64 Processor rev 13 (aarch64)"
+        7,1,"AArch64 Processor rev 13 (aarch64)"
+        """))
+
+  def test_cpu_freq_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('cpu_info.textproto'),
+        trace_modifier=TraceInjector(['cpu_info'], {'machine_id': 1001}),
+        query="""
+        SELECT
+          freq,
+          GROUP_CONCAT(cpu_id) AS cpus
+        FROM cpu_freq
+        WHERE machine_id is not NULL
+        GROUP BY freq
+        ORDER BY freq;
+        """,
+        out=Path('cpu_freq.out'))
+
+  def test_sched_waking_instants_compact_sched_machine_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('compact_sched.pb'),
+        trace_modifier=TraceInjector(
+            ['ftrace_events', 'ftrace_stats', 'system_info'],
+            {'machine_id': 1001}),
+        query="""
+        SELECT ts, thread.name, thread.tid
+        FROM thread_state
+        JOIN thread USING (utid)
+        WHERE state = 'R' AND thread_state.machine_id is not NULL
+        ORDER BY ts;
+        """,
+        out=Path('sched_waking_instants_compact_sched.out'))
diff --git a/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py b/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
index f62ba2c..d678400 100644
--- a/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
+++ b/test/trace_processor/diff_tests/parser/power/tests_energy_breakdown.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -90,3 +90,35 @@
         3,10190
         3,10235
         """))
+
+  def test_energy_breakdown_uid_table_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('energy_breakdown_uid.textproto'),
+        trace_modifier=TraceInjector(['android_energy_estimation_breakdown'],
+                                     {'machine_id': 1001}),
+        query="""
+        SELECT uid, name
+        FROM uid_counter_track
+        WHERE machine_id IS NOT NULL;
+        """,
+        out=Csv("""
+        "uid","name"
+        10234,"GPU"
+        10190,"GPU"
+        10235,"GPU"
+        """))
+
+  def test_energy_breakdown_table_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('energy_breakdown.textproto'),
+        trace_modifier=TraceInjector(['android_energy_estimation_breakdown'],
+                                     {'machine_id': 1001}),
+        query="""
+        SELECT consumer_id, name, consumer_type, ordinal
+        FROM energy_counter_track
+        WHERE machine_id IS NOT NULL;
+        """,
+        out=Csv("""
+        "consumer_id","name","consumer_type","ordinal"
+        0,"CPUCL0","CPU_CLUSTER",0
+        """))
diff --git a/test/trace_processor/diff_tests/parser/process_tracking/tests.py b/test/trace_processor/diff_tests/parser/process_tracking/tests.py
index 9092541..b2a04a7 100644
--- a/test/trace_processor/diff_tests/parser/process_tracking/tests.py
+++ b/test/trace_processor/diff_tests/parser/process_tracking/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -227,6 +227,36 @@
         19999,"[NULL]","[NULL]","real_name"
         """))
 
+  def test_process_tracking_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('synth_process_tracking.py'),
+        trace_modifier=TraceInjector(['ftrace_events', 'process_tree'],
+                                     {'machine_id': 1001}),
+        query="""
+        SELECT tid, pid, process.name AS pname, thread.name AS tname,
+               thread.machine_id as tmachine, process.machine_id as pmachine
+        FROM thread
+        LEFT JOIN process USING(upid)
+        WHERE tid > 0
+        ORDER BY tid;
+        """,
+        out=Csv("""
+        "tid","pid","pname","tname","tmachine","pmachine"
+        10,10,"process1","p1-t0",1,1
+        11,"[NULL]","[NULL]","p1-t1",1,"[NULL]"
+        12,10,"process1","p1-t2",1,1
+        20,20,"process_2","p2-t0",1,1
+        21,20,"process_2","p2-t1",1,1
+        22,20,"process_2","p2-t2",1,1
+        30,30,"process_3","p3-t0",1,1
+        31,30,"process_3","p3-t1",1,1
+        31,40,"process_4","p4-t1",1,1
+        32,30,"process_3","p3-t2",1,1
+        33,30,"process_3","p3-t3",1,1
+        34,30,"process_3","p3-t4",1,1
+        40,40,"process_4","p4-t0",1,1
+        """))
+
   def test_process_stats_process_runtime(self):
     return DiffTestBlueprint(
         trace=TextProto(r"""
diff --git a/test/trace_processor/diff_tests/parser/sched/tests.py b/test/trace_processor/diff_tests/parser/sched/tests.py
index ea04126..5568147 100644
--- a/test/trace_processor/diff_tests/parser/sched/tests.py
+++ b/test/trace_processor/diff_tests/parser/sched/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -72,4 +72,39 @@
         "Cpu 5 Util",13000,125.000000
         "Cpu 5 Cap",13000,757.000000
         "Cpu 5 Nr Running",13000,1.000000
-        """))
\ No newline at end of file
+        """))
+
+  def test_sched_cpu_util_cfs_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('sched_cpu_util_cfs.textproto'),
+        trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}),
+        query="""
+        SELECT
+          t.name,
+          c.ts,
+          c.value,
+          c.machine_id
+        FROM
+          counter AS c
+        LEFT JOIN
+          counter_track AS t
+          ON c.track_id = t.id
+        WHERE
+          name GLOB "Cpu ? Cap" OR name GLOB "Cpu ? Util" OR name GLOB "Cpu ? Nr Running"
+        ORDER BY ts;
+        """,
+        out=Csv("""
+        "name","ts","value","machine_id"
+        "Cpu 6 Util",10000,1.000000,1
+        "Cpu 6 Cap",10000,1004.000000,1
+        "Cpu 6 Nr Running",10000,0.000000,1
+        "Cpu 7 Util",11000,1.000000,1
+        "Cpu 7 Cap",11000,1007.000000,1
+        "Cpu 7 Nr Running",11000,0.000000,1
+        "Cpu 4 Util",12000,43.000000,1
+        "Cpu 4 Cap",12000,760.000000,1
+        "Cpu 4 Nr Running",12000,0.000000,1
+        "Cpu 5 Util",13000,125.000000,1
+        "Cpu 5 Cap",13000,757.000000,1
+        "Cpu 5 Nr Running",13000,1.000000,1
+        """))
diff --git a/test/trace_processor/diff_tests/parser/track_event/tests.py b/test/trace_processor/diff_tests/parser/track_event/tests.py
index 363b907..d71a2fa 100644
--- a/test/trace_processor/diff_tests/parser/track_event/tests.py
+++ b/test/trace_processor/diff_tests/parser/track_event/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -692,3 +692,70 @@
         12000,"slice3"
         13000,"slice4"
         """))
+
+  def test_track_event_tracks_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('track_event_tracks.textproto'),
+        trace_modifier=TraceInjector(['track_descriptor', 'track_event'],
+                                     {'machine_id': 1001}),
+        query="""
+        WITH track_with_name AS (
+          SELECT
+            COALESCE(
+              t1.name,
+              'thread=' || thread.name,
+              'process=' || process.name,
+              'tid=' || thread.tid,
+              'pid=' || process.pid
+            ) AS full_name,
+            *
+          FROM track t1
+          LEFT JOIN thread_track t2 USING (id)
+          LEFT JOIN thread USING (utid)
+          LEFT JOIN process_track t3 USING (id)
+          LEFT JOIN process ON t3.upid = process.id
+          WHERE t1.machine_id IS NOT NULL
+          ORDER BY id
+        )
+        SELECT t1.full_name AS name, t2.full_name AS parent_name,
+               EXTRACT_ARG(t1.source_arg_set_id, 'has_first_packet_on_sequence')
+               AS has_first_packet_on_sequence
+        FROM track_with_name t1
+        LEFT JOIN track_with_name t2 ON t1.parent_id = t2.id
+        ORDER BY 1, 2;
+        """,
+        out=Csv("""
+        "name","parent_name","has_first_packet_on_sequence"
+        "Default Track","[NULL]","[NULL]"
+        "async","process=p1",1
+        "async2","process=p1",1
+        "async3","thread=t2",1
+        "event_and_track_async3","process=p1",1
+        "process=p1","[NULL]","[NULL]"
+        "process=p2","[NULL]","[NULL]"
+        "process=p2","[NULL]","[NULL]"
+        "thread=t1","process=p1",1
+        "thread=t2","process=p1",1
+        "thread=t3","process=p1",1
+        "thread=t4","process=p2","[NULL]"
+        "tid=1","[NULL]","[NULL]"
+        """))
+
+  # Tests thread_counter_track.machine_id is not null.
+  def test_track_event_counters_counters_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('track_event_counters.textproto'),
+        trace_modifier=TraceInjector(
+            ['track_descriptor', 'track_event', 'trace_packet_defaults'],
+            {'machine_id': 1001}),
+        query="""
+        SELECT type, name, machine_id
+        FROM thread_counter_track
+        WHERE machine_id IS NOT NULL
+        """,
+        out=Csv("""
+        "type","name","machine_id"
+        "thread_counter_track","thread_time",1
+        "thread_counter_track","thread_time",1
+        "thread_counter_track","thread_instruction_count",1
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/android/startups_tests.py b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
new file mode 100644
index 0000000..14d002a
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/android/startups_tests.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath
+from python.generators.diff_tests.testing import Csv, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class Startups(TestSuite):
+
+  def test_hot_startups(self):
+    return DiffTestBlueprint(
+        trace=DataPath('api31_startup_hot.perfetto-trace'),
+        query="""
+        INCLUDE PERFETTO MODULE android.startup.startups;
+        SELECT * FROM android_startups;
+        """,
+        out=Csv("""
+        "startup_id","ts","ts_end","dur","package","startup_type"
+        1,186969441973689,186969489302704,47329015,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+        """))
+
+  def test_warm_startups(self):
+    return DiffTestBlueprint(
+        trace=DataPath('api32_startup_warm.perfetto-trace'),
+        query="""
+        INCLUDE PERFETTO MODULE android.startup.startups;
+        SELECT * FROM android_startups;
+        """,
+        out=Csv("""
+        "startup_id","ts","ts_end","dur","package","startup_type"
+        28,157479786566030,157479943081777,156515747,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+        """))
+
+  def test_cold_startups(self):
+    return DiffTestBlueprint(
+        trace=DataPath('api34_startup_cold.perfetto-trace'),
+        query="""
+        INCLUDE PERFETTO MODULE android.startup.startups;
+        SELECT * FROM android_startups;
+        """,
+        out=Csv("""
+        "startup_id","ts","ts_end","dur","package","startup_type"
+        61,17806781251694,17806891032171,109780477,"com.android.systemui.people","warm"
+        """))
+
+  def test_hot_startups_maxsdk28(self):
+    return DiffTestBlueprint(
+        trace=DataPath('api24_startup_hot.perfetto-trace'),
+        query="""
+        INCLUDE PERFETTO MODULE android.startup.startups;
+        SELECT * FROM android_startups;
+        """,
+        out=Csv("""
+        "startup_id","ts","ts_end","dur","package","startup_type"
+        1,779860286416,779893485322,33198906,"com.google.android.googlequicksearchbox","[NULL]"
+        2,780778904571,780813944498,35039927,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+        """))
+
+  def test_warm_startups_maxsdk28(self):
+    return DiffTestBlueprint(
+        trace=DataPath('api24_startup_warm.perfetto-trace'),
+        query="""
+        INCLUDE PERFETTO MODULE android.startup.startups;
+        SELECT * FROM android_startups;
+        """,
+        out=Csv("""
+        "startup_id","ts","ts_end","dur","package","startup_type"
+        1,799979565075,800014194731,34629656,"com.google.android.googlequicksearchbox","[NULL]"
+        2,800868511677,800981929562,113417885,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+        """))
+
+  def test_cold_startups_maxsdk28(self):
+    return DiffTestBlueprint(
+        trace=DataPath('api24_startup_cold.perfetto-trace'),
+        query="""
+        INCLUDE PERFETTO MODULE android.startup.startups;
+        SELECT * FROM android_startups;
+        """,
+        out=Csv("""
+        "startup_id","ts","ts_end","dur","package","startup_type"
+        1,791231114368,791501060868,269946500,"androidx.benchmark.integration.macrobenchmark.target","[NULL]"
+        """))
diff --git a/test/trace_processor/diff_tests/tables/tests.py b/test/trace_processor/diff_tests/tables/tests.py
index eabf714..eef833c 100644
--- a/test/trace_processor/diff_tests/tables/tests.py
+++ b/test/trace_processor/diff_tests/tables/tests.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -414,3 +414,58 @@
           "TO_REALTIME(0)"
           420
         """))
+
+  # Test cpu_track with machine_id ID.
+  def test_cpu_track_table_machine_id(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 100001000000
+              pid: 10
+              irq_handler_entry {
+                irq: 100
+                name : "resource1"
+              }
+            }
+            event {
+              timestamp: 100002000000
+              pid: 10
+              irq_handler_exit {
+                irq: 100
+                ret: 1
+              }
+            }
+          }
+          machine_id: 1001
+        }
+        packet {
+          ftrace_events {
+            cpu: 0
+            event {
+              timestamp: 100003000000
+              pid: 15
+              irq_handler_entry {
+                irq: 100
+                name : "resource1"
+              }
+            }
+          }
+          machine_id: 1001
+        }
+        """),
+        query="""
+        SELECT
+          type,
+          cpu,
+          machine_id
+        FROM cpu_track
+        ORDER BY type, cpu
+        """,
+        out=Csv("""
+        "type","cpu","machine_id"
+        "cpu_track",0,1
+        "cpu_track",1,1
+        """))
diff --git a/test/trace_processor/diff_tests/tables/tests_counters.py b/test/trace_processor/diff_tests/tables/tests_counters.py
index 8ed753f..4cc79a0 100644
--- a/test/trace_processor/diff_tests/tables/tests_counters.py
+++ b/test/trace_processor/diff_tests/tables/tests_counters.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 
 
@@ -125,3 +125,106 @@
         73727335051,23522762
         86726132752,24487554
         """))
+
+  def test_counter_dur_example_android_trace_30s_machine_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        trace_modifier=TraceInjector(
+            ['ftrace_events', 'sys_stats', 'process_stats', 'process_tree'],
+            {'machine_id': 1001}),
+        query="""
+        SELECT ts, dur, machine_id
+        FROM experimental_counter_dur
+        WHERE track_id IN (1, 2, 3)
+        ORDER BY dur LIMIT 10;
+        """,
+        out=Csv("""
+        "ts","dur","machine_id"
+        100351738640,-1,1
+        100351738640,-1,1
+        100351738640,-1,1
+        70731059648,19510835,1
+        70731059648,19510835,1
+        70731059648,19510835,1
+        73727335051,23522762,1
+        73727335051,23522762,1
+        73727335051,23522762,1
+        86726132752,24487554,1
+        """))
+
+  # Tests counter.machine_id and process_counter_track.machine.
+  def test_filter_row_vector_example_android_trace_30s_machine_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('example_android_trace_30s.pb'),
+        trace_modifier=TraceInjector(
+            ['ftrace_events', 'sys_stats', 'process_stats', 'process_tree'],
+            {'machine_id': 1001}),
+        query="""
+        SELECT ts
+        FROM counter
+        WHERE
+          ts > 72563651549
+          AND track_id = (
+            SELECT t.id
+            FROM process_counter_track t
+            JOIN process p USING (upid)
+            WHERE
+              t.name = 'Heap size (KB)'
+              AND p.pid = 1204
+              AND t.machine_id is not NULL
+          )
+          AND value != 17952.000000
+          AND counter.machine_id is not NULL
+        LIMIT 20;
+        """,
+        out=Path('filter_row_vector_example_android_trace_30s.out'))
+
+  def test_counters_where_cpu_counters_where_cpu_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('counters_where_cpu.py'),
+        trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}),
+        query="""
+        SELECT
+          ts,
+          lead(ts, 1, ts) OVER (PARTITION BY name ORDER BY ts) - ts AS dur,
+          value, c.machine_id
+        FROM counter c
+        JOIN cpu_counter_track t ON t.id = c.track_id
+        WHERE cpu = 1;
+        """,
+        out=Csv("""
+        "ts","dur","value","machine_id"
+        1000,1,3000.000000,1
+        1001,0,4000.000000,1
+        """))
+
+  def test_synth_1_filter_counter_machine_id(self):
+    return DiffTestBlueprint(
+        trace=Path('../common/synth_1.py'),
+        trace_modifier=TraceInjector(
+            ['ftrace_events', 'process_stats', 'process_tree'],
+            {'machine_id': 1001}),
+        query="""
+        SELECT COUNT(*), machine_id
+        FROM counter
+        WHERE
+          track_id = 0;
+        """,
+        out=Csv("""
+        "COUNT(*)","machine_id"
+        2,1
+        """))
+
+  def test_memory_counters_machine_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('memory_counters.pb'),
+        trace_modifier=TraceInjector(
+            ['ftrace_events', 'sys_stats', 'process_stats', 'process_tree'],
+            {'machine_id': 1001}),
+        query="""
+        SELECT count(*), machine_id FROM counters WHERE -1 < ts group by machine_id;
+        """,
+        out=Csv("""
+        "count(*)","machine_id"
+        98688,1
+        """))
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 306b8e8..ad23a5d 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -15,7 +15,7 @@
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
-from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import DiffTestBlueprint, TraceInjector
 from python.generators.diff_tests.testing import TestSuite
 from python.generators.diff_tests.testing import PrintProfileProto
 
@@ -675,3 +675,38 @@
         thread_state: S (0x0)
         critical path (0x0)
         """))
+
+  # Test machine_id ID of the sched table.
+  def test_android_sched_and_ps_machine_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_sched_and_ps.pb'),
+        trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}),
+        query="""
+        SELECT ts, cpu, machine_id FROM sched WHERE ts >= 81473797418963 LIMIT 10;
+        """,
+        out=Csv("""
+        "ts","cpu","machine_id"
+        81473797824982,3,1
+        81473797942847,3,1
+        81473798135399,0,1
+        81473798786857,2,1
+        81473798875451,3,1
+        81473799019930,2,1
+        81473799079982,0,1
+        81473800089357,3,1
+        81473800144461,3,1
+        81473800441805,3,1
+        """))
+
+  # Test the support of machine_id ID of the raw table.
+  def test_raw_machine_id(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_sched_and_ps.pb'),
+        trace_modifier=TraceInjector(['ftrace_events'], {'machine_id': 1001}),
+        query="""
+        SELECT count(*) FROM raw WHERE machine_id is NULL;
+        """,
+        out=Csv("""
+        "count(*)"
+        0
+        """))
diff --git a/ui/package.json b/ui/package.json
index e1807ce..9dea540 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -72,5 +72,8 @@
     "build": "node build.js",
     "test": "node build.js --run-unittests",
     "lint": "npx eslint . --ext .js,.ts"
+  },
+  "jest": {
+    "setupFiles": ["jest-localstorage-mock"]
   }
 }
diff --git a/ui/src/frontend/base_counter_track.ts b/ui/src/frontend/base_counter_track.ts
index 202ad44..3fe5c71 100644
--- a/ui/src/frontend/base_counter_track.ts
+++ b/ui/src/frontend/base_counter_track.ts
@@ -891,6 +891,7 @@
     if (maybeCachedCounters) {
       this.countersKey = countersKey;
       this.counters = maybeCachedCounters;
+      return;
     }
 
     const bucketNs = countersKey.bucketSize;
@@ -955,6 +956,7 @@
     data.displayValueRange = [min, max];
 
     this.cache.insert(countersKey, data);
+    this.countersKey = countersKey;
     this.counters = data;
 
     raf.scheduleRedraw();
diff --git a/ui/src/frontend/post_message_handler.ts b/ui/src/frontend/post_message_handler.ts
index 7a7addc..8220b44 100644
--- a/ui/src/frontend/post_message_handler.ts
+++ b/ui/src/frontend/post_message_handler.ts
@@ -35,7 +35,7 @@
 
 // Returns whether incoming traces should be opened automatically or should
 // instead require a user interaction.
-function isTrustedOrigin(origin: string): boolean {
+export function isTrustedOrigin(origin: string): boolean {
   const TRUSTED_ORIGINS = [
     'https://chrometto.googleplex.com',
     'https://uma.googleplex.com',
@@ -46,8 +46,15 @@
   if (isUserTrustedOrigin(origin)) return true;
 
   const hostname = new URL(origin).hostname;
-  if (hostname.endsWith('corp.google.com')) return true;
-  if (hostname === 'localhost' || hostname === '127.0.0.1') return true;
+  if (hostname.endsWith('.corp.google.com')) return true;
+  if (hostname.endsWith('.c.googlers.com')) return true;
+  if (
+    hostname === 'localhost' ||
+    hostname === '127.0.0.1' ||
+    hostname === '[::1]'
+  ) {
+    return true;
+  }
   return false;
 }
 
diff --git a/ui/src/frontend/post_message_handler_unittest.ts b/ui/src/frontend/post_message_handler_unittest.ts
new file mode 100644
index 0000000..c18aff6
--- /dev/null
+++ b/ui/src/frontend/post_message_handler_unittest.ts
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 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.
+
+import {isTrustedOrigin} from './post_message_handler';
+
+describe('postMessageHandler', () => {
+  test('baked-in trusted origins are trusted', () => {
+    expect(isTrustedOrigin('https://chrometto.googleplex.com')).toBeTruthy();
+    expect(isTrustedOrigin('https://uma.googleplex.com')).toBeTruthy();
+    expect(
+      isTrustedOrigin('https://android-build.googleplex.com'),
+    ).toBeTruthy();
+    expect(isTrustedOrigin('https://html5zombo.com')).toBeFalsy();
+  });
+
+  test('user trusted origins in local storage are trusted', () => {
+    try {
+      expect(isTrustedOrigin('https://html5zombo.com')).toBeFalsy();
+      window.localStorage['trustedOrigins'] = '["https://html5zombo.com"]';
+      expect(isTrustedOrigin('https://html5zombo.com')).toBeTruthy();
+    } finally {
+      window.localStorage.clear();
+    }
+  });
+
+  test('developer hostnames are trusted', () => {
+    expect(isTrustedOrigin('https://google.com')).toBeFalsy();
+    expect(isTrustedOrigin('https://broccoliman.corp.google.com')).toBeTruthy();
+    expect(isTrustedOrigin('http://broccoliman.corp.google.com')).toBeTruthy();
+    expect(isTrustedOrigin('https://broccoliman.c.googlers.com')).toBeTruthy();
+    expect(isTrustedOrigin('http://broccoliman.c.googlers.com')).toBeTruthy();
+    expect(isTrustedOrigin('https://broccolimancorp.google.com')).toBeFalsy();
+    expect(isTrustedOrigin('https://broccolimanc.googlers.com')).toBeFalsy();
+    expect(isTrustedOrigin('https://localhost')).toBeTruthy();
+    expect(isTrustedOrigin('http://localhost')).toBeTruthy();
+    expect(isTrustedOrigin('https://127.0.0.1')).toBeTruthy();
+    expect(isTrustedOrigin('http://127.0.0.1')).toBeTruthy();
+    // IPv6 localhost
+    expect(isTrustedOrigin('https://[::1]')).toBeTruthy();
+    expect(isTrustedOrigin('http://[::1]')).toBeTruthy();
+  });
+});
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index c35143e..7247376 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -15,11 +15,7 @@
 import m from 'mithril';
 
 import {LogFilteringCriteria, LogPanel} from './logs_panel';
-import {
-  Plugin,
-  PluginContextTrace,
-  PluginDescriptor,
-} from '../../public';
+import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
 import {NUM} from '../../trace_processor/query_result';
 import {AndroidLogTrack} from './logs_track';
 
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 2ac64d6..a459c8a 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -133,19 +133,22 @@
 
   onMouseClick({x}: {x: number}): boolean {
     const {visibleTimeScale} = globals.timeline;
-    const time = visibleTimeScale.pxToHpTime(x);
+    const time = visibleTimeScale.pxToHpTime(x).toTime('floor');
 
-    const result = this.engine.query(`
-
-      select
+    const query = `
+      SELECT
         id,
         ts as leftTs,
-        max(ts) OVER (ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) as rightTs
-        from
-        ${this.rootTable}
-        where track_id = ${this.trackId} and ts > ${time} limit 1`);
+        min(ts) OVER (ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) as rightTs
+      FROM ${this.rootTable}
+      WHERE
+        track_id = ${this.trackId} AND
+        ts < ${time}
+      ORDER BY ts DESC
+      LIMIT 1
+    `;
 
-    result.then((result) => {
+    this.engine.query(query).then((result) => {
       const it = result.iter({
         id: NUM,
         leftTs: LONG,