Merge "Add cpu_frequency event parsing"
diff --git a/Android.bp b/Android.bp
index aa12cd1..fbf6d49 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3666,6 +3666,7 @@
 genrule {
     name: "perfetto_protos_perfetto_metrics_chrome_descriptor",
     srcs: [
+        "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
         "protos/perfetto/metrics/android/batt_metric.proto",
         "protos/perfetto/metrics/android/camera_metric.proto",
         "protos/perfetto/metrics/android/camera_unagg_metric.proto",
@@ -3727,6 +3728,7 @@
 genrule {
     name: "perfetto_protos_perfetto_metrics_descriptor",
     srcs: [
+        "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
         "protos/perfetto/metrics/android/batt_metric.proto",
         "protos/perfetto/metrics/android/camera_metric.proto",
         "protos/perfetto/metrics/android/camera_unagg_metric.proto",
@@ -4173,10 +4175,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4392,10 +4396,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4445,10 +4451,12 @@
         "external/perfetto/protos/perfetto/trace/ftrace/scm.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sde.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/signal.gen.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/sock.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sync.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/synthetic.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/systrace.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/task.gen.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/tcp.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/thermal.gen.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/vmscan.gen.cc",
@@ -4498,10 +4506,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4551,10 +4561,12 @@
         "external/perfetto/protos/perfetto/trace/ftrace/scm.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sde.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/signal.gen.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/sock.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sync.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/synthetic.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/systrace.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/task.gen.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/tcp.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/thermal.gen.h",
         "external/perfetto/protos/perfetto/trace/ftrace/vmscan.gen.h",
@@ -4608,10 +4620,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4660,10 +4674,12 @@
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sde.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/signal.pb.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/sock.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sync.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/synthetic.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/systrace.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/task.pb.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/tcp.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/thermal.pb.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/vmscan.pb.cc",
@@ -4713,10 +4729,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4765,10 +4783,12 @@
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sde.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/signal.pb.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/sock.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sync.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/synthetic.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/systrace.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/task.pb.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/tcp.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/thermal.pb.h",
         "external/perfetto/protos/perfetto/trace/ftrace/vmscan.pb.h",
@@ -4822,10 +4842,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4875,10 +4897,12 @@
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sde.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/signal.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/sock.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/sync.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/synthetic.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/systrace.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/task.pbzero.cc",
+        "external/perfetto/protos/perfetto/trace/ftrace/tcp.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/thermal.pbzero.cc",
         "external/perfetto/protos/perfetto/trace/ftrace/vmscan.pbzero.cc",
@@ -4928,10 +4952,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
@@ -4981,10 +5007,12 @@
         "external/perfetto/protos/perfetto/trace/ftrace/scm.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sde.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/signal.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/sock.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/sync.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/synthetic.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/systrace.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/task.pbzero.h",
+        "external/perfetto/protos/perfetto/trace/ftrace/tcp.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/test_bundle_wrapper.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/thermal.pbzero.h",
         "external/perfetto/protos/perfetto/trace/ftrace/vmscan.pbzero.h",
@@ -8121,6 +8149,7 @@
         "src/trace_processor/metrics/sql/android/android_task_names.sql",
         "src/trace_processor/metrics/sql/android/android_thread_time_in_state.sql",
         "src/trace_processor/metrics/sql/android/android_trace_quality.sql",
+        "src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
         "src/trace_processor/metrics/sql/android/composer_execution.sql",
         "src/trace_processor/metrics/sql/android/composition_layers.sql",
         "src/trace_processor/metrics/sql/android/cpu_info.sql",
diff --git a/BUILD b/BUILD
index 5b4d5cb..ab6cc4c 100644
--- a/BUILD
+++ b/BUILD
@@ -1070,6 +1070,7 @@
         "src/trace_processor/metrics/sql/android/android_task_names.sql",
         "src/trace_processor/metrics/sql/android/android_thread_time_in_state.sql",
         "src/trace_processor/metrics/sql/android/android_trace_quality.sql",
+        "src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql",
         "src/trace_processor/metrics/sql/android/composer_execution.sql",
         "src/trace_processor/metrics/sql/android/composition_layers.sql",
         "src/trace_processor/metrics/sql/android/cpu_info.sql",
@@ -1263,6 +1264,7 @@
         "src/trace_processor/types/softirq_action.h",
         "src/trace_processor/types/task_state.cc",
         "src/trace_processor/types/task_state.h",
+        "src/trace_processor/types/tcp_state.h",
         "src/trace_processor/types/trace_processor_context.h",
         "src/trace_processor/types/variadic.cc",
         "src/trace_processor/types/variadic.h",
@@ -2605,6 +2607,7 @@
 perfetto_proto_library(
     name = "protos_perfetto_metrics_android_protos",
     srcs = [
+        "protos/perfetto/metrics/android/android_trusty_workqueues.proto",
         "protos/perfetto/metrics/android/batt_metric.proto",
         "protos/perfetto/metrics/android/camera_metric.proto",
         "protos/perfetto/metrics/android/camera_unagg_metric.proto",
@@ -2878,10 +2881,12 @@
         "protos/perfetto/trace/ftrace/scm.proto",
         "protos/perfetto/trace/ftrace/sde.proto",
         "protos/perfetto/trace/ftrace/signal.proto",
+        "protos/perfetto/trace/ftrace/sock.proto",
         "protos/perfetto/trace/ftrace/sync.proto",
         "protos/perfetto/trace/ftrace/synthetic.proto",
         "protos/perfetto/trace/ftrace/systrace.proto",
         "protos/perfetto/trace/ftrace/task.proto",
+        "protos/perfetto/trace/ftrace/tcp.proto",
         "protos/perfetto/trace/ftrace/test_bundle_wrapper.proto",
         "protos/perfetto/trace/ftrace/thermal.proto",
         "protos/perfetto/trace/ftrace/vmscan.proto",
diff --git a/CHANGELOG b/CHANGELOG
index 8dbe728..76b42be 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,50 @@
     *
 
 
+v24.1 - 2022-02-09:
+  Tracing service and probes:
+    * Fixed build failures on Windows.
+  Trace Processor:
+    * Fixed build failures on Windows.
+  UI:
+    *
+  SDK:
+    *
+
+
+v24.0 - 2022-02-08:
+  Tracing service and probes:
+    * Added "cpufreq_period_ms" in data source "linux.sys_stats" to poll
+      /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq periodically.
+    * Added support for Trusty TEE workqueue events.
+    * Added support for more PMU events in traced_perf.
+    * Changed output format of perfetto --query. Made the output more compact
+      and added a summary of ongoing tracing sessions for the caller UID.
+    * Changed timeout for traced stall detection from 2s to 4s.
+    * Changed internal buffer management to split trace filtering in smaller
+      tasks and avoid too large memory allocation when filtering.
+    * Fixed a bug that could cause producers to see Flush() requests after an
+      OnStop() and mis-behave if the tracing session is extremely short.
+  Trace Processor:
+    * Added support for passing multiple SQL statements to ExecuteQuery(). All
+      queries will be executed fully, with the returned iterator yielding rows
+      for the final statement.
+    * Added support for multi-line SQL comments; previously only single line
+      comments were supported.
+  UI:
+    * Added support for parsing instant events from legacy systrace formats.
+    * Added ingestion and visualization for inet_sock_set_state and
+      tcp_retransmit_skb events, showing TCP connections on dedicated tracks.
+    * Changed HTTP+RPC to use the /websocket endpoint available in newer
+      versions of trace_processor --httpd.
+    * Changed text selection/copy: now allowed by default for DOM elements.
+    * Changed search to also lookup slices by ID when the term is a number.
+    * Changed postMessage() API, suppressed confirmation dialog when the opener
+      is in the same origin, for cases when the UI is self-hosted.
+  SDK:
+    *
+
+
 v23.0 - 2022-01-11:
   Tracing service and probes:
     * Added workaround for a kernel ftrace bug causing some "comm" fields to be
@@ -65,6 +109,9 @@
       the middle of a suspend/resume. Switched from SND_TIMEO to poll(POLLOUT).
     * Added "linux.sysfs_power" data source to poll /sys/class/power_supply/
       and report periodically battery charge and drain rate if supported.
+    * Add snapshotting for non-BOOTTIME ftrace clocks. This fixes handling of
+      ftrace events from old Linux kernel versions (i.e. 3.x) and adds
+      proper support for using the "global" clock rather than "boot".
   Trace Processor:
     * Speeded up proto trace ingestion by 2x (~20 MB/s -> ~40 MB/s).
     * Changed LIKE comparisions to be case-senstive. This may break existing
@@ -75,6 +122,7 @@
     * Changed how displayTimeUnit is handled in JSON traces to match catapult.
     * Added websocket endpoint to RPC interface to reduce query latency.
     * Added support for hot-reloading metrics (see //docs/analysis/metrics.md).
+    * Added ingestion support for non-BOOTTIME ftrace clocks.
   UI:
     * Added ability to save/restore record config. Remember last used config.
     * Fixed bug causing the recording page to hold onto the USB interface making
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d971a20..785bb2a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -8,5 +8,10 @@
     {
       "name": "libsurfaceflinger_unittest"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsPerfettoTestCases"
+    }
   ]
 }
diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md
index 67b9407..f45d665 100644
--- a/docs/contributing/testing.md
+++ b/docs/contributing/testing.md
@@ -120,9 +120,9 @@
 output in the whole file (usually at the end). Calling
 `SELECT RUN_METRIC('metric file')` can trip up this check as this query
 generates some hidden output. To address this issue, if a query only has
-column is named `supress_query_output`, even if it has output, this will
+column is named `suppress_query_output`, even if it has output, this will
 be ignored (for example,
-`SELECT RUN_METRIC('metric file') as surpress_query_output`)
+`SELECT RUN_METRIC('metric file') as suppress_query_output`)
 
 UI pixel diff tests
 -----------------
diff --git a/include/perfetto/trace_processor/iterator.h b/include/perfetto/trace_processor/iterator.h
index c3373f3..95aa4c5 100644
--- a/include/perfetto/trace_processor/iterator.h
+++ b/include/perfetto/trace_processor/iterator.h
@@ -31,6 +31,15 @@
 class IteratorImpl;
 
 // Iterator returning SQL rows satisfied by a query.
+//
+// Example usage:
+// auto sql = "select name, ifnull(cat, "[NULL]") from slice";
+// for (auto it = tp.ExecuteQuery(sql); it.Next();)
+//   for (uint32_t i = 0; i < it.ColumnCount(); ++i) {
+//     printf("%s ", it.Get(i).AsString());
+//   }
+//   printf("\n");
+// }
 class PERFETTO_EXPORT Iterator {
  public:
   explicit Iterator(std::unique_ptr<IteratorImpl>);
@@ -40,7 +49,7 @@
   Iterator& operator=(Iterator&) = delete;
 
   Iterator(Iterator&&) noexcept;
-  Iterator& operator=(Iterator&&);
+  Iterator& operator=(Iterator&&) noexcept;
 
   // Forwards the iterator to the next result row and returns a boolean of
   // whether there is a next row. If this method returns false,
@@ -61,6 +70,19 @@
   // even before calling |Next()|.
   uint32_t ColumnCount();
 
+  // Returns the number of statements in the provided SQL (including the final
+  // statement which is iterated using this iterator). Comments and empty
+  // statements are *not* counted i.e.
+  // "SELECT 1; /* comment */; select 2;  -- comment"
+  // returns 2 not 4.
+  uint32_t StatementCount();
+
+  // Returns the number of statements which produced output rows in the provided
+  // SQL (including, potentially, the final statement which is iterated using
+  // this iterator).
+  // This value is guaranteed to be <= |StatementCount()|.
+  uint32_t StatementWithOutputCount();
+
   // Returns the status of the iterator.
   util::Status Status();
 
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index 77a7889..972761d 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -43,23 +43,29 @@
 
   ~TraceProcessor() override;
 
-  // Executes a SQLite query on the loaded portion of the trace. The returned
-  // iterator can be used to load rows from the result.
-  virtual Iterator ExecuteQuery(const std::string& sql,
-                                int64_t time_queued = 0) = 0;
+  // Executes the SQL on the loaded portion of the trace.
+  //
+  // More than one SQL statement can be passed to this function; all but the
+  // last will be fully executed by this function before retuning. The last
+  // statement will be executed and will yield rows as the caller calls Next()
+  // over the returned Iterator.
+  //
+  // See documentation of the Iterator class for an example on how to use
+  // the returned iterator.
+  virtual Iterator ExecuteQuery(const std::string& sql) = 0;
 
   // Registers a metric at the given path which will run the specified SQL.
-  virtual util::Status RegisterMetric(const std::string& path,
+  virtual base::Status RegisterMetric(const std::string& path,
                                       const std::string& sql) = 0;
 
   // Reads the FileDescriptorSet proto message given by |data| and |size| and
   // adds any extensions to the metrics proto to allow them to be available as
   // proto builder functions when computing metrics.
-  virtual util::Status ExtendMetricsProto(const uint8_t* data, size_t size) = 0;
+  virtual base::Status ExtendMetricsProto(const uint8_t* data, size_t size) = 0;
 
   // Behaves exactly as ExtendMetricsProto, except any FileDescriptor with
   // filename matching a prefix in |skip_prefixes| is skipped.
-  virtual util::Status ExtendMetricsProto(
+  virtual base::Status ExtendMetricsProto(
       const uint8_t* data,
       size_t size,
       const std::vector<std::string>& skip_prefixes) = 0;
@@ -68,7 +74,7 @@
   // successful, the output argument |metrics_proto| will be filled with the
   // proto-encoded bytes for the message TraceMetrics in
   // perfetto/metrics/metrics.proto.
-  virtual util::Status ComputeMetric(
+  virtual base::Status ComputeMetric(
       const std::vector<std::string>& metric_names,
       std::vector<uint8_t>* metrics_proto) = 0;
 
@@ -80,7 +86,7 @@
   // Computes metrics as the ComputeMetric function above, but instead of
   // producing proto encoded bytes, the output argument |metrics_string| is
   // filled with the metric formatted in the requested |format|.
-  virtual util::Status ComputeMetricText(
+  virtual base::Status ComputeMetricText(
       const std::vector<std::string>& metric_names,
       MetricResultFormat format,
       std::string* metrics_string) = 0;
@@ -110,7 +116,7 @@
   // Disables "meta-tracing" of trace processor and writes the trace as a
   // sequence of |TracePackets| into |trace_proto| returning the status of this
   // read.
-  virtual util::Status DisableAndReadMetatrace(
+  virtual base::Status DisableAndReadMetatrace(
       std::vector<uint8_t>* trace_proto) = 0;
 
   // Gets all the currently loaded proto descriptors used in metric computation.
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 9229153..3a3db7e 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -35,14 +35,16 @@
 
 namespace perfetto {
 
-// This template provides a way to convert an abstract timestamp into the trace
-// clock timebase in nanoseconds. By specialising this template and defining
+// A function for converting an abstract timestamp into a
+// perfetto::TraceTimestamp struct. By specialising this template and defining
 // static ConvertTimestampToTraceTimeNs function in it the user can register
-// additional timestamp types. The return value should specify the clock used by
-// the timestamp as well as its value in nanoseconds.
+// additional timestamp types. The return value should specify the
+// clock domain used by the timestamp as well as its value.
 //
-// The users should see the specialisation for uint64_t below as an example.
-// Note that the specialisation should be defined in perfetto namespace.
+// The supported clock domains are the ones described in
+// perfetto.protos.ClockSnapshot. However, custom clock IDs (>=64) are
+// reserved for internal use by the SDK for the time being.
+// The timestamp value should be in nanoseconds regardless of the clock domain.
 template <typename T>
 struct TraceTimestampTraits;
 
@@ -239,7 +241,7 @@
                                Arguments&&... args) PERFETTO_NO_INLINE {
     TraceForCategoryImpl(instances, category, event_name, type,
                          TrackEventInternal::kDefaultTrack,
-                         TrackEventInternal::GetTimeNs(),
+                         TrackEventInternal::GetTraceTime(),
                          std::forward<Arguments>(args)...);
   }
 
@@ -261,7 +263,7 @@
                                Arguments&&... args) PERFETTO_NO_INLINE {
     TraceForCategoryImpl(
         instances, category, event_name, type, std::forward<TrackType>(track),
-        TrackEventInternal::GetTimeNs(), std::forward<Arguments>(args)...);
+        TrackEventInternal::GetTraceTime(), std::forward<Arguments>(args)...);
   }
 
   // Trace point which takes a timestamp, but not track.
@@ -314,7 +316,7 @@
                                ValueType value) PERFETTO_ALWAYS_INLINE {
     PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
     TraceForCategory(instances, category, /*name=*/nullptr, type, track,
-                     TrackEventInternal::GetTimeNs(), value);
+                     TrackEventInternal::GetTraceTime(), value);
   }
 
   // Trace point with with a timestamp and a counter sample.
@@ -360,7 +362,8 @@
     TrackRegistry::Get()->UpdateTrack(track, desc.SerializeAsString());
     Base::template Trace([&](typename Base::TraceContext ctx) {
       TrackEventInternal::WriteTrackDescriptor(
-          track, ctx.tls_inst_->trace_writer.get());
+          track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+          TrackEventInternal::GetTraceTime());
     });
   }
 
@@ -452,14 +455,14 @@
           TrackEventIncrementalState* incr_state = ctx.GetIncrementalState();
           if (incr_state->was_cleared) {
             incr_state->was_cleared = false;
-            TrackEventInternal::ResetIncrementalState(trace_writer,
+            TrackEventInternal::ResetIncrementalState(trace_writer, incr_state,
                                                       trace_timestamp);
           }
 
           // Write the track descriptor before any event on the track.
           if (track) {
             TrackEventInternal::WriteTrackDescriptorIfNeeded(
-                track, trace_writer, incr_state);
+                track, trace_writer, incr_state, trace_timestamp);
           }
 
           // Write the event itself.
@@ -513,7 +516,8 @@
     TrackRegistry::Get()->UpdateTrack(track, std::move(callback));
     Base::template Trace([&](typename Base::TraceContext ctx) {
       TrackEventInternal::WriteTrackDescriptor(
-          track, ctx.tls_inst_->trace_writer.get());
+          track, ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
+          TrackEventInternal::GetTraceTime());
     });
   }
 
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index 037dc09..6fed09e 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -35,13 +35,24 @@
 
 // Represents a point in time for the clock specified by |clock_id|.
 struct TraceTimestamp {
-  protos::pbzero::BuiltinClock clock_id;
-  uint64_t nanoseconds;
+  // Clock IDs have the following semantic:
+  // [1, 63]:    Builtin types, see BuiltinClock from
+  //             ../common/builtin_clock.proto.
+  // [64, 127]:  User-defined clocks. These clocks are sequence-scoped. They
+  //             are only valid within the same |trusted_packet_sequence_id|
+  //             (i.e. only for TracePacket(s) emitted by the same TraceWriter
+  //             that emitted the clock snapshot).
+  // [128, MAX]: Reserved for future use. The idea is to allow global clock
+  //             IDs and setting this ID to hash(full_clock_name) & ~127.
+  // Learn more: `clock_snapshot.proto`
+  uint32_t clock_id;
+  uint64_t value;
 };
 
 class EventContext;
 class TrackEventSessionObserver;
 struct Category;
+struct TraceTimestamp;
 namespace protos {
 namespace gen {
 class TrackEventConfig;
@@ -85,6 +96,12 @@
 struct TrackEventIncrementalState {
   static constexpr size_t kMaxInternedDataFields = 32;
 
+  // Packet-sequence-scoped clock that encodes microsecond timestamps in the
+  // domain of the clock returned by GetClockId() as delta values - see
+  // Clock::is_incremental in perfetto/trace/clock_snapshot.proto.
+  // Default unit: nanoseconds.
+  static constexpr uint32_t kClockIdIncremental = 64;
+
   bool was_cleared = true;
 
   // A heap-allocated message for storing newly seen interned data while we are
@@ -116,6 +133,11 @@
   // this tracing session. The value in the map indicates whether the category
   // is enabled or disabled.
   std::unordered_map<std::string, bool> dynamic_categories;
+
+  // The latest reference timestamp that was used in a TracePacket or in a
+  // ClockSnapshot. The increment between this timestamp and the current trace
+  // time (GetTimeNs) is a value in kClockIdIncremental's domain.
+  uint64_t last_timestamp_ns = 0;
 };
 
 // The backend portion of the track event trace point implemention. Outlined to
@@ -146,9 +168,11 @@
       const Category* category,
       const char* name,
       perfetto::protos::pbzero::TrackEvent::Type,
-      TraceTimestamp timestamp = {GetClockId(), GetTimeNs()});
+      const TraceTimestamp& timestamp);
 
-  static void ResetIncrementalState(TraceWriterBase*, TraceTimestamp);
+  static void ResetIncrementalState(TraceWriterBase* trace_writer,
+                                    TrackEventIncrementalState* incr_state,
+                                    const TraceTimestamp& timestamp);
 
   // TODO(altimin): Remove this method once Chrome uses
   // EventContext::AddDebugAnnotation directly.
@@ -168,24 +192,29 @@
   static void WriteTrackDescriptorIfNeeded(
       const TrackType& track,
       TraceWriterBase* trace_writer,
-      TrackEventIncrementalState* incr_state) {
+      TrackEventIncrementalState* incr_state,
+      const TraceTimestamp& timestamp) {
     auto it_and_inserted = incr_state->seen_tracks.insert(track.uuid);
     if (PERFETTO_LIKELY(!it_and_inserted.second))
       return;
-    WriteTrackDescriptor(track, trace_writer);
+    WriteTrackDescriptor(track, trace_writer, incr_state, timestamp);
   }
 
   // Unconditionally write a track descriptor into the trace.
   template <typename TrackType>
   static void WriteTrackDescriptor(const TrackType& track,
-                                   TraceWriterBase* trace_writer) {
+                                   TraceWriterBase* trace_writer,
+                                   TrackEventIncrementalState* incr_state,
+                                   const TraceTimestamp& timestamp) {
     TrackRegistry::Get()->SerializeTrack(
-        track, NewTracePacket(trace_writer, {GetClockId(), GetTimeNs()}));
+        track, NewTracePacket(trace_writer, incr_state, timestamp));
   }
 
   // Get the current time in nanoseconds in the trace clock timebase.
   static uint64_t GetTimeNs();
 
+  static TraceTimestamp GetTraceTime();
+
   // Get the clock used by GetTimeNs().
   static constexpr protos::pbzero::BuiltinClock GetClockId() {
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
@@ -204,7 +233,8 @@
  private:
   static protozero::MessageHandle<protos::pbzero::TracePacket> NewTracePacket(
       TraceWriterBase*,
-      TraceTimestamp,
+      TrackEventIncrementalState*,
+      const TraceTimestamp&,
       uint32_t seq_flags =
           protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
   static protos::pbzero::DebugAnnotation* AddDebugAnnotation(
diff --git a/include/perfetto/tracing/track_event_state_tracker.h b/include/perfetto/tracing/track_event_state_tracker.h
index 7b2437e..e97f035 100644
--- a/include/perfetto/tracing/track_event_state_tracker.h
+++ b/include/perfetto/tracing/track_event_state_tracker.h
@@ -80,6 +80,10 @@
     std::map<uint64_t /*iid*/, std::string> event_names;
     std::map<uint64_t /*iid*/, std::string> event_categories;
     std::map<uint64_t /*iid*/, std::string> debug_annotation_names;
+    // Current absolute timestamp of the incremental clock.
+    uint64_t most_recent_absolute_time_ns = 0;
+    // default_clock_id == 0 means, no default clock_id is set.
+    uint32_t default_clock_id = 0;
   };
 
   // State for the entire tracing session. Shared by all trace writer sequences
diff --git a/infra/luci/generated/cr-buildbucket.cfg b/infra/luci/generated/cr-buildbucket.cfg
index 18a7c5a..95f68d7 100644
--- a/infra/luci/generated/cr-buildbucket.cfg
+++ b/infra/luci/generated/cr-buildbucket.cfg
@@ -29,7 +29,10 @@
         cipd_version: "refs/heads/master"
         cmd: "luciexe"
       }
-      properties: "{\"recipe\":\"perfetto\"}"
+      properties:
+        '{'
+        '  "recipe": "perfetto"'
+        '}'
       service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
         key: "luci.use_realms"
@@ -47,7 +50,10 @@
         cipd_version: "refs/heads/master"
         cmd: "luciexe"
       }
-      properties: "{\"recipe\":\"perfetto\"}"
+      properties:
+        '{'
+        '  "recipe": "perfetto"'
+        '}'
       service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
         key: "luci.use_realms"
@@ -65,7 +71,14 @@
         cipd_version: "refs/heads/master"
         cmd: "luciexe"
       }
-      properties: "{\"recipe\":\"perfetto\"}"
+      properties:
+        '{'
+        '  "recipe": "perfetto"'
+        '}'
+      caches {
+        name: "macos_sdk"
+        path: "macos_sdk"
+      }
       service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
         key: "luci.use_realms"
@@ -83,7 +96,14 @@
         cipd_version: "refs/heads/master"
         cmd: "luciexe"
       }
-      properties: "{\"recipe\":\"perfetto\"}"
+      properties:
+        '{'
+        '  "recipe": "perfetto"'
+        '}'
+      caches {
+        name: "windows_sdk"
+        path: "windows_sdk"
+      }
       service_account: "perfetto-luci-official-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
         key: "luci.use_realms"
diff --git a/infra/luci/generated/project.cfg b/infra/luci/generated/project.cfg
index fbf2a59..809a993 100644
--- a/infra/luci/generated/project.cfg
+++ b/infra/luci/generated/project.cfg
@@ -6,3 +6,9 @@
 
 name: "perfetto"
 access: "group:all"
+lucicfg {
+  version: "1.30.5"
+  package_dir: ".."
+  config_dir: "generated"
+  entry_point: "main.star"
+}
diff --git a/infra/luci/main.star b/infra/luci/main.star
index 4119e2d..5cf3b0c 100755
--- a/infra/luci/main.star
+++ b/infra/luci/main.star
@@ -15,8 +15,7 @@
 
 lucicfg.check_version("1.23.3", "Please update depot_tools")
 
-# Enable LUCI Realms support and launch all builds in realms-aware mode.
-lucicfg.enable_experiment("crbug.com/1085650")
+# Launch all builds in realms-aware mode.
 luci.builder.defaults.experiments.set({"luci.use_realms": 100})
 
 # Enable bbagent.
@@ -75,7 +74,7 @@
     ],
 )
 
-def official_builder(name, os):
+def official_builder(name, os, caches=[]):
     luci.builder(
         name = name,
         bucket = "official",
@@ -98,9 +97,13 @@
                 refs = ["refs/tags/v.+"],
             ),
         ],
+        caches = [
+            swarming.cache(cache, name = cache)
+            for cache in caches
+        ]
     )
 
 official_builder("perfetto-official-builder-linux", "Linux")
-official_builder("perfetto-official-builder-mac", "Mac")
-official_builder("perfetto-official-builder-windows", "Windows")
+official_builder("perfetto-official-builder-mac", "Mac", ["macos_sdk"])
+official_builder("perfetto-official-builder-windows", "Windows", ["windows_sdk"])
 official_builder("perfetto-official-builder-android", "Linux")
diff --git a/protos/perfetto/common/perf_events.proto b/protos/perfetto/common/perf_events.proto
index 8c48396..270bacb 100644
--- a/protos/perfetto/common/perf_events.proto
+++ b/protos/perfetto/common/perf_events.proto
@@ -64,14 +64,56 @@
     optional string name = 10;
   }
 
+  // Builtin counter names from the uapi header. Commented with their perf tool
+  // aliases.
+  // TODO(rsavitski): consider generating enums for cache events (should be
+  // finite), and generally make this list as extensive as possible. Excluding
+  // things like dynamic PMUs since those don't fit into a static enum.
+  // Next id: 21
   enum Counter {
     UNKNOWN_COUNTER = 0;
-    // software:
+
+    // cpu-clock
     SW_CPU_CLOCK = 1;
+    // page-faults, faults
     SW_PAGE_FAULTS = 2;
-    // hardware:
+    // task-clock
+    SW_TASK_CLOCK = 3;
+    // context-switches, cs
+    SW_CONTEXT_SWITCHES = 4;
+    // cpu-migrations, migrations
+    SW_CPU_MIGRATIONS = 5;
+    // minor-faults
+    SW_PAGE_FAULTS_MIN = 6;
+    // major-faults
+    SW_PAGE_FAULTS_MAJ = 7;
+    // alignment-faults
+    SW_ALIGNMENT_FAULTS = 8;
+    // emulation-faults
+    SW_EMULATION_FAULTS = 9;
+    // dummy
+    SW_DUMMY = 20;
+
+    // cpu-cycles, cycles
     HW_CPU_CYCLES = 10;
+    // instructions
     HW_INSTRUCTIONS = 11;
+    // cache-references
+    HW_CACHE_REFERENCES = 12;
+    // cache-misses
+    HW_CACHE_MISSES = 13;
+    // branch-instructions, branches
+    HW_BRANCH_INSTRUCTIONS = 14;
+    // branch-misses
+    HW_BRANCH_MISSES = 15;
+    // bus-cycles
+    HW_BUS_CYCLES = 16;
+    // stalled-cycles-frontend, idle-cycles-frontend
+    HW_STALLED_CYCLES_FRONTEND = 17;
+    // stalled-cycles-backend, idle-cycles-backend
+    HW_STALLED_CYCLES_BACKEND = 18;
+    // ref-cycles
+    HW_REF_CPU_CYCLES = 19;
   }
 
   message Tracepoint {
diff --git a/protos/perfetto/common/tracing_service_state.proto b/protos/perfetto/common/tracing_service_state.proto
index 1ef4c56..6d796bc 100644
--- a/protos/perfetto/common/tracing_service_state.proto
+++ b/protos/perfetto/common/tracing_service_state.proto
@@ -32,6 +32,10 @@
     // Typically matches the process name.
     optional string name = 2;
 
+    // Unix pid of the remote process. Supported only on Linux-based systems.
+    // Introduced in v24 / Android T.
+    optional int32 pid = 5;
+
     // Unix uid of the remote process.
     optional int32 uid = 3;
 
@@ -52,12 +56,53 @@
     optional int32 producer_id = 2;
   }
 
+  message TracingSession {
+    // The TracingSessionID.
+    optional uint64 id = 1;
+
+    // The Unix uid of the consumer that started the session.
+    // This is meaningful only if the caller is root. In all other cases only
+    // tracing sessions that match the caller UID will be displayed.
+    optional int32 consumer_uid = 2;
+
+    // Internal state of the tracing session.
+    // These strings are FYI only and subjected to change.
+    optional string state = 3;
+
+    // The unique_session_name as set in the trace config (might be empty).
+    optional string unique_session_name = 4;
+
+    // The number and size of each buffer.
+    repeated uint32 buffer_size_kb = 5;
+
+    // Duration, as specified in the TraceConfig.duration_ms.
+    optional uint32 duration_ms = 6;
+
+    // Number of data sources involved in the session.
+    optional uint32 num_data_sources = 7;
+
+    // Time when the session was started, in the CLOCK_REALTIME domain.
+    // Available only on Linux-based systems.
+    optional int64 start_realtime_ns = 8;
+  }
+
   // Lists all the producers connected.
   repeated Producer producers = 1;
 
   // Lists the data sources available.
   repeated DataSource data_sources = 2;
 
+  // Lists the tracing sessions active AND owned by a consumer that has the same
+  // UID of the caller (or all of them if the caller is root).
+  // Introduced in v24 / Android T.
+  repeated TracingSession tracing_sessions = 6;
+
+  // This is always set to true from v24 and beyond. This flag is only used to
+  // tell the difference between: (1) talking to a recent service which happens
+  // to have no tracing session active; (2) talking to an older version of the
+  // service which will never report any tracing session.
+  optional bool supports_tracing_sessions = 7;
+
   // Total number of tracing sessions.
   optional int32 num_sessions = 3;
 
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 69078d4..30195a4 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -204,6 +204,10 @@
     // Typically matches the process name.
     optional string name = 2;
 
+    // Unix pid of the remote process. Supported only on Linux-based systems.
+    // Introduced in v24 / Android T.
+    optional int32 pid = 5;
+
     // Unix uid of the remote process.
     optional int32 uid = 3;
 
@@ -224,12 +228,53 @@
     optional int32 producer_id = 2;
   }
 
+  message TracingSession {
+    // The TracingSessionID.
+    optional uint64 id = 1;
+
+    // The Unix uid of the consumer that started the session.
+    // This is meaningful only if the caller is root. In all other cases only
+    // tracing sessions that match the caller UID will be displayed.
+    optional int32 consumer_uid = 2;
+
+    // Internal state of the tracing session.
+    // These strings are FYI only and subjected to change.
+    optional string state = 3;
+
+    // The unique_session_name as set in the trace config (might be empty).
+    optional string unique_session_name = 4;
+
+    // The number and size of each buffer.
+    repeated uint32 buffer_size_kb = 5;
+
+    // Duration, as specified in the TraceConfig.duration_ms.
+    optional uint32 duration_ms = 6;
+
+    // Number of data sources involved in the session.
+    optional uint32 num_data_sources = 7;
+
+    // Time when the session was started, in the CLOCK_REALTIME domain.
+    // Available only on Linux-based systems.
+    optional int64 start_realtime_ns = 8;
+  }
+
   // Lists all the producers connected.
   repeated Producer producers = 1;
 
   // Lists the data sources available.
   repeated DataSource data_sources = 2;
 
+  // Lists the tracing sessions active AND owned by a consumer that has the same
+  // UID of the caller (or all of them if the caller is root).
+  // Introduced in v24 / Android T.
+  repeated TracingSession tracing_sessions = 6;
+
+  // This is always set to true from v24 and beyond. This flag is only used to
+  // tell the difference between: (1) talking to a recent service which happens
+  // to have no tracing session active; (2) talking to an older version of the
+  // service which will never report any tracing session.
+  optional bool supports_tracing_sessions = 7;
+
   // Total number of tracing sessions.
   optional int32 num_sessions = 3;
 
@@ -892,14 +937,56 @@
     optional string name = 10;
   }
 
+  // Builtin counter names from the uapi header. Commented with their perf tool
+  // aliases.
+  // TODO(rsavitski): consider generating enums for cache events (should be
+  // finite), and generally make this list as extensive as possible. Excluding
+  // things like dynamic PMUs since those don't fit into a static enum.
+  // Next id: 21
   enum Counter {
     UNKNOWN_COUNTER = 0;
-    // software:
+
+    // cpu-clock
     SW_CPU_CLOCK = 1;
+    // page-faults, faults
     SW_PAGE_FAULTS = 2;
-    // hardware:
+    // task-clock
+    SW_TASK_CLOCK = 3;
+    // context-switches, cs
+    SW_CONTEXT_SWITCHES = 4;
+    // cpu-migrations, migrations
+    SW_CPU_MIGRATIONS = 5;
+    // minor-faults
+    SW_PAGE_FAULTS_MIN = 6;
+    // major-faults
+    SW_PAGE_FAULTS_MAJ = 7;
+    // alignment-faults
+    SW_ALIGNMENT_FAULTS = 8;
+    // emulation-faults
+    SW_EMULATION_FAULTS = 9;
+    // dummy
+    SW_DUMMY = 20;
+
+    // cpu-cycles, cycles
     HW_CPU_CYCLES = 10;
+    // instructions
     HW_INSTRUCTIONS = 11;
+    // cache-references
+    HW_CACHE_REFERENCES = 12;
+    // cache-misses
+    HW_CACHE_MISSES = 13;
+    // branch-instructions, branches
+    HW_BRANCH_INSTRUCTIONS = 14;
+    // branch-misses
+    HW_BRANCH_MISSES = 15;
+    // bus-cycles
+    HW_BUS_CYCLES = 16;
+    // stalled-cycles-frontend, idle-cycles-frontend
+    HW_STALLED_CYCLES_FRONTEND = 17;
+    // stalled-cycles-backend, idle-cycles-backend
+    HW_STALLED_CYCLES_BACKEND = 18;
+    // ref-cycles
+    HW_REF_CPU_CYCLES = 19;
   }
 
   message Tracepoint {
@@ -1315,6 +1402,10 @@
   // This option can be used to record unchanging values.
   // Updates from frequency changes can come from ftrace/set_clock_rate.
   optional uint32 devfreq_period_ms = 7;
+
+  // Polls /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq every X ms.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  optional uint32 cpufreq_period_ms = 8;
 }
 
 // End of protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -1551,7 +1642,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 34.
+// Next id: 35.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -1962,6 +2053,54 @@
   // old field number for trace_filter
   reserved 32;
   optional TraceFilter trace_filter = 33;
+
+  // Android-only. Not for general use. If set, reports the trace to the
+  // Android framework. This field is read by perfetto_cmd, rather than the
+  // tracing service. This field must be set when passing the --upload flag to
+  // perfetto_cmd.
+  message AndroidReportConfig {
+    // In this message, either:
+    //  * |reporter_service_package| and |reporter_service_class| must be set.
+    //  * |skip_reporting| must be explicitly set to true.
+
+    optional string reporter_service_package = 1;
+    optional string reporter_service_class = 2;
+
+    // If true, then skips reporting the trace to Android framework.
+    //
+    // This flag is useful in testing (e.g. Perfetto-statsd integration tests)
+    // or when we explicitly don't want to report traces to the framework even
+    // when they usually would (e.g. configs deployed using statsd but only
+    // used for inclusion in bugreports using |bugreport_score|).
+    //
+    // The motivation for having this flag, instead of just not setting
+    // |framework_report_config|, is prevent accidents where
+    // |framework_report_config| is omitted by mistake.
+    optional bool skip_report = 3;
+
+    // If true, will direct the Android framework to read the data in trace
+    // file and pass it to the reporter class over a pipe instead of passing
+    // the file descriptor directly.
+    //
+    // This flag is needed because the Android test framework does not
+    // currently support priv-app helper apps (in terms of SELinux) and we
+    // really don't want to add an allow rule for untrusted_app to receive
+    // trace fds.
+    //
+    // Because of this, we instead will direct the framework to create a new
+    // pipe and pass this to the reporter process instead. As the pipe is
+    // created by the framework, we won't have any problems with SELinux
+    // (system_server is already allowed to pass pipe fds, even
+    // to untrusted apps).
+    //
+    // As the name suggests this option *MUST* only be used for testing.
+    // Note that the framework will reject (and drop) files which are too
+    // large both for simplicity and to be minimize the amount of data we
+    // pass to a non-priv app (note that the framework will still check
+    // manifest permissions even though SELinux permissions are worked around).
+    optional bool use_pipe_in_framework_for_testing = 4;
+  }
+  optional AndroidReportConfig android_report_config = 34;
 }
 
 // End of protos/perfetto/config/trace_config.proto
diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto
index 0986924..09d34ea 100644
--- a/protos/perfetto/config/sys_stats/sys_stats_config.proto
+++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -63,4 +63,8 @@
   // This option can be used to record unchanging values.
   // Updates from frequency changes can come from ftrace/set_clock_rate.
   optional uint32 devfreq_period_ms = 7;
+
+  // Polls /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq every X ms.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  optional uint32 cpufreq_period_ms = 8;
 }
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index e845f17..d58239f 100644
--- a/protos/perfetto/config/trace_config.proto
+++ b/protos/perfetto/config/trace_config.proto
@@ -26,7 +26,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 34.
+// Next id: 35.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -437,4 +437,52 @@
   // old field number for trace_filter
   reserved 32;
   optional TraceFilter trace_filter = 33;
+
+  // Android-only. Not for general use. If set, reports the trace to the
+  // Android framework. This field is read by perfetto_cmd, rather than the
+  // tracing service. This field must be set when passing the --upload flag to
+  // perfetto_cmd.
+  message AndroidReportConfig {
+    // In this message, either:
+    //  * |reporter_service_package| and |reporter_service_class| must be set.
+    //  * |skip_reporting| must be explicitly set to true.
+
+    optional string reporter_service_package = 1;
+    optional string reporter_service_class = 2;
+
+    // If true, then skips reporting the trace to Android framework.
+    //
+    // This flag is useful in testing (e.g. Perfetto-statsd integration tests)
+    // or when we explicitly don't want to report traces to the framework even
+    // when they usually would (e.g. configs deployed using statsd but only
+    // used for inclusion in bugreports using |bugreport_score|).
+    //
+    // The motivation for having this flag, instead of just not setting
+    // |framework_report_config|, is prevent accidents where
+    // |framework_report_config| is omitted by mistake.
+    optional bool skip_report = 3;
+
+    // If true, will direct the Android framework to read the data in trace
+    // file and pass it to the reporter class over a pipe instead of passing
+    // the file descriptor directly.
+    //
+    // This flag is needed because the Android test framework does not
+    // currently support priv-app helper apps (in terms of SELinux) and we
+    // really don't want to add an allow rule for untrusted_app to receive
+    // trace fds.
+    //
+    // Because of this, we instead will direct the framework to create a new
+    // pipe and pass this to the reporter process instead. As the pipe is
+    // created by the framework, we won't have any problems with SELinux
+    // (system_server is already allowed to pass pipe fds, even
+    // to untrusted apps).
+    //
+    // As the name suggests this option *MUST* only be used for testing.
+    // Note that the framework will reject (and drop) files which are too
+    // large both for simplicity and to be minimize the amount of data we
+    // pass to a non-priv app (note that the framework will still check
+    // manifest permissions even though SELinux permissions are worked around).
+    optional bool use_pipe_in_framework_for_testing = 4;
+  }
+  optional AndroidReportConfig android_report_config = 34;
 }
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index 4b4923b..251670b 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -20,6 +20,7 @@
     "source_set",
   ]
   sources = [
+    "android_trusty_workqueues.proto",
     "batt_metric.proto",
     "camera_metric.proto",
     "camera_unagg_metric.proto",
diff --git a/protos/perfetto/metrics/android/android_trusty_workqueues.proto b/protos/perfetto/metrics/android/android_trusty_workqueues.proto
new file mode 100644
index 0000000..b7a1d5a
--- /dev/null
+++ b/protos/perfetto/metrics/android/android_trusty_workqueues.proto
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Metric used to generate a simplified view of the Trusty kworker events.
+message AndroidTrustyWorkqueues {}
diff --git a/protos/perfetto/metrics/android/java_heap_histogram.proto b/protos/perfetto/metrics/android/java_heap_histogram.proto
index ce23669..7fa86ef 100644
--- a/protos/perfetto/metrics/android/java_heap_histogram.proto
+++ b/protos/perfetto/metrics/android/java_heap_histogram.proto
@@ -19,13 +19,18 @@
 import "protos/perfetto/metrics/android/process_metadata.proto";
 
 message JavaHeapHistogram {
-  // Next id: 5
+  // Next id: 9
   message TypeCount {
     optional string type_name = 1;
     optional string category = 4;
 
     optional uint32 obj_count = 2;
     optional uint32 reachable_obj_count = 3;
+
+    optional uint32 size_kb = 5;
+    optional uint32 reachable_size_kb = 6;
+    optional uint32 native_size_kb = 7;
+    optional uint32 reachable_native_size_kb = 8;
   }
 
   message Sample {
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 1731c8d..8421007 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -52,6 +52,7 @@
 import "protos/perfetto/metrics/android/task_names.proto";
 import "protos/perfetto/metrics/android/thread_time_in_state_metric.proto";
 import "protos/perfetto/metrics/android/trace_quality.proto";
+import "protos/perfetto/metrics/android/android_trusty_workqueues.proto";
 import "protos/perfetto/metrics/android/unsymbolized_frames.proto";
 
 // Trace processor metadata
@@ -98,7 +99,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 44
+// Next id: 45
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -214,6 +215,9 @@
   // Metrics for IRQ runtime.
   optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
 
+  // Metrics for the Trusty team.
+  optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 4c4a0bc..03f6690 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -13,6 +13,13 @@
 
 option go_package = "github.com/google/perfetto/perfetto_proto";
 
+// Begin of protos/perfetto/metrics/android/android_trusty_workqueues.proto
+
+// Metric used to generate a simplified view of the Trusty kworker events.
+message AndroidTrustyWorkqueues {}
+
+// End of protos/perfetto/metrics/android/android_trusty_workqueues.proto
+
 // Begin of protos/perfetto/metrics/android/batt_metric.proto
 
 message AndroidBatteryMetric {
@@ -636,13 +643,18 @@
 // Begin of protos/perfetto/metrics/android/java_heap_histogram.proto
 
 message JavaHeapHistogram {
-  // Next id: 5
+  // Next id: 9
   message TypeCount {
     optional string type_name = 1;
     optional string category = 4;
 
     optional uint32 obj_count = 2;
     optional uint32 reachable_obj_count = 3;
+
+    optional uint32 size_kb = 5;
+    optional uint32 reachable_size_kb = 6;
+    optional uint32 native_size_kb = 7;
+    optional uint32 reachable_native_size_kb = 8;
   }
 
   message Sample {
@@ -1459,7 +1471,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 44
+// Next id: 45
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -1575,6 +1587,9 @@
   // Metrics for IRQ runtime.
   optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
 
+  // Metrics for the Trusty team.
+  optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 55655f8..ae4e3d0 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -54,10 +54,12 @@
   "scm.proto",
   "sde.proto",
   "signal.proto",
+  "sock.proto",
   "sync.proto",
   "synthetic.proto",
   "systrace.proto",
   "task.proto",
+  "tcp.proto",
   "thermal.proto",
   "vmscan.proto",
   "workqueue.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index dd718ef..5b42169 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -54,10 +54,12 @@
 import "protos/perfetto/trace/ftrace/scm.proto";
 import "protos/perfetto/trace/ftrace/sde.proto";
 import "protos/perfetto/trace/ftrace/signal.proto";
+import "protos/perfetto/trace/ftrace/sock.proto";
 import "protos/perfetto/trace/ftrace/sync.proto";
 import "protos/perfetto/trace/ftrace/synthetic.proto";
 import "protos/perfetto/trace/ftrace/systrace.proto";
 import "protos/perfetto/trace/ftrace/task.proto";
+import "protos/perfetto/trace/ftrace/tcp.proto";
 import "protos/perfetto/trace/ftrace/thermal.proto";
 import "protos/perfetto/trace/ftrace/vmscan.proto";
 import "protos/perfetto/trace/ftrace/workqueue.proto";
@@ -452,5 +454,7 @@
     RssStatThrottledFtraceEvent rss_stat_throttled = 359;
     NetifReceiveSkbFtraceEvent netif_receive_skb = 360;
     NetDevXmitFtraceEvent net_dev_xmit = 361;
+    InetSockSetStateFtraceEvent inet_sock_set_state = 362;
+    TcpRetransmitSkbFtraceEvent tcp_retransmit_skb = 363;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/sock.proto b/protos/perfetto/trace/ftrace/sock.proto
new file mode 100644
index 0000000..9607117
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/sock.proto
@@ -0,0 +1,18 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message InetSockSetStateFtraceEvent {
+  optional uint32 daddr = 1;
+  optional uint32 dport = 2;
+  optional uint32 family = 3;
+  optional int32 newstate = 4;
+  optional int32 oldstate = 5;
+  optional uint32 protocol = 6;
+  optional uint32 saddr = 7;
+  optional uint64 skaddr = 8;
+  optional uint32 sport = 9;
+}
diff --git a/protos/perfetto/trace/ftrace/tcp.proto b/protos/perfetto/trace/ftrace/tcp.proto
new file mode 100644
index 0000000..fb854c3
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/tcp.proto
@@ -0,0 +1,16 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message TcpRetransmitSkbFtraceEvent {
+  optional uint32 daddr = 1;
+  optional uint32 dport = 2;
+  optional uint32 saddr = 3;
+  optional uint64 skaddr = 4;
+  optional uint64 skbaddr = 5;
+  optional uint32 sport = 6;
+  optional int32 state = 7;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index bfee00c..8e6b639 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -204,6 +204,10 @@
     // Typically matches the process name.
     optional string name = 2;
 
+    // Unix pid of the remote process. Supported only on Linux-based systems.
+    // Introduced in v24 / Android T.
+    optional int32 pid = 5;
+
     // Unix uid of the remote process.
     optional int32 uid = 3;
 
@@ -224,12 +228,53 @@
     optional int32 producer_id = 2;
   }
 
+  message TracingSession {
+    // The TracingSessionID.
+    optional uint64 id = 1;
+
+    // The Unix uid of the consumer that started the session.
+    // This is meaningful only if the caller is root. In all other cases only
+    // tracing sessions that match the caller UID will be displayed.
+    optional int32 consumer_uid = 2;
+
+    // Internal state of the tracing session.
+    // These strings are FYI only and subjected to change.
+    optional string state = 3;
+
+    // The unique_session_name as set in the trace config (might be empty).
+    optional string unique_session_name = 4;
+
+    // The number and size of each buffer.
+    repeated uint32 buffer_size_kb = 5;
+
+    // Duration, as specified in the TraceConfig.duration_ms.
+    optional uint32 duration_ms = 6;
+
+    // Number of data sources involved in the session.
+    optional uint32 num_data_sources = 7;
+
+    // Time when the session was started, in the CLOCK_REALTIME domain.
+    // Available only on Linux-based systems.
+    optional int64 start_realtime_ns = 8;
+  }
+
   // Lists all the producers connected.
   repeated Producer producers = 1;
 
   // Lists the data sources available.
   repeated DataSource data_sources = 2;
 
+  // Lists the tracing sessions active AND owned by a consumer that has the same
+  // UID of the caller (or all of them if the caller is root).
+  // Introduced in v24 / Android T.
+  repeated TracingSession tracing_sessions = 6;
+
+  // This is always set to true from v24 and beyond. This flag is only used to
+  // tell the difference between: (1) talking to a recent service which happens
+  // to have no tracing session active; (2) talking to an older version of the
+  // service which will never report any tracing session.
+  optional bool supports_tracing_sessions = 7;
+
   // Total number of tracing sessions.
   optional int32 num_sessions = 3;
 
@@ -892,14 +937,56 @@
     optional string name = 10;
   }
 
+  // Builtin counter names from the uapi header. Commented with their perf tool
+  // aliases.
+  // TODO(rsavitski): consider generating enums for cache events (should be
+  // finite), and generally make this list as extensive as possible. Excluding
+  // things like dynamic PMUs since those don't fit into a static enum.
+  // Next id: 21
   enum Counter {
     UNKNOWN_COUNTER = 0;
-    // software:
+
+    // cpu-clock
     SW_CPU_CLOCK = 1;
+    // page-faults, faults
     SW_PAGE_FAULTS = 2;
-    // hardware:
+    // task-clock
+    SW_TASK_CLOCK = 3;
+    // context-switches, cs
+    SW_CONTEXT_SWITCHES = 4;
+    // cpu-migrations, migrations
+    SW_CPU_MIGRATIONS = 5;
+    // minor-faults
+    SW_PAGE_FAULTS_MIN = 6;
+    // major-faults
+    SW_PAGE_FAULTS_MAJ = 7;
+    // alignment-faults
+    SW_ALIGNMENT_FAULTS = 8;
+    // emulation-faults
+    SW_EMULATION_FAULTS = 9;
+    // dummy
+    SW_DUMMY = 20;
+
+    // cpu-cycles, cycles
     HW_CPU_CYCLES = 10;
+    // instructions
     HW_INSTRUCTIONS = 11;
+    // cache-references
+    HW_CACHE_REFERENCES = 12;
+    // cache-misses
+    HW_CACHE_MISSES = 13;
+    // branch-instructions, branches
+    HW_BRANCH_INSTRUCTIONS = 14;
+    // branch-misses
+    HW_BRANCH_MISSES = 15;
+    // bus-cycles
+    HW_BUS_CYCLES = 16;
+    // stalled-cycles-frontend, idle-cycles-frontend
+    HW_STALLED_CYCLES_FRONTEND = 17;
+    // stalled-cycles-backend, idle-cycles-backend
+    HW_STALLED_CYCLES_BACKEND = 18;
+    // ref-cycles
+    HW_REF_CPU_CYCLES = 19;
   }
 
   message Tracepoint {
@@ -1315,6 +1402,10 @@
   // This option can be used to record unchanging values.
   // Updates from frequency changes can come from ftrace/set_clock_rate.
   optional uint32 devfreq_period_ms = 7;
+
+  // Polls /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq every X ms.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  optional uint32 cpufreq_period_ms = 8;
 }
 
 // End of protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -1551,7 +1642,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 34.
+// Next id: 35.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -1962,6 +2053,54 @@
   // old field number for trace_filter
   reserved 32;
   optional TraceFilter trace_filter = 33;
+
+  // Android-only. Not for general use. If set, reports the trace to the
+  // Android framework. This field is read by perfetto_cmd, rather than the
+  // tracing service. This field must be set when passing the --upload flag to
+  // perfetto_cmd.
+  message AndroidReportConfig {
+    // In this message, either:
+    //  * |reporter_service_package| and |reporter_service_class| must be set.
+    //  * |skip_reporting| must be explicitly set to true.
+
+    optional string reporter_service_package = 1;
+    optional string reporter_service_class = 2;
+
+    // If true, then skips reporting the trace to Android framework.
+    //
+    // This flag is useful in testing (e.g. Perfetto-statsd integration tests)
+    // or when we explicitly don't want to report traces to the framework even
+    // when they usually would (e.g. configs deployed using statsd but only
+    // used for inclusion in bugreports using |bugreport_score|).
+    //
+    // The motivation for having this flag, instead of just not setting
+    // |framework_report_config|, is prevent accidents where
+    // |framework_report_config| is omitted by mistake.
+    optional bool skip_report = 3;
+
+    // If true, will direct the Android framework to read the data in trace
+    // file and pass it to the reporter class over a pipe instead of passing
+    // the file descriptor directly.
+    //
+    // This flag is needed because the Android test framework does not
+    // currently support priv-app helper apps (in terms of SELinux) and we
+    // really don't want to add an allow rule for untrusted_app to receive
+    // trace fds.
+    //
+    // Because of this, we instead will direct the framework to create a new
+    // pipe and pass this to the reporter process instead. As the pipe is
+    // created by the framework, we won't have any problems with SELinux
+    // (system_server is already allowed to pass pipe fds, even
+    // to untrusted apps).
+    //
+    // As the name suggests this option *MUST* only be used for testing.
+    // Note that the framework will reject (and drop) files which are too
+    // large both for simplicity and to be minimize the amount of data we
+    // pass to a non-priv app (note that the framework will still check
+    // manifest permissions even though SELinux permissions are worked around).
+    optional bool use_pipe_in_framework_for_testing = 4;
+  }
+  optional AndroidReportConfig android_report_config = 34;
 }
 
 // End of protos/perfetto/config/trace_config.proto
@@ -5258,6 +5397,22 @@
 
 // End of protos/perfetto/trace/ftrace/signal.proto
 
+// Begin of protos/perfetto/trace/ftrace/sock.proto
+
+message InetSockSetStateFtraceEvent {
+  optional uint32 daddr = 1;
+  optional uint32 dport = 2;
+  optional uint32 family = 3;
+  optional int32 newstate = 4;
+  optional int32 oldstate = 5;
+  optional uint32 protocol = 6;
+  optional uint32 saddr = 7;
+  optional uint64 skaddr = 8;
+  optional uint32 sport = 9;
+}
+
+// End of protos/perfetto/trace/ftrace/sock.proto
+
 // Begin of protos/perfetto/trace/ftrace/sync.proto
 
 message SyncPtFtraceEvent {
@@ -5315,6 +5470,20 @@
 
 // End of protos/perfetto/trace/ftrace/task.proto
 
+// Begin of protos/perfetto/trace/ftrace/tcp.proto
+
+message TcpRetransmitSkbFtraceEvent {
+  optional uint32 daddr = 1;
+  optional uint32 dport = 2;
+  optional uint32 saddr = 3;
+  optional uint64 skaddr = 4;
+  optional uint64 skbaddr = 5;
+  optional uint32 sport = 6;
+  optional int32 state = 7;
+}
+
+// End of protos/perfetto/trace/ftrace/tcp.proto
+
 // Begin of protos/perfetto/trace/ftrace/thermal.proto
 
 message ThermalTemperatureFtraceEvent {
@@ -5761,6 +5930,8 @@
     RssStatThrottledFtraceEvent rss_stat_throttled = 359;
     NetifReceiveSkbFtraceEvent netif_receive_skb = 360;
     NetDevXmitFtraceEvent net_dev_xmit = 361;
+    InetSockSetStateFtraceEvent inet_sock_set_state = 362;
+    TcpRetransmitSkbFtraceEvent tcp_retransmit_skb = 363;
   }
 }
 
@@ -8660,6 +8831,11 @@
 
   // One entry per device.
   repeated DevfreqValue devfreq = 10;
+
+  // Cpu current frequency from
+  // /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq in kHz.
+  // One entry per cpu. Report 0 for offline cpu
+  repeated uint32 cpufreq_khz = 11;
 }
 
 // End of protos/perfetto/trace/sys_stats/sys_stats.proto
diff --git a/protos/perfetto/trace/sys_stats/sys_stats.proto b/protos/perfetto/trace/sys_stats/sys_stats.proto
index 30edac0..5bd421f 100644
--- a/protos/perfetto/trace/sys_stats/sys_stats.proto
+++ b/protos/perfetto/trace/sys_stats/sys_stats.proto
@@ -103,4 +103,9 @@
 
   // One entry per device.
   repeated DevfreqValue devfreq = 10;
+
+  // Cpu current frequency from
+  // /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq in kHz.
+  // One entry per cpu. Report 0 for offline cpu
+  repeated uint32 cpufreq_khz = 11;
 }
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index 02a230f..2d19871 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -136,16 +136,16 @@
 message QueryArgs {
   optional string sql_query = 1;
 
-  // Wall time when the query was queued. Used only for query stats.
-  optional uint64 time_queued_ns = 2;
+  // Was time_queued_ns
+  reserved 2;
 }
 
 // Input for the /raw_query endpoint.
 message RawQueryArgs {
   optional string sql_query = 1;
 
-  // Wall time when the query was queued. Used only for query stats.
-  optional uint64 time_queued_ns = 2;
+  // Was time_queued_ns
+  reserved 2;
 }
 
 // Output for the /raw_query endpoint.
@@ -231,6 +231,12 @@
     reserved 7;
   }
   repeated CellsBatch batch = 3;
+
+  // The number of statements in the provided SQL.
+  optional uint32 statement_count = 4;
+
+  // The number of statements which produced output rows in the provided SQL.
+  optional uint32 statement_with_output_count = 5;
 }
 
 // Input for the /status endpoint.
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 7c82b50..6c95b59 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/metrics.descriptor.sha1 b/python/perfetto/trace_processor/metrics.descriptor.sha1
index fb38d4e..e77358d 100644
--- a/python/perfetto/trace_processor/metrics.descriptor.sha1
+++ b/python/perfetto/trace_processor/metrics.descriptor.sha1
@@ -2,5 +2,5 @@
 // SHA1(tools/gen_binary_descriptors)
 // c4a38769074f8a8c2ffbf514b267919b5f2d47df
 // SHA1(protos/perfetto/metrics/metrics.proto)
-// 3b323de4f6dc7cbf8d725751627d3eb2b0fce8ce
+// 856f1a9caaf87f3c1d47eb5e64bec931fa713434
   
\ No newline at end of file
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index c7b0668..d8fe39e 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
index 92ce69e..3d9e075 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor.sha1
+++ b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
@@ -2,5 +2,5 @@
 // SHA1(tools/gen_binary_descriptors)
 // c4a38769074f8a8c2ffbf514b267919b5f2d47df
 // SHA1(protos/perfetto/trace_processor/trace_processor.proto)
-// e303e1fc877a9fe4f8dd8413c03266ee68dfd3aa
+// ea89764637957b3b71978972257aba2f0fddbfc9
   
\ No newline at end of file
diff --git a/src/android_internal/health_hal.cc b/src/android_internal/health_hal.cc
index d3e0de0..f21af80 100644
--- a/src/android_internal/health_hal.cc
+++ b/src/android_internal/health_hal.cc
@@ -16,8 +16,8 @@
 
 #include "src/android_internal/health_hal.h"
 
-#include <android/binder_manager.h>
 #include <aidl/android/hardware/health/IHealth.h>
+#include <android/binder_manager.h>
 #include <android/hardware/health/2.0/IHealth.h>
 #include <healthhalutils/HealthHalUtils.h>
 
diff --git a/src/android_internal/tracing_service_proxy.cc b/src/android_internal/tracing_service_proxy.cc
index e297b75..a886689 100644
--- a/src/android_internal/tracing_service_proxy.cc
+++ b/src/android_internal/tracing_service_proxy.cc
@@ -17,21 +17,29 @@
 #include "src/android_internal/tracing_service_proxy.h"
 
 #include <android/tracing/ITracingServiceProxy.h>
+#include <android/tracing/TraceReportParams.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
+#include <binder/ParcelFileDescriptor.h>
 #include <binder/Status.h>
+#include <utils/String16.h>
 
 namespace perfetto {
 namespace android_internal {
 
 using android::sp;
 using android::binder::Status;
+using android::os::ParcelFileDescriptor;
 using android::tracing::ITracingServiceProxy;
+using android::tracing::TraceReportParams;
+
+namespace {
+static constexpr char kServiceName[] = "tracing.proxy";
+}
 
 bool NotifyTraceSessionEnded(bool session_stolen) {
-  sp<ITracingServiceProxy> service = android::interface_cast<ITracingServiceProxy>(
-      android::defaultServiceManager()->getService(android::String16("tracing.proxy")));
-
+  auto service = android::waitForService<ITracingServiceProxy>(
+      android::String16(kServiceName));
   if (service == nullptr) {
     return false;
   }
@@ -40,5 +48,38 @@
   return s.isOk();
 }
 
-} // namespace android_internal
-} // namespace perfetto
+bool ReportTrace(const char* reporter_package_name,
+                 const char* reporter_class_name,
+                 int owned_trace_fd,
+                 int64_t uuid_lsb,
+                 int64_t uuid_msb,
+                 bool use_pipe_in_framework_for_testing) {
+  // Keep this first so we recapture the raw fd in a RAII type as soon as
+  // possible.
+  android::base::unique_fd fd(owned_trace_fd);
+
+  auto service = android::waitForService<ITracingServiceProxy>(
+      android::String16(kServiceName));
+  if (service == nullptr) {
+    return false;
+  }
+
+  TraceReportParams params{};
+  params.reporterPackageName = android::String16(reporter_package_name);
+  params.reporterClassName = android::String16(reporter_class_name);
+  params.fd = ParcelFileDescriptor(std::move(fd));
+  params.uuidLsb = uuid_lsb;
+  params.uuidMsb = uuid_msb;
+  params.usePipeForTesting = use_pipe_in_framework_for_testing;
+
+  Status s = service->reportTrace(std::move(params));
+  if (!s.isOk()) {
+    __android_log_print(ANDROID_LOG_ERROR, "perfetto", "reportTrace failed: %s",
+                        s.toString8().c_str());
+  }
+
+  return s.isOk();
+}
+
+}  // namespace android_internal
+}  // namespace perfetto
diff --git a/src/android_internal/tracing_service_proxy.h b/src/android_internal/tracing_service_proxy.h
index 0b045e2..5c45934 100644
--- a/src/android_internal/tracing_service_proxy.h
+++ b/src/android_internal/tracing_service_proxy.h
@@ -17,6 +17,8 @@
 #ifndef SRC_ANDROID_INTERNAL_TRACING_SERVICE_PROXY_H_
 #define SRC_ANDROID_INTERNAL_TRACING_SERVICE_PROXY_H_
 
+#include <stdint.h>
+
 namespace perfetto {
 namespace android_internal {
 
@@ -25,6 +27,14 @@
 bool __attribute__((visibility("default")))
 NotifyTraceSessionEnded(bool session_stolen);
 
+bool __attribute__((visibility("default")))
+ReportTrace(const char* reporter_package_name,
+            const char* reporter_class_name,
+            int owned_trace_fd,
+            int64_t uuid_lsb,
+            int64_t uuid_msb,
+            bool use_pipe_in_framework_for_testing);
+
 } // extern "C"
 
 } // namespace android_internal
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index 0a857b9..2f48055 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -82,19 +82,17 @@
 
   // Deprecated as "success" is misleading; it simply means we were
   // able to communicate with incidentd. Will be removed once
-  // incidentd is properly instrumented.
+  // incidentd is no longer used.
   kUploadIncidentSuccess = 9,
 
-  // Deprecated as has the potential to be too spammy. Will be
-  // replaced with a whole new atom proto which uses a count metric
-  // instead of the event metric used for this proto.
-  kTriggerBegin = 12,
-  kTriggerSuccess = 13,
-  kTriggerFailure = 14,
+  // Contained trigger begin/success/failure. Replaced by
+  // |PerfettoTriggerAtom| to allow aggregation using a count metric
+  // and reduce spam.
+  // reserved 12, 13, 14;
 
-  // Deprecated as too coarse grained to be useful. Will be replaced
-  // with better broken down atoms as we do with traced.
-  kHitGuardrails = 15,
+  // Contained that a guardrail in perfetto_cmd was hit. Replaced with
+  // kCmd* guardrails.
+  // reserved 15;
 
   // Contained status of Dropbox uploads. Removed as Perfetto no
   // longer supports uploading traces using Dropbox.
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 363a0d6..b4da80c 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -94,9 +94,9 @@
   const bool have_sse4_2 = ecx & (1u << 20);
   const bool have_avx =
       // Does the OS save/restore XMM and YMM state?
-      ((GetXCR0EAX() & xcr0_avx_mask) == xcr0_avx_mask) &&
       (ecx & (1u << 27)) &&  // OS support XGETBV.
-      (ecx & (1u << 28));    // AVX supported in hardware
+      (ecx & (1u << 28)) &&  // AVX supported in hardware
+      ((GetXCR0EAX() & xcr0_avx_mask) == xcr0_avx_mask);
 
   if (!have_sse4_2 || !have_popcnt || !have_avx) {
     fprintf(
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index dc0d61a..ed57c99 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -568,29 +568,66 @@
     uuid_ = uuid.ToString();
   }
 
-  if (!trace_config_->incident_report_config().destination_package().empty() &&
-      !upload_flag_) {
+  bool has_incidentd_package =
+      !trace_config_->incident_report_config().destination_package().empty();
+  if (has_incidentd_package && !upload_flag_) {
     PERFETTO_ELOG(
         "Unexpected IncidentReportConfig without --dropbox / --upload.");
     return 1;
   }
 
+  bool has_android_reporter_package = !trace_config_->android_report_config()
+                                           .reporter_service_package()
+                                           .empty();
+  if (has_android_reporter_package && !upload_flag_) {
+    PERFETTO_ELOG(
+        "Unexpected AndroidReportConfig without --dropbox / --upload.");
+    return 1;
+  }
+
+  if (has_incidentd_package && has_android_reporter_package) {
+    PERFETTO_ELOG(
+        "Only one of IncidentReportConfig and AndroidReportConfig "
+        "allowed in the same config.");
+    return 1;
+  }
+
+  // If the upload flag is set, we can only be doing one of three things:
+  // 1. Reporting to either incidentd or Android framework.
+  // 2. Skipping incidentd/Android report because it was explicitly
+  //    specified in the config.
+  // 3. Activating triggers.
+  bool incidentd_valid =
+      has_incidentd_package ||
+      trace_config_->incident_report_config().skip_incidentd();
+  bool android_report_valid =
+      has_android_reporter_package ||
+      trace_config_->android_report_config().skip_report();
+  bool has_triggers = !trace_config_->activate_triggers().empty();
+  if (upload_flag_ && !incidentd_valid && !android_report_valid &&
+      !has_triggers) {
+    PERFETTO_ELOG(
+        "One of IncidentReportConfig, AndroidReportConfig or activate_triggers "
+        "must be specified with --dropbox / --upload.");
+    return 1;
+  }
+
   // Only save to incidentd if:
-  // 1) --upload is set
+  // 1) |destination_package| is set
   // 2) |skip_incidentd| is absent or false.
   // 3) we are not simply activating triggers.
   save_to_incidentd_ =
-      upload_flag_ &&
+      has_incidentd_package &&
       !trace_config_->incident_report_config().skip_incidentd() &&
-      trace_config_->activate_triggers().empty();
+      !has_triggers;
 
-  if (save_to_incidentd_ &&
-      trace_config_->incident_report_config().destination_package().empty()) {
-    PERFETTO_ELOG(
-        "Missing IncidentReportConfig.destination_package with --dropbox / "
-        "--upload.");
-    return 1;
-  }
+  // Only report to the Andorid framework if:
+  // 1) |reporter_service_package| is set
+  // 2) |skip_report| is absent or false.
+  // 3) we are not simply activating triggers.
+  report_to_android_framework_ =
+      has_android_reporter_package &&
+      !trace_config_->android_report_config().skip_report() && !has_triggers;
 
   // Respect the wishes of the config with respect to statsd logging or fall
   // back on the presence of the --upload flag if not set.
@@ -640,7 +677,7 @@
   // In this case we don't intend to send any trace config to the service,
   // rather use that as a signal to the cmdline client to connect as a producer
   // and activate triggers.
-  if (!trace_config_->activate_triggers().empty()) {
+  if (has_triggers) {
     for (const auto& trigger : trace_config_->activate_triggers()) {
       triggers_to_activate_.push_back(trigger);
     }
@@ -698,13 +735,21 @@
     }
   }
 
-  if (save_to_incidentd_ && !ignore_guardrails_ &&
-      (trace_config_->duration_ms() == 0 &&
-       trace_config_->trigger_config().trigger_timeout_ms() == 0)) {
+  bool will_trace_indefinitely =
+      trace_config_->duration_ms() == 0 &&
+      trace_config_->trigger_config().trigger_timeout_ms() == 0;
+  if (will_trace_indefinitely && save_to_incidentd_ && !ignore_guardrails_) {
     PERFETTO_ELOG("Can't trace indefinitely when tracing to Incidentd.");
     return 1;
   }
 
+  if (will_trace_indefinitely && report_to_android_framework_ &&
+      !ignore_guardrails_) {
+    PERFETTO_ELOG(
+        "Can't trace indefinitely when reporting to Android framework.");
+    return 1;
+  }
+
   if (background_) {
     if (background_wait_) {
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
@@ -786,7 +831,6 @@
   // connect as a consumer or run the trace. So bail out after processing all
   // the options.
   if (!triggers_to_activate_.empty()) {
-    LogUploadEvent(PerfettoStatsdAtom::kTriggerBegin);
     LogTriggerEvents(PerfettoTriggerAtom::kCmdTrigger, triggers_to_activate_);
 
     bool finished_with_success = false;
@@ -798,10 +842,7 @@
         },
         &triggers_to_activate_);
     task_runner_.Run();
-    if (finished_with_success) {
-      LogUploadEvent(PerfettoStatsdAtom::kTriggerSuccess);
-    } else {
-      LogUploadEvent(PerfettoStatsdAtom::kTriggerFailure);
+    if (!finished_with_success) {
       LogTriggerEvents(PerfettoTriggerAtom::kCmdTriggerFail,
                        triggers_to_activate_);
     }
@@ -817,7 +858,7 @@
 
   RateLimiter::Args args{};
   args.is_user_build = IsUserBuild();
-  args.is_uploading = save_to_incidentd_;
+  args.is_uploading = save_to_incidentd_ || report_to_android_framework_;
   args.current_time = base::GetWallTimeS();
   args.ignore_guardrails = ignore_guardrails_;
   args.allow_user_build_tracing = trace_config_->allow_user_build_tracing();
@@ -846,8 +887,6 @@
 
   auto err_atom = ConvertRateLimiterResponseToAtom(limiter_->ShouldTrace(args));
   if (err_atom) {
-    // TODO(lalitm): remove this once we're ready on server side.
-    LogUploadEvent(PerfettoStatsdAtom::kHitGuardrails);
     LogUploadEvent(err_atom.value());
     return 1;
   }
@@ -1027,7 +1066,11 @@
 
   if (save_to_incidentd_) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-    SaveTraceIntoDropboxAndIncidentOrCrash();
+    SaveTraceIntoIncidentOrCrash();
+#endif
+  } else if (report_to_android_framework_) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+    ReportTraceToAndroidFrameworkOrCrash();
 #endif
   } else {
     trace_out_stream_.reset();
@@ -1137,43 +1180,86 @@
     return;
   }
 
-  printf("Not meant for machine consumption. Use --query-raw for scripts.\n");
+  printf(
+      "\x1b[31mNot meant for machine consumption. Use --query-raw for "
+      "scripts.\x1b[0m\n\n");
+  printf(
+      "Service: %s\n"
+      "Tracing sessions: %d (started: %d)\n",
+      svc_state.tracing_service_version().c_str(), svc_state.num_sessions(),
+      svc_state.num_sessions_started());
 
+  printf(R"(
+
+PRODUCER PROCESSES CONNECTED:
+
+ID         PID        UID        NAME                             SDK
+==         ===        ===        ====                             ===
+)");
   for (const auto& producer : svc_state.producers()) {
-    printf("producers: {\n");
-    printf("  id: %d\n", producer.id());
-    printf("  name: \"%s\" \n", producer.name().c_str());
-    printf("  uid: %d \n", producer.uid());
-    printf("  sdk_version: \"%s\" \n", producer.sdk_version().c_str());
-    printf("}\n");
+    printf("%-10d %-10d %-10d %-32s %s\n", producer.id(), producer.pid(),
+           producer.uid(), producer.name().c_str(),
+           producer.sdk_version().c_str());
   }
 
+  printf(R"(
+
+DATA SOURCES REGISTERED:
+
+NAME                                     PRODUCER                     DETAILS
+===                                      ========                     ========
+)");
   for (const auto& ds : svc_state.data_sources()) {
-    printf("data_sources: {\n");
-    printf("  producer_id: %d\n", ds.producer_id());
-    printf("  descriptor: {\n");
-    printf("    name: \"%s\"\n", ds.ds_descriptor().name().c_str());
+    char producer_id_and_name[128]{};
+    const int ds_producer_id = ds.producer_id();
+    for (const auto& producer : svc_state.producers()) {
+      if (producer.id() == ds_producer_id) {
+        base::SprintfTrunc(producer_id_and_name, sizeof(producer_id_and_name),
+                           "%s (%d)", producer.name().c_str(), ds_producer_id);
+        break;
+      }
+    }
+
+    printf("%-40s %-40s ", ds.ds_descriptor().name().c_str(),
+           producer_id_and_name);
+    // Print the category names for clients using the track event SDK.
     if (!ds.ds_descriptor().track_event_descriptor_raw().empty()) {
       auto raw = ds.ds_descriptor().track_event_descriptor_raw();
       perfetto::protos::gen::TrackEventDescriptor desc;
       if (desc.ParseFromArray(raw.data(), raw.size())) {
-        printf("    track_event_descriptor: {\n");
         for (const auto& cat : desc.available_categories()) {
-          printf("      available_categories: {\n");
-          printf("        name: \"%s\"\n", cat.name().c_str());
-          printf("        description: \"%s\"\n", cat.description().c_str());
-          printf("      }\n");
+          printf("%s,", cat.name().c_str());
         }
-        printf("    }\n");
       }
     }
-    printf("  }\n");
-    printf("}\n");
-  }
-  printf("tracing_service_version: \"%s\"\n",
-         svc_state.tracing_service_version().c_str());
-  printf("num_sessions: %d\n", svc_state.num_sessions());
-  printf("num_sessions_started: %d\n", svc_state.num_sessions_started());
+    printf("\n");
+  }  // for data_sources()
+
+  if (svc_state.supports_tracing_sessions()) {
+    printf(R"(
+
+TRACING SESSIONS:
+
+ID      UID     STATE      NAME         BUF (#) KB   DUR (s)   #DS  STARTED
+===     ===     =====      ====         ==========   =======   ===  =======
+)");
+    for (const auto& sess : svc_state.tracing_sessions()) {
+      uint32_t buf_tot_kb = 0;
+      for (uint32_t kb : sess.buffer_size_kb())
+        buf_tot_kb += kb;
+      int sec =
+          static_cast<int>((sess.start_realtime_ns() / 1000000000) % 86400);
+      int h = sec / 3600;
+      int m = (sec - (h * 3600)) / 60;
+      int s = (sec - h * 3600 - m * 60);
+      printf("%-7" PRIu64
+             " %-7d %-10s %-12s (%d) %-8u %-9u %-4u %02d:%02d:%02d\n",
+             sess.id(), sess.consumer_uid(), sess.state().c_str(),
+             sess.unique_session_name().c_str(), sess.buffer_size_kb_size(),
+             buf_tot_kb, sess.duration_ms() / 1000, sess.num_data_sources(), h,
+             m, s);
+    }  // for tracing_sessions()
+  }    // if (supports_tracing_sessions)
 }
 
 void PerfettoCmd::OnObservableEvents(
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 9f62f51..28c7733 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -115,8 +115,9 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   static base::ScopedFile CreateUnlinkedTmpFile();
-  void SaveTraceIntoDropboxAndIncidentOrCrash();
+  void SaveTraceIntoIncidentOrCrash();
   void SaveOutputToIncidentTraceOrCrash();
+  void ReportTraceToAndroidFrameworkOrCrash();
 #endif
   void LogUploadEvent(PerfettoStatsdAtom atom);
   void LogTriggerEvents(PerfettoTriggerAtom atom,
@@ -135,6 +136,7 @@
   base::EventFd ctrl_c_evt_;
   base::Pipe background_wait_pipe_;
   bool save_to_incidentd_ = false;
+  bool report_to_android_framework_ = false;
   bool statsd_logging_ = false;
   bool update_guardrail_state_ = false;
   uint64_t bytes_written_ = 0;
diff --git a/src/perfetto_cmd/perfetto_cmd_android.cc b/src/perfetto_cmd/perfetto_cmd_android.cc
index c7953b1..6a2d949 100644
--- a/src/perfetto_cmd/perfetto_cmd_android.cc
+++ b/src/perfetto_cmd/perfetto_cmd_android.cc
@@ -23,10 +23,12 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "src/android_internal/incident_service.h"
 #include "src/android_internal/lazy_library_loader.h"
+#include "src/android_internal/tracing_service_proxy.h"
 
 namespace perfetto {
 namespace {
@@ -35,10 +37,12 @@
 
 }  // namespace
 
-void PerfettoCmd::SaveTraceIntoDropboxAndIncidentOrCrash() {
+void PerfettoCmd::SaveTraceIntoIncidentOrCrash() {
   PERFETTO_CHECK(save_to_incidentd_);
-  PERFETTO_CHECK(
-      !trace_config_->incident_report_config().destination_package().empty());
+
+  const auto& cfg = trace_config_->incident_report_config();
+  PERFETTO_CHECK(!cfg.destination_package().empty());
+  PERFETTO_CHECK(!cfg.skip_incidentd());
 
   if (bytes_written_ == 0) {
     LogUploadEvent(PerfettoStatsdAtom::kNotUploadingEmptyTrace);
@@ -52,7 +56,7 @@
   // Skip the trace-uuid link for traces that are too small. Realistically those
   // traces contain only a marker (e.g. seized_for_bugreport, or the trace
   // expired without triggers). Those are useless and introduce only noise.
-  if (!uuid_.empty() && bytes_written_ > 4096) {
+  if (bytes_written_ > 4096) {
     base::Uuid uuid(uuid_);
     PERFETTO_LOG("go/trace-uuid/%s name=\"%s\" size=%" PRIu64,
                  uuid.ToPrettyString().c_str(),
@@ -61,13 +65,53 @@
 
   // Ask incidentd to create a report, which will read the file we just
   // wrote.
-  const auto& cfg = trace_config_->incident_report_config();
   PERFETTO_LAZY_LOAD(android_internal::StartIncidentReport, incident_fn);
   PERFETTO_CHECK(incident_fn(cfg.destination_package().c_str(),
                              cfg.destination_class().c_str(),
                              cfg.privacy_level()));
 }
 
+void PerfettoCmd::ReportTraceToAndroidFrameworkOrCrash() {
+  PERFETTO_CHECK(report_to_android_framework_);
+  PERFETTO_CHECK(trace_out_stream_);
+
+  const auto& cfg = trace_config_->android_report_config();
+  PERFETTO_CHECK(!cfg.reporter_service_package().empty());
+  PERFETTO_CHECK(!cfg.skip_report());
+
+  if (bytes_written_ == 0) {
+    // TODO(lalitm): change this to a dedicated atom decoupled from
+    // incidentd.
+    LogUploadEvent(PerfettoStatsdAtom::kNotUploadingEmptyTrace);
+    PERFETTO_LOG("Skipping reporting trace to Android. Empty trace.");
+    return;
+  }
+
+  // TODO(lalitm): add atom for beginning report here.
+  base::StackString<128> self_fd("/proc/self/fd/%d",
+                                 fileno(*trace_out_stream_));
+  base::ScopedFile fd(base::OpenFile(self_fd.c_str(), O_RDONLY | O_CLOEXEC));
+  if (!fd) {
+    PERFETTO_FATAL("Failed to dup fd when reporting to Android");
+  }
+
+  base::Uuid uuid(uuid_);
+  PERFETTO_LAZY_LOAD(android_internal::ReportTrace, report_fn);
+  PERFETTO_CHECK(report_fn(cfg.reporter_service_package().c_str(),
+                           cfg.reporter_service_class().c_str(), fd.release(),
+                           uuid.lsb(), uuid.msb(),
+                           cfg.use_pipe_in_framework_for_testing()));
+
+  // Skip the trace-uuid link for traces that are too small. Realistically those
+  // traces contain only a marker (e.g. seized_for_bugreport, or the trace
+  // expired without triggers). Those are useless and introduce only noise.
+  if (bytes_written_ > 4096) {
+    PERFETTO_LOG("go/trace-uuid/%s name=\"%s\" size=%" PRIu64,
+                 uuid.ToPrettyString().c_str(),
+                 trace_config_->unique_session_name().c_str(), bytes_written_);
+  }
+}
+
 // Open a staging file (unlinking the previous instance), copy the trace
 // contents over, then rename to a final hardcoded path (known to incidentd).
 // Such tracing sessions should not normally overlap. We do not use unique
diff --git a/src/profiling/common/producer_support.cc b/src/profiling/common/producer_support.cc
index 90eba38..df8316b 100644
--- a/src/profiling/common/producer_support.cc
+++ b/src/profiling/common/producer_support.cc
@@ -56,11 +56,18 @@
   constexpr auto kAidAppStart = 10000;     // AID_APP_START
   constexpr auto kAidAppEnd = 19999;       // AID_APP_END
   constexpr auto kAidUserOffset = 100000;  // AID_USER_OFFSET
+  constexpr auto kAidSystem = 1000;        // AID_SYSTEM
 
   if (!build_type.empty() && build_type != "user") {
     return true;
   }
 
+  // TODO(b/217368496): remove this.
+  if (uid == kAidSystem) {
+    return ds_config.session_initiator() ==
+      DataSourceConfig::SESSION_INITIATOR_TRUSTED_SYSTEM;
+  }
+
   uint64_t uid_without_profile = uid % kAidUserOffset;
   if (uid_without_profile < kAidAppStart || kAidAppEnd < uid_without_profile) {
     // TODO(fmayer): relax this.
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index c45cb0b..3759533 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -168,6 +168,38 @@
       return PerfCounter::BuiltinCounter(name, PerfEvents::SW_PAGE_FAULTS,
                                          PERF_TYPE_SOFTWARE,
                                          PERF_COUNT_SW_PAGE_FAULTS);
+    case PerfEvents::SW_TASK_CLOCK:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_TASK_CLOCK,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_TASK_CLOCK);
+    case PerfEvents::SW_CONTEXT_SWITCHES:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_CONTEXT_SWITCHES,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_CONTEXT_SWITCHES);
+    case PerfEvents::SW_CPU_MIGRATIONS:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_CPU_MIGRATIONS,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_CPU_MIGRATIONS);
+    case PerfEvents::SW_PAGE_FAULTS_MIN:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_PAGE_FAULTS_MIN,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_PAGE_FAULTS_MIN);
+    case PerfEvents::SW_PAGE_FAULTS_MAJ:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_PAGE_FAULTS_MAJ,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_PAGE_FAULTS_MAJ);
+    case PerfEvents::SW_ALIGNMENT_FAULTS:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_ALIGNMENT_FAULTS,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_ALIGNMENT_FAULTS);
+    case PerfEvents::SW_EMULATION_FAULTS:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::SW_EMULATION_FAULTS,
+                                         PERF_TYPE_SOFTWARE,
+                                         PERF_COUNT_SW_EMULATION_FAULTS);
+    case PerfEvents::SW_DUMMY:
+      return PerfCounter::BuiltinCounter(
+          name, PerfEvents::SW_DUMMY, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_DUMMY);
+
     case PerfEvents::HW_CPU_CYCLES:
       return PerfCounter::BuiltinCounter(name, PerfEvents::HW_CPU_CYCLES,
                                          PERF_TYPE_HARDWARE,
@@ -176,6 +208,39 @@
       return PerfCounter::BuiltinCounter(name, PerfEvents::HW_INSTRUCTIONS,
                                          PERF_TYPE_HARDWARE,
                                          PERF_COUNT_HW_INSTRUCTIONS);
+    case PerfEvents::HW_CACHE_REFERENCES:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::HW_CACHE_REFERENCES,
+                                         PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_CACHE_REFERENCES);
+    case PerfEvents::HW_CACHE_MISSES:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::HW_CACHE_MISSES,
+                                         PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_CACHE_MISSES);
+    case PerfEvents::HW_BRANCH_INSTRUCTIONS:
+      return PerfCounter::BuiltinCounter(
+          name, PerfEvents::HW_BRANCH_INSTRUCTIONS, PERF_TYPE_HARDWARE,
+          PERF_COUNT_HW_BRANCH_INSTRUCTIONS);
+    case PerfEvents::HW_BRANCH_MISSES:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::HW_BRANCH_MISSES,
+                                         PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_BRANCH_MISSES);
+    case PerfEvents::HW_BUS_CYCLES:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::HW_BUS_CYCLES,
+                                         PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_BUS_CYCLES);
+    case PerfEvents::HW_STALLED_CYCLES_FRONTEND:
+      return PerfCounter::BuiltinCounter(
+          name, PerfEvents::HW_STALLED_CYCLES_FRONTEND, PERF_TYPE_HARDWARE,
+          PERF_COUNT_HW_STALLED_CYCLES_FRONTEND);
+    case PerfEvents::HW_STALLED_CYCLES_BACKEND:
+      return PerfCounter::BuiltinCounter(
+          name, PerfEvents::HW_STALLED_CYCLES_BACKEND, PERF_TYPE_HARDWARE,
+          PERF_COUNT_HW_STALLED_CYCLES_BACKEND);
+    case PerfEvents::HW_REF_CPU_CYCLES:
+      return PerfCounter::BuiltinCounter(name, PerfEvents::HW_REF_CPU_CYCLES,
+                                         PERF_TYPE_HARDWARE,
+                                         PERF_COUNT_HW_REF_CPU_CYCLES);
+
     default:
       PERFETTO_ELOG("Unrecognised PerfEvents::Counter enum value: %zu",
                     static_cast<size_t>(pb_enum));
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index bcacb37..1fe4dbd 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -423,8 +423,8 @@
   }
 
   if (base::StartsWith(filename, kApkPrefix)) {
-    symbol_file =
-        root_str + "/" + dirname + "/" + filename.substr(sizeof(kApkPrefix));
+    symbol_file = root_str + "/" + dirname + "/" +
+                  filename.substr(sizeof(kApkPrefix) - 1);
     result = IsCorrectFile(symbol_file, build_id);
     if (result) {
       return result;
@@ -438,7 +438,7 @@
   }
 
   if (base::StartsWith(filename, kApkPrefix)) {
-    symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix));
+    symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix) - 1);
     result = IsCorrectFile(symbol_file, build_id);
     if (result) {
       return result;
diff --git a/src/profiling/symbolizer/local_symbolizer_unittest.cc b/src/profiling/symbolizer/local_symbolizer_unittest.cc
index 921c878..cb0c11d 100644
--- a/src/profiling/symbolizer/local_symbolizer_unittest.cc
+++ b/src/profiling/symbolizer/local_symbolizer_unittest.cc
@@ -162,6 +162,79 @@
   EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2/elf1");
 }
 
+TEST(LocalBinaryFinderTest, AbsolutePath) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("root");
+  tmp.AddDir("root/dir");
+  tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+  LocalBinaryFinder finder({tmp.path() + "/root"});
+
+  base::Optional<FoundBinary> bin1 =
+      finder.FindBinary("/dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, AbsolutePathWithoutBaseApk) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("root");
+  tmp.AddDir("root/dir");
+  tmp.AddFile("root/dir/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+  LocalBinaryFinder finder({tmp.path() + "/root"});
+
+  base::Optional<FoundBinary> bin1 =
+      finder.FindBinary("/dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/dir/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, OnlyFilename) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("root");
+  tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+  LocalBinaryFinder finder({tmp.path() + "/root"});
+
+  base::Optional<FoundBinary> bin1 =
+      finder.FindBinary("/ignored_dir/elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, OnlyFilenameWithoutBaseApk) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("root");
+  tmp.AddFile("root/elf1.so", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+  LocalBinaryFinder finder({tmp.path() + "/root"});
+
+  base::Optional<FoundBinary> bin1 = finder.FindBinary(
+      "/ignored_dir/base.apk!elf1.so", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(bin1.value().file_name, tmp.path() + "/root/elf1.so");
+}
+
+TEST(LocalBinaryFinderTest, BuildIdSubdir) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("root");
+  tmp.AddDir("root/.build-id");
+  tmp.AddDir("root/.build-id/41");
+  tmp.AddFile("root/.build-id/41/41414141414141414141414141414141414141.debug",
+              CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+
+  LocalBinaryFinder finder({tmp.path() + "/root"});
+
+  base::Optional<FoundBinary> bin1 =
+      finder.FindBinary("/ignored_dir/ignored_name.so", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(
+      bin1.value().file_name,
+      tmp.path() +
+          "/root/.build-id/41/41414141414141414141414141414141414141.debug");
+}
+
 }  // namespace
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/trace_processor/containers/nullable_vector.h b/src/trace_processor/containers/nullable_vector.h
index 2a623c6..2fed430 100644
--- a/src/trace_processor/containers/nullable_vector.h
+++ b/src/trace_processor/containers/nullable_vector.h
@@ -86,15 +86,15 @@
   base::Optional<T> Get(uint32_t idx) const {
     if (mode_ == Mode::kDense) {
       bool contains = valid_.Contains(idx);
-      return contains ? base::Optional<T>(data_[idx]) : base::nullopt;
+      return contains ? base::make_optional(data_[idx]) : base::nullopt;
     } else {
-      auto opt_idx = valid_.IndexOf(idx);
-      return opt_idx ? base::Optional<T>(data_[*opt_idx]) : base::nullopt;
+      auto opt_row = valid_.RowOf(idx);
+      return opt_row ? base::make_optional(data_[*opt_row]) : base::nullopt;
     }
   }
 
-  // Returns the non-null value at |ordinal| where |ordinal| gives the index
-  // of the entry in-terms of non-null entries only.
+  // Returns the non-null value at |non_null_idx| where |non_null_idx| gives the
+  // index of the entry in-terms of non-null entries only.
   //
   // For example:
   // this = [0, null, 2, null, 4]
@@ -103,12 +103,12 @@
   // GetNonNull(1) = 2
   // GetNonNull(2) = 4
   // ...
-  T GetNonNull(uint32_t ordinal) const {
+  T GetNonNull(uint32_t non_null_idx) const {
     if (mode_ == Mode::kDense) {
-      return data_[valid_.Get(ordinal)];
+      return data_[valid_.Get(non_null_idx)];
     } else {
-      PERFETTO_DCHECK(ordinal < data_.size());
-      return data_[ordinal];
+      PERFETTO_DCHECK(non_null_idx < data_.size());
+      return data_[non_null_idx];
     }
   }
 
@@ -143,18 +143,18 @@
       }
       data_[idx] = val;
     } else {
-      auto opt_idx = valid_.IndexOf(idx);
+      auto opt_row = valid_.RowOf(idx);
 
       // Generally, we will be setting a null row to non-null so optimize for
       // that path.
-      if (PERFETTO_UNLIKELY(opt_idx)) {
-        data_[*opt_idx] = val;
+      if (PERFETTO_UNLIKELY(opt_row)) {
+        data_[*opt_row] = val;
       } else {
         valid_.Insert(idx);
 
-        opt_idx = valid_.IndexOf(idx);
-        PERFETTO_DCHECK(opt_idx);
-        data_.insert(data_.begin() + static_cast<ptrdiff_t>(*opt_idx), val);
+        opt_row = valid_.RowOf(idx);
+        PERFETTO_DCHECK(opt_row);
+        data_.insert(data_.begin() + static_cast<ptrdiff_t>(*opt_row), val);
       }
     }
   }
@@ -166,7 +166,7 @@
   bool IsDense() const { return mode_ == Mode::kDense; }
 
  private:
-  NullableVector(Mode mode) : mode_(mode) {}
+  explicit NullableVector(Mode mode) : mode_(mode) {}
 
   Mode mode_ = Mode::kSparse;
 
diff --git a/src/trace_processor/containers/row_map.cc b/src/trace_processor/containers/row_map.cc
index 297a0ef..ceacf95 100644
--- a/src/trace_processor/containers/row_map.cc
+++ b/src/trace_processor/containers/row_map.cc
@@ -146,8 +146,8 @@
 
 RowMap::RowMap(uint32_t start, uint32_t end, OptimizeFor optimize_for)
     : mode_(Mode::kRange),
-      start_idx_(start),
-      end_idx_(end),
+      start_index_(start),
+      end_index_(end),
       optimize_for_(optimize_for) {}
 
 RowMap::RowMap(BitVector bit_vector)
@@ -159,7 +159,7 @@
 RowMap RowMap::Copy() const {
   switch (mode_) {
     case Mode::kRange:
-      return RowMap(start_idx_, end_idx_);
+      return RowMap(start_index_, end_index_);
     case Mode::kBitVector:
       return RowMap(bit_vector_.Copy());
     case Mode::kIndexVector:
@@ -176,20 +176,22 @@
     case Mode::kRange:
       switch (mode_) {
         case Mode::kRange:
-          return SelectRangeWithRange(start_idx_, end_idx_, selector.start_idx_,
-                                      selector.end_idx_);
+          return SelectRangeWithRange(start_index_, end_index_,
+                                      selector.start_index_,
+                                      selector.end_index_);
         case Mode::kBitVector:
-          return SelectBvWithRange(bit_vector_, selector.start_idx_,
-                                   selector.end_idx_);
+          return SelectBvWithRange(bit_vector_, selector.start_index_,
+                                   selector.end_index_);
         case Mode::kIndexVector:
-          return SelectIvWithRange(index_vector_, selector.start_idx_,
-                                   selector.end_idx_);
+          return SelectIvWithRange(index_vector_, selector.start_index_,
+                                   selector.end_index_);
       }
       break;
     case Mode::kBitVector:
       switch (mode_) {
         case Mode::kRange:
-          return SelectRangeWithBv(start_idx_, end_idx_, selector.bit_vector_);
+          return SelectRangeWithBv(start_index_, end_index_,
+                                   selector.bit_vector_);
         case Mode::kBitVector:
           return SelectBvWithBv(bit_vector_, selector.bit_vector_);
         case Mode::kIndexVector:
@@ -199,7 +201,7 @@
     case Mode::kIndexVector:
       switch (mode_) {
         case Mode::kRange:
-          return SelectRangeWithIv(start_idx_, end_idx_,
+          return SelectRangeWithIv(start_index_, end_index_,
                                    selector.index_vector_);
         case Mode::kBitVector:
           return SelectBvWithIv(bit_vector_, selector.index_vector_);
diff --git a/src/trace_processor/containers/row_map.h b/src/trace_processor/containers/row_map.h
index cb39ac1..5ca3c9c 100644
--- a/src/trace_processor/containers/row_map.h
+++ b/src/trace_processor/containers/row_map.h
@@ -34,6 +34,20 @@
 // columns can refer to the same RowMap. The RowMap defines the access pattern
 // to iterate on rows.
 //
+// Naming convention:
+//
+// As both the input and output of RowMap is a uint32_t, it can be quite
+// confusing to reason about what parameters/return values of the functions
+// of RowMap actually means. To help with this, we define a strict convention
+// of naming.
+//
+// row:     input - that is, rows are what are passed into operator[]; named as
+//          such because a "row" number in a table is converted to an index to
+//          lookup in the backing vectors.
+// index:   output - that is, indices are what are returned from operator[];
+//          named as such because an "index" is what's used to lookup data
+//          from the backing vectors.
+//
 // Implementation details:
 //
 // Behind the scenes, this class is impelemented using one of three backing
@@ -69,15 +83,15 @@
   // BitVector::SetBitsIterator.
   class RangeIterator {
    public:
-    RangeIterator(const RowMap* rm) : rm_(rm), index_(rm->start_idx_) {}
+    RangeIterator(const RowMap* rm) : rm_(rm), index_(rm->start_index_) {}
 
     void Next() { ++index_; }
 
-    operator bool() const { return index_ < rm_->end_idx_; }
+    operator bool() const { return index_ < rm_->end_index_; }
 
     uint32_t index() const { return index_; }
 
-    uint32_t ordinal() const { return index_ - rm_->start_idx_; }
+    uint32_t ordinal() const { return index_ - rm_->start_index_; }
 
    private:
     const RowMap* rm_ = nullptr;
@@ -105,6 +119,10 @@
   };
 
  public:
+  // Input type.
+  using InputRow = uint32_t;
+  using OutputIndex = uint32_t;
+
   // Allows efficient iteration over the rows of a RowMap.
   //
   // Note: you should usually prefer to use the methods on RowMap directly (if
@@ -158,12 +176,8 @@
       PERFETTO_FATAL("For GCC");
     }
 
-    // Returns the row pointed to by this iterator.
-    uint32_t row() const {
-      // RowMap uses the row/index nomenclature for referring to the mapping
-      // from index to a row (as the name suggests). However, the data
-      // structures used by RowMap use the index/ordinal naming (because they
-      // don't have the concept of a "row"). Switch the naming here.
+    // Returns the index pointed to by this iterator.
+    OutputIndex index() const {
       switch (rm_->mode_) {
         case Mode::kRange:
           return range_it_->index();
@@ -175,12 +189,8 @@
       PERFETTO_FATAL("For GCC");
     }
 
-    // Returns the index of the row the iterator points to.
-    uint32_t index() const {
-      // RowMap uses the row/index nomenclature for referring to the mapping
-      // from index to a row (as the name suggests). However, the data
-      // structures used by RowMap use the index/ordinal naming (because they
-      // don't have the concept of a "row"). Switch the naming here.
+    // Returns the row of the index the iterator points to.
+    InputRow row() const {
       switch (rm_->mode_) {
         case Mode::kRange:
           return range_it_->ordinal();
@@ -216,21 +226,23 @@
   // By default this will be implemented using a range.
   RowMap();
 
-  // Creates a RowMap containing the range of rows between |start| and |end|
-  // i.e. all rows between |start| (inclusive) and |end| (exclusive).
-  explicit RowMap(uint32_t start,
-                  uint32_t end,
+  // Creates a RowMap containing the range of indices between |start| and |end|
+  // i.e. all indices between |start| (inclusive) and |end| (exclusive).
+  explicit RowMap(OutputIndex start,
+                  OutputIndex end,
                   OptimizeFor optimize_for = OptimizeFor::kMemory);
 
   // Creates a RowMap backed by a BitVector.
   explicit RowMap(BitVector bit_vector);
 
   // Creates a RowMap backed by an std::vector<uint32_t>.
-  explicit RowMap(std::vector<uint32_t> vec);
+  explicit RowMap(std::vector<OutputIndex> vec);
 
-  // Creates a RowMap containing just |row|.
+  // Creates a RowMap containing just |index|.
   // By default this will be implemented using a range.
-  static RowMap SingleRow(uint32_t row) { return RowMap(row, row + 1); }
+  static RowMap SingleRow(OutputIndex index) {
+    return RowMap(index, index + 1);
+  }
 
   // Creates a copy of the RowMap.
   // We have an explicit copy function because RowMap can hold onto large chunks
@@ -238,11 +250,12 @@
   // accidental leaks and copies.
   RowMap Copy() const;
 
-  // Returns the size of the RowMap; that is the number of rows in the RowMap.
+  // Returns the size of the RowMap; that is the number of indices in the
+  // RowMap.
   uint32_t size() const {
     switch (mode_) {
       case Mode::kRange:
-        return end_idx_ - start_idx_;
+        return end_index_ - start_index_;
       case Mode::kBitVector:
         return bit_vector_.GetNumBitsSet();
       case Mode::kIndexVector:
@@ -254,54 +267,54 @@
   // Returns whether this rowmap is empty.
   bool empty() const { return size() == 0; }
 
-  // Returns the row at index |row|.
-  uint32_t Get(uint32_t idx) const {
-    PERFETTO_DCHECK(idx < size());
+  // Returns the index at the given |row|.
+  OutputIndex Get(InputRow row) const {
+    PERFETTO_DCHECK(row < size());
     switch (mode_) {
       case Mode::kRange:
-        return GetRange(idx);
+        return GetRange(row);
       case Mode::kBitVector:
-        return GetBitVector(idx);
+        return GetBitVector(row);
       case Mode::kIndexVector:
-        return GetIndexVector(idx);
+        return GetIndexVector(row);
     }
     PERFETTO_FATAL("For GCC");
   }
 
-  // Returns whether the RowMap contains the given row.
-  bool Contains(uint32_t row) const {
+  // Returns whether the RowMap contains the given index.
+  bool Contains(OutputIndex index) const {
     switch (mode_) {
       case Mode::kRange: {
-        return row >= start_idx_ && row < end_idx_;
+        return index >= start_index_ && index < end_index_;
       }
       case Mode::kBitVector: {
-        return row < bit_vector_.size() && bit_vector_.IsSet(row);
+        return index < bit_vector_.size() && bit_vector_.IsSet(index);
       }
       case Mode::kIndexVector: {
-        auto it = std::find(index_vector_.begin(), index_vector_.end(), row);
+        auto it = std::find(index_vector_.begin(), index_vector_.end(), index);
         return it != index_vector_.end();
       }
     }
     PERFETTO_FATAL("For GCC");
   }
 
-  // Returns the first index of the given |row| in the RowMap.
-  base::Optional<uint32_t> IndexOf(uint32_t row) const {
+  // Returns the first row of the given |index| in the RowMap.
+  base::Optional<InputRow> RowOf(OutputIndex index) const {
     switch (mode_) {
       case Mode::kRange: {
-        if (row < start_idx_ || row >= end_idx_)
+        if (index < start_index_ || index >= end_index_)
           return base::nullopt;
-        return row - start_idx_;
+        return index - start_index_;
       }
       case Mode::kBitVector: {
-        return row < bit_vector_.size() && bit_vector_.IsSet(row)
-                   ? base::make_optional(bit_vector_.GetNumBitsSet(row))
+        return index < bit_vector_.size() && bit_vector_.IsSet(index)
+                   ? base::make_optional(bit_vector_.GetNumBitsSet(index))
                    : base::nullopt;
       }
       case Mode::kIndexVector: {
-        auto it = std::find(index_vector_.begin(), index_vector_.end(), row);
+        auto it = std::find(index_vector_.begin(), index_vector_.end(), index);
         return it != index_vector_.end()
-                   ? base::make_optional(static_cast<uint32_t>(
+                   ? base::make_optional(static_cast<InputRow>(
                          std::distance(index_vector_.begin(), it)))
                    : base::nullopt;
       }
@@ -309,8 +322,8 @@
     PERFETTO_FATAL("For GCC");
   }
 
-  // Performs an ordered insert the row into the current RowMap (precondition:
-  // this RowMap is ordered based on the rows it contains).
+  // Performs an ordered insert of the index into the current RowMap
+  // (precondition: this RowMap is ordered based on the indices it contains).
   //
   // Example:
   // this = [1, 5, 10, 11, 20]
@@ -320,41 +333,41 @@
   // Insert(2)   // this = [1, 2, 5, 10, 11, 12, 20, 21]
   //
   // Speecifically, this means that it is only valid to call Insert on a RowMap
-  // which is sorted by the rows it contains; this is automatically true when
+  // which is sorted by the indices it contains; this is automatically true when
   // the RowMap is in range or BitVector mode but is a required condition for
   // IndexVector mode.
-  void Insert(uint32_t row) {
+  void Insert(OutputIndex index) {
     switch (mode_) {
       case Mode::kRange:
-        if (row == end_idx_) {
+        if (index == end_index_) {
           // Fast path: if we're just appending to the end of the range, we can
           // stay in range mode and just bump the end index.
-          end_idx_++;
+          end_index_++;
         } else {
           // Slow path: the insert is somewhere else other than the end. This
           // means we need to switch to using a BitVector instead.
-          bit_vector_.Resize(start_idx_, false);
-          bit_vector_.Resize(end_idx_, true);
+          bit_vector_.Resize(start_index_, false);
+          bit_vector_.Resize(end_index_, true);
           *this = RowMap(std::move(bit_vector_));
 
-          InsertIntoBitVector(row);
+          InsertIntoBitVector(index);
         }
         break;
       case Mode::kBitVector:
-        InsertIntoBitVector(row);
+        InsertIntoBitVector(index);
         break;
       case Mode::kIndexVector: {
         PERFETTO_DCHECK(
             std::is_sorted(index_vector_.begin(), index_vector_.end()));
         auto it =
-            std::upper_bound(index_vector_.begin(), index_vector_.end(), row);
-        index_vector_.insert(it, row);
+            std::upper_bound(index_vector_.begin(), index_vector_.end(), index);
+        index_vector_.insert(it, index);
         break;
       }
     }
   }
 
-  // Updates this RowMap by 'picking' the rows at indicies given by |picker|.
+  // Updates this RowMap by 'picking' the indices given by |picker|.
   // This is easiest to explain with an example; suppose we have the following
   // RowMaps:
   // this  : [0, 1, 4, 10, 11]
@@ -365,8 +378,8 @@
   //
   // Conceptually, we are performing the following algorithm:
   // RowMap rm = Copy()
-  // for (idx : picker)
-  //   rm[i++] = this[idx]
+  // for (p : picker)
+  //   rm[i++] = this[p]
   // return rm;
   RowMap SelectRows(const RowMap& selector) const {
     uint32_t size = selector.size();
@@ -385,8 +398,8 @@
   }
 
   // Intersects |other| with |this| writing the result into |this|.
-  // By "intersect", we mean to keep only the rows present in both RowMaps. The
-  // order of the preserved rows will be the same as |this|.
+  // By "intersect", we mean to keep only the indices present in both RowMaps.
+  // The order of the preserved indices will be the same as |this|.
   //
   // Conceptually, we are performing the following algorithm:
   // for (idx : this)
@@ -398,8 +411,9 @@
       // of them as the new RowMap.
       // We have this as an explicit fast path as this is very common for
       // constraints on id and sorted columns to satisfy this condition.
-      start_idx_ = std::max(start_idx_, other.start_idx_);
-      end_idx_ = std::max(start_idx_, std::min(end_idx_, other.end_idx_));
+      start_index_ = std::max(start_index_, other.start_index_);
+      end_index_ =
+          std::max(start_index_, std::min(end_index_, other.end_index_));
       return;
     }
 
@@ -410,7 +424,7 @@
   // Filters the current RowMap into the RowMap given by |out| based on the
   // return value of |p(idx)|.
   //
-  // Precondition: |out| should be sorted by the rows inside it (this is
+  // Precondition: |out| should be sorted by the indices inside it (this is
   // required to keep this method efficient). This is automatically true if the
   // mode is out is Range or BitVector but needs to be enforced if the mode is
   // IndexVector.
@@ -450,13 +464,13 @@
     // cases where |out| has only a few entries so we can scan |out| instead of
     // scanning |this|.
 
-    // Ideally, we'd always just scan the rows in |out| and keep those which
+    // Ideally, we'd always just scan |out| and keep the indices in |this| which
     // meet |p|. However, if |this| is a BitVector, we end up needing expensive
-    // |IndexOfNthSet| calls (as we need to lookup the row before passing it to
-    // |p|).
+    // |IndexOfNthSet| calls (as we need to convert the row to an index before
+    // passing it to |p|).
     switch (mode_) {
       case Mode::kRange: {
-        auto ip = [this, p](uint32_t idx) { return p(GetRange(idx)); };
+        auto ip = [this, p](uint32_t row) { return p(GetRange(row)); };
         out->Filter(ip);
         break;
       }
@@ -542,12 +556,12 @@
       case Mode::kRange: {
         // TODO(lalitm): investigate whether we can reuse the data inside
         // out->bit_vector_ at some point.
-        BitVector bv(out->end_idx_, false);
+        BitVector bv(out->end_index_, false);
         for (auto out_it = bv.IterateAllBits(); it; it.Next(), out_it.Next()) {
           uint32_t ordinal = it.ordinal();
-          if (ordinal < out->start_idx_)
+          if (ordinal < out->start_index_)
             continue;
-          if (ordinal >= out->end_idx_)
+          if (ordinal >= out->end_index_)
             break;
 
           if (p(it.index())) {
@@ -587,16 +601,16 @@
 
   template <typename Predicate>
   void FilterRange(Predicate p) {
-    uint32_t count = end_idx_ - start_idx_;
+    uint32_t count = end_index_ - start_index_;
 
-    // Optimization: if we are only going to scan a few rows, it's not
+    // Optimization: if we are only going to scan a few indices, it's not
     // worth the haslle of working with a BitVector.
     constexpr uint32_t kSmallRangeLimit = 2048;
     bool is_small_range = count < kSmallRangeLimit;
 
     // Optimization: weif the cost of a BitVector is more than the highest
     // possible cost an index vector could have, use the index vector.
-    uint32_t bit_vector_cost = BitVector::ApproxBytesCost(end_idx_);
+    uint32_t bit_vector_cost = BitVector::ApproxBytesCost(end_index_);
     uint32_t index_vector_cost_ub = sizeof(uint32_t) * count;
 
     // If either of the conditions hold which make it better to use an
@@ -608,21 +622,21 @@
       // big and good performance.
       std::vector<uint32_t> iv(std::min(kSmallRangeLimit, count));
 
-      uint32_t out_idx = 0;
+      uint32_t out_i = 0;
       for (uint32_t i = 0; i < count; ++i) {
         // If we reach the capacity add another small set of indices.
-        if (PERFETTO_UNLIKELY(out_idx == iv.size()))
+        if (PERFETTO_UNLIKELY(out_i == iv.size()))
           iv.resize(iv.size() + kSmallRangeLimit);
 
         // We keep this branch free by always writing the index but only
         // incrementing the out index if the return value is true.
-        bool value = p(i + start_idx_);
-        iv[out_idx] = i + start_idx_;
-        out_idx += value;
+        bool value = p(i + start_index_);
+        iv[out_i] = i + start_index_;
+        out_i += value;
       }
 
       // Make the vector the correct size and as small as possible.
-      iv.resize(out_idx);
+      iv.resize(out_i);
       iv.shrink_to_fit();
 
       *this = RowMap(std::move(iv));
@@ -631,7 +645,7 @@
 
     // Otherwise, create a bitvector which spans the full range using
     // |p| as the filler for the bits between start and end.
-    *this = RowMap(BitVector::Range(start_idx_, end_idx_, p));
+    *this = RowMap(BitVector::Range(start_index_, end_index_, p));
   }
 
   void InsertIntoBitVector(uint32_t row) {
@@ -642,17 +656,17 @@
     bit_vector_.Set(row);
   }
 
-  PERFETTO_ALWAYS_INLINE uint32_t GetRange(uint32_t idx) const {
+  PERFETTO_ALWAYS_INLINE OutputIndex GetRange(InputRow row) const {
     PERFETTO_DCHECK(mode_ == Mode::kRange);
-    return start_idx_ + idx;
+    return start_index_ + row;
   }
-  PERFETTO_ALWAYS_INLINE uint32_t GetBitVector(uint32_t idx) const {
+  PERFETTO_ALWAYS_INLINE OutputIndex GetBitVector(uint32_t row) const {
     PERFETTO_DCHECK(mode_ == Mode::kBitVector);
-    return bit_vector_.IndexOfNthSet(idx);
+    return bit_vector_.IndexOfNthSet(row);
   }
-  PERFETTO_ALWAYS_INLINE uint32_t GetIndexVector(uint32_t idx) const {
+  PERFETTO_ALWAYS_INLINE OutputIndex GetIndexVector(uint32_t row) const {
     PERFETTO_DCHECK(mode_ == Mode::kIndexVector);
-    return index_vector_[idx];
+    return index_vector_[row];
   }
 
   RowMap SelectRowsSlow(const RowMap& selector) const;
@@ -660,14 +674,14 @@
   Mode mode_ = Mode::kRange;
 
   // Only valid when |mode_| == Mode::kRange.
-  uint32_t start_idx_ = 0;  // This is an inclusive index.
-  uint32_t end_idx_ = 0;    // This is an exclusive index.
+  OutputIndex start_index_ = 0;  // This is an inclusive index.
+  OutputIndex end_index_ = 0;    // This is an exclusive index.
 
   // Only valid when |mode_| == Mode::kBitVector.
   BitVector bit_vector_;
 
   // Only valid when |mode_| == Mode::kIndexVector.
-  std::vector<uint32_t> index_vector_;
+  std::vector<OutputIndex> index_vector_;
 
   OptimizeFor optimize_for_ = OptimizeFor::kMemory;
 };
diff --git a/src/trace_processor/containers/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
index ac54cb1..16022b2 100644
--- a/src/trace_processor/containers/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -34,11 +34,11 @@
   ASSERT_EQ(rm.Get(1), 31u);
   ASSERT_EQ(rm.Get(16), 46u);
 
-  ASSERT_EQ(rm.IndexOf(29), base::nullopt);
-  ASSERT_EQ(rm.IndexOf(30), 0u);
-  ASSERT_EQ(rm.IndexOf(37), 7u);
-  ASSERT_EQ(rm.IndexOf(46), 16u);
-  ASSERT_EQ(rm.IndexOf(47), base::nullopt);
+  ASSERT_EQ(rm.RowOf(29), base::nullopt);
+  ASSERT_EQ(rm.RowOf(30), 0u);
+  ASSERT_EQ(rm.RowOf(37), 7u);
+  ASSERT_EQ(rm.RowOf(46), 16u);
+  ASSERT_EQ(rm.RowOf(47), base::nullopt);
 }
 
 TEST(RowMapUnittest, SmokeBitVector) {
@@ -50,12 +50,12 @@
   ASSERT_EQ(rm.Get(1u), 4u);
   ASSERT_EQ(rm.Get(2u), 5u);
 
-  ASSERT_EQ(rm.IndexOf(0u), 0u);
-  ASSERT_EQ(rm.IndexOf(4u), 1u);
-  ASSERT_EQ(rm.IndexOf(5u), 2u);
+  ASSERT_EQ(rm.RowOf(0u), 0u);
+  ASSERT_EQ(rm.RowOf(4u), 1u);
+  ASSERT_EQ(rm.RowOf(5u), 2u);
 
-  ASSERT_EQ(rm.IndexOf(1u), base::nullopt);
-  ASSERT_EQ(rm.IndexOf(100u), base::nullopt);
+  ASSERT_EQ(rm.RowOf(1u), base::nullopt);
+  ASSERT_EQ(rm.RowOf(100u), base::nullopt);
 }
 
 TEST(RowMapUnittest, SmokeIndexVector) {
@@ -70,12 +70,12 @@
   ASSERT_EQ(rm.Get(4u), 100u);
   ASSERT_EQ(rm.Get(5u), 1u);
 
-  ASSERT_EQ(rm.IndexOf(32u), 0u);
-  ASSERT_EQ(rm.IndexOf(56u), 1u);
-  ASSERT_EQ(rm.IndexOf(24u), 2u);
-  ASSERT_EQ(rm.IndexOf(0u), 3u);
-  ASSERT_EQ(rm.IndexOf(100u), 4u);
-  ASSERT_EQ(rm.IndexOf(1u), 5u);
+  ASSERT_EQ(rm.RowOf(32u), 0u);
+  ASSERT_EQ(rm.RowOf(56u), 1u);
+  ASSERT_EQ(rm.RowOf(24u), 2u);
+  ASSERT_EQ(rm.RowOf(0u), 3u);
+  ASSERT_EQ(rm.RowOf(100u), 4u);
+  ASSERT_EQ(rm.RowOf(1u), 5u);
 }
 
 TEST(RowMapUnittest, InsertToRangeAfter) {
@@ -84,7 +84,7 @@
 
   ASSERT_EQ(rm.size(), 5u);
   ASSERT_EQ(rm.Get(4u), 10u);
-  ASSERT_EQ(rm.IndexOf(10u), 4u);
+  ASSERT_EQ(rm.RowOf(10u), 4u);
 }
 
 TEST(RowMapUnittest, InsertToBitVectorBefore) {
@@ -105,7 +105,7 @@
 
   ASSERT_EQ(rm.size(), 5u);
   ASSERT_EQ(rm.Get(4u), 10u);
-  ASSERT_EQ(rm.IndexOf(10u), 4u);
+  ASSERT_EQ(rm.RowOf(10u), 4u);
 }
 
 TEST(RowMapUnittest, InsertToIndexVectorAfter) {
@@ -114,7 +114,7 @@
 
   ASSERT_EQ(rm.size(), 5u);
   ASSERT_EQ(rm.Get(4u), 10u);
-  ASSERT_EQ(rm.IndexOf(10u), 4u);
+  ASSERT_EQ(rm.RowOf(10u), 4u);
 }
 
 TEST(RowMapUnittest, ContainsRange) {
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 9df9f70..dacbe60 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -229,7 +229,7 @@
       case ColumnType::kId: {
         if (value.type != SqlValue::Type::kLong)
           return base::nullopt;
-        return row_map().IndexOf(static_cast<uint32_t>(value.long_value));
+        return row_map().RowOf(static_cast<uint32_t>(value.long_value));
       }
     }
     PERFETTO_FATAL("For GCC");
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index fb8b73b..e13ad58 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -60,7 +60,7 @@
     // Returns the value at the current row for column |col_idx|.
     SqlValue Get(uint32_t col_idx) const {
       const auto& col = table_->columns_[col_idx];
-      return col.GetAtIdx(its_[col.row_map_idx_].row());
+      return col.GetAtIdx(its_[col.row_map_idx_].index());
     }
 
    private:
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index a8fc424..d286834 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -162,7 +162,7 @@
 struct IdColumn : public Column {
   Id operator[](uint32_t row) const { return Id(row_map().Get(row)); }
   base::Optional<uint32_t> IndexOf(Id id) const {
-    return row_map().IndexOf(id.value);
+    return row_map().RowOf(id.value);
   }
 
   // Reinterpret cast a Column to IdColumn or crash if that is likely to be
diff --git a/src/trace_processor/dynamic/ancestor_generator.cc b/src/trace_processor/dynamic/ancestor_generator.cc
index 1e479bc..f603d67 100644
--- a/src/trace_processor/dynamic/ancestor_generator.cc
+++ b/src/trace_processor/dynamic/ancestor_generator.cc
@@ -139,11 +139,11 @@
           slice_table.FilterToRowMap({slice_table.stack_id().eq(start_id)});
 
       for (auto id_it = slice_ids.IterateRows(); id_it; id_it.Next()) {
-        auto slice_id = slice_table.id()[id_it.row()];
+        auto slice_id = slice_table.id()[id_it.index()];
 
         auto ancestors = GetAncestorSlices(slice_table, slice_id);
         for (auto row_it = ancestors->IterateRows(); row_it; row_it.Next()) {
-          result.Insert(row_it.row());
+          result.Insert(row_it.index());
         }
       }
 
diff --git a/src/trace_processor/dynamic/connected_flow_generator.cc b/src/trace_processor/dynamic/connected_flow_generator.cc
index 6dd0a90..d3cf976 100644
--- a/src/trace_processor/dynamic/connected_flow_generator.cc
+++ b/src/trace_processor/dynamic/connected_flow_generator.cc
@@ -162,8 +162,8 @@
     auto rows = flow.FilterToRowMap({start_col.eq(slice_id.value)});
 
     for (auto row_it = rows.IterateRows(); row_it; row_it.Next()) {
-      flow_rows_.push_back(row_it.row());
-      SliceId next_slice_id = end_col[row_it.row()];
+      flow_rows_.push_back(row_it.index());
+      SliceId next_slice_id = end_col[row_it.index()];
       if (known_slices_.count(next_slice_id) != 0) {
         continue;
       }
@@ -179,7 +179,7 @@
   void GoToRelativesImpl(RowMap::Iterator it) {
     const auto& slice = context_->storage->slice_table();
     for (; it; it.Next()) {
-      auto relative_slice_id = slice.id()[it.row()];
+      auto relative_slice_id = slice.id()[it.index()];
       if (known_slices_.count(relative_slice_id))
         continue;
       known_slices_.insert(relative_slice_id);
diff --git a/src/trace_processor/dynamic/descendant_generator.cc b/src/trace_processor/dynamic/descendant_generator.cc
index 863178a..cc33bf1 100644
--- a/src/trace_processor/dynamic/descendant_generator.cc
+++ b/src/trace_processor/dynamic/descendant_generator.cc
@@ -115,11 +115,11 @@
       auto slice_ids = slices.FilterToRowMap({slices.stack_id().eq(start_id)});
 
       for (auto id_it = slice_ids.IterateRows(); id_it; id_it.Next()) {
-        auto slice_id = slices.id()[id_it.row()];
+        auto slice_id = slices.id()[id_it.index()];
 
         auto descendants = GetDescendantSlices(slices, slice_id);
         for (auto row_it = descendants->IterateRows(); row_it; row_it.Next()) {
-          result.Insert(row_it.row());
+          result.Insert(row_it.index());
         }
       }
 
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 08c4fa2..961ecfc 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -32,6 +32,7 @@
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/metadata_tracker.h"
 #include "src/trace_processor/importers/proto/track_event_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 #include "test/gtest_and_gmock.h"
@@ -230,8 +231,8 @@
 }
 
 TEST_F(ExportJsonTest, SystemEventsIgnored) {
-  TrackId track = context_.track_tracker->CreateAndroidAsyncTrack(
-      /*name=*/kNullStringId, /*upid=*/0);
+  TrackId track = context_.track_tracker->CreateProcessAsyncTrack(
+      /*name=*/kNullStringId, /*upid=*/0, /*source=*/kNullStringId);
   context_.args_tracker->Flush();  // Flush track args.
 
   // System events have no category.
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 73aef4e..36a282d 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -31,7 +31,6 @@
       category_key_(context->storage->InternString("category")),
       fuchsia_source_(context->storage->InternString("fuchsia")),
       chrome_source_(context->storage->InternString("chrome")),
-      android_source_(context->storage->InternString("android")),
       context_(context) {}
 
 TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
@@ -133,28 +132,29 @@
   return id;
 }
 
-TrackId TrackTracker::CreateGlobalAsyncTrack(StringId name) {
+TrackId TrackTracker::CreateGlobalAsyncTrack(StringId name, StringId source) {
   tables::TrackTable::Row row(name);
   auto id = context_->storage->mutable_track_table()->Insert(row).id;
+  if (!source.is_null()) {
+    context_->args_tracker->AddArgsTo(id).AddArg(source_key_,
+                                                 Variadic::String(source));
+  }
   return id;
 }
 
-TrackId TrackTracker::CreateAndroidAsyncTrack(StringId name, UniquePid upid) {
+TrackId TrackTracker::CreateProcessAsyncTrack(StringId name,
+                                              UniquePid upid,
+                                              StringId source) {
   tables::ProcessTrackTable::Row row(name);
   row.upid = upid;
   auto id = context_->storage->mutable_process_track_table()->Insert(row).id;
-  context_->args_tracker->AddArgsTo(id).AddArg(
-      source_key_, Variadic::String(android_source_));
+  if (!source.is_null()) {
+    context_->args_tracker->AddArgsTo(id).AddArg(source_key_,
+                                                 Variadic::String(source));
+  }
   return id;
 }
 
-TrackId TrackTracker::CreateFrameTimelineAsyncTrack(StringId name,
-                                                    UniquePid upid) {
-  tables::ProcessTrackTable::Row row(name);
-  row.upid = upid;
-  return context_->storage->mutable_process_track_table()->Insert(row).id;
-}
-
 TrackId TrackTracker::InternLegacyChromeProcessInstantTrack(UniquePid upid) {
   auto it = chrome_process_instant_tracks_.find(upid);
   if (it != chrome_process_instant_tracks_.end())
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index 6ed95b9..e38cc66 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -52,15 +52,6 @@
                                        bool source_id_is_process_scoped,
                                        StringId source_scope);
 
-  // Creates and inserts a global async track into the storage.
-  TrackId CreateGlobalAsyncTrack(StringId name);
-
-  // Creates and inserts a Android async track into the storage.
-  TrackId CreateAndroidAsyncTrack(StringId name, UniquePid upid);
-
-  // Creates and inserts a FrameTimeline async track into the storage.
-  TrackId CreateFrameTimelineAsyncTrack(StringId name, UniquePid upid);
-
   // Interns a track for legacy Chrome process-scoped instant events into the
   // storage.
   TrackId InternLegacyChromeProcessInstantTrack(UniquePid upid);
@@ -111,6 +102,17 @@
                                  uint32_t cpu,
                                  bool is_timebase);
 
+  // NOTE:
+  // The below method should only be called by AsyncTrackSetTracker
+
+  // Creates and inserts a global async track into the storage.
+  TrackId CreateGlobalAsyncTrack(StringId name, StringId source);
+
+  // Creates and inserts a Android async track into the storage.
+  TrackId CreateProcessAsyncTrack(StringId name,
+                                  UniquePid upid,
+                                  StringId source);
+
  private:
   struct GpuTrackTuple {
     StringId track_name;
@@ -163,7 +165,6 @@
 
   const StringId fuchsia_source_ = kNullStringId;
   const StringId chrome_source_ = kNullStringId;
-  const StringId android_source_ = kNullStringId;
 
   TraceProcessorContext* const context_;
 };
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index bf50559..4782a3b 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<MessageDescriptor, 362> descriptors{{
+std::array<MessageDescriptor, 364> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -3875,6 +3875,36 @@
             {"skbaddr", ProtoSchemaType::kUint64},
         },
     },
+    {
+        "tcp_retransmit_skb",
+        7,
+        {
+            {},
+            {"daddr", ProtoSchemaType::kUint32},
+            {"dport", ProtoSchemaType::kUint32},
+            {"saddr", ProtoSchemaType::kUint32},
+            {"skaddr", ProtoSchemaType::kUint64},
+            {"skbaddr", ProtoSchemaType::kUint64},
+            {"sport", ProtoSchemaType::kUint32},
+            {"state", ProtoSchemaType::kInt32},
+        },
+    },
+    {
+        "inet_sock_set_state",
+        9,
+        {
+            {},
+            {"daddr", ProtoSchemaType::kUint32},
+            {"dport", ProtoSchemaType::kUint32},
+            {"family", ProtoSchemaType::kUint32},
+            {"newstate", ProtoSchemaType::kInt32},
+            {"oldstate", ProtoSchemaType::kInt32},
+            {"protocol", ProtoSchemaType::kUint32},
+            {"saddr", ProtoSchemaType::kUint32},
+            {"skaddr", ProtoSchemaType::kUint64},
+            {"sport", ProtoSchemaType::kUint32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 186b541..e9bdddb 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -27,6 +27,7 @@
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/softirq_action.h"
+#include "src/trace_processor/types/tcp_state.h"
 
 #include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
 #include "protos/perfetto/trace/ftrace/binder.pbzero.h"
@@ -54,8 +55,10 @@
 #include "protos/perfetto/trace/ftrace/scm.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sde.pbzero.h"
 #include "protos/perfetto/trace/ftrace/signal.pbzero.h"
+#include "protos/perfetto/trace/ftrace/sock.pbzero.h"
 #include "protos/perfetto/trace/ftrace/systrace.pbzero.h"
 #include "protos/perfetto/trace/ftrace/task.pbzero.h"
+#include "protos/perfetto/trace/ftrace/tcp.pbzero.h"
 #include "protos/perfetto/trace/ftrace/thermal.pbzero.h"
 #include "protos/perfetto/trace/ftrace/vmscan.pbzero.h"
 #include "protos/perfetto/trace/ftrace/workqueue.pbzero.h"
@@ -104,6 +107,7 @@
       rss_stat_tracker_(context),
       sched_wakeup_name_id_(context->storage->InternString("sched_wakeup")),
       sched_waking_name_id_(context->storage->InternString("sched_waking")),
+      cpu_id_(context->storage->InternString("cpu")),
       cpu_freq_name_id_(context->storage->InternString("cpufreq")),
       gpu_freq_name_id_(context->storage->InternString("gpufreq")),
       cpu_idle_name_id_(context->storage->InternString("cpuidle")),
@@ -126,6 +130,10 @@
       oom_kill_id_(context_->storage->InternString("mem.oom_kill")),
       workqueue_id_(context_->storage->InternString("workqueue")),
       irq_id_(context_->storage->InternString("irq")),
+      tcp_state_id_(context_->storage->InternString("tcp_state")),
+      tcp_event_id_(context_->storage->InternString("tcp_event")),
+      tcp_retransmited_name_id_(
+          context_->storage->InternString("TCP Retransmit Skb")),
       ret_arg_id_(context_->storage->InternString("ret")),
       direct_reclaim_nr_reclaimed_id_(
           context->storage->InternString("direct_reclaim_nr_reclaimed")),
@@ -583,7 +591,7 @@
         break;
       }
       case FtraceEvent::kWorkqueueExecuteStartFieldNumber: {
-        ParseWorkqueueExecuteStart(ts, pid, data, seq_state);
+        ParseWorkqueueExecuteStart(cpu, ts, pid, data, seq_state);
         break;
       }
       case FtraceEvent::kWorkqueueExecuteEndFieldNumber: {
@@ -650,6 +658,14 @@
         ParseNetDevXmit(cpu, ts, data);
         break;
       }
+      case FtraceEvent::kInetSockSetStateFieldNumber: {
+        ParseInetSockSetState(ts, pid, data);
+        break;
+      }
+      case FtraceEvent::kTcpRetransmitSkbFieldNumber: {
+        ParseTcpRetransmitSkb(ts, data);
+        break;
+      }
       default:
         break;
     }
@@ -1416,6 +1432,7 @@
 }
 
 void FtraceParser::ParseWorkqueueExecuteStart(
+    uint32_t cpu,
     int64_t timestamp,
     uint32_t pid,
     ConstBytes blob,
@@ -1438,7 +1455,12 @@
 
   UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
   TrackId track = context_->track_tracker->InternThreadTrack(utid);
-  context_->slice_tracker->Begin(timestamp, track, workqueue_id_, name_id);
+
+  auto args_inserter = [this, cpu](ArgsTracker::BoundInserter* inserter) {
+    inserter->AddArg(cpu_id_, Variadic::Integer(cpu));
+  };
+  context_->slice_tracker->Begin(timestamp, track, workqueue_id_, name_id,
+                                 args_inserter);
 }
 
 void FtraceParser::ParseWorkqueueExecuteEnd(int64_t timestamp,
@@ -1710,5 +1732,82 @@
       .AddArg(len_key, Variadic::UnsignedInteger(evt.len()));
 }
 
+void FtraceParser::ParseInetSockSetState(int64_t timestamp,
+                                         uint32_t pid,
+                                         protozero::ConstBytes blob) {
+  protos::pbzero::InetSockSetStateFtraceEvent::Decoder evt(blob.data,
+                                                           blob.size);
+
+  // Skip non TCP protocol.
+  if (evt.protocol() != kIpprotoTcp) {
+    PERFETTO_ELOG("skip non tcp protocol");
+    return;
+  }
+
+  // Skip non IP protocol.
+  if (evt.family() != kAfNet && evt.family() != kAfNet6) {
+    PERFETTO_ELOG("skip non IP protocol");
+    return;
+  }
+
+  // Skip invalid TCP state.
+  if (evt.newstate() >= TCP_MAX_STATES || evt.oldstate() >= TCP_MAX_STATES) {
+    PERFETTO_ELOG("skip invalid tcp state");
+    return;
+  }
+
+  auto got = skaddr_to_stream_.find(evt.skaddr());
+  if (got == skaddr_to_stream_.end()) {
+    skaddr_to_stream_[evt.skaddr()] = ++num_of_tcp_stream_;
+  }
+  uint32_t stream = skaddr_to_stream_[evt.skaddr()];
+  char stream_str[64];
+  sprintf(stream_str, "TCP stream#%" PRIu32 "", stream);
+  StringId stream_id = context_->storage->InternString(stream_str);
+
+  StringId slice_name_id;
+  if (evt.newstate() == TCP_SYN_SENT) {
+    base::StackString<32> str("%s(pid=%" PRIu32 ")",
+                              kTcpStateNames[evt.newstate()], pid);
+    slice_name_id = context_->storage->InternString(str.string_view());
+  } else if (evt.newstate() == TCP_ESTABLISHED) {
+    base::StackString<64> str("%s(sport=%" PRIu32 ",dport=%" PRIu32 ")",
+                              kTcpStateNames[evt.newstate()], evt.sport(),
+                              evt.dport());
+    slice_name_id = context_->storage->InternString(str.string_view());
+  } else {
+    base::StringView slice_name = kTcpStateNames[evt.newstate()];
+    slice_name_id = context_->storage->InternString(slice_name);
+  }
+
+  // Push to async task set tracker.
+  auto async_track =
+      context_->async_track_set_tracker->InternGlobalTrackSet(stream_id);
+  TrackId end_id = context_->async_track_set_tracker->End(
+      async_track, static_cast<int64_t>(evt.skaddr()));
+  context_->slice_tracker->End(timestamp, end_id);
+  TrackId start_id = context_->async_track_set_tracker->Begin(
+      async_track, static_cast<int64_t>(evt.skaddr()));
+  context_->slice_tracker->Begin(timestamp, start_id, tcp_state_id_,
+                                 slice_name_id);
+}
+
+void FtraceParser::ParseTcpRetransmitSkb(int64_t timestamp,
+                                         protozero::ConstBytes blob) {
+  protos::pbzero::TcpRetransmitSkbFtraceEvent::Decoder evt(blob.data,
+                                                           blob.size);
+
+  // Push event as instant to async task set tracker.
+  auto async_track = context_->async_track_set_tracker->InternGlobalTrackSet(
+      tcp_retransmited_name_id_);
+  base::StackString<64> str("sport=%" PRIu32 ",dport=%" PRIu32 "", evt.sport(),
+                            evt.dport());
+  StringId slice_name_id = context_->storage->InternString(str.string_view());
+  TrackId track_id =
+      context_->async_track_set_tracker->Scoped(async_track, timestamp, 0);
+  context_->slice_tracker->Scoped(timestamp, track_id, tcp_event_id_,
+                                  slice_name_id, 0);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index c9ae2c5..6863cac 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -125,7 +125,8 @@
   void ParseDirectReclaimEnd(int64_t timestamp,
                              uint32_t pid,
                              protozero::ConstBytes);
-  void ParseWorkqueueExecuteStart(int64_t timestamp,
+  void ParseWorkqueueExecuteStart(uint32_t cpu,
+                                  int64_t timestamp,
                                   uint32_t pid,
                                   protozero::ConstBytes,
                                   PacketSequenceStateGeneration* seq_state);
@@ -156,12 +157,17 @@
                             int64_t timestamp,
                             protozero::ConstBytes);
   void ParseNetDevXmit(uint32_t cpu, int64_t timestamp, protozero::ConstBytes);
+  void ParseInetSockSetState(int64_t timestamp,
+                             uint32_t pid,
+                             protozero::ConstBytes);
+  void ParseTcpRetransmitSkb(int64_t timestamp, protozero::ConstBytes);
 
   TraceProcessorContext* context_;
   RssStatTracker rss_stat_tracker_;
 
   const StringId sched_wakeup_name_id_;
   const StringId sched_waking_name_id_;
+  const StringId cpu_id_;
   const StringId cpu_freq_name_id_;
   const StringId gpu_freq_name_id_;
   const StringId cpu_idle_name_id_;
@@ -182,6 +188,9 @@
   const StringId oom_kill_id_;
   const StringId workqueue_id_;
   const StringId irq_id_;
+  const StringId tcp_state_id_;
+  const StringId tcp_event_id_;
+  const StringId tcp_retransmited_name_id_;
   const StringId ret_arg_id_;
   const StringId direct_reclaim_nr_reclaimed_id_;
   const StringId direct_reclaim_order_id_;
@@ -228,6 +237,12 @@
   // Record number of transmitted bytes to the network interface card.
   std::unordered_map<StringId, uint64_t> nic_transmitted_bytes_;
 
+  // Keep sock to stream number mapping.
+  std::unordered_map<uint64_t, uint32_t> skaddr_to_stream_;
+
+  // Record number of tcp steams.
+  uint32_t num_of_tcp_stream_ = 0;
+
   bool has_seen_first_ftrace_packet_ = false;
 
   // Stores information about the timestamp from the metadata table which is
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker.cc b/src/trace_processor/importers/proto/async_track_set_tracker.cc
index 4f2714c..c45148c 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker.cc
+++ b/src/trace_processor/importers/proto/async_track_set_tracker.cc
@@ -17,13 +17,15 @@
 #include "src/trace_processor/importers/proto/async_track_set_tracker.h"
 
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 namespace perfetto {
 namespace trace_processor {
 
 AsyncTrackSetTracker::AsyncTrackSetTracker(TraceProcessorContext* context)
-    : context_(context) {}
+    : android_source_(context->storage->InternString("android")),
+      context_(context) {}
 
 AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternGlobalTrackSet(
     StringId name) {
@@ -42,45 +44,45 @@
   return global_track_set_ids_[name] = id;
 }
 
-AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternAndroidSet(
+AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternProcessTrackSet(
     UniquePid upid,
     StringId name) {
-  AndroidTuple tuple{upid, name};
+  ProcessTuple tuple{upid, name};
 
-  auto it = android_track_set_ids_.find(tuple);
-  if (it != android_track_set_ids_.end())
+  auto it = process_track_set_ids_.find(tuple);
+  if (it != process_track_set_ids_.end())
     return it->second;
 
   uint32_t id = static_cast<uint32_t>(track_sets_.size());
 
   TrackSet set;
-  set.android_tuple = tuple;
-  set.type = TrackSetType::kAndroid;
-  set.nesting_behaviour = NestingBehaviour::kLegacySaturatingUnnestable;
-  track_sets_.emplace_back(set);
-
-  android_track_set_ids_[tuple] = id;
-  return id;
-}
-
-AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternFrameTimelineSet(
-    UniquePid upid,
-    StringId name) {
-  FrameTimelineTuple tuple{upid, name};
-
-  auto it = frame_timeline_track_set_ids_.find(tuple);
-  if (it != frame_timeline_track_set_ids_.end())
-    return it->second;
-
-  uint32_t id = static_cast<uint32_t>(track_sets_.size());
-
-  TrackSet set;
-  set.frame_timeline_tuple = tuple;
-  set.type = TrackSetType::kFrameTimeline;
+  set.process_tuple = tuple;
+  set.type = TrackSetType::kProcess;
   set.nesting_behaviour = NestingBehaviour::kUnnestable;
   track_sets_.emplace_back(set);
 
-  frame_timeline_track_set_ids_[tuple] = id;
+  process_track_set_ids_[tuple] = id;
+  return id;
+}
+
+AsyncTrackSetTracker::TrackSetId
+AsyncTrackSetTracker::InternAndroidLegacyUnnestableTrackSet(UniquePid upid,
+                                                            StringId name) {
+  ProcessTuple tuple{upid, name};
+
+  auto it = android_legacy_unnestable_track_set_ids_.find(tuple);
+  if (it != android_legacy_unnestable_track_set_ids_.end())
+    return it->second;
+
+  uint32_t id = static_cast<uint32_t>(track_sets_.size());
+
+  TrackSet set;
+  set.process_tuple = tuple;
+  set.type = TrackSetType::kAndroidLegacyUnnestable;
+  set.nesting_behaviour = NestingBehaviour::kLegacySaturatingUnnestable;
+  track_sets_.emplace_back(set);
+
+  android_legacy_unnestable_track_set_ids_[tuple] = id;
   return id;
 }
 
@@ -178,14 +180,18 @@
 TrackId AsyncTrackSetTracker::CreateTrackForSet(const TrackSet& set) {
   switch (set.type) {
     case TrackSetType::kGlobal:
+      // TODO(lalitm): propogate source from callers rather than just passing
+      // kNullStringId here.
       return context_->track_tracker->CreateGlobalAsyncTrack(
-          set.global_track_name);
-    case TrackSetType::kAndroid:
-      return context_->track_tracker->CreateAndroidAsyncTrack(
-          set.android_tuple.name, set.android_tuple.upid);
-    case TrackSetType::kFrameTimeline:
-      return context_->track_tracker->CreateFrameTimelineAsyncTrack(
-          set.frame_timeline_tuple.name, set.frame_timeline_tuple.upid);
+          set.global_track_name, kNullStringId);
+    case TrackSetType::kProcess:
+      // TODO(lalitm): propogate source from callers rather than just passing
+      // kNullStringId here.
+      return context_->track_tracker->CreateProcessAsyncTrack(
+          set.process_tuple.name, set.process_tuple.upid, kNullStringId);
+    case TrackSetType::kAndroidLegacyUnnestable:
+      return context_->track_tracker->CreateProcessAsyncTrack(
+          set.process_tuple.name, set.process_tuple.upid, android_source_);
   }
   PERFETTO_FATAL("For GCC");
 }
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker.h b/src/trace_processor/importers/proto/async_track_set_tracker.h
index 6e7fa81..83fd95c 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker.h
+++ b/src/trace_processor/importers/proto/async_track_set_tracker.h
@@ -42,7 +42,7 @@
 // The intended usage of this class is for callers to first call one of the
 // Intern* methods to obtain a TrackSetId followed by Begin/End just before
 // calling into SliceTracker's Begin/End respectively. For example:
-//  TrackSetId set_id = track_set_tracker->InternAndroidSet(upid, name);
+//  TrackSetId set_id = track_set_tracker->InternProcessTrackSet(upid, name);
 //  if (event.begin) {
 //    TrackId id = track_set_tracker->Begin(set_id, cookie);
 //    slice_tracker->Begin(ts, id, ...)
@@ -61,18 +61,18 @@
   // Interns a set of global async slice tracks associated with the given name.
   TrackSetId InternGlobalTrackSet(StringId name);
 
-  // Interns a set of Android async slice tracks associated with the given
-  // upid and name.
+  // Interns a set of process async slice tracks associated with the given name
+  // and upid.
+  TrackSetId InternProcessTrackSet(UniquePid, StringId name);
+
+  // Interns a set of Android legacy unnesteable async slice tracks
+  // associated with the given upid and name.
   // Scoped is *not* supported for this track set type.
-  TrackSetId InternAndroidSet(UniquePid, StringId name);
+  TrackSetId InternAndroidLegacyUnnestableTrackSet(UniquePid, StringId name);
 
   // Starts a new slice on the given async track set which has the given cookie.
   TrackId Begin(TrackSetId id, int64_t cookie);
 
-  // Interns the expected and actual timeline tracks coming from FrameTimeline
-  // producer for the associated upid.
-  TrackSetId InternFrameTimelineSet(UniquePid, StringId name);
-
   // Ends a new slice on the given async track set which has the given cookie.
   TrackId End(TrackSetId id, int64_t cookie);
 
@@ -86,21 +86,11 @@
  private:
   friend class AsyncTrackSetTrackerUnittest;
 
-  struct AndroidTuple {
+  struct ProcessTuple {
     UniquePid upid;
     StringId name;
 
-    friend bool operator<(const AndroidTuple& l, const AndroidTuple& r) {
-      return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
-    }
-  };
-
-  struct FrameTimelineTuple {
-    UniquePid upid;
-    StringId name;
-
-    friend bool operator<(const FrameTimelineTuple& l,
-                          const FrameTimelineTuple& r) {
+    friend bool operator<(const ProcessTuple& l, const ProcessTuple& r) {
       return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
     }
   };
@@ -125,8 +115,8 @@
 
   enum class TrackSetType {
     kGlobal,
-    kAndroid,
-    kFrameTimeline,
+    kProcess,
+    kAndroidLegacyUnnestable,
   };
 
   struct TrackState {
@@ -150,11 +140,11 @@
   struct TrackSet {
     TrackSetType type;
     union {
+      // Only set when |type| == |TrackSetType::kGlobal|.
       StringId global_track_name;
-      // Only set when |type| == |TrackSetType::kAndroid|.
-      AndroidTuple android_tuple;
-      // Only set when |type| == |TrackSetType::kFrameTimeline|.
-      FrameTimelineTuple frame_timeline_tuple;
+      // Only set when |type| == |TrackSetType::kFrameTimeline| or
+      // |TrackSetType::kAndroidLegacyUnnestable|.
+      ProcessTuple process_tuple;
     };
     NestingBehaviour nesting_behaviour;
     std::vector<TrackState> tracks;
@@ -162,8 +152,8 @@
 
   TrackSetId CreateUnnestableTrackSetForTesting(UniquePid upid, StringId name) {
     AsyncTrackSetTracker::TrackSet set;
-    set.android_tuple = AndroidTuple{upid, name};
-    set.type = AsyncTrackSetTracker::TrackSetType::kAndroid;
+    set.process_tuple = ProcessTuple{upid, name};
+    set.type = AsyncTrackSetTracker::TrackSetType::kAndroidLegacyUnnestable;
     set.nesting_behaviour = NestingBehaviour::kUnnestable;
     track_sets_.emplace_back(set);
     return static_cast<TrackSetId>(track_sets_.size() - 1);
@@ -180,10 +170,12 @@
   TrackId CreateTrackForSet(const TrackSet& set);
 
   std::map<StringId, TrackSetId> global_track_set_ids_;
-  std::map<AndroidTuple, TrackSetId> android_track_set_ids_;
-  std::map<FrameTimelineTuple, TrackSetId> frame_timeline_track_set_ids_;
+  std::map<ProcessTuple, TrackSetId> process_track_set_ids_;
+  std::map<ProcessTuple, TrackSetId> android_legacy_unnestable_track_set_ids_;
   std::vector<TrackSet> track_sets_;
 
+  const StringId android_source_ = kNullStringId;
+
   TraceProcessorContext* const context_;
 };
 
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
index 852c5c2..eb2e041 100644
--- a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
@@ -39,8 +39,8 @@
 
     unnestable_id_ = tracker_->CreateUnnestableTrackSetForTesting(
         1, storage_->InternString("test"));
-    legacy_unnestable_id_ =
-        tracker_->InternAndroidSet(2, storage_->InternString("test"));
+    legacy_unnestable_id_ = tracker_->InternAndroidLegacyUnnestableTrackSet(
+        2, storage_->InternString("test"));
   }
 
  protected:
@@ -57,7 +57,8 @@
 namespace {
 
 TEST_F(AsyncTrackSetTrackerUnittest, Smoke) {
-  auto set_id = tracker_->InternAndroidSet(1, storage_->InternString("test"));
+  auto set_id = tracker_->InternAndroidLegacyUnnestableTrackSet(
+      1, storage_->InternString("test"));
 
   auto begin = tracker_->Begin(set_id, 1);
   auto end = tracker_->End(set_id, 1);
diff --git a/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc b/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
index 67184e5..77dc9ec 100644
--- a/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
+++ b/src/trace_processor/importers/proto/flamegraph_construction_algorithms.cc
@@ -372,7 +372,7 @@
   }
 
   for (auto it = threads_in_pid_rm.IterateRows(); it; it.Next()) {
-    utids.insert(storage->thread_table().id()[it.row()]);
+    utids.insert(storage->thread_table().id()[it.index()]);
   }
 
   // 3.Get all row indices in perf_sample that correspond to the requested utids
diff --git a/src/trace_processor/importers/proto/frame_timeline_event_parser.cc b/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
index 70efab4..d03dc73 100644
--- a/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
+++ b/src/trace_processor/importers/proto/frame_timeline_event_parser.cc
@@ -92,20 +92,29 @@
 }
 
 static bool DisplayFrameJanky(int32_t jank_type) {
-  if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED || jank_type == FrameTimelineEvent::JANK_NONE)
+  if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED ||
+      jank_type == FrameTimelineEvent::JANK_NONE)
     return false;
 
-  int32_t display_frame_jank_bitmask = FrameTimelineEvent::JANK_SF_SCHEDULING | FrameTimelineEvent::JANK_PREDICTION_ERROR | FrameTimelineEvent::JANK_DISPLAY_HAL | FrameTimelineEvent::JANK_SF_CPU_DEADLINE_MISSED | FrameTimelineEvent::JANK_SF_GPU_DEADLINE_MISSED;
+  int32_t display_frame_jank_bitmask =
+      FrameTimelineEvent::JANK_SF_SCHEDULING |
+      FrameTimelineEvent::JANK_PREDICTION_ERROR |
+      FrameTimelineEvent::JANK_DISPLAY_HAL |
+      FrameTimelineEvent::JANK_SF_CPU_DEADLINE_MISSED |
+      FrameTimelineEvent::JANK_SF_GPU_DEADLINE_MISSED;
   if (jank_type & display_frame_jank_bitmask)
-      return true;
+    return true;
   return false;
 }
 
 static bool SurfaceFrameJanky(int32_t jank_type) {
-  if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED || jank_type == FrameTimelineEvent::JANK_NONE)
+  if (jank_type == FrameTimelineEvent::JANK_UNSPECIFIED ||
+      jank_type == FrameTimelineEvent::JANK_NONE)
     return false;
 
-  int32_t surface_frame_jank_bitmask = FrameTimelineEvent::JANK_APP_DEADLINE_MISSED | FrameTimelineEvent::JANK_UNKNOWN;
+  int32_t surface_frame_jank_bitmask =
+      FrameTimelineEvent::JANK_APP_DEADLINE_MISSED |
+      FrameTimelineEvent::JANK_UNKNOWN;
   if (jank_type & surface_frame_jank_bitmask)
     return true;
   return false;
@@ -206,7 +215,7 @@
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
       static_cast<uint32_t>(event.pid()));
   auto expected_track_set_id =
-      context_->async_track_set_tracker->InternFrameTimelineSet(
+      context_->async_track_set_tracker->InternProcessTrackSet(
           upid, expected_timeline_track_name_);
   cookie_track_set_id_map_[cookie] = expected_track_set_id;
 
@@ -255,7 +264,7 @@
   UniquePid upid = context_->process_tracker->GetOrCreateProcess(
       static_cast<uint32_t>(event.pid()));
   auto actual_track_set_id =
-      context_->async_track_set_tracker->InternFrameTimelineSet(
+      context_->async_track_set_tracker->InternProcessTrackSet(
           upid, actual_timeline_track_name_);
   cookie_track_set_id_map_[cookie] = actual_track_set_id;
 
@@ -291,22 +300,21 @@
     actual_row.jank_tag = jank_tag_none_id_;
   }
 
-  base::Optional<SliceId> opt_slice_id =
-      context_->slice_tracker->BeginTyped(
-          context_->storage->mutable_actual_frame_timeline_slice_table(),
-          actual_row,
-          [this, token, jank_type, present_type, prediction_type,
-           &event](ArgsTracker::BoundInserter* inserter) {
-            inserter->AddArg(display_frame_token_id_, Variadic::Integer(token));
-            inserter->AddArg(present_type_id_, Variadic::String(present_type));
-            inserter->AddArg(on_time_finish_id_,
-                             Variadic::Integer(event.on_time_finish()));
-            inserter->AddArg(gpu_composition_id_,
-                             Variadic::Integer(event.gpu_composition()));
-            inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
-            inserter->AddArg(prediction_type_id_,
-                             Variadic::String(prediction_type));
-          });
+  base::Optional<SliceId> opt_slice_id = context_->slice_tracker->BeginTyped(
+      context_->storage->mutable_actual_frame_timeline_slice_table(),
+      actual_row,
+      [this, token, jank_type, present_type, prediction_type,
+       &event](ArgsTracker::BoundInserter* inserter) {
+        inserter->AddArg(display_frame_token_id_, Variadic::Integer(token));
+        inserter->AddArg(present_type_id_, Variadic::String(present_type));
+        inserter->AddArg(on_time_finish_id_,
+                         Variadic::Integer(event.on_time_finish()));
+        inserter->AddArg(gpu_composition_id_,
+                         Variadic::Integer(event.gpu_composition()));
+        inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
+        inserter->AddArg(prediction_type_id_,
+                         Variadic::String(prediction_type));
+      });
 
   // SurfaceFrames will always be parsed before the matching DisplayFrame
   // (since the app works on the frame before SurfaceFlinger does). Because
@@ -380,7 +388,7 @@
       context_->storage->InternString(base::StringView(std::to_string(token)));
 
   auto expected_track_set_id =
-      context_->async_track_set_tracker->InternFrameTimelineSet(
+      context_->async_track_set_tracker->InternProcessTrackSet(
           upid, expected_timeline_track_name_);
   cookie_track_set_id_map_[cookie] = expected_track_set_id;
 
@@ -446,7 +454,7 @@
       context_->storage->InternString(base::StringView(std::to_string(token)));
 
   auto actual_track_set_id =
-      context_->async_track_set_tracker->InternFrameTimelineSet(
+      context_->async_track_set_tracker->InternProcessTrackSet(
           upid, actual_timeline_track_name_);
   cookie_track_set_id_map_[cookie] = actual_track_set_id;
 
@@ -498,27 +506,26 @@
       is_buffer = context_->storage->InternString("No");
   }
 
-  base::Optional<SliceId> opt_slice_id =
-      context_->slice_tracker->BeginTyped(
-          context_->storage->mutable_actual_frame_timeline_slice_table(),
-          actual_row,
-          [this, jank_type, present_type, token, layer_name_id,
-           display_frame_token, prediction_type, is_buffer,
-           &event](ArgsTracker::BoundInserter* inserter) {
-            inserter->AddArg(surface_frame_token_id_, Variadic::Integer(token));
-            inserter->AddArg(display_frame_token_id_,
-                             Variadic::Integer(display_frame_token));
-            inserter->AddArg(layer_name_id_, Variadic::String(layer_name_id));
-            inserter->AddArg(present_type_id_, Variadic::String(present_type));
-            inserter->AddArg(on_time_finish_id_,
-                             Variadic::Integer(event.on_time_finish()));
-            inserter->AddArg(gpu_composition_id_,
-                             Variadic::Integer(event.gpu_composition()));
-            inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
-            inserter->AddArg(prediction_type_id_,
-                             Variadic::String(prediction_type));
-            inserter->AddArg(is_buffer_id_, Variadic::String(is_buffer));
-          });
+  base::Optional<SliceId> opt_slice_id = context_->slice_tracker->BeginTyped(
+      context_->storage->mutable_actual_frame_timeline_slice_table(),
+      actual_row,
+      [this, jank_type, present_type, token, layer_name_id, display_frame_token,
+       prediction_type, is_buffer,
+       &event](ArgsTracker::BoundInserter* inserter) {
+        inserter->AddArg(surface_frame_token_id_, Variadic::Integer(token));
+        inserter->AddArg(display_frame_token_id_,
+                         Variadic::Integer(display_frame_token));
+        inserter->AddArg(layer_name_id_, Variadic::String(layer_name_id));
+        inserter->AddArg(present_type_id_, Variadic::String(present_type));
+        inserter->AddArg(on_time_finish_id_,
+                         Variadic::Integer(event.on_time_finish()));
+        inserter->AddArg(gpu_composition_id_,
+                         Variadic::Integer(event.gpu_composition()));
+        inserter->AddArg(jank_type_id_, Variadic::String(jank_type));
+        inserter->AddArg(prediction_type_id_,
+                         Variadic::String(prediction_type));
+        inserter->AddArg(is_buffer_id_, Variadic::String(is_buffer));
+      });
 
   if (opt_slice_id) {
     display_token_to_surface_slice_.emplace(display_frame_token, *opt_slice_id);
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 182265e..f789511 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -758,14 +758,14 @@
       class_tbl.FilterToRowMap({class_tbl.name().eq("sun.misc.Cleaner")});
   for (auto class_it = cleaner_classes.IterateRows(); class_it;
        class_it.Next()) {
-    auto class_id = class_tbl.id()[class_it.row()];
+    auto class_id = class_tbl.id()[class_it.index()];
     auto cleaner_objs = objects_tbl.FilterToRowMap(
         {objects_tbl.type_id().eq(class_id.value),
          objects_tbl.upid().eq(seq.current_upid),
          objects_tbl.graph_sample_ts().eq(seq.current_ts)});
     for (auto obj_it = cleaner_objs.IterateRows(); obj_it; obj_it.Next()) {
       tables::HeapGraphObjectTable::Id cleaner_obj_id =
-          objects_tbl.id()[obj_it.row()];
+          objects_tbl.id()[obj_it.index()];
       base::Optional<tables::HeapGraphObjectTable::Id> referent_id =
           GetReferenceByFieldName(cleaner_obj_id, referent_str_id_);
       base::Optional<tables::HeapGraphObjectTable::Id> thunk_id =
diff --git a/src/trace_processor/importers/proto/perf_sample_tracker.cc b/src/trace_processor/importers/proto/perf_sample_tracker.cc
index 042f33c..4da16ad 100644
--- a/src/trace_processor/importers/proto/perf_sample_tracker.cc
+++ b/src/trace_processor/importers/proto/perf_sample_tracker.cc
@@ -36,14 +36,49 @@
 const char* StringifyCounter(int32_t counter) {
   using protos::pbzero::PerfEvents;
   switch (counter) {
-    case (PerfEvents::SW_CPU_CLOCK):
+    // software:
+    case PerfEvents::SW_CPU_CLOCK:
       return "cpu-clock";
-    case (PerfEvents::SW_PAGE_FAULTS):
+    case PerfEvents::SW_PAGE_FAULTS:
       return "page-faults";
-    case (PerfEvents::HW_CPU_CYCLES):
+    case PerfEvents::SW_TASK_CLOCK:
+      return "task-clock";
+    case PerfEvents::SW_CONTEXT_SWITCHES:
+      return "context-switches";
+    case PerfEvents::SW_CPU_MIGRATIONS:
+      return "cpu-migrations";
+    case PerfEvents::SW_PAGE_FAULTS_MIN:
+      return "minor-faults";
+    case PerfEvents::SW_PAGE_FAULTS_MAJ:
+      return "major-faults";
+    case PerfEvents::SW_ALIGNMENT_FAULTS:
+      return "alignment-faults";
+    case PerfEvents::SW_EMULATION_FAULTS:
+      return "emulation-faults";
+    case PerfEvents::SW_DUMMY:
+      return "dummy";
+    // hardware:
+    case PerfEvents::HW_CPU_CYCLES:
       return "cpu-cycles";
-    case (PerfEvents::HW_INSTRUCTIONS):
+    case PerfEvents::HW_INSTRUCTIONS:
       return "instructions";
+    case PerfEvents::HW_CACHE_REFERENCES:
+      return "cache-references";
+    case PerfEvents::HW_CACHE_MISSES:
+      return "cache-misses";
+    case PerfEvents::HW_BRANCH_INSTRUCTIONS:
+      return "branch-instructions";
+    case PerfEvents::HW_BRANCH_MISSES:
+      return "branch-misses";
+    case PerfEvents::HW_BUS_CYCLES:
+      return "bus-cycles";
+    case PerfEvents::HW_STALLED_CYCLES_FRONTEND:
+      return "stalled-cycles-frontend";
+    case PerfEvents::HW_STALLED_CYCLES_BACKEND:
+      return "stalled-cycles-backend";
+    case PerfEvents::HW_REF_CPU_CYCLES:
+      return "ref-cycles";
+
     default:
       break;
   }
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index ce94e27..d98af2d 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -283,9 +283,9 @@
     RowMap rm = args.FilterToRowMap({args.arg_set_id().eq(set_id)});
     bool found = false;
     for (auto it = rm.IterateRows(); it; it.Next()) {
-      if (args.key()[it.row()] == key_id) {
-        EXPECT_EQ(args.flat_key()[it.row()], key_id);
-        if (storage_->GetArgValue(it.row()) == value) {
+      if (args.key()[it.index()] == key_id) {
+        EXPECT_EQ(args.flat_key()[it.index()], key_id);
+        if (storage_->GetArgValue(it.index()) == value) {
           found = true;
           break;
         }
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index 1eca41c..7e9accd 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -23,6 +23,7 @@
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/async_track_set_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -61,7 +62,7 @@
                                     int64_t value) {
   systrace_utils::SystraceTracePoint point{};
   point.name = name;
-  point.value = value;
+  point.int_value = value;
 
   // Hardcode the tgid to 0 (i.e. no tgid available) because zero events can
   // come from kernel threads and as we group kernel threads into the kthreadd
@@ -110,7 +111,7 @@
   // the UI.
   point.tgid = 0;
 
-  point.value = value;
+  point.int_value = value;
   // Some versions of this trace point fill trace_type with one of (B/E/C),
   // others use the trace_begin boolean and only support begin/end events:
   if (trace_type == 0) {
@@ -129,15 +130,16 @@
     int64_t ts,
     uint32_t pid,
     systrace_utils::SystraceTracePoint point) {
+  auto get_utid = [pid, &point, this]() {
+    if (point.tgid == 0)
+      return context_->process_tracker->GetOrCreateThread(pid);
+    return context_->process_tracker->UpdateThread(pid, point.tgid);
+  };
+
   switch (point.phase) {
     case 'B': {
       StringId name_id = context_->storage->InternString(point.name);
-      UniqueTid utid;
-      if (point.tgid == 0) {
-        utid = context_->process_tracker->GetOrCreateThread(pid);
-      } else {
-        utid = context_->process_tracker->UpdateThread(pid, point.tgid);
-      }
+      UniqueTid utid = get_utid();
       TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
       context_->slice_tracker->Begin(ts, track_id, kNullStringId /* cat */,
                                      name_id);
@@ -167,12 +169,13 @@
     case 'S':
     case 'F': {
       StringId name_id = context_->storage->InternString(point.name);
-      int64_t cookie = point.value;
+      int64_t cookie = point.int_value;
       UniquePid upid =
           context_->process_tracker->GetOrCreateProcess(point.tgid);
 
       auto track_set_id =
-          context_->async_track_set_tracker->InternAndroidSet(upid, name_id);
+          context_->async_track_set_tracker
+              ->InternAndroidLegacyUnnestableTrackSet(upid, name_id);
 
       if (point.phase == 'S') {
         // Historically, async slices on Android did not support nesting async
@@ -188,7 +191,7 @@
         // the *most recent* emitted 'S' event which leads even more inaccurate
         // behaviour. To support these quirks, we have the special 'unnestable'
         // slice concept which implements workarounds for these very specific
-        // issues. No other code should ever use this method.
+        // issues. No other code should ever use |BeginLegacyUnnestable|.
         tables::SliceTable::Row row;
         row.ts = ts;
         row.track_id =
@@ -206,6 +209,28 @@
       break;
     }
 
+    case 'I': {
+      StringId name_id = context_->storage->InternString(point.name);
+      UniqueTid utid = get_utid();
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
+      break;
+    }
+
+    case 'N': {
+      StringId name_id = context_->storage->InternString(point.name);
+      StringId track_name_id = context_->storage->InternString(point.str_value);
+      UniquePid upid =
+          context_->process_tracker->GetOrCreateProcess(point.tgid);
+      auto track_set_id =
+          context_->async_track_set_tracker->InternProcessTrackSet(
+              upid, track_name_id);
+      TrackId track_id =
+          context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
+      context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
+      break;
+    }
+
     case 'C': {
       // LMK events from userspace are hacked as counter events with the "value"
       // of the counter representing the pid of the killed process which is
@@ -213,7 +238,7 @@
       // Homogenise this with kernel LMK events as an instant event, ignoring
       // the resets to 0.
       if (point.name == "kill_one_process") {
-        auto killed_pid = static_cast<uint32_t>(point.value);
+        auto killed_pid = static_cast<uint32_t>(point.int_value);
         if (killed_pid != 0) {
           UniquePid killed_upid =
               context_->process_tracker->GetOrCreateProcess(killed_pid);
@@ -227,7 +252,7 @@
         TrackId track =
             context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
         context_->event_tracker->PushCounter(
-            ts, static_cast<double>(point.value), track);
+            ts, static_cast<double>(point.int_value), track);
         return;
       }
 
@@ -247,8 +272,8 @@
         track_id =
             context_->track_tracker->InternProcessCounterTrack(name_id, upid);
       }
-      context_->event_tracker->PushCounter(ts, static_cast<double>(point.value),
-                                           track_id);
+      context_->event_tracker->PushCounter(
+          ts, static_cast<double>(point.int_value), track_id);
     }
   }
 }
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index df7b92f..c6e506a 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -39,33 +39,48 @@
   SystraceTracePoint() {}
 
   static SystraceTracePoint B(uint32_t tgid, base::StringView name) {
-    return SystraceTracePoint('B', tgid, std::move(name), 0);
+    return SystraceTracePoint('B', tgid, std::move(name), 0, {});
   }
 
   static SystraceTracePoint E(uint32_t tgid) {
-    return SystraceTracePoint('E', tgid, {}, 0);
+    return SystraceTracePoint('E', tgid, {}, 0, {});
   }
 
   static SystraceTracePoint C(uint32_t tgid,
                               base::StringView name,
                               int64_t value) {
-    return SystraceTracePoint('C', tgid, std::move(name), value);
+    return SystraceTracePoint('C', tgid, std::move(name), value, {});
   }
 
   static SystraceTracePoint S(uint32_t tgid,
                               base::StringView name,
                               int64_t cookie) {
-    return SystraceTracePoint('S', tgid, std::move(name), cookie);
+    return SystraceTracePoint('S', tgid, std::move(name), cookie, {});
   }
 
   static SystraceTracePoint F(uint32_t tgid,
                               base::StringView name,
                               int64_t cookie) {
-    return SystraceTracePoint('F', tgid, std::move(name), cookie);
+    return SystraceTracePoint('F', tgid, std::move(name), cookie, {});
   }
 
-  SystraceTracePoint(char p, uint32_t tg, base::StringView n, int64_t v)
-      : phase(p), tgid(tg), name(std::move(n)), value(v) {}
+  static SystraceTracePoint I(uint32_t tgid, base::StringView name) {
+    return SystraceTracePoint('I', tgid, std::move(name), 0, {});
+  }
+
+  static SystraceTracePoint N(uint32_t tgid,
+                              base::StringView track_name,
+                              base::StringView name) {
+    return SystraceTracePoint('N', tgid, std::move(name), 0,
+                              std::move(track_name));
+  }
+
+  SystraceTracePoint(char p,
+                     uint32_t tg,
+                     base::StringView n,
+                     int64_t v,
+                     base::StringView s)
+      : phase(p), tgid(tg), name(std::move(n)), int_value(v), str_value(s) {}
 
   // Phase can be one of B, E, C, S, F.
   char phase = '\0';
@@ -76,13 +91,17 @@
   base::StringView name;
 
   // For phase = 'C' (counter value) and 'B', 'F' (async cookie).
-  int64_t value = 0;
+  int64_t int_value = 0;
+
+  // For phase = 'N' (instant on track)
+  base::StringView str_value;
 
   // Visible for unittesting.
   friend std::ostream& operator<<(std::ostream& os,
                                   const SystraceTracePoint& point) {
     return os << "SystraceTracePoint{'" << point.phase << "', " << point.tgid
-              << ", \"" << point.name.ToStdString() << "\", " << point.value
+              << ", \"" << point.name.ToStdString() << "\", " << point.int_value
+              << ", \"" << point.str_value.ToStdString() << "\""
               << "}";
   }
 };
@@ -97,6 +116,8 @@
 // Counters emitted by chromium can have a further "category group" appended
 // ("Blob" in the example below). We ignore the category group.
 // 7. C|3209|TransfersBytesPendingOnDisk-value|0|Blob
+// 8. I|4820|instant
+// 9. N|1938|track_name|instant_name
 inline SystraceParseResult ParseSystraceTracePoint(
     base::StringView str_untrimmed,
     SystraceTracePoint* out) {
@@ -164,7 +185,26 @@
         return SystraceParseResult::kFailure;
       }
       out->name = f2_name;
-      out->value = *maybe_cookie;
+      out->int_value = *maybe_cookie;
+      return SystraceParseResult::kSuccess;
+    }
+    case 'I': {  // Instant.
+      auto f2_name = read_next_field();
+      if (PERFETTO_UNLIKELY(!has_tgid || f2_name.empty())) {
+        return SystraceParseResult::kFailure;
+      }
+      out->name = f2_name;
+      return SystraceParseResult::kSuccess;
+    }
+    case 'N': {  // Instant on track.
+      auto f2_track_name = read_next_field();
+      auto f3_name = read_next_field();
+      if (PERFETTO_UNLIKELY(!has_tgid || f2_track_name.empty() ||
+                            f3_name.empty())) {
+        return SystraceParseResult::kFailure;
+      }
+      out->name = f3_name;
+      out->str_value = f2_track_name;
       return SystraceParseResult::kSuccess;
     }
     case 'C': {  // Counter.
@@ -176,7 +216,7 @@
         return SystraceParseResult::kFailure;
       }
       out->name = f2_name;
-      out->value = *maybe_value;
+      out->int_value = *maybe_value;
       return SystraceParseResult::kSuccess;
     }
     default:
@@ -189,8 +229,8 @@
 // Visible for unittesting.
 inline bool operator==(const SystraceTracePoint& x,
                        const SystraceTracePoint& y) {
-  return std::tie(x.phase, x.tgid, x.name, x.value) ==
-         std::tie(y.phase, y.tgid, y.name, y.value);
+  return std::tie(x.phase, x.tgid, x.name, x.int_value, x.str_value) ==
+         std::tie(y.phase, y.tgid, y.name, y.int_value, y.str_value);
 }
 
 }  // namespace systrace_utils
diff --git a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
index d0c89cf..be0d801 100644
--- a/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser_unittest.cc
@@ -45,6 +45,8 @@
   ASSERT_EQ(ParseSystraceTracePoint("C", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("S", &result), Result::kFailure);
   ASSERT_EQ(ParseSystraceTracePoint("F", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("I", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("N", &result), Result::kFailure);
 
   ASSERT_EQ(ParseSystraceTracePoint("B|42|\n", &result), Result::kSuccess);
   EXPECT_EQ(result, SystraceTracePoint::B(42, "[empty slice name]"));
@@ -85,6 +87,19 @@
             Result::kSuccess);
   EXPECT_EQ(result, SystraceTracePoint::F(123, "foo", 456));
 
+  ASSERT_EQ(ParseSystraceTracePoint("I||test", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("I|123|", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("I|123|event\n", &result),
+            Result::kSuccess);
+  EXPECT_EQ(result, SystraceTracePoint::I(123, "event"));
+
+  ASSERT_EQ(ParseSystraceTracePoint("N||test|test", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("N|123|test|", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("N|123||test", &result), Result::kFailure);
+  ASSERT_EQ(ParseSystraceTracePoint("N|123|track|event\n", &result),
+            Result::kSuccess);
+  EXPECT_EQ(result, SystraceTracePoint::N(123, "track", "event"));
+
   ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: parent_ts=0.123\n",
                                     &result),
             Result::kUnsupported);
diff --git a/src/trace_processor/iterator_impl.cc b/src/trace_processor/iterator_impl.cc
index c0b3aac..5ee1e1d 100644
--- a/src/trace_processor/iterator_impl.cc
+++ b/src/trace_processor/iterator_impl.cc
@@ -18,6 +18,7 @@
 
 #include "perfetto/base/time.h"
 #include "perfetto/trace_processor/trace_processor_storage.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/trace_processor_impl.h"
 
@@ -26,15 +27,15 @@
 
 IteratorImpl::IteratorImpl(TraceProcessorImpl* trace_processor,
                            sqlite3* db,
+                           base::Status status,
                            ScopedStmt stmt,
-                           uint32_t column_count,
-                           util::Status status,
+                           StmtMetadata metadata,
                            uint32_t sql_stats_row)
     : trace_processor_(trace_processor),
       db_(db),
-      stmt_(std::move(stmt)),
-      column_count_(column_count),
       status_(std::move(status)),
+      stmt_(std::move(stmt)),
+      stmt_metadata_(std::move(metadata)),
       sql_stats_row_(sql_stats_row) {}
 
 IteratorImpl::~IteratorImpl() {
@@ -58,7 +59,7 @@
 Iterator::~Iterator() = default;
 
 Iterator::Iterator(Iterator&&) noexcept = default;
-Iterator& Iterator::operator=(Iterator&&) = default;
+Iterator& Iterator::operator=(Iterator&&) noexcept = default;
 
 bool Iterator::Next() {
   return iterator_->Next();
@@ -76,9 +77,17 @@
   return iterator_->ColumnCount();
 }
 
-util::Status Iterator::Status() {
+base::Status Iterator::Status() {
   return iterator_->Status();
 }
 
+uint32_t Iterator::StatementCount() {
+  return iterator_->StatementCount();
+}
+
+uint32_t Iterator::StatementWithOutputCount() {
+  return iterator_->StatementCountWithOutput();
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/iterator_impl.h b/src/trace_processor/iterator_impl.h
index f286f2c..68068e6 100644
--- a/src/trace_processor/iterator_impl.h
+++ b/src/trace_processor/iterator_impl.h
@@ -28,6 +28,7 @@
 #include "perfetto/trace_processor/iterator.h"
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -36,11 +37,17 @@
 
 class IteratorImpl {
  public:
+  struct StmtMetadata {
+    uint32_t column_count = 0;
+    uint32_t statement_count = 0;
+    uint32_t statement_count_with_output = 0;
+  };
+
   IteratorImpl(TraceProcessorImpl* impl,
                sqlite3* db,
+               base::Status,
                ScopedStmt,
-               uint32_t column_count,
-               util::Status,
+               StmtMetadata,
                uint32_t sql_stats_row);
   ~IteratorImpl();
 
@@ -52,10 +59,25 @@
 
   // Methods called by the base Iterator class.
   bool Next() {
-    // Delegate to the cc file to prevent trace_storage.h include in this file.
+    PERFETTO_DCHECK(stmt_ || !status_.ok());
+
     if (!called_next_) {
+      // Delegate to the cc file to prevent trace_storage.h include in this
+      // file.
       RecordFirstNextInSqlStats();
       called_next_ = true;
+
+      // In the past, we used to call sqlite3_step for the first time in this
+      // function which 1:1 matched Next calls to sqlite3_step calls. However,
+      // with the introduction of multi-statement support, we call
+      // sqlite3_step when tokenizing the queries and so we need to *not* call
+      // step the first time Next is called.
+      //
+      // Aside: if we could, we would change the API to match the new setup
+      // (i.e. implement operator bool, make Next return nothing similar to C++
+      // iterators); however, too many clients depend on the current behavior so
+      // we have to keep the API as is.
+      return status_.ok() && !sqlite_utils::IsStmtDone(*stmt_);
     }
 
     if (!status_.ok())
@@ -63,7 +85,8 @@
 
     int ret = sqlite3_step(*stmt_);
     if (PERFETTO_UNLIKELY(ret != SQLITE_ROW && ret != SQLITE_DONE)) {
-      status_ = util::ErrStatus("%s", sqlite3_errmsg(db_));
+      status_ = base::ErrStatus("%s (errcode %d)", sqlite3_errmsg(db_), ret);
+      stmt_.reset();
       return false;
     }
     return ret == SQLITE_ROW;
@@ -101,12 +124,18 @@
   }
 
   std::string GetColumnName(uint32_t col) {
-    return sqlite3_column_name(stmt_.get(), static_cast<int>(col));
+    return stmt_ ? sqlite3_column_name(*stmt_, static_cast<int>(col)) : "";
   }
 
-  uint32_t ColumnCount() { return column_count_; }
+  base::Status Status() { return status_; }
 
-  util::Status Status() { return status_; }
+  uint32_t ColumnCount() { return stmt_metadata_.column_count; }
+
+  uint32_t StatementCount() { return stmt_metadata_.statement_count; }
+
+  uint32_t StatementCountWithOutput() {
+    return stmt_metadata_.statement_count_with_output;
+  }
 
  private:
   // Dummy function to pass to ScopedResource.
@@ -125,9 +154,10 @@
 
   ScopedTraceProcessor trace_processor_;
   sqlite3* db_ = nullptr;
+  base::Status status_;
+
   ScopedStmt stmt_;
-  uint32_t column_count_ = 0;
-  util::Status status_;
+  StmtMetadata stmt_metadata_;
 
   uint32_t sql_stats_row_ = 0;
   bool called_next_ = false;
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 6c2536e..757781a 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -623,7 +623,6 @@
   if (metric_it == ctx->metrics->end()) {
     return base::ErrStatus("RUN_METRIC: Unknown filename provided %s", path);
   }
-  const auto& sql = metric_it->sql;
 
   std::unordered_map<std::string, std::string> substitutions;
   for (size_t i = 1; i < argc; i += 2) {
@@ -642,27 +641,21 @@
     substitutions[*key_str] = *value_str;
   }
 
-  for (const auto& query : base::SplitString(sql, ";\n")) {
-    const auto& trimmed = base::TrimLeading(query);
-    if (trimmed.empty())
-      continue;
+  std::string subbed_sql;
+  int ret = TemplateReplace(metric_it->sql, substitutions, &subbed_sql);
+  if (ret) {
+    return base::ErrStatus(
+        "RUN_METRIC: Error when performing substitutions: %s",
+        metric_it->sql.c_str());
+  }
 
-    std::string buffer;
-    int ret = TemplateReplace(trimmed, substitutions, &buffer);
-    if (ret) {
-      return base::ErrStatus(
-          "RUN_METRIC: Error when performing substitutions: %s", query.c_str());
-    }
+  auto it = ctx->tp->ExecuteQuery(subbed_sql);
+  it.Next();
 
-    PERFETTO_DLOG("RUN_METRIC: Executing query: %s", buffer.c_str());
-    auto it = ctx->tp->ExecuteQuery(buffer);
-    it.Next();
-
-    base::Status status = it.Status();
-    if (!status.ok()) {
-      return base::ErrStatus("RUN_METRIC: Error when running file %s: %s", path,
-                             status.c_message());
-    }
+  base::Status status = it.Status();
+  if (!status.ok()) {
+    return base::ErrStatus("RUN_METRIC: Error when running file %s: %s", path,
+                           status.c_message());
   }
   return base::OkStatus();
 }
@@ -731,18 +724,12 @@
       return base::ErrStatus("Unknown metric %s", name.c_str());
 
     const auto& sql_metric = *metric_it;
-    for (const auto& outer : base::SplitString(sql_metric.sql, ";\n")) {
-      for (const auto& query : base::SplitString(outer, ";\r\n")) {
-        PERFETTO_DLOG("Executing query: %s", query.c_str());
-        auto prep_it = tp->ExecuteQuery(query);
-        prep_it.Next();
-        RETURN_IF_ERROR(prep_it.Status());
-      }
-    }
+    auto prep_it = tp->ExecuteQuery(sql_metric.sql);
+    prep_it.Next();
+    RETURN_IF_ERROR(prep_it.Status());
 
     auto output_query =
         "SELECT * FROM " + sql_metric.output_table_name.value() + ";";
-    PERFETTO_DLOG("Executing output query: %s", output_query.c_str());
     PERFETTO_TP_TRACE("COMPUTE_METRIC_QUERY", [&](metatrace::Record* r) {
       r->AddArg("SQL", output_query);
     });
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index 9fd8e96..4135e50 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -74,6 +74,7 @@
   "android/global_counter_span_view.sql",
   "android/gpu_counter_span_view.sql",
   "android/thread_counter_span_view.sql",
+  "android/android_trusty_workqueues.sql",
   "android/unsymbolized_frames.sql",
   "android/startup/launches_maxsdk28.sql",
   "android/startup/launches_minsdk29.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_irq_runtime.sql b/src/trace_processor/metrics/sql/android/android_irq_runtime.sql
index e57bdf5..e69683c 100644
--- a/src/trace_processor/metrics/sql/android/android_irq_runtime.sql
+++ b/src/trace_processor/metrics/sql/android/android_irq_runtime.sql
@@ -27,7 +27,7 @@
 AS
 SELECT ts, dur, name
 FROM irq_runtime_all
-WHERE name LIKE 'IRQ (%)'
+WHERE name GLOB 'IRQ (*)'
 ORDER BY dur DESC;
 
 DROP VIEW IF EXISTS hw_irq_runtime_statistics;
@@ -46,7 +46,7 @@
 AS
 SELECT ts, dur, name
 FROM irq_runtime_all
-WHERE name NOT LIKE 'IRQ (%)'
+WHERE name NOT GLOB 'IRQ (*)'
 ORDER BY dur DESC;
 
 CREATE VIEW sw_irq_runtime_statistics
diff --git a/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql b/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql
new file mode 100644
index 0000000..a036c9d
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_trusty_workqueues.sql
@@ -0,0 +1,21 @@
+-- Gather the `nop_work_func` slices and the CPU they each ran on and use that
+-- information to generate a metric that displays just the Trusty workqueue
+-- events grouped by CPU.
+DROP VIEW IF EXISTS android_trusty_workqueues_event;
+CREATE VIEW android_trusty_workqueues_event AS
+SELECT
+  'slice' as track_type,
+  name as slice_name,
+  ts,
+  dur,
+  'Cpu ' || EXTRACT_ARG(arg_set_id, 'cpu') as track_name,
+  'Trusty Workqueues' as group_name
+FROM slice
+WHERE slice.name GLOB 'nop_work_func*';
+
+-- Generate the final metric output. This is empty because we're only using the
+-- metric to generate custom tracks, and so don't have any aggregate data to
+-- generate.
+DROP VIEW IF EXISTS android_trusty_workqueues_output;
+CREATE VIEW android_trusty_workqueues_output AS
+SELECT AndroidTrustyWorkqueues();
diff --git a/src/trace_processor/metrics/sql/android/java_heap_histogram.sql b/src/trace_processor/metrics/sql/android/java_heap_histogram.sql
index 7fd968c..7518594 100644
--- a/src/trace_processor/metrics/sql/android/java_heap_histogram.sql
+++ b/src/trace_processor/metrics/sql/android/java_heap_histogram.sql
@@ -40,36 +40,43 @@
 )
 SELECT * FROM cls_visitor;
 
-DROP VIEW IF EXISTS java_heap_histogram_output;
-CREATE VIEW java_heap_histogram_output AS
-WITH
--- Base histogram table
-heap_obj_histograms AS (
+DROP TABLE IF EXISTS heap_obj_histograms;
+CREATE TABLE heap_obj_histograms AS
   SELECT
     o.upid,
     o.graph_sample_ts,
-    IFNULL(c.deobfuscated_name, c.name) AS type_name,
-    special.category,
+    o.type_id cls_id,
     COUNT(1) obj_count,
-    SUM(CASE o.reachable WHEN TRUE THEN 1 ELSE 0 END) reachable_obj_count
+    SUM(IIF(o.reachable, 1, 0)) reachable_obj_count,
+    SUM(self_size) / 1024 size_kb,
+    SUM(IIF(o.reachable, self_size, 0)) / 1024 reachable_size_kb,
+    SUM(native_size) / 1024 native_size_kb,
+    SUM(IIF(o.reachable, native_size, 0)) / 1024 reachable_native_size_kb
   FROM heap_graph_object o
-  JOIN heap_graph_class c ON o.type_id = c.id
-  LEFT JOIN android_special_classes special ON special.cls_id = c.id
-  GROUP BY 1, 2, 3, 4
-  ORDER BY 6 DESC
-),
+  GROUP BY 1, 2, 3
+  ORDER BY 1, 2, 3;
+
+DROP VIEW IF EXISTS java_heap_histogram_output;
+CREATE VIEW java_heap_histogram_output AS
+WITH
 -- Group by to build the repeated field by upid, ts
 heap_obj_histogram_count_protos AS (
   SELECT
     upid,
     graph_sample_ts,
     RepeatedField(JavaHeapHistogram_TypeCount(
-      'type_name', type_name,
+      'type_name', IFNULL(c.deobfuscated_name, c.name),
       'category', category,
       'obj_count', obj_count,
-      'reachable_obj_count', reachable_obj_count
+      'reachable_obj_count', reachable_obj_count,
+      'size_kb', size_kb,
+      'reachable_size_kb', reachable_size_kb,
+      'native_size_kb', native_size_kb,
+      'reachable_native_size_kb', reachable_native_size_kb
     )) AS count_protos
-  FROM heap_obj_histograms
+  FROM heap_obj_histograms hist
+  JOIN heap_graph_class c ON hist.cls_id = c.id
+  LEFT JOIN android_special_classes special USING(cls_id)
   GROUP BY 1, 2
 ),
 -- Group by to build the repeated field by upid
diff --git a/src/trace_processor/metrics/sql/android/startup/launches.sql b/src/trace_processor/metrics/sql/android/startup/launches.sql
index 492bac5..999ba6e 100644
--- a/src/trace_processor/metrics/sql/android/startup/launches.sql
+++ b/src/trace_processor/metrics/sql/android/startup/launches.sql
@@ -37,17 +37,17 @@
     WHERE name = 'android_sdk_version'
   ");
 
+SELECT CREATE_FUNCTION(
+  'METRICS_LOGGER_SLICE_COUNT()',
+  'INT',
+  "SELECT COUNT(1) FROM slice WHERE name GLOB 'MetricsLogger:*'"
+);
+
 -- Note: on Q, we didn't have Android fingerprints but we *did*
 -- have ActivityMetricsLogger events so we will use this approach
 -- if we see any such events.
 SELECT CASE
-  WHEN (
-    ANDROID_SDK_LEVEL() >= 29
-    OR (
-      SELECT COUNT(1) FROM slice
-      WHERE name GLOB 'MetricsLogger:*'
-    ) > 0
-  )
+  WHEN (ANDROID_SDK_LEVEL() >= 29 OR METRICS_LOGGER_SLICE_COUNT() > 0)
   THEN RUN_METRIC('android/startup/launches_minsdk29.sql')
   ELSE RUN_METRIC('android/startup/launches_maxsdk28.sql')
 END;
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql b/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql
index b9b54c2..69b824d 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql
@@ -67,12 +67,13 @@
         ts AS gesture_ts,
         dur AS {{prefix}}_dur,
         track_id AS gesture_track_id,
-        trace_id AS {{id_field}},
+        trace_id AS {{prefix}}_trace_id,
         jank,
-        {{id_field}}
+        {{id_field}},
+        avg_vsync_interval
       FROM {{prefix}}_jank
   ) gesture ON
-    flow.trace_id = gesture.{{id_field}}
+    flow.trace_id = gesture.{{prefix}}_trace_id
   UNION ALL
   SELECT
     'InputLatency::{{gesture_update}}' AS name,
@@ -94,9 +95,10 @@
     ts AS gesture_ts,
     dur AS {{prefix}}_dur,
     track_id AS gesture_track_id,
-    trace_id AS {{id_field}},
+    trace_id AS {{prefix}}_trace_id,
     jank,
-    {{id_field}}
+    {{id_field}},
+    avg_vsync_interval
   FROM {{prefix}}_jank
   ORDER BY {{id_field}} ASC, trace_id ASC, ts ASC;
 
@@ -138,7 +140,7 @@
     MIN(ts) AS max_flow_ts
   FROM {{prefix}}_latency_info_flow_step
   WHERE
-    trace_id = {{id_field}} AND
+    trace_id = {{prefix}}_trace_id AND
     ts > gesture_ts + {{prefix}}_dur
   GROUP BY gesture_slice_id;
 
@@ -183,6 +185,7 @@
     curr.track_id,
     curr.trace_id,
     curr.{{id_field}},
+    curr.avg_vsync_interval,
     curr.gesture_slice_id,
     curr.gesture_ts,
     curr.{{prefix}}_dur,
@@ -230,6 +233,7 @@
     curr.dur,
     curr.track_id,
     curr.{{id_field}},
+    curr.avg_vsync_interval,
     curr.gesture_slice_id AS {{prefix}}_slice_id,
     curr.gesture_ts AS {{prefix}}_ts,
     curr.{{prefix}}_dur AS {{prefix}}_dur,
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql b/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql
index 6701158..e2c069c 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_flow_event_queuing_delay.sql
@@ -35,6 +35,7 @@
     dur,
     track_id,
     {{id_field}},
+    avg_vsync_interval,
     {{prefix}}_slice_id,
     {{prefix}}_ts,
     {{prefix}}_dur,
diff --git a/src/trace_processor/metrics/sql/chrome/gesture_jank.sql b/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
index d025622..cfbc96b 100644
--- a/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
+++ b/src/trace_processor/metrics/sql/chrome/gesture_jank.sql
@@ -170,7 +170,8 @@
     dur,
     track_id,
     trace_id,
-    dur/avg_vsync_interval AS gesture_frames_exact
+    dur/avg_vsync_interval AS gesture_frames_exact,
+    avg_vsync_interval
   FROM joined_{{prefix}}_begin_and_end begin_and_end JOIN gesture_update ON
   gesture_update.ts <= begin_and_end.end_ts AND
   gesture_update.ts >= begin_and_end.begin_ts AND
diff --git a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
index 23a9908..35220d9 100644
--- a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
+++ b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
@@ -79,6 +79,7 @@
     EXTRACT_ARG(slice.arg_set_id, "task.posted_from.function_name") as function,
     trace_id,
     queuing_time_ns,
+    avg_vsync_interval,
     next_track_id,
     CASE WHEN queuing.ancestor_end <= slice.ts THEN
       CASE WHEN slice.ts + slice.dur <= queuing.maybe_next_ancestor_ts THEN
@@ -116,24 +117,23 @@
 -- descendant slice. So all fields in base.* will be repeated ONCE for each
 -- child, but if it has no slice it will occur only once but all the
 -- |descendant_.*| fields will be NULL because of the LEFT JOIN.
--- Additionally for mojo events, append "(interface_name)" to the end of the
--- descendant name.
+-- Additionally for mojo events we replace the descendant_name with just the
+-- "interface_name" since that is more descriptive for our jank purposes.
 DROP VIEW IF EXISTS all_descendant_blocking_tasks_queuing_delay;
 CREATE VIEW all_descendant_blocking_tasks_queuing_delay AS
   SELECT
     descendant.id AS descendant_id,
     descendant.ts AS descendant_ts,
     descendant.dur AS descendant_dur,
-    COALESCE(descendant.name || "(" ||
+    COALESCE(
       IIF(descendant.arg_set_id IS NOT NULL,
           EXTRACT_ARG(descendant.arg_set_id,
               "chrome_mojo_event_info.watcher_notify_interface_tag"),
-          NULL) || ")",
-      descendant.name || "(" ||
-          IIF(descendant.arg_set_id IS NOT NULL,
+          NULL),
+      IIF(descendant.arg_set_id IS NOT NULL,
           EXTRACT_ARG(descendant.arg_set_id,
               "chrome_mojo_event_info.mojo_interface_tag"),
-          NULL) || ")",
+          NULL),
       descendant.name) AS descendant_name,
     descendant.parent_id As descendant_parent_id,
     descendant.depth AS descendant_depth,
@@ -244,6 +244,7 @@
     thread_name,
     process_name,
     function,
+    avg_vsync_interval,
     GROUP_CONCAT(
       CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
         descendant_id
@@ -310,7 +311,7 @@
     , "-") AS java_name
   FROM
     blocking_tasks_queuing_delay_with_invalid_depth
-  GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
+  GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
   ORDER BY descendant_cpu_percentage DESC;
 
 
@@ -351,20 +352,46 @@
       END'
 );
 
+SELECT CREATE_FUNCTION(
+  -- Function prototype: Takes a slice name, function, and file, and determines
+  -- if we should use the slice name, or if its a RunTask event uses the
+  -- function & file name, however if the RunTask posted from is one of the
+  -- simple_watcher paths we collapse them for attributation.
+  'TopLevelName(name STRING, function STRING, file STRING)',
+  'STRING',
+  -- The difference for the mojom functions are:
+  --  1) PostDispatchNextMessageFromPipe:
+  --         We knew that there is a message in the pipe, didn't try to set up a
+  --         SimpleWatcher to monitor when a new one arrives.
+  --  2) ArmOrNotify:
+  --         We tried to set up SimpleWatcher, but the setup failed as the
+  --         message arrived as we were setting this up, so we posted a task
+  --         instead.
+  --  3) Notify:
+  --         SimpleWatcher was set up and after a period of monitoring detected
+  --         a new message.
+  -- For our jank use case this distinction isn't very useful so we group them
+  -- together.
+  'SELECT
+     CASE WHEN $name = "ThreadControllerImpl::RunTask" THEN
+       CASE WHEN $function IN
+           ("PostDispatchNextMessageFromPipe", "ArmOrNotify", "Notify") THEN
+         "posted-from-mojo-pipe"
+        ELSE
+         "posted-from-" || $function || "()-in-" || $file
+        END
+    ELSE
+      $name
+    END'
+);
+
 -- Create a common name for each "cause" based on the slice stack we found.
 DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_temp;
 CREATE VIEW scroll_jank_cause_queuing_delay_temp AS
   SELECT
-    CASE WHEN name = "ThreadControllerImpl::RunTask" THEN
-      'posted-from-' || function || '()-in-' || file
-    ELSE
-      name
-    END || COALESCE("-" || descendant_name, "") AS location,
-    CASE WHEN name = "ThreadControllerImpl::RunTask" THEN
-      'posted-from-' || function || '()-in-' || file
-    ELSE
-      name
-    END || COALESCE(
+    TopLevelName(name, function, file) || COALESCE(
+      "-" || descendant_name, "") AS location,
+    TopLevelName(name, function, file) || COALESCE(
       "-" || GetFirstSliceNameOrNull(mojom_name),
       "-" || GetFirstSliceNameOrNull(toplevel_name),
       "-" || GetFirstSliceNameOrNull(java_name),
diff --git a/src/trace_processor/rpc/query_result_serializer.cc b/src/trace_processor/rpc/query_result_serializer.cc
index f6042fd..d3b9ba2 100644
--- a/src/trace_processor/rpc/query_result_serializer.cc
+++ b/src/trace_processor/rpc/query_result_serializer.cc
@@ -62,9 +62,9 @@
 bool QueryResultSerializer::Serialize(protos::pbzero::QueryResult* res) {
   PERFETTO_CHECK(!eof_reached_);
 
-  if (!did_write_column_names_) {
-    SerializeColumnNames(res);
-    did_write_column_names_ = true;
+  if (!did_write_metadata_) {
+    SerializeMetadata(res);
+    did_write_metadata_ = true;
   }
 
   // In case of an error we still want to go through SerializeBatch(). That will
@@ -258,11 +258,13 @@
   res->set_error(err);
 }
 
-void QueryResultSerializer::SerializeColumnNames(
+void QueryResultSerializer::SerializeMetadata(
     protos::pbzero::QueryResult* res) {
-  PERFETTO_DCHECK(!did_write_column_names_);
+  PERFETTO_DCHECK(!did_write_metadata_);
   for (uint32_t c = 0; c < num_cols_; c++)
     res->add_column_names(iter_->GetColumnName(c));
+  res->set_statement_count(iter_->StatementCount());
+  res->set_statement_with_output_count(iter_->StatementCountWithOutput());
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/rpc/query_result_serializer.h b/src/trace_processor/rpc/query_result_serializer.h
index c29d66d..6cc55df 100644
--- a/src/trace_processor/rpc/query_result_serializer.h
+++ b/src/trace_processor/rpc/query_result_serializer.h
@@ -76,13 +76,13 @@
   }
 
  private:
-  void SerializeColumnNames(protos::pbzero::QueryResult*);
+  void SerializeMetadata(protos::pbzero::QueryResult*);
   void SerializeBatch(protos::pbzero::QueryResult*);
   void MaybeSerializeError(protos::pbzero::QueryResult*);
 
   std::unique_ptr<IteratorImpl> iter_;
   const uint32_t num_cols_;
-  bool did_write_column_names_ = false;
+  bool did_write_metadata_ = false;
   bool eof_reached_ = false;
   uint32_t col_ = UINT32_MAX;
 
diff --git a/src/trace_processor/rpc/query_result_serializer_unittest.cc b/src/trace_processor/rpc/query_result_serializer_unittest.cc
index a655410..85c882d 100644
--- a/src/trace_processor/rpc/query_result_serializer_unittest.cc
+++ b/src/trace_processor/rpc/query_result_serializer_unittest.cc
@@ -408,7 +408,7 @@
   TestDeserializer deser;
   deser.SerializeAndDeserialize(&ser);
   EXPECT_EQ(deser.cells.size(), 0u);
-  EXPECT_EQ(deser.error, "incomplete input");
+  EXPECT_EQ(deser.error, "incomplete input (errcode: 1)");
   EXPECT_TRUE(deser.eof_reached);
 }
 
diff --git a/src/trace_processor/sqlite/sql_stats_table.cc b/src/trace_processor/sqlite/sql_stats_table.cc
index 372dc1e..5902257 100644
--- a/src/trace_processor/sqlite/sql_stats_table.cc
+++ b/src/trace_processor/sqlite/sql_stats_table.cc
@@ -39,8 +39,6 @@
   *schema = Schema(
       {
           SqliteTable::Column(Column::kQuery, "query", SqlValue::Type::kString),
-          SqliteTable::Column(Column::kTimeQueued, "queued",
-                              SqlValue::Type::kLong),
           SqliteTable::Column(Column::kTimeStarted, "started",
                               SqlValue::Type::kLong),
           SqliteTable::Column(Column::kTimeFirstNext, "first_next",
@@ -48,7 +46,7 @@
           SqliteTable::Column(Column::kTimeEnded, "ended",
                               SqlValue::Type::kLong),
       },
-      {Column::kTimeQueued});
+      {Column::kTimeStarted});
   return util::OkStatus();
 }
 
@@ -89,9 +87,6 @@
       sqlite3_result_text(context, stats.queries()[row_].c_str(), -1,
                           sqlite_utils::kSqliteStatic);
       break;
-    case Column::kTimeQueued:
-      sqlite3_result_int64(context, stats.times_queued()[row_]);
-      break;
     case Column::kTimeStarted:
       sqlite3_result_int64(context, stats.times_started()[row_]);
       break;
diff --git a/src/trace_processor/sqlite/sql_stats_table.h b/src/trace_processor/sqlite/sql_stats_table.h
index c146ee8..d2d1c5d 100644
--- a/src/trace_processor/sqlite/sql_stats_table.h
+++ b/src/trace_processor/sqlite/sql_stats_table.h
@@ -34,10 +34,9 @@
  public:
   enum Column {
     kQuery = 0,
-    kTimeQueued = 1,
-    kTimeStarted = 2,
-    kTimeFirstNext = 3,
-    kTimeEnded = 4,
+    kTimeStarted = 1,
+    kTimeFirstNext = 2,
+    kTimeEnded = 3,
   };
 
   // Implementation of the SQLite cursor interface.
diff --git a/src/trace_processor/sqlite/sqlite_raw_table.cc b/src/trace_processor/sqlite/sqlite_raw_table.cc
index ec4c263..74475f0 100644
--- a/src/trace_processor/sqlite/sqlite_raw_table.cc
+++ b/src/trace_processor/sqlite/sqlite_raw_table.cc
@@ -182,9 +182,9 @@
   // Go through each field id and find the entry in the args table for that
   for (uint32_t i = 1; i <= max; ++i) {
     for (auto it = row_map_.IterateRows(); it; it.Next()) {
-      base::StringView key = args.key().GetString(it.row());
+      base::StringView key = args.key().GetString(it.index());
       if (key == descriptor->fields[i].name) {
-        (*field_id_to_arg_index)[i] = it.index();
+        (*field_id_to_arg_index)[i] = it.row();
         break;
       }
     }
@@ -468,7 +468,7 @@
     return;
   }
   for (auto it = row_map_.IterateRows(); it; it.Next()) {
-    WriteArgAtRow(it.row(), DVW());
+    WriteArgAtRow(it.index(), DVW());
   }
 }
 
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 9bb26d7..5315b0c 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -123,6 +123,38 @@
   }
 }
 
+inline base::Status PrepareStmt(sqlite3* db,
+                                const char* sql,
+                                ScopedStmt* stmt,
+                                const char** tail) {
+  sqlite3_stmt* raw_stmt = nullptr;
+  int err = sqlite3_prepare_v2(db, sql, -1, &raw_stmt, tail);
+  stmt->reset(raw_stmt);
+  if (err != SQLITE_OK)
+    return base::ErrStatus("%s (errcode: %d)", sqlite3_errmsg(db), err);
+  return base::OkStatus();
+}
+
+inline bool IsStmtDone(sqlite3_stmt* stmt) {
+  return !sqlite3_stmt_busy(stmt);
+}
+
+inline base::Status StepStmtUntilDone(sqlite3_stmt* stmt) {
+  PERFETTO_DCHECK(stmt);
+
+  if (IsStmtDone(stmt))
+    return base::OkStatus();
+
+  int err;
+  for (err = sqlite3_step(stmt); err == SQLITE_ROW; err = sqlite3_step(stmt)) {
+  }
+  if (err != SQLITE_DONE) {
+    return base::ErrStatus("%s (errcode: %d)",
+                           sqlite3_errmsg(sqlite3_db_handle(stmt)), err);
+  }
+  return base::OkStatus();
+}
+
 inline util::Status GetColumnsForTable(
     sqlite3* db,
     const std::string& raw_table_name,
diff --git a/src/trace_processor/storage/trace_storage.cc b/src/trace_processor/storage/trace_storage.cc
index 20fdd51..1260d8f 100644
--- a/src/trace_processor/storage/trace_storage.cc
+++ b/src/trace_processor/storage/trace_storage.cc
@@ -80,18 +80,15 @@
 TraceStorage::~TraceStorage() {}
 
 uint32_t TraceStorage::SqlStats::RecordQueryBegin(const std::string& query,
-                                                  int64_t time_queued,
                                                   int64_t time_started) {
   if (queries_.size() >= kMaxLogEntries) {
     queries_.pop_front();
-    times_queued_.pop_front();
     times_started_.pop_front();
     times_first_next_.pop_front();
     times_ended_.pop_front();
     popped_queries_++;
   }
   queries_.push_back(query);
-  times_queued_.push_back(time_queued);
   times_started_.push_back(time_started);
   times_first_next_.push_back(0);
   times_ended_.push_back(0);
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index af98024..14ecdb3 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -191,13 +191,11 @@
    public:
     static constexpr size_t kMaxLogEntries = 100;
     uint32_t RecordQueryBegin(const std::string& query,
-                              int64_t time_queued,
                               int64_t time_started);
     void RecordQueryFirstNext(uint32_t row, int64_t time_first_next);
     void RecordQueryEnd(uint32_t row, int64_t time_end);
     size_t size() const { return queries_.size(); }
     const std::deque<std::string>& queries() const { return queries_; }
-    const std::deque<int64_t>& times_queued() const { return times_queued_; }
     const std::deque<int64_t>& times_started() const { return times_started_; }
     const std::deque<int64_t>& times_first_next() const {
       return times_first_next_;
@@ -208,7 +206,6 @@
     uint32_t popped_queries_ = 0;
 
     std::deque<std::string> queries_;
-    std::deque<int64_t> times_queued_;
     std::deque<int64_t> times_started_;
     std::deque<int64_t> times_first_next_;
     std::deque<int64_t> times_ended_;
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 2976e2c..97db4640 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -17,7 +17,6 @@
 #include "src/trace_processor/trace_processor_impl.h"
 
 #include <algorithm>
-#include <cinttypes>
 #include <memory>
 
 #include "perfetto/base/logging.h"
@@ -49,6 +48,7 @@
 #include "src/trace_processor/iterator_impl.h"
 #include "src/trace_processor/sqlite/create_function.h"
 #include "src/trace_processor/sqlite/register_function.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/span_join_operator_table.h"
 #include "src/trace_processor/sqlite/sql_stats_table.h"
 #include "src/trace_processor/sqlite/sqlite3_str_split.h"
@@ -738,6 +738,106 @@
   }
 }
 
+void IncrementCountForStmt(sqlite3_stmt* stmt,
+                           IteratorImpl::StmtMetadata* metadata) {
+  metadata->statement_count++;
+
+  // If the stmt is already done, it clearly didn't have any output.
+  if (sqlite_utils::IsStmtDone(stmt))
+    return;
+
+  // If the statement only has a single column and that column is named
+  // "suppress_query_output", treat it as a statement without output for
+  // accounting purposes. This is done so that embedders (e.g. shell) can
+  // strictly check that only the last query produces output while also
+  // providing an escape hatch for SELECT RUN_METRIC() invocations (which
+  // sadly produce output).
+  if (sqlite3_column_count(stmt) == 1 &&
+      strcmp(sqlite3_column_name(stmt, 0), "suppress_query_output") == 0) {
+    return;
+  }
+
+  // Otherwise, the statement has output and so increment the count.
+  metadata->statement_count_with_output++;
+}
+
+base::Status PrepareAndStepUntilLastValidStmt(
+    sqlite3* db,
+    const std::string& sql,
+    ScopedStmt* output_stmt,
+    IteratorImpl::StmtMetadata* metadata) {
+  ScopedStmt prev_stmt;
+  // A sql string can contain several statements. Some of them might be comment
+  // only, e.g. "SELECT 1; /* comment */; SELECT 2;". Here we process one
+  // statement on each iteration. SQLite's sqlite_prepare_v2 (wrapped by
+  // PrepareStmt) returns on each iteration a pointer to the unprocessed string.
+  //
+  // Unfortunately we cannot call PrepareStmt and tokenize all statements
+  // upfront because sqlite_prepare_v2 also semantically checks the statement
+  // against the schema. In some cases statements might depend on the execution
+  // of previous ones (e.e. CREATE VIEW x; SELECT FROM x; DELETE VIEW x;).
+  //
+  // Also, unfortunately, we need to PrepareStmt to find out if a statement is a
+  // comment or a real statement.
+  //
+  // The logic here is the following:
+  //  - We invoke PrepareStmt on each statement.
+  //  - If the statement is a comment we simply skip it.
+  //  - If the statement is valid, we step once to make sure side effects take
+  //    effect.
+  //  - If we encounter a valid statement afterwards, we step internally through
+  //    all rows of the previous one. This ensures that any further side effects
+  //    take hold *before* we step into the next statement.
+  //  - Once no further non-comment statements are encountered, we return an
+  //    iterator to the last valid statement.
+  for (const char* rem_sql = sql.c_str(); rem_sql && rem_sql[0];) {
+    ScopedStmt cur_stmt;
+    {
+      PERFETTO_TP_TRACE("QUERY_PREPARE");
+      const char* tail = nullptr;
+      RETURN_IF_ERROR(sqlite_utils::PrepareStmt(db, rem_sql, &cur_stmt, &tail));
+      rem_sql = tail;
+    }
+
+    // The only situation where we'd have an ok status but also no prepared
+    // statement is if the statement of SQL we parsed was a pure comment. In
+    // this case, just continue to the next statement.
+    if (!cur_stmt)
+      continue;
+
+    // Before stepping into |cur_stmt|, we need to finish iterating through
+    // the previous statement so we don't have two clashing statements (e.g.
+    // SELECT * FROM v and DROP VIEW v) partially stepped into.
+    if (prev_stmt)
+      RETURN_IF_ERROR(sqlite_utils::StepStmtUntilDone(prev_stmt.get()));
+
+    PERFETTO_DLOG("Executing statement: %s", sqlite3_sql(*cur_stmt));
+
+    // Now step once into |cur_stmt| so that when we prepare the next statment
+    // we will have executed any dependent bytecode in this one.
+    int err = sqlite3_step(*cur_stmt);
+    if (err != SQLITE_ROW && err != SQLITE_DONE)
+      return base::ErrStatus("%s (errcode: %d)", sqlite3_errmsg(db), err);
+
+    // Increment the neecessary counts for the statement.
+    IncrementCountForStmt(cur_stmt.get(), metadata);
+
+    // Propogate the current statement to the next iteration.
+    prev_stmt = std::move(cur_stmt);
+  }
+
+  // If we didn't manage to prepare a single statment, that means everything
+  // in the SQL was treated as a comment.
+  if (!prev_stmt)
+    return base::ErrStatus("No valid SQL to run");
+
+  // Update the output statment and column count.
+  *output_stmt = std::move(prev_stmt);
+  metadata->column_count =
+      static_cast<uint32_t>(sqlite3_column_count(output_stmt->get()));
+  return base::OkStatus();
+}
+
 }  // namespace
 
 TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
@@ -908,7 +1008,7 @@
 
 TraceProcessorImpl::~TraceProcessorImpl() = default;
 
-util::Status TraceProcessorImpl::Parse(TraceBlobView blob) {
+base::Status TraceProcessorImpl::Parse(TraceBlobView blob) {
   bytes_parsed_ += blob.size();
   return TraceProcessorStorageImpl::Parse(std::move(blob));
 }
@@ -980,31 +1080,21 @@
   return deletion_list.size();
 }
 
-Iterator TraceProcessorImpl::ExecuteQuery(const std::string& sql,
-                                          int64_t time_queued) {
-  sqlite3_stmt* raw_stmt;
-  int err;
-  {
-    PERFETTO_TP_TRACE("QUERY_PREPARE");
-    err = sqlite3_prepare_v2(*db_, sql.c_str(), static_cast<int>(sql.size()),
-                             &raw_stmt, nullptr);
-  }
+Iterator TraceProcessorImpl::ExecuteQuery(const std::string& sql) {
+  PERFETTO_TP_TRACE("QUERY_EXECUTE");
 
-  util::Status status;
-  uint32_t col_count = 0;
-  if (err != SQLITE_OK) {
-    status = util::ErrStatus("%s", sqlite3_errmsg(*db_));
-  } else {
-    col_count = static_cast<uint32_t>(sqlite3_column_count(raw_stmt));
-  }
-
-  base::TimeNanos t_start = base::GetWallTimeNs();
   uint32_t sql_stats_row =
-      context_.storage->mutable_sql_stats()->RecordQueryBegin(sql, time_queued,
-                                                              t_start.count());
+      context_.storage->mutable_sql_stats()->RecordQueryBegin(
+          sql, base::GetWallTimeNs().count());
+
+  ScopedStmt stmt;
+  IteratorImpl::StmtMetadata metadata;
+  base::Status status =
+      PrepareAndStepUntilLastValidStmt(*db_, sql, &stmt, &metadata);
+  PERFETTO_DCHECK((status.ok() && stmt) || (!status.ok() && !stmt));
 
   std::unique_ptr<IteratorImpl> impl(new IteratorImpl(
-      this, *db_, ScopedStmt(raw_stmt), col_count, status, sql_stats_row));
+      this, *db_, status, std::move(stmt), std::move(metadata), sql_stats_row));
   return Iterator(std::move(impl));
 }
 
@@ -1024,7 +1114,7 @@
   return field_idx != nullptr;
 }
 
-util::Status TraceProcessorImpl::RegisterMetric(const std::string& path,
+base::Status TraceProcessorImpl::RegisterMetric(const std::string& path,
                                                 const std::string& sql) {
   std::string stripped_sql;
   for (base::StringSplitter sp(sql, '\n'); sp.Next();) {
@@ -1041,7 +1131,7 @@
       [&path](const metrics::SqlMetricFile& m) { return m.path == path; });
   if (it != sql_metrics_.end()) {
     it->sql = stripped_sql;
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   auto sep_idx = path.rfind('/');
@@ -1050,7 +1140,7 @@
 
   auto sql_idx = basename.rfind(".sql");
   if (sql_idx == std::string::npos) {
-    return util::ErrStatus("Unable to find .sql extension for metric");
+    return base::ErrStatus("Unable to find .sql extension for metric");
   }
   auto no_ext_name = basename.substr(0, sql_idx);
 
@@ -1082,19 +1172,19 @@
   }
 
   sql_metrics_.emplace_back(metric);
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
-util::Status TraceProcessorImpl::ExtendMetricsProto(const uint8_t* data,
+base::Status TraceProcessorImpl::ExtendMetricsProto(const uint8_t* data,
                                                     size_t size) {
   return ExtendMetricsProto(data, size, /*skip_prefixes*/ {});
 }
 
-util::Status TraceProcessorImpl::ExtendMetricsProto(
+base::Status TraceProcessorImpl::ExtendMetricsProto(
     const uint8_t* data,
     size_t size,
     const std::vector<std::string>& skip_prefixes) {
-  util::Status status =
+  base::Status status =
       pool_.AddFromFileDescriptorSet(data, size, skip_prefixes);
   if (!status.ok())
     return status;
@@ -1109,27 +1199,27 @@
         std::unique_ptr<metrics::BuildProto::Context>(
             new metrics::BuildProto::Context{this, &pool_, &desc}));
   }
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
-util::Status TraceProcessorImpl::ComputeMetric(
+base::Status TraceProcessorImpl::ComputeMetric(
     const std::vector<std::string>& metric_names,
     std::vector<uint8_t>* metrics_proto) {
   auto opt_idx = pool_.FindDescriptorIdx(".perfetto.protos.TraceMetrics");
   if (!opt_idx.has_value())
-    return util::Status("Root metrics proto descriptor not found");
+    return base::Status("Root metrics proto descriptor not found");
 
   const auto& root_descriptor = pool_.descriptors()[opt_idx.value()];
   return metrics::ComputeMetrics(this, metric_names, sql_metrics_, pool_,
                                  root_descriptor, metrics_proto);
 }
 
-util::Status TraceProcessorImpl::ComputeMetricText(
+base::Status TraceProcessorImpl::ComputeMetricText(
     const std::vector<std::string>& metric_names,
     TraceProcessor::MetricResultFormat format,
     std::string* metrics_string) {
   std::vector<uint8_t> metrics_proto;
-  util::Status status = ComputeMetric(metric_names, &metrics_proto);
+  base::Status status = ComputeMetric(metric_names, &metrics_proto);
   if (!status.ok())
     return status;
   switch (format) {
@@ -1155,7 +1245,7 @@
   metatrace::Enable();
 }
 
-util::Status TraceProcessorImpl::DisableAndReadMetatrace(
+base::Status TraceProcessorImpl::DisableAndReadMetatrace(
     std::vector<uint8_t>* trace_proto) {
   protozero::HeapBuffered<protos::pbzero::Trace> trace;
   metatrace::DisableAndReadBuffer([&trace](metatrace::Record* record) {
@@ -1180,7 +1270,7 @@
     }
   });
   *trace_proto = trace.SerializeAsArray();
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 33ec127..7195a5c 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -57,27 +57,26 @@
   ~TraceProcessorImpl() override;
 
   // TraceProcessorStorage implementation:
-  util::Status Parse(TraceBlobView) override;
+  base::Status Parse(TraceBlobView) override;
   void NotifyEndOfFile() override;
 
   // TraceProcessor implementation:
-  Iterator ExecuteQuery(const std::string& sql,
-                        int64_t time_queued = 0) override;
+  Iterator ExecuteQuery(const std::string& sql) override;
 
-  util::Status RegisterMetric(const std::string& path,
+  base::Status RegisterMetric(const std::string& path,
                               const std::string& sql) override;
 
-  util::Status ExtendMetricsProto(const uint8_t* data, size_t size) override;
+  base::Status ExtendMetricsProto(const uint8_t* data, size_t size) override;
 
-  util::Status ExtendMetricsProto(
+  base::Status ExtendMetricsProto(
       const uint8_t* data,
       size_t size,
       const std::vector<std::string>& skip_prefixes) override;
 
-  util::Status ComputeMetric(const std::vector<std::string>& metric_names,
+  base::Status ComputeMetric(const std::vector<std::string>& metric_names,
                              std::vector<uint8_t>* metrics) override;
 
-  util::Status ComputeMetricText(const std::vector<std::string>& metric_names,
+  base::Status ComputeMetricText(const std::vector<std::string>& metric_names,
                                  TraceProcessor::MetricResultFormat format,
                                  std::string* metrics_string) override;
 
@@ -92,7 +91,7 @@
 
   void EnableMetatrace() override;
 
-  util::Status DisableAndReadMetatrace(
+  base::Status DisableAndReadMetatrace(
       std::vector<uint8_t>* trace_proto) override;
 
  private:
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index f601930..1050120 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -192,7 +192,7 @@
 
 #endif  // PERFETTO_TP_LINENOISE
 
-util::Status PrintStats() {
+base::Status PrintStats() {
   auto it = g_tp->ExecuteQuery(
       "SELECT name, idx, source, value from stats "
       "where severity IN ('error', 'data_loss') and value > 0");
@@ -237,20 +237,20 @@
     fprintf(stderr, "\n");
   }
 
-  util::Status status = it.Status();
+  base::Status status = it.Status();
   if (!status.ok()) {
-    return util::ErrStatus("Error while iterating stats (%s)",
+    return base::ErrStatus("Error while iterating stats (%s)",
                            status.c_message());
   }
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
-util::Status ExportTraceToDatabase(const std::string& output_name) {
+base::Status ExportTraceToDatabase(const std::string& output_name) {
   PERFETTO_CHECK(output_name.find('\'') == std::string::npos);
   {
     base::ScopedFile fd(base::OpenFile(output_name, O_CREAT | O_RDWR, 0600));
     if (!fd)
-      return util::ErrStatus("Failed to create file: %s", output_name.c_str());
+      return base::ErrStatus("Failed to create file: %s", output_name.c_str());
     int res = ftruncate(fd.get(), 0);
     PERFETTO_CHECK(res == 0);
   }
@@ -261,9 +261,9 @@
   bool attach_has_more = attach_it.Next();
   PERFETTO_DCHECK(!attach_has_more);
 
-  util::Status status = attach_it.Status();
+  base::Status status = attach_it.Status();
   if (!status.ok())
-    return util::ErrStatus("SQLite error: %s", status.c_message());
+    return base::ErrStatus("SQLite error: %s", status.c_message());
 
   // Export real and virtual tables.
   auto tables_it = g_tp->ExecuteQuery(
@@ -281,11 +281,11 @@
 
     status = export_it.Status();
     if (!status.ok())
-      return util::ErrStatus("SQLite error: %s", status.c_message());
+      return base::ErrStatus("SQLite error: %s", status.c_message());
   }
   status = tables_it.Status();
   if (!status.ok())
-    return util::ErrStatus("SQLite error: %s", status.c_message());
+    return base::ErrStatus("SQLite error: %s", status.c_message());
 
   // Export views.
   auto views_it =
@@ -305,18 +305,18 @@
 
     status = export_it.Status();
     if (!status.ok())
-      return util::ErrStatus("SQLite error: %s", status.c_message());
+      return base::ErrStatus("SQLite error: %s", status.c_message());
   }
   status = views_it.Status();
   if (!status.ok())
-    return util::ErrStatus("SQLite error: %s", status.c_message());
+    return base::ErrStatus("SQLite error: %s", status.c_message());
 
   auto detach_it = g_tp->ExecuteQuery("DETACH DATABASE perfetto_export");
   bool detach_has_more = attach_it.Next();
   PERFETTO_DCHECK(!detach_has_more);
   status = detach_it.Status();
-  return status.ok() ? util::OkStatus()
-                     : util::ErrStatus("SQLite error: %s", status.c_message());
+  return status.ok() ? base::OkStatus()
+                     : base::ErrStatus("SQLite error: %s", status.c_message());
 }
 
 class ErrorPrinter : public google::protobuf::io::ErrorCollector {
@@ -338,7 +338,7 @@
                                         : metric_path.substr(slash_idx + 1);
 }
 
-util::Status RegisterMetric(const std::string& register_metric) {
+base::Status RegisterMetric(const std::string& register_metric) {
   std::string sql;
   base::ReadFile(register_metric, &sql);
 
@@ -363,7 +363,7 @@
   return base::OkStatus();
 }
 
-util::Status ExtendMetricsProto(const std::string& extend_metrics_proto,
+base::Status ExtendMetricsProto(const std::string& extend_metrics_proto,
                                 google::protobuf::DescriptorPool* pool) {
   google::protobuf::FileDescriptorSet desc_set;
   auto* file_desc = desc_set.add_file();
@@ -392,7 +392,7 @@
   base::Optional<std::string> no_ext_path;
 };
 
-util::Status RunMetrics(const std::vector<MetricNameAndPath>& metrics,
+base::Status RunMetrics(const std::vector<MetricNameAndPath>& metrics,
                         OutputFormat format,
                         const google::protobuf::DescriptorPool& pool) {
   std::vector<std::string> metric_names(metrics.size());
@@ -402,21 +402,21 @@
 
   if (format == OutputFormat::kTextProto) {
     std::string out;
-    util::Status status =
+    base::Status status =
         g_tp->ComputeMetricText(metric_names, TraceProcessor::kProtoText, &out);
     if (!status.ok()) {
-      return util::ErrStatus("Error when computing metrics: %s",
+      return base::ErrStatus("Error when computing metrics: %s",
                              status.c_message());
     }
     out += '\n';
     fwrite(out.c_str(), sizeof(char), out.size(), stdout);
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   std::vector<uint8_t> metric_result;
-  util::Status status = g_tp->ComputeMetric(metric_names, &metric_result);
+  base::Status status = g_tp->ComputeMetric(metric_names, &metric_result);
   if (!status.ok()) {
-    return util::ErrStatus("Error when computing metrics: %s",
+    return base::ErrStatus("Error when computing metrics: %s",
                            status.c_message());
   }
 
@@ -451,16 +451,18 @@
       PERFETTO_FATAL("This case was already handled.");
   }
 
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 void PrintQueryResultInteractively(Iterator* it,
                                    base::TimeNanos t_start,
                                    uint32_t column_width) {
-  base::TimeNanos t_end = t_start;
+  base::TimeNanos t_end = base::GetWallTimeNs();
   for (uint32_t rows = 0; it->Next(); rows++) {
     if (rows % 32 == 0) {
-      if (rows > 0) {
+      if (rows == 0) {
+        t_end = base::GetWallTimeNs();
+      } else {
         fprintf(stderr, "...\nType 'q' to stop, Enter for more records: ");
         fflush(stderr);
         char input[32];
@@ -468,8 +470,6 @@
           exit(0);
         if (input[0] == 'q')
           break;
-      } else {
-        t_end = base::GetWallTimeNs();
       }
       for (uint32_t i = 0; i < it->ColumnCount(); i++)
         printf("%-*.*s ", column_width, column_width,
@@ -507,7 +507,7 @@
     printf("\n");
   }
 
-  util::Status status = it->Status();
+  base::Status status = it->Status();
   if (!status.ok()) {
     PERFETTO_ELOG("SQLite error: %s", status.c_message());
   }
@@ -515,7 +515,7 @@
          static_cast<double>((t_end - t_start).count()) / 1E6);
 }
 
-util::Status PrintQueryResultAsCsv(Iterator* it, FILE* output) {
+base::Status PrintQueryResultAsCsv(Iterator* it, bool has_more, FILE* output) {
   for (uint32_t c = 0; c < it->ColumnCount(); c++) {
     if (c > 0)
       fprintf(output, ",");
@@ -523,7 +523,8 @@
   }
   fprintf(output, "\n");
 
-  for (uint32_t rows = 0; it->Next(); rows++) {
+  uint32_t rows;
+  for (rows = 0; has_more; rows++, has_more = it->Next()) {
     for (uint32_t c = 0; c < it->ColumnCount(); c++) {
       if (c > 0)
         fprintf(output, ",");
@@ -552,112 +553,59 @@
   return it->Status();
 }
 
-bool IsCommentLine(const std::string& buffer) {
-  return base::StartsWith(buffer, "--");
+base::Status RunQueriesWithoutOutput(const std::string& sql_query) {
+  auto it = g_tp->ExecuteQuery(sql_query);
+  if (it.StatementWithOutputCount() > 0)
+    return base::ErrStatus("Unexpected result from a query.");
+
+  RETURN_IF_ERROR(it.Status());
+  return it.Next() ? base::ErrStatus("Unexpected result from a query.")
+                   : it.Status();
 }
 
-bool HasEndOfQueryDelimiter(const std::string& buffer) {
-  return base::EndsWith(buffer, ";\n") || base::EndsWith(buffer, ";") ||
-         base::EndsWith(buffer, ";\r\n");
-}
-
-util::Status LoadQueries(FILE* input, std::vector<std::string>* output) {
-  char buffer[4096];
-  while (!feof(input) && !ferror(input)) {
-    std::string sql_query;
-    while (fgets(buffer, sizeof(buffer), input)) {
-      std::string line = base::TrimLeading(buffer);
-
-      if (IsCommentLine(line))
-        continue;
-
-      sql_query.append(line);
-
-      if (HasEndOfQueryDelimiter(line))
-        break;
-    }
-    if (!sql_query.empty() && sql_query.back() == '\n')
-      sql_query.resize(sql_query.size() - 1);
-
-    // If we have a new line at the end of the file or an extra new line
-    // somewhere in the file, we'll end up with an empty query which we should
-    // just ignore.
-    if (sql_query.empty())
-      continue;
-
-    output->push_back(sql_query);
-  }
-  if (ferror(input)) {
-    return util::ErrStatus("Error reading query file");
-  }
-  return util::OkStatus();
-}
-
-util::Status RunQueriesWithoutOutput(const std::vector<std::string>& queries) {
-  for (const auto& sql_query : queries) {
-    PERFETTO_DLOG("Executing query: %s", sql_query.c_str());
-
-    auto it = g_tp->ExecuteQuery(sql_query);
-    RETURN_IF_ERROR(it.Status());
-    if (it.Next()) {
-      return util::ErrStatus("Unexpected result from a query.");
-    }
-    RETURN_IF_ERROR(it.Status());
-  }
-  return util::OkStatus();
-}
-
-util::Status RunQueriesAndPrintResult(const std::vector<std::string>& queries,
+base::Status RunQueriesAndPrintResult(const std::string& sql_query,
                                       FILE* output) {
-  bool is_first_query = true;
-  bool has_output = false;
-  for (const auto& sql_query : queries) {
-    // Add an extra newline separator between query results.
-    if (!is_first_query)
-      fprintf(output, "\n");
-    is_first_query = false;
+  PERFETTO_ILOG("Executing query: %s", sql_query.c_str());
+  auto query_start = std::chrono::steady_clock::now();
 
-    PERFETTO_ILOG("Executing query: %s", sql_query.c_str());
+  auto it = g_tp->ExecuteQuery(sql_query);
+  RETURN_IF_ERROR(it.Status());
 
-    auto it = g_tp->ExecuteQuery(sql_query);
-    RETURN_IF_ERROR(it.Status());
-    if (it.ColumnCount() == 0) {
-      bool it_has_more = it.Next();
-      RETURN_IF_ERROR(it.Status());
-      PERFETTO_DCHECK(!it_has_more);
-      continue;
-    }
+  bool has_more = it.Next();
+  RETURN_IF_ERROR(it.Status());
 
-    // If we have a single column with the name |suppress_query_output| that's
-    // a hint to shell that it should not treat the query as having real
-    // meaning.
-    if (it.ColumnCount() == 1 &&
-        it.GetColumnName(0) == "suppress_query_output") {
-      // We should only see a single null value as this feature is usually used
-      // as SELECT RUN_METRIC(<metric file>) as suppress_query_output and
-      // RUN_METRIC returns a single null.
-      bool has_next = it.Next();
-      RETURN_IF_ERROR(it.Status());
-      PERFETTO_DCHECK(has_next);
-      PERFETTO_DCHECK(it.Get(0).is_null());
-
-      has_next = it.Next();
-      RETURN_IF_ERROR(it.Status());
-      PERFETTO_DCHECK(!has_next);
-      continue;
-    }
-
-    if (has_output) {
-      return util::ErrStatus(
-          "More than one query generated result rows. This is unsupported.");
-    }
-    has_output = true;
-    RETURN_IF_ERROR(PrintQueryResultAsCsv(&it, output));
+  uint32_t prev_count = it.StatementCount() - 1;
+  uint32_t prev_with_output = has_more ? it.StatementWithOutputCount() - 1
+                                       : it.StatementWithOutputCount();
+  uint32_t prev_without_output_count = prev_count - prev_with_output;
+  if (prev_with_output > 0) {
+    return base::ErrStatus(
+        "Result rows were returned for multiples queries. Ensure that only the "
+        "final statement is a SELECT statment or use `suppress_query_output` "
+        "to prevent function invocations causing this "
+        "error (see "
+        "https://perfetto.dev/docs/contributing/"
+        "testing#trace-processor-diff-tests).");
   }
-  return util::OkStatus();
+  for (uint32_t i = 0; i < prev_without_output_count; ++i) {
+    fprintf(output, "\n");
+  }
+  if (it.ColumnCount() == 0) {
+    PERFETTO_DCHECK(!has_more);
+    return base::OkStatus();
+  }
+
+  auto query_end = std::chrono::steady_clock::now();
+  RETURN_IF_ERROR(PrintQueryResultAsCsv(&it, has_more, output));
+
+  auto dur = query_end - query_start;
+  PERFETTO_ILOG(
+      "Query execution time: %lld ms",
+      std::chrono::duration_cast<std::chrono::milliseconds>(dur).count());
+  return base::OkStatus();
 }
 
-util::Status PrintPerfFile(const std::string& perf_file_path,
+base::Status PrintPerfFile(const std::string& perf_file_path,
                            base::TimeNanos t_load,
                            base::TimeNanos t_run) {
   char buf[128];
@@ -665,15 +613,15 @@
                                     static_cast<int64_t>(t_load.count()),
                                     static_cast<int64_t>(t_run.count()));
   if (count == 0) {
-    return util::ErrStatus("Failed to write perf data");
+    return base::ErrStatus("Failed to write perf data");
   }
 
   auto fd(base::OpenFile(perf_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666));
   if (!fd) {
-    return util::ErrStatus("Failed to open perf file");
+    return base::ErrStatus("Failed to open perf file");
   }
   base::WriteAll(fd.get(), buf, count);
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 class MetricExtension {
@@ -948,14 +896,14 @@
   }
 }
 
-util::Status LoadTrace(const std::string& trace_file_path, double* size_mb) {
-  util::Status read_status =
+base::Status LoadTrace(const std::string& trace_file_path, double* size_mb) {
+  base::Status read_status =
       ReadTrace(g_tp, trace_file_path.c_str(), [&size_mb](size_t parsed_size) {
         *size_mb = static_cast<double>(parsed_size) / 1E6;
         fprintf(stderr, "\rLoading trace: %.2f MB\r", *size_mb);
       });
   if (!read_status.ok()) {
-    return util::ErrStatus("Could not read trace file (path: %s): %s",
+    return base::ErrStatus("Could not read trace file (path: %s): %s",
                            trace_file_path.c_str(), read_status.c_message());
   }
 
@@ -992,30 +940,25 @@
           }
         });
   }
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
-util::Status RunQueries(const std::string& query_file_path,
+base::Status RunQueries(const std::string& query_file_path,
                         bool expect_output) {
-  std::vector<std::string> queries;
-  base::ScopedFstream file(fopen(query_file_path.c_str(), "r"));
-  if (!file) {
-    return util::ErrStatus("Could not open query file (path: %s)",
-                           query_file_path.c_str());
-  }
-  RETURN_IF_ERROR(LoadQueries(file.get(), &queries));
+  std::string queries;
+  base::ReadFile(query_file_path.c_str(), &queries);
 
-  util::Status status;
+  base::Status status;
   if (expect_output) {
     status = RunQueriesAndPrintResult(queries, stdout);
   } else {
     status = RunQueriesWithoutOutput(queries);
   }
   if (!status.ok()) {
-    return util::ErrStatus("Encountered error while running queries: %s",
+    return base::ErrStatus("Encountered error while running queries: %s",
                            status.c_message());
   }
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 base::Status ParseSingleMetricExtensionPath(bool dev,
@@ -1045,7 +988,7 @@
     return base::Status(
         "Cannot have 'shell/' as metric extension virtual path.");
   }
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 base::Status CheckForDuplicateMetricExtension(
@@ -1157,7 +1100,7 @@
   return base::OkStatus();
 }
 
-util::Status PopulateDescriptorPool(
+base::Status PopulateDescriptorPool(
     google::protobuf::DescriptorPool& pool,
     const std::vector<MetricExtension>& metric_extensions) {
   // TODO(b/182165266): There is code duplication here with trace_processor_impl
@@ -1176,7 +1119,7 @@
   return base::OkStatus();
 }
 
-util::Status LoadMetrics(const std::string& raw_metric_names,
+base::Status LoadMetrics(const std::string& raw_metric_names,
                          google::protobuf::DescriptorPool& pool,
                          std::vector<MetricNameAndPath>& name_and_path) {
   std::vector<std::string> split;
@@ -1198,15 +1141,15 @@
     std::string no_ext_path = metric_or_path.substr(0, ext_idx);
 
     // The proto must be extended before registering the metric.
-    util::Status status = ExtendMetricsProto(no_ext_path + ".proto", &pool);
+    base::Status status = ExtendMetricsProto(no_ext_path + ".proto", &pool);
     if (!status.ok()) {
-      return util::ErrStatus("Unable to extend metrics proto %s: %s",
+      return base::ErrStatus("Unable to extend metrics proto %s: %s",
                              metric_or_path.c_str(), status.c_message());
     }
 
     status = RegisterMetric(no_ext_path + ".sql");
     if (!status.ok()) {
-      return util::ErrStatus("Unable to register metric %s: %s",
+      return base::ErrStatus("Unable to register metric %s: %s",
                              metric_or_path.c_str(), status.c_message());
     }
     name_and_path.emplace_back(
@@ -1268,7 +1211,7 @@
   const google::protobuf::DescriptorPool* pool;
 };
 
-util::Status StartInteractiveShell(const InteractiveOptions& options) {
+base::Status StartInteractiveShell(const InteractiveOptions& options) {
   SetupLineEditor();
 
   uint32_t column_width = options.column_width;
@@ -1294,7 +1237,7 @@
       } else if (strcmp(command, "reset") == 0) {
         g_tp->RestoreInitialTables();
       } else if (strcmp(command, "read") == 0 && strlen(arg)) {
-        util::Status status = RunQueries(arg, true);
+        base::Status status = RunQueries(arg, true);
         if (!status.ok()) {
           PERFETTO_ELOG("%s", status.c_message());
         }
@@ -1332,10 +1275,10 @@
     auto it = g_tp->ExecuteQuery(line.get());
     PrintQueryResultInteractively(&it, t_start, column_width);
   }
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
-util::Status TraceProcessorMain(int argc, char** argv) {
+base::Status TraceProcessorMain(int argc, char** argv) {
   CommandLineOptions options = ParseCommandLineOptions(argc, argv);
 
   Config config;
@@ -1434,21 +1377,21 @@
 
   if (!options.metatrace_path.empty()) {
     std::vector<uint8_t> serialized;
-    util::Status status = g_tp->DisableAndReadMetatrace(&serialized);
+    base::Status status = g_tp->DisableAndReadMetatrace(&serialized);
     if (!status.ok())
       return status;
 
     auto file =
         base::OpenFile(options.metatrace_path, O_CREAT | O_RDWR | O_TRUNC);
     if (!file)
-      return util::ErrStatus("Unable to open metatrace file");
+      return base::ErrStatus("Unable to open metatrace file");
 
     ssize_t res = base::WriteAll(*file, serialized.data(), serialized.size());
     if (res < 0)
-      return util::ErrStatus("Error while writing metatrace file");
+      return base::ErrStatus("Error while writing metatrace file");
   }
 
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 }  // namespace
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 6625373..7df33a6 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -17,22 +17,19 @@
 #ifndef SRC_TRACE_PROCESSOR_TRACE_SORTER_H_
 #define SRC_TRACE_PROCESSOR_TRACE_SORTER_H_
 
+#include <algorithm>
+#include <memory>
+#include <utility>
 #include <vector>
 
 #include "perfetto/ext/base/circular_queue.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/trace_blob_view.h"
-#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 
-namespace Json {
-class Value;
-}  // namespace Json
-
 namespace perfetto {
 namespace trace_processor {
 
-class FuchsiaProviderView;
 class PacketSequenceState;
 struct SystraceLine;
 
diff --git a/src/trace_processor/types/BUILD.gn b/src/trace_processor/types/BUILD.gn
index 46d16b9..ba26e24 100644
--- a/src/trace_processor/types/BUILD.gn
+++ b/src/trace_processor/types/BUILD.gn
@@ -21,6 +21,7 @@
     "softirq_action.h",
     "task_state.cc",
     "task_state.h",
+    "tcp_state.h",
     "trace_processor_context.h",
     "variadic.cc",
     "variadic.h",
diff --git a/src/trace_processor/types/tcp_state.h b/src/trace_processor/types/tcp_state.h
new file mode 100644
index 0000000..1db06de
--- /dev/null
+++ b/src/trace_processor/types/tcp_state.h
@@ -0,0 +1,55 @@
+/*
+ * 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_TYPES_TCP_STATE_H_
+#define SRC_TRACE_PROCESSOR_TYPES_TCP_STATE_H_
+
+namespace perfetto {
+namespace trace_processor {
+
+// IPV4 protocol
+constexpr int kAfNet = 2;
+// IPV6 protocol
+constexpr int kAfNet6 = 10;
+// TCP protocol
+constexpr int kIpprotoTcp = 6;
+// TCP protocol states, from include/net/tcp_states.h.
+enum {
+  TCP_ESTABLISHED = 1,
+  TCP_SYN_SENT,
+  TCP_SYN_RECV,
+  TCP_FIN_WAIT1,
+  TCP_FIN_WAIT2,
+  TCP_TIME_WAIT,
+  TCP_CLOSE,
+  TCP_CLOSE_WAIT,
+  TCP_LAST_ACK,
+  TCP_LISTEN,
+  TCP_CLOSING,
+  TCP_NEW_SYN_RECV,
+  TCP_MAX_STATES
+};
+// TCP protocol state to string mapping.
+static constexpr const char* const kTcpStateNames[] = {
+    "TCP_UNKNOWN", "TCP_ESTABLISHED", "TCP_SYN_SENT", "TCP_SYN_RECV",
+    "TCP_FIN_WAIT1", "TCP_FIN_WAIT2","TCP_TIME_WAIT", "TCP_CLOSE",
+    "TCP_CLOSE_WAIT","TCP_LAST_ACK", "TCP_LISTEN", "TCP_CLOSING",
+    "TCP_NEW_SYN_RECV","TCP_MAX_STATES"};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_TYPES_TCP_STATE_H_
diff --git a/src/traced/probes/common/cpu_freq_info.cc b/src/traced/probes/common/cpu_freq_info.cc
index 0137369..2d3c1f9 100644
--- a/src/traced/probes/common/cpu_freq_info.cc
+++ b/src/traced/probes/common/cpu_freq_info.cc
@@ -16,6 +16,8 @@
 
 #include "src/traced/probes/common/cpu_freq_info.h"
 
+#include <unistd.h>
+
 #include <set>
 
 #include "perfetto/ext/base/file_utils.h"
@@ -42,10 +44,11 @@
 
 }  // namespace
 
-CpuFreqInfo::CpuFreqInfo(std::string cpu_dir_path) {
-  base::ScopedDir cpu_dir(opendir(cpu_dir_path.c_str()));
+CpuFreqInfo::CpuFreqInfo(std::string sysfs_cpu_path)
+    : sysfs_cpu_path_{sysfs_cpu_path} {
+  base::ScopedDir cpu_dir(opendir(sysfs_cpu_path_.c_str()));
   if (!cpu_dir) {
-    PERFETTO_PLOG("Failed to opendir(%s)", cpu_dir_path.c_str());
+    PERFETTO_PLOG("Failed to opendir(%s)", sysfs_cpu_path_.c_str());
     return;
   }
   // Accumulate cpu and freqs into a set to ensure stable order.
@@ -64,11 +67,11 @@
     uint32_t cpu_index = maybe_cpu_index.value();
     ReadAndAppendFreqs(
         &freqs, cpu_index,
-        ReadFile(cpu_dir_path + "/cpu" + std::to_string(cpu_index) +
+        ReadFile(sysfs_cpu_path_ + "/cpu" + std::to_string(cpu_index) +
                  "/cpufreq/scaling_available_frequencies"));
     ReadAndAppendFreqs(
         &freqs, cpu_index,
-        ReadFile(cpu_dir_path + "/cpu" + std::to_string(cpu_index) +
+        ReadFile(sysfs_cpu_path_ + "/cpu" + std::to_string(cpu_index) +
                  "/cpufreq/scaling_boost_frequencies"));
   }
 
@@ -118,4 +121,28 @@
   return contents;
 }
 
+const std::vector<uint32_t>& CpuFreqInfo::ReadCpuCurrFreq() {
+  // Check if capacity of cpu_curr_freq_ is enough for all CPUs
+  auto num_cpus = static_cast<size_t>(sysconf(_SC_NPROCESSORS_CONF));
+  if (cpu_curr_freq_.size() < num_cpus)
+    cpu_curr_freq_.resize(num_cpus);
+
+  for (uint32_t i = 0; i < cpu_curr_freq_.size(); i++) {
+    // Read CPU current frequency. Set 0 for offline/disabled cpus.
+    std::string buf(ReadFile(sysfs_cpu_path_ + "/cpu" + std::to_string(i) +
+                             "/cpufreq/scaling_cur_freq"));
+    if (buf.empty()) {
+      cpu_curr_freq_[i] = 0;
+      continue;
+    }
+    auto freq = base::StringToUInt32(base::StripSuffix(buf, "\n"));
+    if (!freq.has_value()) {
+      cpu_curr_freq_[i] = 0;
+      continue;
+    }
+    cpu_curr_freq_[i] = freq.value();
+  }
+  return cpu_curr_freq_;
+}
+
 }  // namespace perfetto
diff --git a/src/traced/probes/common/cpu_freq_info.h b/src/traced/probes/common/cpu_freq_info.h
index 1bcdd79..36f7f9c 100644
--- a/src/traced/probes/common/cpu_freq_info.h
+++ b/src/traced/probes/common/cpu_freq_info.h
@@ -35,13 +35,19 @@
   Range GetFreqs(uint32_t cpu);
   uint32_t GetCpuFreqIndex(uint32_t cpu, uint32_t freq);
 
+  const std::vector<uint32_t>& ReadCpuCurrFreq();
+
  private:
+  // e.g. /sys/devices/system/cpu
+  std::string sysfs_cpu_path_;
   // All frequencies of all CPUs, ordered by CPU and frequency. Includes a guard
   // at the end.
   std::vector<uint32_t> frequencies_;
   // frequencies_index_[cpu] points to first frequency in frequencies_. Includes
   // a guard at the end.
   std::vector<size_t> frequencies_index_;
+  // Placeholder for CPU current frequency, refresh in ReadCpuCurrFreq()
+  std::vector<uint32_t> cpu_curr_freq_;
 
   std::string ReadFile(std::string path);
 };
diff --git a/src/traced/probes/common/cpu_freq_info_for_testing.cc b/src/traced/probes/common/cpu_freq_info_for_testing.cc
index ddfa5d2..2fa1637 100644
--- a/src/traced/probes/common/cpu_freq_info_for_testing.cc
+++ b/src/traced/probes/common/cpu_freq_info_for_testing.cc
@@ -44,6 +44,7 @@
           kCpuFrequenciesAndroidLittleCore);
   tmpdir_.AddFile("cpu0/cpufreq/scaling_boost_frequencies",
           kCpuBoostFrequenciesAndroidLittleCore);
+  tmpdir_.AddFile("cpu0/cpufreq/scaling_cur_freq", "2650000");
   tmpdir_.AddDir("cpufreq");
   tmpdir_.AddDir("cpu1");
   tmpdir_.AddDir("cpu1/cpufreq");
@@ -51,6 +52,7 @@
           kCpuFrequenciesAndroidBigCore);
   tmpdir_.AddFile("cpu1/cpufreq/scaling_boost_frequencies",
           kCpuBoostFrequenciesAndroidBigCore);
+  tmpdir_.AddFile("cpu1/cpufreq/scaling_cur_freq", "3698200");
   tmpdir_.AddDir("power");
 }
 
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 6234363..0b3d5b3 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -6450,6 +6450,40 @@
        kUnsetFtraceId,
        325,
        kUnsetSize},
+      {"inet_sock_set_state",
+       "sock",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "daddr", 1, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dport", 2, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "family", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "newstate", 4, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "oldstate", 5, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "protocol", 6, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "saddr", 7, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "skaddr", 8, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "sport", 9, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       362,
+       kUnsetSize},
       {"sync_pt",
        "sync",
        {
@@ -6568,6 +6602,34 @@
        kUnsetFtraceId,
        236,
        kUnsetSize},
+      {"tcp_retransmit_skb",
+       "tcp",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "daddr", 1, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dport", 2, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "saddr", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "skaddr", 4, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "skbaddr", 5, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "sport", 6, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "state", 7, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       363,
+       kUnsetSize},
       {"thermal_temperature",
        "thermal",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/sock/inet_sock_set_state/format b/src/traced/probes/ftrace/test/data/synthetic/events/sock/inet_sock_set_state/format
new file mode 100644
index 0000000..d7c36c7
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/sock/inet_sock_set_state/format
@@ -0,0 +1,21 @@
+name: inet_sock_set_state
+ID: 924
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:const void * skaddr;	offset:8;	size:8;	signed:0;
+	field:int oldstate;	offset:16;	size:4;	signed:1;
+	field:int newstate;	offset:20;	size:4;	signed:1;
+	field:__u16 sport;	offset:24;	size:2;	signed:0;
+	field:__u16 dport;	offset:26;	size:2;	signed:0;
+	field:__u16 family;	offset:28;	size:2;	signed:0;
+	field:__u16 protocol;	offset:30;	size:2;	signed:0;
+	field:__u8 saddr[4];	offset:32;	size:4;	signed:0;
+	field:__u8 daddr[4];	offset:36;	size:4;	signed:0;
+	field:__u8 saddr_v6[16];	offset:40;	size:16;	signed:0;
+	field:__u8 daddr_v6[16];	offset:56;	size:16;	signed:0;
+
+print fmt: "family=%s protocol=%s sport=%hu dport=%hu saddr=%pI4 daddr=%pI4 saddrv6=%pI6c daddrv6=%pI6c oldstate=%s newstate=%s", __print_symbolic(REC->family, { 2, "AF_INET" }, { 10, "AF_INET6" }), __print_symbolic(REC->protocol, { 6, "IPPROTO_TCP" }, { 33, "IPPROTO_DCCP" }, { 132, "IPPROTO_SCTP" }, { 262, "IPPROTO_MPTCP" }), REC->sport, REC->dport, REC->saddr, REC->daddr, REC->saddr_v6, REC->daddr_v6, __print_symbolic(REC->oldstate, { 1, "TCP_ESTABLISHED" }, { 2, "TCP_SYN_SENT" }, { 3, "TCP_SYN_RECV" }, { 4, "TCP_FIN_WAIT1" }, { 5, "TCP_FIN_WAIT2" }, { 6, "TCP_TIME_WAIT" }, { 7, "TCP_CLOSE" }, { 8, "TCP_CLOSE_WAIT" }, { 9, "TCP_LAST_ACK" }, { 10, "TCP_LISTEN" }, { 11, "TCP_CLOSING" }, { 12, "TCP_NEW_SYN_RECV" }), __print_symbolic(REC->newstate, { 1, "TCP_ESTABLISHED" }, { 2, "TCP_SYN_SENT" }, { 3, "TCP_SYN_RECV" }, { 4, "TCP_FIN_WAIT1" }, { 5, "TCP_FIN_WAIT2" }, { 6, "TCP_TIME_WAIT" }, { 7, "TCP_CLOSE" }, { 8, "TCP_CLOSE_WAIT" }, { 9, "TCP_LAST_ACK" }, { 10, "TCP_LISTEN" }, { 11, "TCP_CLOSING" }, { 12, "TCP_NEW_SYN_RECV" })
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/tcp/tcp_retransmit_skb/format b/src/traced/probes/ftrace/test/data/synthetic/events/tcp/tcp_retransmit_skb/format
new file mode 100644
index 0000000..a73ee0e
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/tcp/tcp_retransmit_skb/format
@@ -0,0 +1,19 @@
+name: tcp_retransmit_skb
+ID: 967
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:const void * skbaddr;	offset:8;	size:8;	signed:0;
+	field:const void * skaddr;	offset:16;	size:8;	signed:0;
+	field:int state;	offset:24;	size:4;	signed:1;
+	field:__u16 sport;	offset:28;	size:2;	signed:0;
+	field:__u16 dport;	offset:30;	size:2;	signed:0;
+	field:__u8 saddr[4];	offset:32;	size:4;	signed:0;
+	field:__u8 daddr[4];	offset:36;	size:4;	signed:0;
+	field:__u8 saddr_v6[16];	offset:40;	size:16;	signed:0;
+	field:__u8 daddr_v6[16];	offset:56;	size:16;	signed:0;
+
+print fmt: "sport=%hu dport=%hu saddr=%pI4 daddr=%pI4 saddrv6=%pI6c daddrv6=%pI6c state=%s", REC->sport, REC->dport, REC->saddr, REC->daddr, REC->saddr_v6, REC->daddr_v6, __print_symbolic(REC->state, { TCP_ESTABLISHED, "TCP_ESTABLISHED" }, { TCP_SYN_SENT, "TCP_SYN_SENT" }, { TCP_SYN_RECV, "TCP_SYN_RECV" }, { TCP_FIN_WAIT1, "TCP_FIN_WAIT1" }, { TCP_FIN_WAIT2, "TCP_FIN_WAIT2" }, { TCP_TIME_WAIT, "TCP_TIME_WAIT" }, { TCP_CLOSE, "TCP_CLOSE" }, { TCP_CLOSE_WAIT, "TCP_CLOSE_WAIT" }, { TCP_LAST_ACK, "TCP_LAST_ACK" }, { TCP_LISTEN, "TCP_LISTEN" }, { TCP_CLOSING, "TCP_CLOSING" }, { TCP_NEW_SYN_RECV, "TCP_NEW_SYN_RECV" })
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index b2c4297..ebe5745 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -324,9 +324,9 @@
     TracingSessionID session_id,
     const DataSourceConfig& config) {
   auto buffer_id = static_cast<BufferID>(config.target_buffer());
-  return std::unique_ptr<SysStatsDataSource>(
-      new SysStatsDataSource(task_runner_, session_id,
-                             endpoint_->CreateTraceWriter(buffer_id), config));
+  return std::unique_ptr<SysStatsDataSource>(new SysStatsDataSource(
+      task_runner_, session_id, endpoint_->CreateTraceWriter(buffer_id), config,
+      std::unique_ptr<CpuFreqInfo>(new CpuFreqInfo())));
 }
 
 std::unique_ptr<ProbesDataSource> ProbesProducer::CreateMetatraceDataSource(
diff --git a/src/traced/probes/sys_stats/BUILD.gn b/src/traced/probes/sys_stats/BUILD.gn
index 9c02da8..ceeaaf5 100644
--- a/src/traced/probes/sys_stats/BUILD.gn
+++ b/src/traced/probes/sys_stats/BUILD.gn
@@ -26,6 +26,7 @@
     "../../../../protos/perfetto/trace:zero",
     "../../../../protos/perfetto/trace/sys_stats:zero",
     "../../../base",
+    "../common",
   ]
   sources = [
     "sys_stats_data_source.cc",
@@ -43,6 +44,7 @@
     "../../../../protos/perfetto/trace/sys_stats:cpp",
     "../../../../src/base:test_support",
     "../../../../src/tracing/test:test_support",
+    "../common:test_support",
   ]
   sources = [ "sys_stats_data_source_unittest.cc" ]
 }
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index a79cb40..93c292b 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -72,14 +72,17 @@
     /*flags*/ Descriptor::kFlagsNone,
 };
 
-SysStatsDataSource::SysStatsDataSource(base::TaskRunner* task_runner,
-                                       TracingSessionID session_id,
-                                       std::unique_ptr<TraceWriter> writer,
-                                       const DataSourceConfig& ds_config,
-                                       OpenFunction open_fn)
+SysStatsDataSource::SysStatsDataSource(
+    base::TaskRunner* task_runner,
+    TracingSessionID session_id,
+    std::unique_ptr<TraceWriter> writer,
+    const DataSourceConfig& ds_config,
+    std::unique_ptr<CpuFreqInfo> cpu_freq_info,
+    OpenFunction open_fn)
     : ProbesDataSource(session_id, &descriptor),
       task_runner_(task_runner),
       writer_(std::move(writer)),
+      cpu_freq_info_(std::move(cpu_freq_info)),
       weak_factory_(this) {
   ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
 
@@ -139,14 +142,15 @@
     stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
   }
 
-  std::array<uint32_t, 4> periods_ms{};
-  std::array<uint32_t, 4> ticks{};
+  std::array<uint32_t, 5> periods_ms{};
+  std::array<uint32_t, 5> ticks{};
   static_assert(periods_ms.size() == ticks.size(), "must have same size");
 
   periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
   periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
   periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
   periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms");
+  periods_ms[4] = ClampTo10Ms(cfg.cpufreq_period_ms(), "cpufreq_period_ms");
 
   tick_period_ms_ = 0;
   for (uint32_t ms : periods_ms) {
@@ -169,6 +173,7 @@
   vmstat_ticks_ = ticks[1];
   stat_ticks_ = ticks[2];
   devfreq_ticks_ = ticks[3];
+  cpufreq_ticks_ = ticks[4];
 }
 
 void SysStatsDataSource::Start() {
@@ -212,6 +217,9 @@
   if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0)
     ReadDevfreq(sys_stats);
 
+  if (cpufreq_ticks_ && tick_ % cpufreq_ticks_ == 0)
+    ReadCpufreq(sys_stats);
+
   sys_stats->set_collection_end_timestamp(
       static_cast<uint64_t>(base::GetBootTimeNs().count()));
 
@@ -235,6 +243,13 @@
   }
 }
 
+void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) {
+  const auto& cpufreq = cpu_freq_info_->ReadCpuCurrFreq();
+
+  for (const auto& c : cpufreq)
+    sys_stats->add_cpufreq_khz(c);
+}
+
 base::ScopedDir SysStatsDataSource::OpenDevfreqDir() {
   const char* base_dir = "/sys/class/devfreq/";
   base::ScopedDir devfreq_dir(opendir(base_dir));
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.h b/src/traced/probes/sys_stats/sys_stats_data_source.h
index cf47770..3140efa 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.h
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.h
@@ -29,6 +29,7 @@
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/tracing/core/data_source_config.h"
+#include "src/traced/probes/common/cpu_freq_info.h"
 #include "src/traced/probes/probes_data_source.h"
 
 namespace perfetto {
@@ -52,6 +53,7 @@
                      TracingSessionID,
                      std::unique_ptr<TraceWriter> writer,
                      const DataSourceConfig&,
+                     std::unique_ptr<CpuFreqInfo> cpu_freq_info,
                      OpenFunction = nullptr);
   ~SysStatsDataSource() override;
 
@@ -84,6 +86,7 @@
   void ReadVmstat(protos::pbzero::SysStats* sys_stats);
   void ReadStat(protos::pbzero::SysStats* sys_stats);
   void ReadDevfreq(protos::pbzero::SysStats* sys_stats);
+  void ReadCpufreq(protos::pbzero::SysStats* sys_stats);
   size_t ReadFile(base::ScopedFile*, const char* path);
 
   base::TaskRunner* const task_runner_;
@@ -103,8 +106,11 @@
   uint32_t stat_ticks_ = 0;
   uint32_t stat_enabled_fields_ = 0;
   uint32_t devfreq_ticks_ = 0;
+  uint32_t cpufreq_ticks_ = 0;
   bool devfreq_error_logged_ = false;
 
+  std::unique_ptr<CpuFreqInfo> cpu_freq_info_;
+
   base::WeakPtrFactory<SysStatsDataSource> weak_factory_;  // Keep last.
 };
 
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
index b239ffa..a585edf 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
@@ -19,6 +19,7 @@
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "src/base/test/test_task_runner.h"
+#include "src/traced/probes/common/cpu_freq_info_for_testing.h"
 #include "src/traced/probes/sys_stats/sys_stats_data_source.h"
 #include "src/tracing/core/trace_writer_for_testing.h"
 #include "test/gtest_and_gmock.h"
@@ -196,11 +197,13 @@
                          TracingSessionID id,
                          std::unique_ptr<TraceWriter> writer,
                          const DataSourceConfig& config,
+                         std::unique_ptr<CpuFreqInfo> cpu_freq_info,
                          OpenFunction open_fn)
       : SysStatsDataSource(task_runner,
                            id,
                            std::move(writer),
                            config,
+                           std::move(cpu_freq_info),
                            open_fn) {}
 
   MOCK_METHOD0(OpenDevfreqDir, base::ScopedDir());
@@ -230,7 +233,8 @@
     writer_raw_ = writer.get();
     auto instance =
         std::unique_ptr<TestSysStatsDataSource>(new TestSysStatsDataSource(
-            &task_runner_, 0, std::move(writer), cfg, MockOpenReadOnly));
+            &task_runner_, 0, std::move(writer), cfg,
+            cpu_freq_info_for_testing_.GetInstance(), MockOpenReadOnly));
     instance->set_ns_per_user_hz_for_testing(1000000000ull / 100);  // 100 Hz.
     instance->Start();
     return instance;
@@ -252,6 +256,7 @@
 
   TraceWriterForTesting* writer_raw_ = nullptr;
   base::TestTaskRunner task_runner_;
+  CpuFreqInfoForTesting cpu_freq_info_for_testing_;
 };
 
 TEST_F(SysStatsDataSourceTest, Meminfo) {
@@ -482,5 +487,31 @@
   ASSERT_EQ(sys_stats.num_softirq_size(), 0);
 }
 
+TEST_F(SysStatsDataSourceTest, Cpufreq) {
+  protos::gen::SysStatsConfig cfg;
+  cfg.set_cpufreq_period_ms(10);
+  DataSourceConfig config_obj;
+  config_obj.set_sys_stats_config_raw(cfg.SerializeAsString());
+  auto data_source = GetSysStatsDataSource(config_obj);
+
+  WaitTick(data_source.get());
+
+  protos::gen::TracePacket packet = writer_raw_->GetOnlyTracePacket();
+  ASSERT_TRUE(packet.has_sys_stats());
+  const auto& sys_stats = packet.sys_stats();
+  EXPECT_GT(sys_stats.cpufreq_khz_size(), 0);
+  EXPECT_EQ(sys_stats.cpufreq_khz()[0], 2650000u);
+  if (sys_stats.cpufreq_khz_size() > 1) {
+    // We emulated 2 CPUs but it is possible the test system is single core.
+    EXPECT_EQ(sys_stats.cpufreq_khz()[1], 3698200u);
+  }
+  for (unsigned int i = 2;
+       i < static_cast<unsigned int>(sys_stats.cpufreq_khz_size()); i++) {
+    // For cpux which scaling_cur_freq was not emulated in unittest, cpufreq
+    // should be recorded as 0
+    EXPECT_EQ(sys_stats.cpufreq_khz()[i], 0u);
+  }
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/service/builtin_producer.cc b/src/traced/service/builtin_producer.cc
index 20d9849..a2007be 100644
--- a/src/traced/service/builtin_producer.cc
+++ b/src/traced/service/builtin_producer.cc
@@ -67,8 +67,16 @@
 }
 
 void BuiltinProducer::ConnectInProcess(TracingService* svc) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // TODO(primiano): ConnectProducer should take a base::PlatformProcessId not
+  // pid_t, as they are different on Windows. But that is a larger refactoring
+  // and not worth given this is the only use case where it clashes.
+  const pid_t cur_proc_id = 0;
+#else
+  const pid_t cur_proc_id = base::GetProcessId();
+#endif
   endpoint_ = svc->ConnectProducer(
-      this, base::GetCurrentUserId(), base::GetProcessId(), "traced",
+      this, base::GetCurrentUserId(), cur_proc_id, "traced",
       /*shared_memory_size_hint_bytes=*/16 * 1024, /*in_process=*/true,
       TracingService::ProducerSMBScrapingMode::kDisabled,
       /*shared_memory_page_size_hint_bytes=*/4096);
diff --git a/src/tracing/core/BUILD.gn b/src/tracing/core/BUILD.gn
index c81c7163..e082c08 100644
--- a/src/tracing/core/BUILD.gn
+++ b/src/tracing/core/BUILD.gn
@@ -92,6 +92,7 @@
     "../../../protos/perfetto/trace:zero",
     "../../../protos/perfetto/trace/ftrace:cpp",
     "../../../protos/perfetto/trace/perfetto:cpp",
+    "../../../src/protozero/filtering:bytecode_generator",
     "../../base",
     "../../base:test_support",
     "../test:test_support",
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index ad5cda3..696732e 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -626,9 +626,10 @@
       cfg.write_into_file()) {
     // We don't support this usecase because there are subtle assumptions which
     // break around TracingServiceEvents and windowed sorting (i.e. if we don't
-    // drain the events in ReadBuffers because we are waiting for STOP_TRACING,
-    // we can end up queueing up a lot of TracingServiceEvents and emitting them
-    // wildy out of order breaking windowed sorting in trace processor).
+    // drain the events in ReadBuffersIntoFile because we are waiting for
+    // STOP_TRACING, we can end up queueing up a lot of TracingServiceEvents and
+    // emitting them wildy out of order breaking windowed sorting in trace
+    // processor).
     MaybeLogUploadEvent(
         cfg, PerfettoStatsdAtom::kTracedEnableTracingStopTracingWriteIntoFile);
     return PERFETTO_SVC_ERR(
@@ -1649,6 +1650,12 @@
     return;
   }
 
+  if (tracing_session->state != TracingSession::STARTED) {
+    PERFETTO_ELOG("Flush() called, but tracing has not been started");
+    callback(false);
+    return;
+  }
+
   FlushRequestID flush_request_id = ++last_flush_request_id_;
   PendingFlush& pending_flush =
       tracing_session->pending_flushes
@@ -2012,7 +2019,35 @@
   if (IsWaitingForTrigger(tracing_session))
     return false;
 
-  return ReadBuffers(tsid, tracing_session, consumer);
+  // This is a rough threshold to determine how much to read from the buffer in
+  // each task. This is to avoid executing a single huge sending task for too
+  // long and risk to hit the watchdog. This is *not* an upper bound: we just
+  // stop accumulating new packets and PostTask *after* we cross this threshold.
+  // This constant essentially balances the PostTask and IPC overhead vs the
+  // responsiveness of the service. An extremely small value will cause one IPC
+  // and one PostTask for each slice but will keep the service extremely
+  // responsive. An extremely large value will batch the send for the full
+  // buffer in one large task, will hit the blocking send() once the socket
+  // buffers are full and hang the service for a bit (until the consumer
+  // catches up).
+  static constexpr size_t kApproxBytesPerTask = 32768;
+  bool has_more;
+  std::vector<TracePacket> packets =
+      ReadBuffers(tracing_session, kApproxBytesPerTask, &has_more);
+
+  if (has_more) {
+    auto weak_consumer = consumer->weak_ptr_factory_.GetWeakPtr();
+    auto weak_this = weak_ptr_factory_.GetWeakPtr();
+    task_runner_->PostTask([weak_this, weak_consumer, tsid] {
+      if (!weak_this || !weak_consumer)
+        return;
+      weak_this->ReadBuffersIntoConsumer(tsid, weak_consumer.get());
+    });
+  }
+
+  // Keep this as tail call, just in case the consumer re-enters.
+  consumer->consumer_->OnTraceData(std::move(packets), has_more);
+  return true;
 }
 
 bool TracingServiceImpl::ReadBuffersIntoFile(TracingSessionID tsid) {
@@ -2033,7 +2068,47 @@
       IsWaitingForTrigger(tracing_session))
     return false;
 
-  return ReadBuffers(tsid, tracing_session, nullptr);
+  // Speculative fix for the memory watchdog crash in b/195145848. This function
+  // uses the heap extensively and might need a M_PURGE. window.gc() is back.
+  // TODO(primiano): if this fixes the crash we might want to coalesce the purge
+  // and throttle it.
+  auto on_ret = base::OnScopeExit([] { base::MaybeReleaseAllocatorMemToOS(); });
+
+  // ReadBuffers() can allocate memory internally, for filtering. By limiting
+  // the data that ReadBuffers() reads to kWriteIntoChunksSize per iteration,
+  // we limit the amount of memory used on each iteration.
+  //
+  // It would be tempting to split this into multiple tasks like in
+  // ReadBuffersIntoConsumer, but that's not currently possible.
+  // ReadBuffersIntoFile has to read the whole available data before returning,
+  // to support the disable_immediately=true code paths.
+  bool has_more = true;
+  bool stop_writing_into_file = false;
+  do {
+    std::vector<TracePacket> packets =
+        ReadBuffers(tracing_session, kWriteIntoFileChunkSize, &has_more);
+
+    stop_writing_into_file = WriteIntoFile(tracing_session, std::move(packets));
+  } while (has_more && !stop_writing_into_file);
+
+  if (stop_writing_into_file || tracing_session->write_period_ms == 0) {
+    // Ensure all data was written to the file before we close it.
+    base::FlushFile(tracing_session->write_into_file.get());
+    tracing_session->write_into_file.reset();
+    tracing_session->write_period_ms = 0;
+    if (tracing_session->state == TracingSession::STARTED)
+      DisableTracing(tsid);
+    return true;
+  }
+
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+  task_runner_->PostDelayedTask(
+      [weak_this, tsid] {
+        if (weak_this)
+          weak_this->ReadBuffersIntoFile(tsid);
+      },
+      tracing_session->delay_to_next_write_period_ms());
+  return true;
 }
 
 bool TracingServiceImpl::IsWaitingForTrigger(TracingSession* tracing_session) {
@@ -2051,20 +2126,13 @@
   return false;
 }
 
-// Note: when this is called to write into a file passed when starting tracing
-// |consumer| will be == nullptr (as opposite to the case of a consumer asking
-// to send the trace data back over IPC).
-bool TracingServiceImpl::ReadBuffers(TracingSessionID tsid,
-                                     TracingSession* tracing_session,
-                                     ConsumerEndpointImpl* consumer) {
+std::vector<TracePacket> TracingServiceImpl::ReadBuffers(
+    TracingSession* tracing_session,
+    size_t threshold,
+    bool* has_more) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   PERFETTO_DCHECK(tracing_session);
-
-  // Speculative fix for the memory watchdog crash in b/195145848. This function
-  // uses the heap extensively and might need a M_PURGE. window.gc() is back.
-  // TODO(primiano): if this fixes the crash we might want to coalesce the purge
-  // and throttle it.
-  auto on_ret = base::OnScopeExit([] { base::MaybeReleaseAllocatorMemToOS(); });
+  *has_more = false;
 
   std::vector<TracePacket> packets;
   packets.reserve(1024);  // Just an educated guess to avoid trivial expansions.
@@ -2106,22 +2174,8 @@
     packets_bytes += packet.size();
   }
 
-  // This is a rough threshold to determine how much to read from the buffer in
-  // each task. This is to avoid executing a single huge sending task for too
-  // long and risk to hit the watchdog. This is *not* an upper bound: we just
-  // stop accumulating new packets and PostTask *after* we cross this threshold.
-  // This constant essentially balances the PostTask and IPC overhead vs the
-  // responsiveness of the service. An extremely small value will cause one IPC
-  // and one PostTask for each slice but will keep the service extremely
-  // responsive. An extremely large value will batch the send for the full
-  // buffer in one large task, will hit the blocking send() once the socket
-  // buffers are full and hang the service for a bit (until the consumer
-  // catches up).
-  static constexpr size_t kApproxBytesPerTask = 32768;
   bool did_hit_threshold = false;
 
-  // TODO(primiano): Extend the ReadBuffers API to allow reading only some
-  // buffers, not all of them in one go.
   for (size_t buf_idx = 0;
        buf_idx < tracing_session->num_buffers() && !did_hit_threshold;
        buf_idx++) {
@@ -2182,16 +2236,19 @@
 
       // Append the packet (inclusive of the trusted uid) to |packets|.
       packets_bytes += packet.size();
-      did_hit_threshold = packets_bytes >= kApproxBytesPerTask &&
-                          !tracing_session->write_into_file;
+      did_hit_threshold = packets_bytes >= threshold;
       packets.emplace_back(std::move(packet));
     }  // for(packets...)
   }    // for(buffers...)
 
-  const bool has_more = did_hit_threshold;
+  *has_more = did_hit_threshold;
 
-  if (!tracing_session->config.builtin_data_sources()
-           .disable_service_events()) {
+  // Only emit the "read complete" lifetime event when there is no more trace
+  // data available to read. These events are used as safe points to limit
+  // sorting in trace processor: the code shouldn't emit the event unless the
+  // buffers are empty.
+  if (!*has_more && !tracing_session->config.builtin_data_sources()
+                         .disable_service_events()) {
     // We don't bother snapshotting clocks here because we wouldn't be able to
     // emit it and we shouldn't have significant drift from the last snapshot in
     // any case.
@@ -2207,155 +2264,127 @@
   // reflected in the emitted stats. This is particularly important for use
   // cases where ReadBuffers is only ever called after the tracing session is
   // stopped.
-  if (!has_more && tracing_session->should_emit_stats) {
+  if (!*has_more && tracing_session->should_emit_stats) {
     EmitStats(tracing_session, &packets);
     tracing_session->should_emit_stats = false;
   }
 
-  // +-------------------------------------------------------------------------+
-  // | NO MORE CHANGES TO |packets| AFTER THIS POINT.                          |
-  // +-------------------------------------------------------------------------+
+  MaybeFilterPackets(tracing_session, &packets);
 
+  return packets;
+}
+
+void TracingServiceImpl::MaybeFilterPackets(TracingSession* tracing_session,
+                                            std::vector<TracePacket>* packets) {
   // If the tracing session specified a filter, run all packets through the
   // filter and replace them with the filter results.
   // The process below mantains the cardinality of input packets. Even if an
   // entire packet is filtered out, we emit a zero-sized TracePacket proto. That
   // makes debugging and reasoning about the trace stats easier.
   // This place swaps the contents of each |packets| entry in place.
-  if (tracing_session->trace_filter) {
-    auto& trace_filter = *tracing_session->trace_filter;
-    // The filter root shoud be reset from protos.Trace to protos.TracePacket
-    // by the earlier call to SetFilterRoot() in EnableTracing().
-    PERFETTO_DCHECK(trace_filter.root_msg_index() != 0);
-    std::vector<protozero::MessageFilter::InputSlice> filter_input;
-    for (auto it = packets.begin(); it != packets.end(); ++it) {
-      const auto& packet_slices = it->slices();
-      filter_input.clear();
-      filter_input.resize(packet_slices.size());
-      ++tracing_session->filter_input_packets;
-      tracing_session->filter_input_bytes += it->size();
-      for (size_t i = 0; i < packet_slices.size(); ++i)
-        filter_input[i] = {packet_slices[i].start, packet_slices[i].size};
-      auto filtered_packet = trace_filter.FilterMessageFragments(
-          &filter_input[0], filter_input.size());
+  if (!tracing_session->trace_filter) {
+    return;
+  }
+  protozero::MessageFilter& trace_filter = *tracing_session->trace_filter;
+  // The filter root shoud be reset from protos.Trace to protos.TracePacket
+  // by the earlier call to SetFilterRoot() in EnableTracing().
+  PERFETTO_DCHECK(trace_filter.root_msg_index() != 0);
+  std::vector<protozero::MessageFilter::InputSlice> filter_input;
+  for (TracePacket& packet : *packets) {
+    const auto& packet_slices = packet.slices();
+    filter_input.clear();
+    filter_input.resize(packet_slices.size());
+    ++tracing_session->filter_input_packets;
+    tracing_session->filter_input_bytes += packet.size();
+    for (size_t i = 0; i < packet_slices.size(); ++i)
+      filter_input[i] = {packet_slices[i].start, packet_slices[i].size};
+    auto filtered_packet = trace_filter.FilterMessageFragments(
+        &filter_input[0], filter_input.size());
 
-      // Replace the packet in-place with the filtered one (unless failed).
-      *it = TracePacket();
-      if (filtered_packet.error) {
-        ++tracing_session->filter_errors;
-        PERFETTO_DLOG("Trace packet filtering failed @ packet %" PRIu64,
-                      tracing_session->filter_input_packets);
-        continue;
-      }
-      tracing_session->filter_output_bytes += filtered_packet.size;
-      AppendOwnedSlicesToPacket(std::move(filtered_packet.data),
-                                filtered_packet.size, kMaxTracePacketSliceSize,
-                                &*it);
-
-    }  // for (packet)
-  }    // if (trace_filter)
-
-  // If the caller asked us to write into a file by setting
-  // |write_into_file| == true in the trace config, drain the packets read
-  // (if any) into the given file descriptor.
-  if (tracing_session->write_into_file) {
-    const uint64_t max_size = tracing_session->max_file_size_bytes
-                                  ? tracing_session->max_file_size_bytes
-                                  : std::numeric_limits<size_t>::max();
-
-    size_t total_slices = 0;
-    for (const TracePacket& packet : packets) {
-      total_slices += packet.slices().size();
+    // Replace the packet in-place with the filtered one (unless failed).
+    packet = TracePacket();
+    if (filtered_packet.error) {
+      ++tracing_session->filter_errors;
+      PERFETTO_DLOG("Trace packet filtering failed @ packet %" PRIu64,
+                    tracing_session->filter_input_packets);
+      continue;
     }
-    // When writing into a file, the file should look like a root trace.proto
-    // message. Each packet should be prepended with a proto preamble stating
-    // its field id (within trace.proto) and size. Hence the addition below.
-    const size_t max_iovecs = total_slices + packets.size();
+    tracing_session->filter_output_bytes += filtered_packet.size;
+    AppendOwnedSlicesToPacket(std::move(filtered_packet.data),
+                              filtered_packet.size, kMaxTracePacketSliceSize,
+                              &packet);
+  }
+}
 
-    size_t num_iovecs = 0;
-    bool stop_writing_into_file = false;
-    std::unique_ptr<struct iovec[]> iovecs(new struct iovec[max_iovecs]);
-    size_t num_iovecs_at_last_packet = 0;
-    uint64_t bytes_about_to_be_written = 0;
-    for (TracePacket& packet : packets) {
-      std::tie(iovecs[num_iovecs].iov_base, iovecs[num_iovecs].iov_len) =
-          packet.GetProtoPreamble();
-      bytes_about_to_be_written += iovecs[num_iovecs].iov_len;
-      num_iovecs++;
-      for (const Slice& slice : packet.slices()) {
-        // writev() doesn't change the passed pointer. However, struct iovec
-        // take a non-const ptr because it's the same struct used by readv().
-        // Hence the const_cast here.
-        char* start = static_cast<char*>(const_cast<void*>(slice.start));
-        bytes_about_to_be_written += slice.size;
-        iovecs[num_iovecs++] = {start, slice.size};
-      }
+bool TracingServiceImpl::WriteIntoFile(TracingSession* tracing_session,
+                                       std::vector<TracePacket> packets) {
+  if (!tracing_session->write_into_file) {
+    return false;
+  }
+  const uint64_t max_size = tracing_session->max_file_size_bytes
+                                ? tracing_session->max_file_size_bytes
+                                : std::numeric_limits<size_t>::max();
 
-      if (tracing_session->bytes_written_into_file +
-              bytes_about_to_be_written >=
-          max_size) {
-        stop_writing_into_file = true;
-        num_iovecs = num_iovecs_at_last_packet;
-        break;
-      }
+  size_t total_slices = 0;
+  for (const TracePacket& packet : packets) {
+    total_slices += packet.slices().size();
+  }
+  // When writing into a file, the file should look like a root trace.proto
+  // message. Each packet should be prepended with a proto preamble stating
+  // its field id (within trace.proto) and size. Hence the addition below.
+  const size_t max_iovecs = total_slices + packets.size();
 
-      num_iovecs_at_last_packet = num_iovecs;
-    }
-    PERFETTO_DCHECK(num_iovecs <= max_iovecs);
-    int fd = *tracing_session->write_into_file;
-
-    uint64_t total_wr_size = 0;
-
-    // writev() can take at most IOV_MAX entries per call. Batch them.
-    constexpr size_t kIOVMax = IOV_MAX;
-    for (size_t i = 0; i < num_iovecs; i += kIOVMax) {
-      int iov_batch_size = static_cast<int>(std::min(num_iovecs - i, kIOVMax));
-      ssize_t wr_size = PERFETTO_EINTR(writev(fd, &iovecs[i], iov_batch_size));
-      if (wr_size <= 0) {
-        PERFETTO_PLOG("writev() failed");
-        stop_writing_into_file = true;
-        break;
-      }
-      total_wr_size += static_cast<size_t>(wr_size);
+  size_t num_iovecs = 0;
+  bool stop_writing_into_file = false;
+  std::unique_ptr<struct iovec[]> iovecs(new struct iovec[max_iovecs]);
+  size_t num_iovecs_at_last_packet = 0;
+  uint64_t bytes_about_to_be_written = 0;
+  for (TracePacket& packet : packets) {
+    std::tie(iovecs[num_iovecs].iov_base, iovecs[num_iovecs].iov_len) =
+        packet.GetProtoPreamble();
+    bytes_about_to_be_written += iovecs[num_iovecs].iov_len;
+    num_iovecs++;
+    for (const Slice& slice : packet.slices()) {
+      // writev() doesn't change the passed pointer. However, struct iovec
+      // take a non-const ptr because it's the same struct used by readv().
+      // Hence the const_cast here.
+      char* start = static_cast<char*>(const_cast<void*>(slice.start));
+      bytes_about_to_be_written += slice.size;
+      iovecs[num_iovecs++] = {start, slice.size};
     }
 
-    tracing_session->bytes_written_into_file += total_wr_size;
-
-    PERFETTO_DLOG("Draining into file, written: %" PRIu64 " KB, stop: %d",
-                  (total_wr_size + 1023) / 1024, stop_writing_into_file);
-    if (stop_writing_into_file || tracing_session->write_period_ms == 0) {
-      // Ensure all data was written to the file before we close it.
-      base::FlushFile(fd);
-      tracing_session->write_into_file.reset();
-      tracing_session->write_period_ms = 0;
-      if (tracing_session->state == TracingSession::STARTED)
-        DisableTracing(tsid);
-      return true;
+    if (tracing_session->bytes_written_into_file + bytes_about_to_be_written >=
+        max_size) {
+      stop_writing_into_file = true;
+      num_iovecs = num_iovecs_at_last_packet;
+      break;
     }
 
-    auto weak_this = weak_ptr_factory_.GetWeakPtr();
-    task_runner_->PostDelayedTask(
-        [weak_this, tsid] {
-          if (weak_this)
-            weak_this->ReadBuffersIntoFile(tsid);
-        },
-        tracing_session->delay_to_next_write_period_ms());
-    return true;
-  }  // if (tracing_session->write_into_file)
+    num_iovecs_at_last_packet = num_iovecs;
+  }
+  PERFETTO_DCHECK(num_iovecs <= max_iovecs);
+  int fd = *tracing_session->write_into_file;
 
-  if (has_more) {
-    auto weak_consumer = consumer->weak_ptr_factory_.GetWeakPtr();
-    auto weak_this = weak_ptr_factory_.GetWeakPtr();
-    task_runner_->PostTask([weak_this, weak_consumer, tsid] {
-      if (!weak_this || !weak_consumer)
-        return;
-      weak_this->ReadBuffersIntoConsumer(tsid, weak_consumer.get());
-    });
+  uint64_t total_wr_size = 0;
+
+  // writev() can take at most IOV_MAX entries per call. Batch them.
+  constexpr size_t kIOVMax = IOV_MAX;
+  for (size_t i = 0; i < num_iovecs; i += kIOVMax) {
+    int iov_batch_size = static_cast<int>(std::min(num_iovecs - i, kIOVMax));
+    ssize_t wr_size = PERFETTO_EINTR(writev(fd, &iovecs[i], iov_batch_size));
+    if (wr_size <= 0) {
+      PERFETTO_PLOG("writev() failed");
+      stop_writing_into_file = true;
+      break;
+    }
+    total_wr_size += static_cast<size_t>(wr_size);
   }
 
-  // Keep this as tail call, just in case the consumer re-enters.
-  consumer->consumer_->OnTraceData(std::move(packets), has_more);
-  return true;
+  tracing_session->bytes_written_into_file += total_wr_size;
+
+  PERFETTO_DLOG("Draining into file, written: %" PRIu64 " KB, stop: %d",
+                (total_wr_size + 1023) / 1024, stop_writing_into_file);
+  return stop_writing_into_file;
 }
 
 void TracingServiceImpl::FreeBuffers(TracingSessionID tsid) {
@@ -3356,7 +3385,7 @@
     // If we are stealing a write_into_file session, add a marker that explains
     // why the trace has been stolen rather than creating an empty file. This is
     // only for write_into_file traces. A similar code path deals with the case
-    // of reading-back a seized trace from IPC in ReadBuffers().
+    // of reading-back a seized trace from IPC in ReadBuffersIntoConsumer().
     if (!max_session->config.builtin_data_sources().disable_service_events()) {
       std::vector<TracePacket> packets;
       EmitSeizedForBugreportLifecycleEvent(&packets);
@@ -3667,6 +3696,7 @@
     producer->set_name(kv.second->name_);
     producer->set_sdk_version(kv.second->sdk_version_);
     producer->set_uid(static_cast<int32_t>(kv.second->uid()));
+    producer->set_pid(static_cast<int32_t>(kv.second->pid()));
   }
 
   for (const auto& kv : service_->data_sources_) {
@@ -3676,6 +3706,42 @@
     data_source->set_producer_id(
         static_cast<int>(registered_data_source.producer_id));
   }
+
+  svc_state.set_supports_tracing_sessions(true);
+  for (const auto& kv : service_->tracing_sessions_) {
+    const TracingSession& s = kv.second;
+    // List only tracing sessions for the calling UID (or everything for root).
+    if (uid_ != 0 && uid_ != s.consumer_uid)
+      continue;
+    auto* session = svc_state.add_tracing_sessions();
+    session->set_id(s.id);
+    session->set_consumer_uid(static_cast<int>(s.consumer_uid));
+    session->set_duration_ms(s.config.duration_ms());
+    session->set_num_data_sources(
+        static_cast<uint32_t>(s.data_source_instances.size()));
+    session->set_unique_session_name(s.config.unique_session_name());
+    for (const auto& snap_kv : s.initial_clock_snapshot) {
+      if (snap_kv.first == protos::pbzero::BUILTIN_CLOCK_REALTIME)
+        session->set_start_realtime_ns(static_cast<int64_t>(snap_kv.second));
+    }
+    for (const auto& buf : s.config.buffers())
+      session->add_buffer_size_kb(buf.size_kb());
+
+    switch (s.state) {
+      case TracingSession::State::DISABLED:
+        session->set_state("DISABLED");
+        break;
+      case TracingSession::State::CONFIGURED:
+        session->set_state("CONFIGURED");
+        break;
+      case TracingSession::State::STARTED:
+        session->set_state("STARTED");
+        break;
+      case TracingSession::State::DISABLING_WAITING_STOP_ACKS:
+        session->set_state("STOP_WAIT");
+        break;
+    }
+  }
   callback(/*success=*/true, svc_state);
 }
 
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 5c4e650..99ee6ea 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -81,6 +81,11 @@
       128 * 1024 - 512;  // This is ipc::kIPCBufferSize - 512, see assertion in
                          // tracing_integration_test.cc and b/195065199
 
+  // This is a rough threshold to determine how many bytes to read from the
+  // buffers on each iteration when writing into a file. Since filtering
+  // allocates memory, this limits the amount of memory allocated.
+  static constexpr size_t kWriteIntoFileChunkSize = 1024 * 1024ul;
+
   // The implementation behind the service endpoint exposed to each producer.
   class ProducerEndpointImpl : public TracingService::ProducerEndpoint {
    public:
@@ -140,6 +145,7 @@
     }
 
     uid_t uid() const { return uid_; }
+    pid_t pid() const { return pid_; }
 
    private:
     friend class TracingServiceImpl;
@@ -681,10 +687,33 @@
   void ScrapeSharedMemoryBuffers(TracingSession*, ProducerEndpointImpl*);
   void PeriodicClearIncrementalStateTask(TracingSessionID, bool post_next_only);
   TraceBuffer* GetBufferByID(BufferID);
-  bool ReadBuffers(TracingSessionID, TracingSession*, ConsumerEndpointImpl*);
+
   // Returns true if `*tracing_session` is waiting for a trigger that hasn't
   // happened.
-  static bool IsWaitingForTrigger(TracingSession*);
+  static bool IsWaitingForTrigger(TracingSession* tracing_session);
+
+  // Reads the buffers from `*tracing_session` and returns them (along with some
+  // metadata packets).
+  //
+  // The function stops when the cumulative size of the return packets exceeds
+  // `threshold` (so it's not a strict upper bound) and sets `*has_more` to
+  // true, or when there are no more packets (and sets `*has_more` to false).
+  std::vector<TracePacket> ReadBuffers(TracingSession* tracing_session,
+                                       size_t threshold,
+                                       bool* has_more);
+
+  // If `*tracing_session` has a filter, applies it to `*packets`. Doesn't
+  // change the number of `*packets`, only their content.
+  void MaybeFilterPackets(TracingSession* tracing_session,
+                          std::vector<TracePacket>* packets);
+
+  // If `*tracing_session` is configured to write into a file, writes `packets`
+  // into the file.
+  //
+  // Returns true if the file should be closed (because it's full or there has
+  // been an error), false otherwise.
+  bool WriteIntoFile(TracingSession* tracing_session,
+                     std::vector<TracePacket> packets);
   void OnStartTriggersTimeout(TracingSessionID tsid);
   void MaybeLogUploadEvent(const TraceConfig&,
                            PerfettoStatsdAtom atom,
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index 41ef5d8..00b7a3a 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -30,6 +30,7 @@
 #include "perfetto/tracing/core/data_source_config.h"
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "src/base/test/test_task_runner.h"
+#include "src/protozero/filtering/filter_bytecode_generator.h"
 #include "src/tracing/core/shared_memory_arbiter_impl.h"
 #include "src/tracing/core/trace_writer_impl.h"
 #include "src/tracing/test/mock_consumer.h"
@@ -1731,6 +1732,73 @@
                   Property(&protos::gen::TestEvent::str, Eq("payload")))));
 }
 
+TEST_F(TracingServiceImplTest, WriteIntoFileFilterMultipleChunks) {
+  static const size_t kNumTestPackets = 5;
+  static const size_t kPayloadSize = 500 * 1024UL;
+  static_assert(kNumTestPackets * kPayloadSize >
+                    TracingServiceImpl::kWriteIntoFileChunkSize,
+                "This test covers filtering multiple chunks");
+
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+  producer->RegisterDataSource("data_source");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("data_source");
+  ds_config->set_target_buffer(0);
+  trace_config.set_write_into_file(true);
+  trace_config.set_file_write_period_ms(100000);  // 100s
+
+  protozero::FilterBytecodeGenerator filt;
+  // Message 0: root Trace proto.
+  filt.AddNestedField(1 /* root trace.packet*/, 1);
+  filt.EndMessage();
+  // Message 1: TracePacket proto. Allow all fields.
+  filt.AddSimpleFieldRange(1, 1000);
+  filt.EndMessage();
+  trace_config.mutable_trace_filter()->set_bytecode(filt.Serialize());
+
+  base::TempFile tmp_file = base::TempFile::Create();
+  consumer->EnableTracing(trace_config, base::ScopedFile(dup(tmp_file.fd())));
+
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("data_source");
+  producer->WaitForDataSourceStart("data_source");
+
+  std::unique_ptr<TraceWriter> writer =
+      producer->CreateTraceWriter("data_source");
+  for (size_t i = 0; i < kNumTestPackets; i++) {
+    auto tp = writer->NewTracePacket();
+    std::string payload(kPayloadSize, 'c');
+    tp->set_for_testing()->set_str(payload.c_str(), payload.size());
+  }
+
+  writer->Flush();
+  writer.reset();
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("data_source");
+  consumer->WaitForTracingDisabled();
+
+  consumer->GetTraceStats();
+  TraceStats stats = consumer->WaitForTraceStats(true);
+
+  std::string trace_raw;
+  ASSERT_TRUE(base::ReadFile(tmp_file.path().c_str(), &trace_raw));
+  protozero::ProtoDecoder dec(trace_raw.data(), trace_raw.size());
+  size_t total_size = 0;
+  for (auto field = dec.ReadField(); field.valid(); field = dec.ReadField()) {
+    total_size += field.size();
+  }
+  EXPECT_EQ(total_size, stats.filter_stats().output_bytes());
+  EXPECT_GT(total_size, kNumTestPackets * kPayloadSize);
+}
+
 // Test the logic that allows the trace config to set the shm total size and
 // page size from the trace config. Also check that, if the config doesn't
 // specify a value we fall back on the hint provided by the producer.
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 3717fb2..0c27e4a 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -26,11 +26,14 @@
 #include "perfetto/tracing/track_event_interned_data_index.h"
 #include "protos/perfetto/common/data_source_descriptor.gen.h"
 #include "protos/perfetto/common/track_event_descriptor.pbzero.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
 #include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 
+using perfetto::protos::pbzero::ClockSnapshot;
+
 namespace perfetto {
 
 TrackEventSessionObserver::~TrackEventSessionObserver() = default;
@@ -48,6 +51,13 @@
 static constexpr const char kLegacySlowPrefix[] = "disabled-by-default-";
 static constexpr const char kSlowTag[] = "slow";
 static constexpr const char kDebugTag[] = "debug";
+// Allows to specify a custom unit different than the default (ns) for
+// the incremental clock.
+// A multiplier of 1000 means that a timestamp = 3 should be
+// interpreted as 3000 ns = 3 us.
+// TODO(mohitms): Move it to TrackEventConfig.
+constexpr uint64_t kIncrementalTimestampUnitMultiplier = 1;
+static_assert(kIncrementalTimestampUnitMultiplier >= 1, "");
 
 void ForEachObserver(
     std::function<bool(TrackEventSessionObserver*&)> callback) {
@@ -303,50 +313,94 @@
 }
 
 // static
+TraceTimestamp TrackEventInternal::GetTraceTime() {
+  return {TrackEventIncrementalState::kClockIdIncremental, GetTimeNs()};
+}
+
+// static
 int TrackEventInternal::GetSessionCount() {
   return session_count_.load();
 }
 
 // static
-void TrackEventInternal::ResetIncrementalState(TraceWriterBase* trace_writer,
-                                               TraceTimestamp timestamp) {
+void TrackEventInternal::ResetIncrementalState(
+    TraceWriterBase* trace_writer,
+    TrackEventIncrementalState* incr_state,
+    const TraceTimestamp& timestamp) {
+  auto sequence_timestamp = timestamp;
+  if (timestamp.clock_id != TrackEventInternal::GetClockId() &&
+      timestamp.clock_id != TrackEventIncrementalState::kClockIdIncremental) {
+    sequence_timestamp = TrackEventInternal::GetTraceTime();
+  }
+
+  incr_state->last_timestamp_ns = sequence_timestamp.value;
   auto default_track = ThreadTrack::Current();
   {
     // Mark any incremental state before this point invalid. Also set up
     // defaults so that we don't need to repeat constant data for each packet.
     auto packet = NewTracePacket(
-        trace_writer, timestamp,
+        trace_writer, incr_state, timestamp,
         protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
     auto defaults = packet->set_trace_packet_defaults();
-    defaults->set_timestamp_clock_id(GetClockId());
+    defaults->set_timestamp_clock_id(
+        TrackEventIncrementalState::kClockIdIncremental);
 
     // Establish the default track for this event sequence.
     auto track_defaults = defaults->set_track_event_defaults();
     track_defaults->set_track_uuid(default_track.uuid);
+
+    ClockSnapshot* clocks = packet->set_clock_snapshot();
+    // Trace clock.
+    ClockSnapshot::Clock* trace_clock = clocks->add_clocks();
+    trace_clock->set_clock_id(GetClockId());
+    trace_clock->set_timestamp(sequence_timestamp.value);
+    // Delta-encoded incremental clock in nano seconds.
+    // TODO(b/168311581): Make the unit of this clock configurable to allow
+    // trade-off between precision and encoded trace size.
+    ClockSnapshot::Clock* clock_incremental = clocks->add_clocks();
+    clock_incremental->set_clock_id(
+        TrackEventIncrementalState::kClockIdIncremental);
+    auto ts_unit_multiplier = kIncrementalTimestampUnitMultiplier;
+    clock_incremental->set_timestamp(sequence_timestamp.value /
+                                     ts_unit_multiplier);
+    clock_incremental->set_is_incremental(true);
+    clock_incremental->set_unit_multiplier_ns(ts_unit_multiplier);
   }
 
   // Every thread should write a descriptor for its default track, because most
   // trace points won't explicitly reference it. We also write the process
   // descriptor from every thread that writes trace events to ensure it gets
   // emitted at least once.
-  WriteTrackDescriptor(default_track, trace_writer);
-  WriteTrackDescriptor(ProcessTrack::Current(), trace_writer);
+  WriteTrackDescriptor(default_track, trace_writer, incr_state,
+                       sequence_timestamp);
+
+  WriteTrackDescriptor(ProcessTrack::Current(), trace_writer, incr_state,
+                       sequence_timestamp);
 }
 
 // static
 protozero::MessageHandle<protos::pbzero::TracePacket>
 TrackEventInternal::NewTracePacket(TraceWriterBase* trace_writer,
-                                   TraceTimestamp timestamp,
+                                   TrackEventIncrementalState* incr_state,
+                                   const TraceTimestamp& timestamp,
                                    uint32_t seq_flags) {
   auto packet = trace_writer->NewTracePacket();
-  packet->set_timestamp(timestamp.nanoseconds);
-  if (timestamp.clock_id != GetClockId()) {
-    packet->set_timestamp_clock_id(static_cast<uint32_t>(timestamp.clock_id));
-  } else if (GetClockId() != protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {
-    // TODO(skyostil): Stop emitting the clock id for the default trace clock
-    // for every event once the trace processor understands trace packet
-    // defaults.
-    packet->set_timestamp_clock_id(GetClockId());
+  if (timestamp.clock_id == TrackEventIncrementalState::kClockIdIncremental) {
+    if (incr_state->last_timestamp_ns <= timestamp.value) {
+      // No need to set the clock id here, since kClockIdIncremental is the
+      // clock id assumed by default.
+      auto ts_unit_multiplier = kIncrementalTimestampUnitMultiplier;
+      auto time_diff_ns = timestamp.value - incr_state->last_timestamp_ns;
+      packet->set_timestamp(time_diff_ns / ts_unit_multiplier);
+      incr_state->last_timestamp_ns = timestamp.value;
+    } else {
+      // TODO(mohitms): Consider using kIncrementalTimestampUnitMultiplier.
+      packet->set_timestamp(timestamp.value);
+      packet->set_timestamp_clock_id(GetClockId());
+    }
+  } else {
+    packet->set_timestamp(timestamp.value);
+    packet->set_timestamp_clock_id(timestamp.clock_id);
   }
   packet->set_sequence_flags(seq_flags);
   return packet;
@@ -359,11 +413,10 @@
     const Category* category,
     const char* name,
     perfetto::protos::pbzero::TrackEvent::Type type,
-    TraceTimestamp timestamp) {
+    const TraceTimestamp& timestamp) {
   PERFETTO_DCHECK(g_main_thread);
   PERFETTO_DCHECK(!incr_state->was_cleared);
-
-  auto packet = NewTracePacket(trace_writer, timestamp);
+  auto packet = NewTracePacket(trace_writer, incr_state, timestamp);
   EventContext ctx(std::move(packet), incr_state);
 
   auto track_event = ctx.event();
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 26d8a03..8aa589c 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -59,6 +59,7 @@
 #include "protos/perfetto/common/track_event_descriptor.pbzero.h"
 #include "protos/perfetto/config/interceptor_config.gen.h"
 #include "protos/perfetto/config/track_event/track_event_config.gen.h"
+#include "protos/perfetto/trace/clock_snapshot.gen.h"
 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_render_stage_event.gen.h"
 #include "protos/perfetto/trace/gpu/gpu_render_stage_event.pbzero.h"
@@ -72,6 +73,7 @@
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/trace_packet_defaults.gen.h"
 #include "protos/perfetto/trace/track_event/chrome_process_descriptor.gen.h"
 #include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/counter_descriptor.gen.h"
@@ -968,6 +970,11 @@
   bool process_descriptor_found = false;
   uint32_t sequence_id = 0;
   int32_t cur_pid = perfetto::test::GetCurrentProcessId();
+  uint64_t recent_absolute_time_ns = 0;
+  bool found_incremental_clock = false;
+  constexpr auto kClockIdIncremental =
+      perfetto::internal::TrackEventIncrementalState::kClockIdIncremental;
+
   for (const auto& packet : trace.packet()) {
     if (packet.has_track_descriptor()) {
       const auto& desc = packet.track_descriptor();
@@ -984,6 +991,17 @@
       incremental_state_was_cleared = true;
       categories.clear();
       event_names.clear();
+      EXPECT_EQ(kClockIdIncremental,
+                packet.trace_packet_defaults().timestamp_clock_id());
+    }
+    if (packet.has_clock_snapshot()) {
+      for (auto& clock : packet.clock_snapshot().clocks()) {
+        if (clock.is_incremental()) {
+          found_incremental_clock = true;
+          recent_absolute_time_ns = clock.timestamp();
+          EXPECT_EQ(kClockIdIncremental, clock.clock_id());
+        }
+      }
     }
 
     if (!packet.has_track_event())
@@ -1013,18 +1031,13 @@
         event_names[it.iid()] = it.name();
       }
     }
-
-    EXPECT_GT(packet.timestamp(), 0u);
-    EXPECT_LE(packet.timestamp(), now);
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
-    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    EXPECT_TRUE(found_incremental_clock);
+    uint64_t absolute_timestamp = packet.timestamp() + recent_absolute_time_ns;
+    recent_absolute_time_ns = absolute_timestamp;
+    EXPECT_GT(absolute_timestamp, 0u);
+    EXPECT_LE(absolute_timestamp, now);
+    // Packet uses default (incremental) clock.
     EXPECT_FALSE(packet.has_timestamp_clock_id());
-#else
-    constexpr auto kClockMonotonic =
-        perfetto::protos::pbzero::ClockSnapshot::Clock::MONOTONIC;
-    EXPECT_EQ(packet.timestamp_clock_id(),
-              static_cast<uint32_t>(kClockMonotonic));
-#endif
     if (track_event.type() ==
         perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) {
       EXPECT_FALSE(begin_found);
@@ -1051,6 +1064,84 @@
   TestCategoryAsTemplateParameter<kTestCategory>();
 }
 
+TEST_P(PerfettoApiTest, TrackEventWithIncrementalTimestamp) {
+  // Create a new trace session.
+  auto* tracing_session = NewTraceWithCategories({"bar"});
+  constexpr auto kClockIdIncremental =
+      perfetto::internal::TrackEventIncrementalState::kClockIdIncremental;
+  tracing_session->get()->StartBlocking();
+
+  std::map<uint64_t, std::string> event_names;
+
+  auto empty_lambda = [](perfetto::EventContext) {};
+
+  constexpr uint64_t kInstantEvent1Time = 92718891479583;
+  TRACE_EVENT_INSTANT(
+      "bar", "InstantEvent1",
+      perfetto::TraceTimestamp{kClockIdIncremental, kInstantEvent1Time},
+      empty_lambda);
+
+  constexpr uint64_t kInstantEvent2Time = 92718891618959;
+  TRACE_EVENT_INSTANT(
+      "bar", "InstantEvent2",
+      perfetto::TraceTimestamp{kClockIdIncremental, kInstantEvent2Time},
+      empty_lambda);
+
+  perfetto::TrackEvent::Flush();
+  tracing_session->get()->StopBlocking();
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+
+  uint64_t absolute_timestamp = 0;
+  int event_count = 0;
+  // Go through the packets and add the timestamps of those packets that use the
+  // incremental clock - in order to get the absolute timestamps of the track
+  // events.
+  for (const auto& packet : trace.packet()) {
+    if (packet.has_clock_snapshot()) {
+      for (auto& clock : packet.clock_snapshot().clocks()) {
+        if (clock.is_incremental()) {
+          absolute_timestamp = clock.timestamp();
+          EXPECT_EQ(clock.clock_id(), kClockIdIncremental);
+        }
+      }
+    } else if (!packet.has_timestamp_clock_id()) {
+      // Packets that don't have a timestamp_clock_id default to the incremental
+      // clock.
+      absolute_timestamp += packet.timestamp();
+    }
+
+    if (packet.sequence_flags() &
+        perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+      event_names.clear();
+    }
+
+    if (!packet.has_track_event())
+      continue;
+
+    // Update incremental state.
+    if (packet.has_interned_data()) {
+      const auto& interned_data = packet.interned_data();
+      for (const auto& it : interned_data.event_names()) {
+        EXPECT_EQ(event_names.find(it.iid()), event_names.end());
+        event_names[it.iid()] = it.name();
+      }
+    }
+
+    if (event_names[packet.track_event().name_iid()] == "InstantEvent1") {
+      event_count++;
+      ASSERT_EQ(absolute_timestamp, kInstantEvent1Time);
+    } else if (event_names[packet.track_event().name_iid()] ==
+               "InstantEvent2") {
+      event_count++;
+      ASSERT_EQ(absolute_timestamp, kInstantEvent2Time);
+    }
+  }
+  ASSERT_EQ(event_count, 2);
+}
+
 TEST_P(PerfettoApiTest, TrackEventCategories) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"bar"});
@@ -1754,6 +1845,9 @@
   for (const auto& packet : trace.packet()) {
     if (!packet.has_track_event())
       continue;
+
+    EXPECT_EQ(packet.timestamp_clock_id(),
+              static_cast<uint32_t>(perfetto::TrackEvent::GetTraceClockId()));
     event_count++;
     switch (packet.track_event().type()) {
       case perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN:
diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc
index 2d7dc4c..cfc2458 100644
--- a/src/tracing/test/mock_consumer.cc
+++ b/src/tracing/test/mock_consumer.cc
@@ -125,11 +125,13 @@
   service_endpoint_->GetTraceStats();
 }
 
-void MockConsumer::WaitForTraceStats(bool success) {
+TraceStats MockConsumer::WaitForTraceStats(bool success) {
   static int i = 0;
   auto checkpoint_name = "on_trace_stats_" + std::to_string(i++);
   auto on_trace_stats = task_runner_->CreateCheckpoint(checkpoint_name);
-  auto result_callback = [on_trace_stats](bool, const TraceStats&) {
+  TraceStats stats;
+  auto result_callback = [on_trace_stats, &stats](bool, const TraceStats& s) {
+    stats = s;
     on_trace_stats();
   };
   if (success) {
@@ -142,6 +144,7 @@
         .WillOnce(Invoke(result_callback));
   }
   task_runner_->RunUntilCheckpoint(checkpoint_name);
+  return stats;
 }
 
 void MockConsumer::ObserveEvents(uint32_t enabled_event_types) {
diff --git a/src/tracing/test/mock_consumer.h b/src/tracing/test/mock_consumer.h
index 9ba12b4..f6329bf 100644
--- a/src/tracing/test/mock_consumer.h
+++ b/src/tracing/test/mock_consumer.h
@@ -57,7 +57,7 @@
   FlushRequest Flush(uint32_t timeout_ms = 10000);
   std::vector<protos::gen::TracePacket> ReadBuffers();
   void GetTraceStats();
-  void WaitForTraceStats(bool success);
+  TraceStats WaitForTraceStats(bool success);
   TracingServiceState QueryServiceState();
   void ObserveEvents(uint32_t enabled_event_types);
   ObservableEvents WaitForObservableEvents();
diff --git a/src/tracing/track_event_state_tracker.cc b/src/tracing/track_event_state_tracker.cc
index 7cb1b09..7ff50ac 100644
--- a/src/tracing/track_event_state_tracker.cc
+++ b/src/tracing/track_event_state_tracker.cc
@@ -17,8 +17,10 @@
 #include "perfetto/tracing/track_event_state_tracker.h"
 
 #include "perfetto/ext/base/hash.h"
+#include "perfetto/tracing/internal/track_event_internal.h"
 
 #include "protos/perfetto/common/interceptor_descriptor.gen.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
@@ -30,6 +32,8 @@
 
 namespace perfetto {
 
+using internal::TrackEventIncrementalState;
+
 TrackEventStateTracker::~TrackEventStateTracker() = default;
 TrackEventStateTracker::Delegate::~Delegate() = default;
 
@@ -45,8 +49,15 @@
   perfetto::protos::pbzero::TrackEvent::Decoder track_event(
       packet.track_event());
 
-  // TODO(skyostil): Support incremental timestamps.
+  auto clock_id = packet.timestamp_clock_id();
+  if (!packet.has_timestamp_clock_id())
+    clock_id = sequence_state.default_clock_id;
   uint64_t timestamp = packet.timestamp();
+  // TODO(mohitms): Incorporate unit multiplier as well.
+  if (clock_id == TrackEventIncrementalState::kClockIdIncremental) {
+    timestamp += sequence_state.most_recent_absolute_time_ns;
+    sequence_state.most_recent_absolute_time_ns = timestamp;
+  }
 
   Track* track = &sequence_state.track;
   if (track_event.has_track_uuid()) {
@@ -163,6 +174,19 @@
   }
 #endif
 
+  perfetto::protos::pbzero::ClockSnapshot::Decoder snapshot(
+      packet.clock_snapshot());
+  for (auto it = snapshot.clocks(); it; ++it) {
+    perfetto::protos::pbzero::ClockSnapshot::Clock::Decoder clock(*it);
+    // TODO(mohitms) : Handle the incremental clock other than default one.
+    if (clock.is_incremental() &&
+        clock.clock_id() == TrackEventIncrementalState::kClockIdIncremental) {
+      sequence_state.most_recent_absolute_time_ns =
+          clock.timestamp() * clock.unit_multiplier_ns();
+      break;
+    }
+  }
+
   if (packet.sequence_flags() &
       perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
     // Convert any existing event names and categories on the stack to
@@ -208,6 +232,8 @@
       perfetto::protos::pbzero::TrackEventDefaults::Decoder
           track_event_defaults(defaults.track_event_defaults());
       sequence_state.track.uuid = track_event_defaults.track_uuid();
+      if (defaults.has_timestamp_clock_id())
+        sequence_state.default_clock_id = defaults.timestamp_clock_id();
     }
   }
   if (packet.has_track_descriptor()) {
diff --git a/test/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc
index 0fc0520..0747789 100644
--- a/test/cmdline_integrationtest.cc
+++ b/test/cmdline_integrationtest.cc
@@ -20,10 +20,8 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
-#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/pipe.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/subprocess.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
@@ -68,103 +66,6 @@
   return path;
 }
 
-// This class is a reference to a child process that has in essence been execv
-// to the requested binary. The process will start and then wait for Run()
-// before proceeding. We use this to fork new processes before starting any
-// additional threads in the parent process (otherwise you would risk
-// deadlocks), but pause the forked processes until remaining setup (including
-// any necessary threads) in the parent process is complete.
-class Exec {
- public:
-  // Starts the forked process that was created. If not null then |stderr_out|
-  // will contain the stderr of the process.
-  int Run(std::string* stderr_out = nullptr) {
-    // We can't be the child process.
-    PERFETTO_CHECK(getpid() != subprocess_.pid());
-    // Will cause the entrypoint to continue.
-    PERFETTO_CHECK(write(*sync_pipe_.wr, "1", 1) == 1);
-    sync_pipe_.wr.reset();
-    subprocess_.Wait();
-
-    if (stderr_out) {
-      *stderr_out = std::move(subprocess_.output());
-    } else {
-      PERFETTO_LOG("Child proc %d exited with stderr: \"%s\"",
-                   subprocess_.pid(), subprocess_.output().c_str());
-    }
-    return subprocess_.returncode();
-  }
-
-  Exec(const std::string& argv0,
-       std::initializer_list<std::string> args,
-       std::string input = "") {
-    subprocess_.args.stderr_mode = base::Subprocess::kBuffer;
-    subprocess_.args.stdout_mode = base::Subprocess::kDevNull;
-    subprocess_.args.input = input;
-
-#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
-    constexpr bool kUseSystemBinaries = false;
-#else
-    constexpr bool kUseSystemBinaries = true;
-#endif
-
-    std::vector<std::string>& cmd = subprocess_.args.exec_cmd;
-    if (kUseSystemBinaries) {
-      PERFETTO_CHECK(TestHelper::kDefaultMode ==
-                     TestHelper::Mode::kUseSystemService);
-      cmd.push_back("/system/bin/" + argv0);
-      cmd.insert(cmd.end(), args.begin(), args.end());
-    } else {
-      PERFETTO_CHECK(TestHelper::kDefaultMode ==
-                     TestHelper::Mode::kStartDaemons);
-      subprocess_.args.env.push_back(
-          std::string("PERFETTO_PRODUCER_SOCK_NAME=") +
-          TestHelper::GetDefaultModeProducerSocketName());
-      subprocess_.args.env.push_back(
-          std::string("PERFETTO_CONSUMER_SOCK_NAME=") +
-          TestHelper::GetDefaultModeConsumerSocketName());
-      cmd.push_back(base::GetCurExecutableDir() + "/" + argv0);
-      cmd.insert(cmd.end(), args.begin(), args.end());
-    }
-
-    if (!base::FileExists(cmd[0])) {
-      PERFETTO_FATAL(
-          "Cannot find %s. Make sure that the target has been built and, on "
-          "Android, pushed to the device.",
-          cmd[0].c_str());
-    }
-
-    // This pipe blocks the execution of the child process until the main test
-    // process calls Run(). There are two conflicting problems here:
-    // 1) We can't fork() subprocesses too late, because the test spawns threads
-    //    for hosting the service. fork+threads = bad (see aosp/1089744).
-    // 2) We can't run the subprocess too early, because we need to wait that
-    //    the service threads are ready before trying to connect from the child
-    //    process.
-    sync_pipe_ = base::Pipe::Create();
-    int sync_pipe_rd = *sync_pipe_.rd;
-    subprocess_.args.preserve_fds.push_back(sync_pipe_rd);
-
-    // This lambda will be called on the forked child process after having
-    // setup pipe redirection and closed all FDs, right before the exec().
-    // The Subprocesss harness will take care of closing also |sync_pipe_.wr|.
-    subprocess_.args.posix_entrypoint_for_testing = [sync_pipe_rd] {
-      // Don't add any logging here, all file descriptors are closed and trying
-      // to log will likely cause undefined behaviors.
-      char ignored = 0;
-      PERFETTO_CHECK(PERFETTO_EINTR(read(sync_pipe_rd, &ignored, 1)) > 0);
-      PERFETTO_CHECK(close(sync_pipe_rd) == 0 || errno == EINTR);
-    };
-
-    subprocess_.Start();
-    sync_pipe_.rd.reset();
-  }
-
- private:
-  base::Subprocess subprocess_;
-  base::Pipe sync_pipe_;
-};
-
 class PerfettoCmdlineTest : public ::testing::Test {
  public:
   void SetUp() override {
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index 3958566..e8571a6 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -14,6 +14,7 @@
     "end_to_end_integrationtest_cts.cc",
     "heapprofd_java_test_cts.cc",
     "heapprofd_test_cts.cc",
+    "reporter_test_cts.cc",
     "traced_perf_test_cts.cc",
     ":perfetto_protos_perfetto_config_cpp_gen",
   ],
@@ -48,6 +49,9 @@
         suffix: "64",
     },
   },
+  data: [
+    ":CtsPerfettoReporterApp"
+  ],
   stl: "libc++_static",
   defaults: [
     "perfetto_defaults",
diff --git a/test/cts/AndroidTest.xml b/test/cts/AndroidTest.xml
index e9f5a5f..db14fe6 100644
--- a/test/cts/AndroidTest.xml
+++ b/test/cts/AndroidTest.xml
@@ -27,6 +27,7 @@
         <option name="test-file-name" value="CtsPerfettoReleaseApp.apk" />
         <option name="test-file-name" value="CtsPerfettoProfileableApp.apk" />
         <option name="test-file-name" value="CtsPerfettoNonProfileableApp.apk" />
+        <option name="test-file-name" value="CtsPerfettoReporterApp.apk" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/test/cts/reporter/Android.bp b/test/cts/reporter/Android.bp
new file mode 100644
index 0000000..ab2633b
--- /dev/null
+++ b/test/cts/reporter/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_perfetto_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["external_perfetto_license"],
+}
+
+android_test_helper_app {
+    name: "CtsPerfettoReporterApp",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    privileged: true,
+}
diff --git a/test/cts/reporter/AndroidManifest.xml b/test/cts/reporter/AndroidManifest.xml
new file mode 100755
index 0000000..9a2ef40
--- /dev/null
+++ b/test/cts/reporter/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.perfetto.cts.reporter">
+    <uses-permission android:name="android.permission.DUMP"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+1
+    <application>
+        <service
+            android:name=".PerfettoReportService"
+            android:permission="android.permission.BIND_TRACE_REPORT_SERVICE"/>
+    </application>
+</manifest>
diff --git a/test/cts/reporter/src/android/perfetto/cts/reporter/PerfettoReportService.java b/test/cts/reporter/src/android/perfetto/cts/reporter/PerfettoReportService.java
new file mode 100644
index 0000000..5d0c8af
--- /dev/null
+++ b/test/cts/reporter/src/android/perfetto/cts/reporter/PerfettoReportService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package android.perfetto.cts.reporter;
+
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.service.tracing.TraceReportService;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+public class PerfettoReportService extends TraceReportService {
+    public static final String TAG = "PerfettoReportService";
+
+    @Override
+    public void onReportTrace(TraceParams args) {
+        File f = new File(getExternalFilesDir(null), args.getUuid().toString());
+        try {
+            boolean created = f.createNewFile();
+            if (!created) {
+                throw new IllegalStateException("Failed to create file");
+            }
+            try (AutoCloseInputStream i = new AutoCloseInputStream(args.getFd())) {
+                try (FileOutputStream o = new FileOutputStream((f))) {
+                    o.write(i.readAllBytes());
+                }
+            }
+        } catch (IOException ex) {
+            throw new IllegalStateException("IO Exception", ex);
+        }
+    }
+}
diff --git a/test/cts/reporter_test_cts.cc b/test/cts/reporter_test_cts.cc
new file mode 100644
index 0000000..f9294f4
--- /dev/null
+++ b/test/cts/reporter_test_cts.cc
@@ -0,0 +1,111 @@
+/*
+ * 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 <sys/system_properties.h>
+#include <random>
+#include "test/gtest_and_gmock.h"
+
+#include "perfetto/ext/base/android_utils.h"
+#include "perfetto/ext/base/uuid.h"
+#include "perfetto/tracing/core/data_source_config.h"
+#include "src/base/test/test_task_runner.h"
+#include "test/android_test_utils.h"
+#include "test/test_helper.h"
+
+#include "protos/perfetto/config/test_config.gen.h"
+#include "protos/perfetto/trace/test_event.gen.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto {
+namespace {
+
+TEST(PerfettoReporterTest, TestEndToEndReport) {
+  base::TestTaskRunner task_runner;
+  TestHelper helper(&task_runner);
+  helper.ConnectFakeProducer();
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);
+  trace_config.set_duration_ms(200);
+  trace_config.set_allow_user_build_tracing(true);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.perfetto.FakeProducer");
+  ds_config->set_target_buffer(0);
+
+  base::Uuid uuid = base::Uuidv4();
+  trace_config.set_trace_uuid_lsb(uuid.lsb());
+  trace_config.set_trace_uuid_msb(uuid.msb());
+
+  static constexpr uint32_t kRandomSeed = 42;
+  static constexpr uint32_t kEventCount = 1;
+  static constexpr uint32_t kMessageSizeBytes = 2;
+  ds_config->mutable_for_testing()->set_seed(kRandomSeed);
+  ds_config->mutable_for_testing()->set_message_count(kEventCount);
+  ds_config->mutable_for_testing()->set_message_size(kMessageSizeBytes);
+  ds_config->mutable_for_testing()->set_send_batch_on_register(true);
+
+  auto* report_config = trace_config.mutable_android_report_config();
+  report_config->set_reporter_service_package("android.perfetto.cts.reporter");
+  report_config->set_reporter_service_class(
+      "android.perfetto.cts.reporter.PerfettoReportService");
+  report_config->set_use_pipe_in_framework_for_testing(true);
+
+  // We have to construct all the processes we want to fork before we start the
+  // service with |StartServiceIfRequired()|. this is because it is unsafe
+  // (could deadlock) to fork after we've spawned some threads which might
+  // printf (and thus hold locks).
+  auto perfetto_proc = Exec("perfetto",
+                            {
+                                "--upload",
+                                "-c",
+                                "-",
+                            },
+                            trace_config.SerializeAsString());
+
+  std::string stderr_str;
+  EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
+
+  static constexpr char kPath[] =
+      "/sdcard/Android/data/android.perfetto.cts.reporter/files/";
+  std::string path = kPath + uuid.ToPrettyString();
+  static constexpr uint32_t kIterationSleepMs = 500;
+  static constexpr uint32_t kIterationCount =
+      kDefaultTestTimeoutMs / kIterationSleepMs;
+  for (size_t i = 0; i < kIterationCount; ++i) {
+    if (!base::FileExists(path)) {
+      base::SleepMicroseconds(kIterationSleepMs * 1000);
+      continue;
+    }
+
+    std::string trace_str;
+    ASSERT_TRUE(base::ReadFile(path, &trace_str));
+
+    protos::gen::Trace trace;
+    ASSERT_TRUE(trace.ParseFromString(trace_str));
+    int for_testing = 0;
+    for (const auto& packet : trace.packet()) {
+      for_testing += packet.has_for_testing();
+    }
+    ASSERT_EQ(for_testing, kEventCount);
+    return;
+  }
+  FAIL() << "Timed out waiting for trace file";
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
index 58243f3..9972f70 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
@@ -1 +1 @@
-29cdb8b1a7fb2df704fa16fb9d342aef9f61d17dea58c5c2a57ccc0bda3b874f
\ No newline at end of file
+11b4adcbe2171d25356df8bf15e96c2d87a15a9e6e59f9b6349bbfa919620255
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index 2104823..4514e39 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-459f3bcd870dc4d697510e380983e1daf39355732766e754f6649f066c60007e
\ No newline at end of file
+2a99973826b842e7f64d79bf8ddf5cc188e6bf8973e1fda073f9cd9cb6a9a40d
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index 89e28ad..356685a 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-c64e2df83124c5e42b7552d8e673fa390ee31d1b6c5a8acd07eb022afd97b84d
\ No newline at end of file
+5c8a69fa3913fe79644858f21f08f580d8f7a14467fa772bca96ea641ad3a58e
\ No newline at end of file
diff --git a/test/synth_common.py b/test/synth_common.py
index d8c39fc..92d8e3f 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -16,7 +16,7 @@
 import argparse
 
 from collections import namedtuple
-from google.protobuf import descriptor, descriptor_pb2, message_factory, descriptor_pool
+from google.protobuf import descriptor_pb2, message_factory, descriptor_pool
 
 CLONE_THREAD = 0x00010000
 CLONE_VFORK = 0x00004000
diff --git a/test/test_helper.h b/test/test_helper.h
index 904623f..778f88a 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -20,8 +20,11 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/subprocess.h"
 #include "perfetto/ext/base/thread_task_runner.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/tracing/core/consumer.h"
@@ -315,6 +318,107 @@
   std::unique_ptr<TracingService::ConsumerEndpoint> endpoint_;  // Keep last.
 };
 
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+// This class is a reference to a child process that has in essence been execv
+// to the requested binary. The process will start and then wait for Run()
+// before proceeding. We use this to fork new processes before starting any
+// additional threads in the parent process (otherwise you would risk
+// deadlocks), but pause the forked processes until remaining setup (including
+// any necessary threads) in the parent process is complete.
+class Exec {
+ public:
+  // Starts the forked process that was created. If not null then |stderr_out|
+  // will contain the stderr of the process.
+  int Run(std::string* stderr_out = nullptr) {
+    // We can't be the child process.
+    PERFETTO_CHECK(getpid() != subprocess_.pid());
+    // Will cause the entrypoint to continue.
+    PERFETTO_CHECK(write(*sync_pipe_.wr, "1", 1) == 1);
+    sync_pipe_.wr.reset();
+    subprocess_.Wait();
+
+    if (stderr_out) {
+      *stderr_out = std::move(subprocess_.output());
+    } else {
+      PERFETTO_LOG("Child proc %d exited with stderr: \"%s\"",
+                   subprocess_.pid(), subprocess_.output().c_str());
+    }
+    return subprocess_.returncode();
+  }
+
+  Exec(const std::string& argv0,
+       std::initializer_list<std::string> args,
+       std::string input = "") {
+    subprocess_.args.stderr_mode = base::Subprocess::kBuffer;
+    subprocess_.args.stdout_mode = base::Subprocess::kDevNull;
+    subprocess_.args.input = input;
+
+#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
+    constexpr bool kUseSystemBinaries = false;
+#else
+    constexpr bool kUseSystemBinaries = true;
+#endif
+
+    std::vector<std::string>& cmd = subprocess_.args.exec_cmd;
+    if (kUseSystemBinaries) {
+      PERFETTO_CHECK(TestHelper::kDefaultMode ==
+                     TestHelper::Mode::kUseSystemService);
+      cmd.push_back("/system/bin/" + argv0);
+      cmd.insert(cmd.end(), args.begin(), args.end());
+    } else {
+      PERFETTO_CHECK(TestHelper::kDefaultMode ==
+                     TestHelper::Mode::kStartDaemons);
+      subprocess_.args.env.push_back(
+          std::string("PERFETTO_PRODUCER_SOCK_NAME=") +
+          TestHelper::GetDefaultModeProducerSocketName());
+      subprocess_.args.env.push_back(
+          std::string("PERFETTO_CONSUMER_SOCK_NAME=") +
+          TestHelper::GetDefaultModeConsumerSocketName());
+      cmd.push_back(base::GetCurExecutableDir() + "/" + argv0);
+      cmd.insert(cmd.end(), args.begin(), args.end());
+    }
+
+    if (!base::FileExists(cmd[0])) {
+      PERFETTO_FATAL(
+          "Cannot find %s. Make sure that the target has been built and, on "
+          "Android, pushed to the device.",
+          cmd[0].c_str());
+    }
+
+    // This pipe blocks the execution of the child process until the main test
+    // process calls Run(). There are two conflicting problems here:
+    // 1) We can't fork() subprocesses too late, because the test spawns threads
+    //    for hosting the service. fork+threads = bad (see aosp/1089744).
+    // 2) We can't run the subprocess too early, because we need to wait that
+    //    the service threads are ready before trying to connect from the child
+    //    process.
+    sync_pipe_ = base::Pipe::Create();
+    int sync_pipe_rd = *sync_pipe_.rd;
+    subprocess_.args.preserve_fds.push_back(sync_pipe_rd);
+
+    // This lambda will be called on the forked child process after having
+    // setup pipe redirection and closed all FDs, right before the exec().
+    // The Subprocesss harness will take care of closing also |sync_pipe_.wr|.
+    subprocess_.args.posix_entrypoint_for_testing = [sync_pipe_rd] {
+      // Don't add any logging here, all file descriptors are closed and trying
+      // to log will likely cause undefined behaviors.
+      char ignored = 0;
+      PERFETTO_CHECK(PERFETTO_EINTR(read(sync_pipe_rd, &ignored, 1)) > 0);
+      PERFETTO_CHECK(close(sync_pipe_rd) == 0 || errno == EINTR);
+    };
+
+    subprocess_.Start();
+    sync_pipe_.rd.reset();
+  }
+
+ private:
+  base::Subprocess subprocess_;
+  base::Pipe sync_pipe_;
+};
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
 }  // namespace perfetto
 
 #endif  // TEST_TEST_HELPER_H_
diff --git a/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out b/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out
index 13709b3..207e7d2 100644
--- a/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out
+++ b/test/trace_processor/chrome/scroll_jank_mojo_simple_watcher.out
@@ -2,4 +2,4 @@
 "trace_id","jank","dur_overlapping_ns","metric_name"
 34577,0,6000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.task-subtask"
 34578,1,29000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.task-subtask"
-34579,0,29000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.task-subtask(foo)"
+34579,0,29000000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.task-foo"
diff --git a/test/trace_processor/network/index b/test/trace_processor/network/index
index a21082a..51ce794 100644
--- a/test/trace_processor/network/index
+++ b/test/trace_processor/network/index
@@ -2,3 +2,5 @@
 netif_receive_skb.textproto netif_receive_skb.sql netif_receive_skb.out
 net_dev_xmit.textproto net_dev_xmit.sql net_dev_xmit.out
 netperf_metric.textproto android_netperf netperf_metric.out
+inet_sock_set_state.textproto inet_sock_set_state.sql inet_sock_set_state.out
+tcp_retransmit_skb.textproto tcp_retransmit_skb.sql tcp_retransmit_skb.out
diff --git a/test/trace_processor/network/inet_sock_set_state.out b/test/trace_processor/network/inet_sock_set_state.out
new file mode 100644
index 0000000..fd67a19
--- /dev/null
+++ b/test/trace_processor/network/inet_sock_set_state.out
@@ -0,0 +1,7 @@
+"ts","name","dur","name"
+10000000,"TCP_SYN_SENT(pid=123)",100000000,"TCP stream#1"
+110000000,"TCP_ESTABLISHED(sport=56789,dport=5001)",500000000,"TCP stream#1"
+610000000,"TCP_CLOSE_WAIT",-1,"TCP stream#1"
+710000000,"TCP_SYN_SENT(pid=567)",10000000,"TCP stream#2"
+720000000,"TCP_ESTABLISHED(sport=56790,dport=5002)",300000000,"TCP stream#2"
+1020000000,"TCP_CLOSE_WAIT",-1,"TCP stream#2"
diff --git a/test/trace_processor/network/inet_sock_set_state.sql b/test/trace_processor/network/inet_sock_set_state.sql
new file mode 100644
index 0000000..badba1e
--- /dev/null
+++ b/test/trace_processor/network/inet_sock_set_state.sql
@@ -0,0 +1,12 @@
+SELECT
+  ts,
+  s.name,
+  dur,
+  t.name
+FROM
+  slice AS s
+  LEFT JOIN track AS t
+  ON s.track_id = t.id
+WHERE
+  t.name GLOB "TCP stream#*"
+ORDER BY ts;
diff --git a/test/trace_processor/network/inet_sock_set_state.textproto b/test/trace_processor/network/inet_sock_set_state.textproto
new file mode 100644
index 0000000..00362f9
--- /dev/null
+++ b/test/trace_processor/network/inet_sock_set_state.textproto
@@ -0,0 +1,121 @@
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 10000000
+      pid: 123
+      inet_sock_set_state {
+        family: 2
+        protocol: 6
+        daddr: 19216801
+        saddr: 127001
+        dport: 5001
+        sport: 0
+        newstate: 2
+        oldstate: 7
+        skaddr: 77889900
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 1
+    event {
+      timestamp: 110000000
+      pid: 234
+      inet_sock_set_state {
+        family: 2
+        protocol: 6
+        daddr: 19216801
+        saddr: 127001
+        dport: 5001
+        sport: 56789
+        newstate: 1
+        oldstate: 2
+        skaddr: 77889900
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 610000000
+      pid: 456
+      inet_sock_set_state {
+        family: 2
+        protocol: 6
+        daddr: 19216801
+        saddr: 127001
+        dport: 5001
+        sport: 56789
+        newstate: 8
+        oldstate: 1
+        skaddr: 77889900
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 710000000
+      pid:567
+      inet_sock_set_state {
+        family: 10
+        protocol: 6
+        daddr: 0
+        saddr: 0
+        dport: 5002
+        sport: 0
+        newstate: 2
+        oldstate: 7
+        skaddr: 33445566
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 1
+    event {
+      timestamp: 720000000
+      pid: 234
+      inet_sock_set_state {
+        family: 10
+        protocol: 6
+        daddr: 0
+        saddr: 0
+        dport: 5002
+        sport: 56790
+        newstate: 1
+        oldstate: 2
+        skaddr: 33445566
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 0
+    event {
+      timestamp: 1020000000
+      pid: 456
+      inet_sock_set_state {
+        family: 10
+        protocol: 6
+        daddr: 0
+        saddr: 0
+        dport: 5002
+        sport: 567090
+        newstate: 8
+        oldstate: 1
+        skaddr: 33445566
+      }
+    }
+  }
+}
+
diff --git a/test/trace_processor/network/tcp_retransmit_skb.out b/test/trace_processor/network/tcp_retransmit_skb.out
new file mode 100644
index 0000000..9a3629f
--- /dev/null
+++ b/test/trace_processor/network/tcp_retransmit_skb.out
@@ -0,0 +1,3 @@
+"ts","name","dur"
+110000000,"sport=56789,dport=5001",0
+720000000,"sport=56790,dport=5002",0
diff --git a/test/trace_processor/network/tcp_retransmit_skb.sql b/test/trace_processor/network/tcp_retransmit_skb.sql
new file mode 100644
index 0000000..44dacb3
--- /dev/null
+++ b/test/trace_processor/network/tcp_retransmit_skb.sql
@@ -0,0 +1,11 @@
+SELECT
+  ts,
+  s.name,
+  dur
+FROM
+  slice AS s
+  LEFT JOIN track AS t
+  ON s.track_id = t.id
+WHERE
+  t.name = "TCP Retransmit Skb"
+ORDER BY ts;
diff --git a/test/trace_processor/network/tcp_retransmit_skb.textproto b/test/trace_processor/network/tcp_retransmit_skb.textproto
new file mode 100644
index 0000000..3e96e75
--- /dev/null
+++ b/test/trace_processor/network/tcp_retransmit_skb.textproto
@@ -0,0 +1,34 @@
+packet {
+  ftrace_events {
+    cpu: 1
+    event {
+      timestamp: 110000000
+      pid: 234
+      tcp_retransmit_skb {
+        daddr: 19216801
+        saddr: 127001
+        dport: 5001
+        sport: 56789
+        state: 1
+        skaddr: 77889900
+      }
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 1
+    event {
+      timestamp: 720000000
+      pid: 234
+      tcp_retransmit_skb {
+        daddr: 0
+        saddr: 0
+        dport: 5002
+        sport: 56790
+        state: 2
+        skaddr: 33445566
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/parsing/index b/test/trace_processor/parsing/index
index ce6f074..42b2535 100644
--- a/test/trace_processor/parsing/index
+++ b/test/trace_processor/parsing/index
@@ -78,6 +78,8 @@
 # Check error handling when parsing print events.
 bad_print.textproto list_slices.sql bad_print_textproto_list_slices.out
 bad_print.systrace list_slices.sql bad_print_systrace_list_slices.out
+instant_atrace.py instant_with_thread.sql instant_atrace_instant_with_thread.out
+instant_async_atrace.py instant_async.sql instant_async_atrace_instant_async.out
 
 # Match legacy Catapult behaviour when we see multiple S events b2b with the same cookie
 # name and upid.
diff --git a/test/trace_processor/parsing/instant_async.sql b/test/trace_processor/parsing/instant_async.sql
new file mode 100644
index 0000000..a02b2ba
--- /dev/null
+++ b/test/trace_processor/parsing/instant_async.sql
@@ -0,0 +1,9 @@
+SELECT
+  process.name AS process_name,
+  process_track.name as track_name,
+  instant.name as instant_name,
+  ts
+FROM slice instant
+JOIN process_track ON instant.track_id = process_track.id
+JOIN process USING (upid)
+WHERE dur = 0;
diff --git a/test/trace_processor/parsing/instant_async_atrace.py b/test/trace_processor/parsing/instant_async_atrace.py
new file mode 100644
index 0000000..effdf8b
--- /dev/null
+++ b/test/trace_processor/parsing/instant_async_atrace.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_packet()
+trace.add_process(pid=1, ppid=0, cmdline="p1")
+trace.add_process(pid=2, ppid=1, cmdline="p2")
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_sched(ts=50, prev_pid=1, next_pid=2, prev_comm='t1', next_comm='t2')
+trace.add_print(ts=51, tid=2, buf='N|2|track_p2|ev1\n')
+trace.add_sched(ts=52, prev_pid=2, next_pid=1, prev_comm='t2', next_comm='t1')
+trace.add_print(ts=53, tid=1, buf='N|1|track_p1|ev2\n')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/parsing/instant_async_atrace_instant_async.out b/test/trace_processor/parsing/instant_async_atrace_instant_async.out
new file mode 100644
index 0000000..d95fe51
--- /dev/null
+++ b/test/trace_processor/parsing/instant_async_atrace_instant_async.out
@@ -0,0 +1,3 @@
+"process_name","track_name","instant_name","ts"
+"p2","track_p2","ev1",51
+"p1","track_p1","ev2",53
diff --git a/test/trace_processor/parsing/instant_atrace.py b/test/trace_processor/parsing/instant_atrace.py
new file mode 100644
index 0000000..c7d6b62
--- /dev/null
+++ b/test/trace_processor/parsing/instant_atrace.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_sched(ts=50, prev_pid=1, next_pid=2, prev_comm='t1', next_comm='t2')
+trace.add_print(ts=51, tid=2, buf='I|2|t2_event\n')
+trace.add_sched(ts=52, prev_pid=2, next_pid=1, prev_comm='t2', next_comm='t1')
+trace.add_print(ts=53, tid=1, buf='I|1|t1_event\n')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/parsing/instant_atrace_instant_with_thread.out b/test/trace_processor/parsing/instant_atrace_instant_with_thread.out
new file mode 100644
index 0000000..c1b7305
--- /dev/null
+++ b/test/trace_processor/parsing/instant_atrace_instant_with_thread.out
@@ -0,0 +1,3 @@
+"thread_name","track_name","ts"
+"t2","t2_event",51
+"t1","t1_event",53
diff --git a/test/trace_processor/parsing/instant_with_thread.sql b/test/trace_processor/parsing/instant_with_thread.sql
new file mode 100644
index 0000000..2e39608
--- /dev/null
+++ b/test/trace_processor/parsing/instant_with_thread.sql
@@ -0,0 +1,5 @@
+SELECT thread.name as thread_name, instant.name as track_name, instant.ts
+FROM slice instant
+JOIN thread_track ON instant.track_id = thread_track.id
+JOIN thread USING (utid)
+WHERE dur = 0;
diff --git a/test/trace_processor/profiling/heap_graph.textproto b/test/trace_processor/profiling/heap_graph.textproto
index b4c2037..b3decef 100644
--- a/test/trace_processor/profiling/heap_graph.textproto
+++ b/test/trace_processor/profiling/heap_graph.textproto
@@ -124,7 +124,7 @@
       id: 3
       class_name: "a"
       location_id: 1
-      object_size: 256
+      object_size: 1024
     }
     types {
       id: 4
diff --git a/test/trace_processor/profiling/heap_graph_baseapk.textproto b/test/trace_processor/profiling/heap_graph_baseapk.textproto
index 18cae91..86d2d90 100644
--- a/test/trace_processor/profiling/heap_graph_baseapk.textproto
+++ b/test/trace_processor/profiling/heap_graph_baseapk.textproto
@@ -70,7 +70,7 @@
     objects {
       id: 0x04
       type_id: 3
-      self_size: 256
+      self_size: 1024
       reference_field_id: 2
       reference_object_id: 0x01
     }
diff --git a/test/trace_processor/profiling/heap_graph_legacy.textproto b/test/trace_processor/profiling/heap_graph_legacy.textproto
index 54d19cd..d0bc1c8 100644
--- a/test/trace_processor/profiling/heap_graph_legacy.textproto
+++ b/test/trace_processor/profiling/heap_graph_legacy.textproto
@@ -70,7 +70,7 @@
     objects {
       id: 0x04
       type_id: 3
-      self_size: 256
+      self_size: 1024
       reference_field_id: 2
       reference_object_id: 0x01
     }
diff --git a/test/trace_processor/profiling/heap_graph_object.out b/test/trace_processor/profiling/heap_graph_object.out
index e4aa0b1..ac4a6a3 100644
--- a/test/trace_processor/profiling/heap_graph_object.out
+++ b/test/trace_processor/profiling/heap_graph_object.out
@@ -2,6 +2,6 @@
 0,"heap_graph_object",2,10,64,0,1,"FactoryProducerDelegateImplActor","[NULL]","ROOT_JAVA_FRAME"
 1,"heap_graph_object",2,10,32,"[NULL]",1,"Foo","[NULL]","[NULL]"
 2,"heap_graph_object",2,10,128,"[NULL]",0,"Foo","[NULL]","[NULL]"
-3,"heap_graph_object",2,10,256,3,0,"a","DeobfuscatedA","[NULL]"
+3,"heap_graph_object",2,10,1024,3,0,"a","DeobfuscatedA","[NULL]"
 4,"heap_graph_object",2,10,256,"[NULL]",1,"a[]","DeobfuscatedA[]","ROOT_JAVA_FRAME"
 5,"heap_graph_object",2,10,256,"[NULL]",0,"java.lang.Class<a[]>","java.lang.Class<DeobfuscatedA[]>","[NULL]"
diff --git a/test/trace_processor/profiling/java_heap_histogram.out b/test/trace_processor/profiling/java_heap_histogram.out
index 824b68c..206dc7c 100644
--- a/test/trace_processor/profiling/java_heap_histogram.out
+++ b/test/trace_processor/profiling/java_heap_histogram.out
@@ -8,30 +8,50 @@
     samples {
       ts: 10
       type_count {
-        type_name: "DeobfuscatedA[]"
-        obj_count: 1
-        reachable_obj_count: 1
-      }
-      type_count {
         type_name: "FactoryProducerDelegateImplActor"
         obj_count: 1
         reachable_obj_count: 1
+        size_kb: 0
+        reachable_size_kb: 0
+        native_size_kb: 0
+        reachable_native_size_kb: 0
       }
       type_count {
         type_name: "Foo"
         obj_count: 2
         reachable_obj_count: 1
+        size_kb: 0
+        reachable_size_kb: 0
+        native_size_kb: 0
+        reachable_native_size_kb: 0
       }
       type_count {
         type_name: "DeobfuscatedA"
         obj_count: 1
         reachable_obj_count: 0
+        size_kb: 1
+        reachable_size_kb: 0
+        native_size_kb: 0
+        reachable_native_size_kb: 0
+      }
+      type_count {
+        type_name: "DeobfuscatedA[]"
+        obj_count: 1
+        reachable_obj_count: 1
+        size_kb: 0
+        reachable_size_kb: 0
+        native_size_kb: 0
+        reachable_native_size_kb: 0
       }
       type_count {
         type_name: "java.lang.Class<DeobfuscatedA[]>"
         obj_count: 1
         reachable_obj_count: 0
-      }
-    }
+        size_kb: 0
+        reachable_size_kb: 0
+        native_size_kb: 0
+        reachable_native_size_kb: 0
+       }
+     }
   }
 }
diff --git a/test/trace_processor/profiling/java_heap_stats.out b/test/trace_processor/profiling/java_heap_stats.out
index 4cecef7..6a1237f 100644
--- a/test/trace_processor/profiling/java_heap_stats.out
+++ b/test/trace_processor/profiling/java_heap_stats.out
@@ -7,7 +7,7 @@
     }
     samples {
       ts: 10
-      heap_size: 992
+      heap_size: 1760
       heap_native_size: 0
       reachable_heap_size: 352
       reachable_heap_native_size: 0
diff --git a/tools/build_all_configs.py b/tools/build_all_configs.py
index 0c0ec5d..95fe044 100755
--- a/tools/build_all_configs.py
+++ b/tools/build_all_configs.py
@@ -100,11 +100,6 @@
   else:
     assert False, 'Unsupported system %r' % system
 
-  machine = platform.machine()
-  if machine == 'arm64':
-    for name, config in configs.items():
-      configs[name] = config + ('host_cpu="arm64"',)
-
   if args.ccache:
     for config_name, gn_args in iteritems(configs):
       configs[config_name] = gn_args + ('cc_wrapper="ccache"',)
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index 65dcff2..b45edf8 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -57,7 +57,8 @@
 
     if 'like' in line.casefold():
       sys.stderr.write(
-          'LIKE is banned in trace processor metrics. Prefer GLOB instead.')
+          'LIKE is banned in trace processor metrics. Prefer GLOB instead.\n')
+      sys.stderr.write('Offending file: %s\n' % path)
       errors += 1
 
   return errors
diff --git a/tools/ftrace_proto_gen/event_list b/tools/ftrace_proto_gen/event_list
index 5a0f31b..0535103 100644
--- a/tools/ftrace_proto_gen/event_list
+++ b/tools/ftrace_proto_gen/event_list
@@ -356,3 +356,5 @@
 synthetic/rss_stat_throttled
 net/netif_receive_skb
 net/net_dev_xmit
+sock/inet_sock_set_state
+tcp/tcp_retransmit_skb
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 89c1484..2d9d595 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -37,7 +37,7 @@
 # root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have
 # instead just buildtools/protobuf).
 # |target_os| is either 'darwin', 'linux', 'windows' or 'all'
-# |target_arch| is either 'x64', 'aarch64' or 'all'
+# |target_arch| is either 'x64', 'arm64' or 'all'
 # in both cases the dep only applies on matching platforms
 # |target_arch| can be 'all' when 'target_os' is not 'all' for example in the
 # case of MacOS universal binaries.
@@ -57,21 +57,26 @@
 # Dependencies required to build code on the host or when targeting desktop OS.
 BUILD_DEPS_TOOLCHAIN_HOST = [
     # GN. From https://chrome-infra-packages.appspot.com/dl/gn/gn/.
-    # git_revision:83dad00afb232d7235dd70dff1ee90292d72a01e .
+    # git_revision:0725d7827575b239594fbc8fd5192873a1d62f44 .
     Dependency(
         'buildtools/mac/gn',
-        'https://storage.googleapis.com/perfetto/gn-mac-1695-83dad00a',
-        '513d3adeb56b745e62af4e3ccb76b76f023c6aaa25d6a2be9a89e44cd10a4c1a',
+        'https://storage.googleapis.com/perfetto/gn-mac-1968-0725d782',
+        '9ced623a664560bba38bbadb9b91158ca4186358c847e17ab7d982b351373c2e',
         'darwin', 'x64'),
     Dependency(
+        'buildtools/mac/gn',
+        'https://storage.googleapis.com/perfetto/gn-mac-arm64-1968-0725d782',
+        'd22336b5210b4dad5e36e8c28ce81187f491822cf4d8fd0a257b30d6bee3fd3f',
+        'darwin', 'arm64'),
+    Dependency(
         'buildtools/linux64/gn',
-        'https://storage.googleapis.com/perfetto/gn-linux64-1695-83dad00a',
-        '4f589364153f182b05cd845e93407489d6ce8acc03290c897928a7bd22b20cce',
+        'https://storage.googleapis.com/perfetto/gn-linux64-1968-0725d782',
+        'f706aaa0676e3e22f5fc9ca482295d7caee8535d1869f99efa2358177b64f5cd',
         'linux', 'x64'),
     Dependency(
         'buildtools/win/gn.exe',
-        'https://storage.googleapis.com/perfetto/gn-win-1695-83dad00a',
-        '908c29556539292203d2952ebf55df03697cbc7cf526a3e295f31ba2576e4cac',
+        'https://storage.googleapis.com/perfetto/gn-win-1968-0725d782',
+        '001f777f023c7a6959c778fb3a6b6cfc63f6baef953410ecdeaec350fb12285b',
         'windows', 'x64'),
 
     # clang-format
@@ -80,7 +85,7 @@
         'buildtools/mac/clang-format',
         'https://storage.googleapis.com/chromium-clang-format/62bde1baa7196ad9df969fc1f06b66360b1a927b',
         '6df686a937443cbe6efc013467a7ba5f98d3f187eb7765bb7abc6ce47626cf66',
-        'darwin', 'x64'),
+        'darwin', 'all'),
     # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/linux64/clang-format.sha1
     Dependency(
         'buildtools/linux64/clang-format',
@@ -103,18 +108,18 @@
     # Ninja
     Dependency(
         'buildtools/mac/ninja',
-        'https://storage.googleapis.com/perfetto/ninja-mac-c15b0698da038b2bd2e8970c14c75fadc06b1add',
-        '4224b90734590b0148ad8ee63ee7b295e88e0652e4d1f4271ef2b91d880b0e19',
-        'darwin', 'x64'),
+        'https://storage.googleapis.com/perfetto/ninja-mac-x64_and_arm64-182',
+        '36e8b7aaa06911e1334feb664dd731a1cd69a15eb916a231a3d10ff65fca2c73',
+        'darwin', 'all'),
     Dependency(
         'buildtools/linux64/ninja',
-        'https://storage.googleapis.com/perfetto/ninja-linux64-c866952bda50c29a669222477309287119bbb7e8',
+        'https://storage.googleapis.com/perfetto/ninja-linux64-182',
         '54ac6a01362190aaabf4cf276f9c8982cdf11b225438940fdde3339be0f2ecdc',
         'linux', 'x64'),
     Dependency(
         'buildtools/win/ninja.exe',
-        'https://storage.googleapis.com/perfetto/ninja-win-4a5f05c24afef05ef03329a1bbfedee0678b524a',
-        '6f8af488be74ed8787d04e107080d05330587a4198ba047bd5b7f5b0c3150d61',
+        'https://storage.googleapis.com/perfetto/ninja-win-182',
+        '09ced0fcd1a4dec7d1b798a2cf9ce5d20e5d2fbc2337343827f192ce47d0f491',
         'windows', 'x64'),
 
     # Keep the revision in sync with Chrome's PACKAGE_VERSION in
@@ -244,7 +249,7 @@
         'buildtools/bloaty.zip',
         'https://storage.googleapis.com/perfetto/bloaty-1.1-b3b829de35babc2fe831b9488ad2e50bca939412-mac.zip',
         '2d301bd72a20e3f42888c9274ceb4dca76c103608053572322412c2c65ab8cb8',
-        'darwin', 'x64'),
+        'darwin', 'all'),
 ]
 
 # Dependencies required to build Android code.
@@ -257,7 +262,7 @@
         'buildtools/ndk.zip',
         'https://dl.google.com/android/repository/android-ndk-r21e-darwin-x86_64.zip',
         '437278103a3db12632c05b1be5c41bbb8522791a67e415cc54411a65366f499d',
-        'darwin', 'x64'),
+        'darwin', 'all'),
     Dependency(
         'buildtools/ndk.zip',
         'https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip',
@@ -279,7 +284,7 @@
         'buildtools/android_sdk/platform-tools.zip',
         'https://dl.google.com/android/repository/platform-tools_r26.0.0-darwin.zip',
         '98d392cbd21ca20d643c7e1605760cc49075611e317c534096b5564053f4ac8e',
-        'darwin', 'x64'),
+        'darwin', 'all'),
     Dependency(
         'buildtools/android_sdk/platform-tools.zip',
         'https://dl.google.com/android/repository/platform-tools_r26.0.0-linux.zip',
@@ -303,7 +308,7 @@
         'buildtools/mac/nodejs.tgz',
         'https://storage.googleapis.com/chromium-nodejs/14.15.4/17ba7216e09de1bffb9dc80b7ec617a1cee40330',
         'b81a466347d2ae34b1370b6681ba173e9fb082338170a41624b37be7a2052b7e',
-        'darwin', 'x64'),
+        'darwin', 'all'),
     Dependency(
         'buildtools/linux64/nodejs.tgz',
         'https://storage.googleapis.com/chromium-nodejs/14.15.4/b2e40ddbac04d05baafbb007f203c6663c9d4ca9',
@@ -313,7 +318,7 @@
         'buildtools/mac/emsdk.tgz',
         'https://storage.googleapis.com/perfetto/emscripten-2.0.12-mac.tgz',
         'aa125f8c8ff8a386d43e18c8ea0c98c875cc19160a899403e8967a5478f96f31',
-        'darwin', 'x64'),
+        'darwin', 'all'),
     Dependency(
         'buildtools/linux64/emsdk.tgz',
         'https://storage.googleapis.com/perfetto/emscripten-2.0.12-linux.tgz',
@@ -361,8 +366,8 @@
 
 def GetArch():
   arch = machine()
-  if arch == 'aarch64':
-    return 'aarch64'
+  if arch == 'arm64':
+    return 'arm64'
   else:
     # Assume everything else is x64 matching previous behaviour.
     return 'x64'
diff --git a/tools/trace_to_text/trace_to_text.cc b/tools/trace_to_text/trace_to_text.cc
index 0bca321..7507c75 100644
--- a/tools/trace_to_text/trace_to_text.cc
+++ b/tools/trace_to_text/trace_to_text.cc
@@ -165,10 +165,9 @@
   OstreamOutputStream zero_copy_output(output);
   OstreamOutputStream* zero_copy_output_ptr = &zero_copy_output;
 
-  constexpr uint32_t kCompressedPacketFieldDescriptor = 50;
   const Reflection* reflect = msg->GetReflection();
-  const FieldDescriptor* compressed_desc =
-      trace_descriptor->FindFieldByNumber(kCompressedPacketFieldDescriptor);
+  const FieldDescriptor* compressed_desc = trace_descriptor->FindFieldByNumber(
+      protos::pbzero::TracePacket::kCompressedPacketsFieldNumber);
 
   std::unique_ptr<Message> compressed_packets_msg(prototype->New());
   std::string compressed_packets;
@@ -177,7 +176,7 @@
   printer.SetInitialIndentLevel(1);
 
   static constexpr size_t kMaxMsgSize = protozero::ProtoRingBuffer::kMaxMsgSize;
-  std::unique_ptr<char> data(new char[kMaxMsgSize]);
+  std::unique_ptr<char[]> data(new char[kMaxMsgSize]);
   protozero::ProtoRingBuffer ring_buffer;
 
   uint32_t packet = 0;
@@ -200,7 +199,7 @@
         break;
       bytes_processed += token.len;
 
-      if (token.field_id != 1) {
+      if (token.field_id != protos::pbzero::Trace::kPacketFieldNumber) {
         PERFETTO_ELOG("Skipping invalid field");
         continue;
       }
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 90350bb..4f32d9c 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
   "channels": [
     {
       "name": "stable",
-      "rev": "5e6f200f1188f87c3eab9c0e9cdc57e7c947d982"
+      "rev": "c4eb91619a0927616f43959a91cbc5b51bd79439"
     },
     {
       "name": "canary",
-      "rev": "3ff938b95864694fcb3df4cd0741889dbe36edd8"
+      "rev": "88e543ae67d219635038cd007ab3b5a45a0e2614"
     },
     {
       "name": "autopush",
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index f98aa30..c98a6a1 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -235,6 +235,12 @@
         display: inline-block;
         position: relative;
       }
+      .indent {
+        display: inline-block;
+        // 24px is the width of expand_more/expand_less icon to pad out cells
+        // without the button
+        width: 24px;
+      }
       .popup-menu {
         position: absolute;
         background-color: white;
@@ -696,6 +702,26 @@
   user-select: none;
 }
 
+.pivot-table-redux {
+  user-select: text;
+
+  button.mode-button {
+    border-radius: 10px;
+    padding: 7px;
+    margin: 5px;
+    background-color: #c7d0db;
+  }
+
+  &.edit {
+    padding: 10px;
+    display: flex;
+  }
+
+  &.query-error {
+    color: red;
+  }
+}
+
 .pivot-table-editor-container {
   font: inherit;
   width: 670px;
diff --git a/ui/src/assets/index.html b/ui/src/assets/index.html
index 2e9329f..817945f 100644
--- a/ui/src/assets/index.html
+++ b/ui/src/assets/index.html
@@ -13,9 +13,10 @@
     error reporting.
   -->
   <style>
-  #app_load_failure {opacity:0;transition:opacity 1s ease;position:absolute;background:#080082;top:0;left:0;width:100%;height:100%;bottom:0;right:0;margin:0;opacity:0;user-select:text}
-  #app_load_failure > pre {color:#fff;position:absolute;margin:auto;white-space:pre-wrap;top:50%;transform:translate(0,-50%);max-width:90vw;width:880px;left:0;right:0;font-size:16px;line-height:30px;font-weight:700}
+  #app_load_failure {opacity:0;transition:opacity 1s ease;position:absolute;overflow:auto;background:#080082;top:0;left:0;width:100%;height:100%;bottom:0;right:0;margin:0;opacity:0;user-select:text}
+  #app_load_failure > pre {color:#fff;position:absolute;margin:auto;white-space:pre-wrap;top:10vh;max-width:90vw;width:880px;left:0;right:0;font-size:16px;line-height:30px;font-weight:700}
   #app_load_failure > pre span {background:#fff;color:#080082;padding:2px}
+  #app_load_failure_dbg { overflow-wrap: break-word; font-size: 12px; line-height: 1; font-weight: initial;}
   #app_load_failure a {color:#fff}
   #app_load { position: absolute; top: 0; left: 0; right:0; bottom: 0; background-color: #2c3e50;}
   #app_load_spinner { margin: 30vh auto; width: 150px; height: 150px; border: 3px solid rgba(255,255,255,.3); border-radius: 50%; border-top-color: #fff; animation: app_load_spin 1s ease-in-out infinite; }
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 8ab5034..900c317 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -51,6 +51,7 @@
   LogsPagination,
   NewEngineMode,
   OmniboxState,
+  PivotTableReduxState,
   RecordingTarget,
   SCROLLING_TRACK_GROUP,
   State,
@@ -183,14 +184,14 @@
       state: StateDraft, trackState: TrackState, uiTrackId: string) {
     const config = trackState.config as {trackId: number};
     if (config.trackId !== undefined) {
-      state.uiTrackIdByTraceTrackId.set(config.trackId, uiTrackId);
+      state.uiTrackIdByTraceTrackId[config.trackId] = uiTrackId;
       return;
     }
 
     const multiple = trackState.config as {trackIds: number[]};
     if (multiple.trackIds !== undefined) {
       for (const trackId of multiple.trackIds) {
-        state.uiTrackIdByTraceTrackId.set(trackId, uiTrackId);
+        state.uiTrackIdByTraceTrackId[trackId] = uiTrackId;
       }
     }
   },
@@ -855,10 +856,11 @@
     state.metrics.requestedMetric = undefined;
   },
 
-  setAvailableMetrics(state: StateDraft, args: {metrics: string[]}): void {
-    state.metrics.availableMetrics = args.metrics;
-    if (args.metrics.length > 0) state.metrics.selectedIndex = 0;
-  },
+  setAvailableMetrics(state: StateDraft, args: {availableMetrics: string[]}):
+      void {
+        state.metrics.availableMetrics = args.availableMetrics;
+        if (args.availableMetrics.length > 0) state.metrics.selectedIndex = 0;
+      },
 
   setMetricSelectedIndex(state: StateDraft, args: {index: number}): void {
     if (!state.metrics.availableMetrics ||
@@ -915,6 +917,10 @@
     }
   },
 
+  togglePivotTableRedux(state: StateDraft, args: {enabled: boolean}) {
+    state.pivotTableRedux.enabled = args.enabled;
+  },
+
   addNewPivotTable(state: StateDraft, args: {
     name: string,
     pivotTableId: string,
@@ -940,7 +946,9 @@
 
   resetPivotTableRequest(state: StateDraft, args: {pivotTableId: string}):
       void {
-        state.pivotTable[args.pivotTableId].requestedAction = undefined;
+        if (state.pivotTable[args.pivotTableId] !== undefined) {
+          state.pivotTable[args.pivotTableId].requestedAction = undefined;
+        }
       },
 
   setPivotTableRequest(
@@ -987,6 +995,11 @@
     const pivotTable = state.pivotTable[args.pivotTableId];
     pivotTable.traceTime = args.traceTime;
     pivotTable.selectedTrackIds = args.selectedTrackIds;
+  },
+
+  setPivotStateReduxState(
+      state: StateDraft, args: {pivotTableState: PivotTableReduxState}) {
+    state.pivotTableRedux = args.pivotTableState;
   }
 };
 
diff --git a/ui/src/common/arg_types.ts b/ui/src/common/arg_types.ts
index 6ca6a04..864dc4b 100644
--- a/ui/src/common/arg_types.ts
+++ b/ui/src/common/arg_types.ts
@@ -22,9 +22,9 @@
 }
 
 export function isArgTreeArray(item: ArgsTree): item is ArgsTreeArray {
-  return typeof item === 'object' && item.length !== undefined;
+  return typeof item === 'object' && Array.isArray(item);
 }
 
 export function isArgTreeMap(item: ArgsTree): item is ArgsTreeMap {
-  return typeof item === 'object' && item.length === undefined;
+  return typeof item === 'object' && !Array.isArray(item);
 }
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index a3b1fdc..2b9396e 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -39,7 +39,7 @@
     engines: {},
     traceTime: {...defaultTraceTime},
     tracks: {},
-    uiTrackIdByTraceTrackId: new Map<number, string>(),
+    uiTrackIdByTraceTrackId: {},
     aggregatePreferences: {},
     trackGroups: {},
     visibleTracks: [],
@@ -102,5 +102,7 @@
 
     fetchChromeCategories: false,
     chromeCategories: undefined,
+    pivotTableRedux:
+        {enabled: false, query: null, queryId: 0, queryResult: null},
   };
 }
\ No newline at end of file
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index aea57a9..01c1207 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -293,7 +293,6 @@
     rpc.request = TPM.TPM_QUERY_STREAMING;
     rpc.queryArgs = new QueryArgs();
     rpc.queryArgs.sqlQuery = sqlQuery;
-    rpc.queryArgs.timeQueuedNs = Math.floor(performance.now() * 1e6);
     const result = createQueryResult({
       query: sqlQuery,
     });
diff --git a/ui/src/common/http_rpc_engine.ts b/ui/src/common/http_rpc_engine.ts
index ba9c72e..436dd42 100644
--- a/ui/src/common/http_rpc_engine.ts
+++ b/ui/src/common/http_rpc_engine.ts
@@ -13,12 +13,14 @@
 // limitations under the License.
 
 import {fetchWithTimeout} from '../base/http_utils';
-import {assertTrue} from '../base/logging';
+import {assertExists} from '../base/logging';
 import {StatusResult} from '../common/protos';
 
 import {Engine, LoadingTracker} from './engine';
 
 export const RPC_URL = 'http://127.0.0.1:9001/';
+export const WS_URL = 'ws://127.0.0.1:9001/websocket';
+
 const RPC_CONNECT_TIMEOUT_MS = 2000;
 
 export interface HttpRpcState {
@@ -29,9 +31,10 @@
 
 export class HttpRpcEngine extends Engine {
   readonly id: string;
-  private requestQueue = new Array<Uint8Array>();
-  private requestPending = false;
   errorHandler: (err: string) => void = () => {};
+  private requestQueue = new Array<Uint8Array>();
+  private websocket?: WebSocket;
+  private connected = false;
 
   constructor(id: string, loadingTracker?: LoadingTracker) {
     super(loadingTracker);
@@ -39,47 +42,35 @@
   }
 
   rpcSendRequestBytes(data: Uint8Array): void {
-    if (!this.requestPending && this.requestQueue.length === 0) {
-      this.beginFetch(data);
+    if (this.websocket === undefined) {
+      this.websocket = new WebSocket(WS_URL);
+      this.websocket.onopen = () => this.onWebsocketConnected();
+      this.websocket.onmessage = (e) => this.onWebsocketMessage(e);
+      this.websocket.onclose = (e) =>
+          this.errorHandler(`Websocket closed (${e.code}: ${e.reason})`);
+      this.websocket.onerror = (e) =>
+          this.errorHandler(`WebSocket error: ${e}`);
+    }
+
+    if (this.connected) {
+      this.websocket.send(data);
     } else {
-      this.requestQueue.push(data);
+      this.requestQueue.push(data);  // onWebsocketConnected() will flush this.
     }
   }
 
-  private beginFetch(data: Uint8Array) {
-    assertTrue(!this.requestPending);
-    this.requestPending = true;
-    // Deliberately not using fetchWithTimeout() here. These queries can be
-    // arbitrarily long.
-    // Deliberately not setting cache: no-cache. Doing so invalidates also the
-    // CORS pre-flight responses, causing one OPTIONS request for each POST.
-    // no-cache is also useless because trace-processor's replies are already
-    // marked as no-cache and browsers generally already assume that POST
-    // requests are not idempotent.
-    fetch(RPC_URL + 'rpc', {
-      method: 'post',
-      headers: {'Content-Type': 'application/x-protobuf'},
-      body: data,
-    })
-        .then(resp => this.endFetch(resp))
-        .catch(err => this.errorHandler(err));
+  private onWebsocketConnected() {
+    for (;;) {
+      const queuedMsg = this.requestQueue.shift();
+      if (queuedMsg === undefined) break;
+      assertExists(this.websocket).send(queuedMsg);
+    }
+    this.connected = true;
   }
 
-  private endFetch(resp: Response) {
-    assertTrue(this.requestPending);
-    if (resp.status !== 200) {
-      throw new Error(`HTTP ${resp.status} - ${resp.statusText}`);
-    }
-    resp.arrayBuffer().then(arrBuf => {
-      // Note: another request can sneak in via enqueueRequest() between the
-      // arrayBuffer() call and this continuation. At this point
-      // this.pendingRequest might be set again.
-      // If not (the most common case) submit the next queued request, if any.
-      this.requestPending = false;
-      if (this.requestQueue.length > 0) {
-        this.beginFetch(this.requestQueue.shift()!);
-      }
-      super.onRpcResponseBytes(new Uint8Array(arrBuf));
+  private onWebsocketMessage(e: MessageEvent) {
+    assertExists(e.data as Blob).arrayBuffer().then(buf => {
+      super.onRpcResponseBytes(new Uint8Array(buf));
     });
   }
 
diff --git a/ui/src/common/queries.ts b/ui/src/common/queries.ts
index 1e7e671..a91b126 100644
--- a/ui/src/common/queries.ts
+++ b/ui/src/common/queries.ts
@@ -25,6 +25,8 @@
   durationMs: number;
   columns: string[];
   rows: Row[];
+  statementCount: number;
+  statementWithOutputCount: number;
 }
 
 export async function runQuery(
@@ -68,6 +70,8 @@
     totalRowCount: queryRes.numRows(),
     columns,
     rows,
+    statementCount: queryRes.statementCount(),
+    statementWithOutputCount: queryRes.statementWithOutputCount(),
   };
   return result;
 }
\ No newline at end of file
diff --git a/ui/src/common/query_result.ts b/ui/src/common/query_result.ts
index dd80b4d..73f0a98 100644
--- a/ui/src/common/query_result.ts
+++ b/ui/src/common/query_result.ts
@@ -187,6 +187,14 @@
   // This should be called only after having awaited for at least one batch.
   columns(): string[];
 
+  // Returns the number of SQL statements in the query
+  // (e.g. 2 'if SELECT 1; SELECT 2;')
+  statementCount(): number;
+
+  // Returns the number of SQL statement that produced output rows. This number
+  // is <= statementCount().
+  statementWithOutputCount(): number;
+
   // TODO(primiano): next CLs will introduce a waitMoreRows() to allow tracks
   // to await until some more data (but not necessarily all) is available. For
   // now everything uses waitAllRows().
@@ -218,6 +226,8 @@
   private _numRows = 0;
   private _isComplete = false;
   private _errorInfo: QueryErrorInfo;
+  private _statementCount = 0;
+  private _statementWithOutputCount = 0;
 
   constructor(errorInfo: QueryErrorInfo) {
     this._errorInfo = errorInfo;
@@ -250,6 +260,12 @@
   columns(): string[] {
     return this.columnNames;
   }
+  statementCount(): number {
+    return this._statementCount;
+  }
+  statementWithOutputCount(): number {
+    return this._statementWithOutputCount;
+  }
 
   iter<T extends Row>(spec: T): RowIterator<T> {
     const impl = new RowIteratorImplWithRowData(spec, this);
@@ -337,6 +353,15 @@
             assertTrue(parsedBatch.numCells === 0);
           }
           break;
+
+        case 4:
+          this._statementCount = reader.uint32();
+          break;
+
+        case 5:
+          this._statementWithOutputCount = reader.uint32();
+          break;
+
         default:
           console.warn(`Unexpected QueryResult field ${tag >>> 3}`);
           reader.skipType(tag & 7);
@@ -755,6 +780,12 @@
   error() {
      return this.impl.error();
   }
+  statementCount() {
+    return this.impl.statementCount();
+  }
+  statementWithOutputCount() {
+    return this.impl.statementWithOutputCount();
+  }
 
   // WritableQueryResult implementation.
   appendResultBatch(resBytes: Uint8Array) {
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 865982f..db5e9c6 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {assertTrue} from '../base/logging';
+import {PivotTree} from '../controller/pivot_table_redux_controller';
 import {RecordConfig} from '../controller/record_config_types';
 
 import {
@@ -73,7 +75,11 @@
 // 12: Add a field to cache mapping from UI track ID to trace track ID in order
 //     to speed up flow arrows rendering.
 // 13: FlamegraphState changed to support area selection.
-export const STATE_VERSION = 13;
+// 14: Changed the type of uiTrackIdByTraceTrackId from `Map` to an object with
+// typed key/value because a `Map` does not preserve type during
+// serialisation+deserialisation.
+// 15: Added state for Pivot Table V2
+export const STATE_VERSION = 15;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -116,7 +122,7 @@
   url?: string;
   fileName?: string;
 
-  // |uuid| is set only when loading from the cache via ?trace_id=123. When set,
+  // |uuid| is set only when loading via ?local_cache_key=1234. When set,
   // this matches global.state.traceUuid, with the exception of the following
   // time window: When a trace T1 is loaded and the user loads another trace T2,
   // this |uuid| will be == T2, but the globals.state.traceUuid will be
@@ -325,6 +331,41 @@
   selectedTrackIds?: number[];
 }
 
+// Auxiliary metadata needed to parse the query result, as well as to render it
+// correctly. Generated together with the text of query and passed without the
+// change to the query response.
+export interface PivotTableReduxQueryMetadata {
+  pivotColumns: string[];
+  aggregationColumns: string[];
+}
+
+// Everything that's necessary to run the query for pivot table
+export interface PivotTableReduxQuery {
+  text: string;
+  metadata: PivotTableReduxQueryMetadata;
+}
+
+// Pivot table query result
+export interface PivotTableReduxResult {
+  // Hierarchical pivot structure on top of rows
+  tree: PivotTree;
+  // Copy of the query metadata from the request, bundled up with the query
+  // result to ensure the correct rendering.
+  metadata: PivotTableReduxQueryMetadata;
+}
+
+export interface PivotTableReduxState {
+  // Whether the panel should be visible
+  enabled: boolean;
+  // Increasing identifier of the query request, used to avoid performing the
+  // same query more than once.
+  queryId: number;
+  // Query request
+  query: PivotTableReduxQuery|null;
+  // Query response
+  queryResult: PivotTableReduxResult|null;
+}
+
 export interface LoadedConfigNone {
   type: 'NONE';
 }
@@ -365,7 +406,7 @@
   traceUuid?: string;
   trackGroups: ObjectById<TrackGroupState>;
   tracks: ObjectById<TrackState>;
-  uiTrackIdByTraceTrackId: Map<number, string>;
+  uiTrackIdByTraceTrackId: {[key: number]: string;};
   areas: ObjectById<AreaById>;
   aggregatePreferences: ObjectById<AggregationState>;
   visibleTracks: string[];
@@ -384,6 +425,7 @@
   traceConversionInProgress: boolean;
   pivotTableConfig: PivotTableConfig;
   pivotTable: ObjectById<PivotTableState>;
+  pivotTableRedux: PivotTableReduxState;
 
   /**
    * This state is updated on the frontend at 60Hz and eventually syncronised to
@@ -436,7 +478,12 @@
     'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE';
 
 // 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
-export declare type TargetOs = 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
+export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
+
+export function isTargetOsAtLeast(target: RecordingTarget, osVersion: string) {
+  assertTrue(osVersion.length === 1);
+  return target.os >= osVersion;
+}
 
 export function isAndroidP(target: RecordingTarget) {
   return target.os === 'P';
diff --git a/ui/src/controller/metrics_controller.ts b/ui/src/controller/metrics_controller.ts
index 92afb8a..d0e0916 100644
--- a/ui/src/controller/metrics_controller.ts
+++ b/ui/src/controller/metrics_controller.ts
@@ -14,7 +14,7 @@
 
 import {Actions} from '../common/actions';
 import {Engine} from '../common/engine';
-import {QueryError, STR} from '../common/query_result';
+import {QueryError} from '../common/query_result';
 import {publishMetricResult} from '../frontend/publish';
 
 import {Controller} from './controller';
@@ -27,24 +27,7 @@
   constructor(args: {engine: Engine}) {
     super('main');
     this.engine = args.engine;
-    this.setup().finally(() => {
-      this.run();
-    });
-  }
-
-  private async getMetricNames() {
-    const metrics = [];
-    const result = await this.engine.query('select name from trace_metrics');
-    const it = result.iter({name: STR});
-    for (; it.valid(); it.next()) {
-      metrics.push(it.name);
-    }
-    return metrics;
-  }
-
-  private async setup() {
-    const metrics = await this.getMetricNames();
-    globals.dispatch(Actions.setAvailableMetrics({metrics}));
+    this.run();
   }
 
   private async computeMetric(name: string) {
diff --git a/ui/src/controller/pivot_table_redux_controller.ts b/ui/src/controller/pivot_table_redux_controller.ts
new file mode 100644
index 0000000..59a4c1b
--- /dev/null
+++ b/ui/src/controller/pivot_table_redux_controller.ts
@@ -0,0 +1,170 @@
+import {Actions} from '../common/actions';
+import {Engine} from '../common/engine';
+import {featureFlags} from '../common/feature_flags';
+import {ColumnType} from '../common/query_result';
+import {aggregationIndex} from '../frontend/pivot_table_redux_query_generator';
+
+import {Controller} from './controller';
+import {globals} from './globals';
+
+export const PIVOT_TABLE_REDUX_FLAG = featureFlags.register({
+  id: 'pivotTableRedux',
+  name: 'Pivot tables V2',
+  description: 'Second version of pivot table',
+  defaultValue: false,
+});
+
+// Node in the hierarchical pivot tree. Only leaf nodes contain data from the
+// query result.
+export interface PivotTree {
+  // Whether the node should be collapsed in the UI, false by default and can
+  // be toggled with the button.
+  isCollapsed: boolean;
+
+  // Non-empty only in internal nodes.
+  children: Map<ColumnType, PivotTree>;
+  aggregates: ColumnType[];
+
+  // Non-empty only in leaf nodes.
+  rows: ColumnType[][];
+}
+
+// Auxiliary class to build the tree from query response.
+class TreeBuilder {
+  private readonly root: PivotTree;
+  lastRow: ColumnType[];
+  pivotColumns: number;
+  aggregateColumns: number;
+
+  constructor(
+      pivotColumns: number, aggregateColumns: number, firstRow: ColumnType[]) {
+    this.pivotColumns = pivotColumns;
+    this.aggregateColumns = aggregateColumns;
+    this.root = this.createNode(0, firstRow);
+    let tree = this.root;
+    for (let i = 0; i + 1 < this.pivotColumns; i++) {
+      const value = firstRow[i];
+      tree = TreeBuilder.insertChild(
+          tree, value, this.createNode(i + 1, firstRow));
+    }
+    this.lastRow = firstRow;
+  }
+
+  // Add incoming row to the tree being built.
+  ingestRow(row: ColumnType[]) {
+    let tree = this.root;
+    for (let i = 0; i + 1 < this.pivotColumns; i++) {
+      const nextTree = tree.children.get(row[i]);
+      if (nextTree === undefined) {
+        // Insert the new node into the tree, and make variable `tree` point
+        // to the newly created node.
+        tree =
+            TreeBuilder.insertChild(tree, row[i], this.createNode(i + 1, row));
+      } else {
+        tree = nextTree;
+      }
+    }
+    tree.rows.push(row);
+    this.lastRow = row;
+  }
+
+  build(): PivotTree {
+    return this.root;
+  }
+
+  // Helper method that inserts child node into the tree and returns it, used
+  // for more concise modification of local variable pointing to the current
+  // node being built.
+  static insertChild(tree: PivotTree, key: ColumnType, child: PivotTree):
+      PivotTree {
+    tree.children.set(key, child);
+    return child;
+  }
+
+  // Initialize PivotTree from a row.
+  createNode(depth: number, row: ColumnType[]): PivotTree {
+    const aggregates = [];
+
+    for (let j = 0; j < this.aggregateColumns; j++) {
+      aggregates.push(row[aggregationIndex(this.pivotColumns, j, depth)]);
+    }
+
+    return {
+      isCollapsed: false,
+      children: new Map(),
+      aggregates,
+      rows: [],
+    };
+  }
+}
+
+// Controller responsible for showing the panel with pivot table, as well as
+// executing its queries and post-processing query results.
+export class PivotTableReduxController extends Controller<{}> {
+  engine: Engine;
+  lastStartedQueryId: number;
+
+  constructor(args: {engine: Engine}) {
+    super({});
+    this.engine = args.engine;
+    this.lastStartedQueryId = 0;
+  }
+
+  run() {
+    if (!PIVOT_TABLE_REDUX_FLAG.get()) {
+      return;
+    }
+
+    const selection = globals.state.currentSelection;
+    const hasSelection = selection !== null;
+
+    const pivotTableState = globals.state.pivotTableRedux;
+    if (pivotTableState.queryId > this.lastStartedQueryId &&
+        pivotTableState.query !== null) {
+      this.lastStartedQueryId = pivotTableState.queryId;
+      const query = pivotTableState.query;
+
+      this.engine.query(query.text).then(async (result) => {
+        try {
+          await result.waitAllRows();
+        } catch {
+          // waitAllRows() frequently throws an exception, which is ignored in
+          // its other calls, so it's ignored here as well.
+        }
+
+        const columns = result.columns();
+
+        const it = result.iter({});
+        function nextRow(): ColumnType[] {
+          const row: ColumnType[] = [];
+          for (const column of columns) {
+            row.push(it.get(column));
+          }
+          it.next();
+          return row;
+        }
+        const treeBuilder = new TreeBuilder(
+            query.metadata.pivotColumns.length,
+            query.metadata.aggregationColumns.length,
+            nextRow());
+        while (it.valid()) {
+          treeBuilder.ingestRow(nextRow());
+        }
+
+        globals.dispatch(Actions.setPivotStateReduxState({
+          pivotTableState: {
+            queryId: this.lastStartedQueryId,
+            query: null,
+            queryResult: {
+              tree: treeBuilder.build(),
+              metadata: query.metadata,
+            },
+            enabled: true
+          }
+        }));
+      });
+    }
+
+    globals.dispatch(Actions.togglePivotTableRedux({enabled: hasSelection}));
+  }
+}
\ No newline at end of file
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 0868940..a476450 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -44,6 +44,7 @@
   isChromeTarget,
   isCrOSTarget,
   isLinuxTarget,
+  isTargetOsAtLeast,
   RecordingTarget
 } from '../common/state';
 import {publishBufferUsage, publishTrackData} from '../frontend/publish';
@@ -127,7 +128,9 @@
     procThreadAssociationPolling = true;
     procThreadAssociationFtrace = true;
     uiCfg.ftrace = true;
-    uiCfg.symbolizeKsyms = true;
+    if (isTargetOsAtLeast(target, 'S')) {
+      uiCfg.symbolizeKsyms = true;
+    }
     ftraceEvents.add('sched/sched_switch');
     ftraceEvents.add('power/suspend_resume');
     ftraceEvents.add('sched/sched_wakeup');
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index d263cf0..e1a3b80 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -39,7 +39,20 @@
   expect(ftraceEvents.includes('raw_syscalls/sys_exit')).toBe(true);
 });
 
-test('cpu scheduling includes kSyms', () => {
+test('cpu scheduling includes kSyms if OS >= S', () => {
+  const config = createEmptyRecordConfig();
+  config.cpuSched = true;
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'S', name: 'Android S'}));
+  const sources = assertExists(result.dataSources);
+  const srcConfig = assertExists(sources[1].config);
+  const ftraceConfig = assertExists(srcConfig.ftraceConfig);
+  const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
+  expect(ftraceConfig.symbolizeKsyms).toBe(true);
+  expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(true);
+});
+
+test('cpu scheduling does not include kSyms if OS <= S', () => {
   const config = createEmptyRecordConfig();
   config.cpuSched = true;
   const result =
@@ -48,8 +61,8 @@
   const srcConfig = assertExists(sources[1].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
   const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
-  expect(ftraceConfig.symbolizeKsyms).toBe(true);
-  expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(true);
+  expect(ftraceConfig.symbolizeKsyms).toBe(false);
+  expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(false);
 });
 
 test('kSyms can be enabled individually', () => {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 3ec461fb..8c0946a 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -257,6 +257,7 @@
       0 as utid
       from slice
       where slice.name like ${searchLiteral}
+        ${isNaN(Number(search)) ? '' : `or sliceId = ${search}`}
     union
     select
       slice_id as sliceId,
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 3979b2b..6b621cf 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -378,7 +378,7 @@
     const delta = value - previousValue;
     const duration = endTs - ts;
     const startTime = fromNs(ts) - globals.state.traceTime.startSec;
-    const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
     const name = uiTrackId ? globals.state.tracks[uiTrackId].name : undefined;
     return {startTime, value, delta, duration, name};
   }
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 1686210..d6b4bc0 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -12,20 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists, assertTrue} from '../base/logging';
+import { assertExists, assertTrue } from '../base/logging';
 import {
   Actions,
   DeferredAction,
 } from '../common/actions';
-import {cacheTrace} from '../common/cache_manager';
-import {TRACE_MARGIN_TIME_S} from '../common/constants';
-import {Engine} from '../common/engine';
-import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../common/feature_flags';
-import {HttpRpcEngine} from '../common/http_rpc_engine';
-import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result';
-import {EngineMode} from '../common/state';
-import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time';
-import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
+import { cacheTrace } from '../common/cache_manager';
+import { TRACE_MARGIN_TIME_S } from '../common/constants';
+import { Engine } from '../common/engine';
+import { featureFlags, Flag, PERF_SAMPLE_FLAG } from '../common/feature_flags';
+import { HttpRpcEngine } from '../common/http_rpc_engine';
+import { NUM, NUM_NULL, QueryError, STR, STR_NULL } from '../common/query_result';
+import { EngineMode } from '../common/state';
+import { TimeSpan, toNs, toNsCeil, toNsFloor } from '../common/time';
+import { resetEngineWorker, WasmEngineProxy } from '../common/wasm_engine_proxy';
 import {
   globals as frontendGlobals,
   QuantizedLoad,
@@ -37,7 +37,7 @@
   publishOverviewData,
   publishThreads
 } from '../frontend/publish';
-import {Router} from '../frontend/router';
+import { Router } from '../frontend/router';
 
 import {
   CounterAggregationController
@@ -57,7 +57,7 @@
 import {
   ThreadAggregationController
 } from './aggregation/thread_aggregation_controller';
-import {Child, Children, Controller} from './controller';
+import { Child, Children, Controller } from './controller';
 import {
   CpuProfileController,
   CpuProfileControllerArgs
@@ -70,14 +70,15 @@
   FlowEventsController,
   FlowEventsControllerArgs
 } from './flow_events_controller';
-import {globals} from './globals';
-import {LoadingManager} from './loading_manager';
-import {LogsController} from './logs_controller';
-import {MetricsController} from './metrics_controller';
+import { globals } from './globals';
+import { LoadingManager } from './loading_manager';
+import { LogsController } from './logs_controller';
+import { MetricsController } from './metrics_controller';
 import {
   PivotTableController,
   PivotTableControllerArgs
 } from './pivot_table_controller';
+import {PivotTableReduxController} from './pivot_table_redux_controller';
 import {QueryController, QueryControllerArgs} from './query_controller';
 import {SearchController} from './search_controller';
 import {
@@ -93,10 +94,10 @@
   TraceHttpStream,
   TraceStream
 } from './trace_stream';
-import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
-import {decideTracks} from './track_decider';
+import { TrackControllerArgs, trackControllerRegistry } from './track_controller';
+import { decideTracks } from './track_decider';
 
-type States = 'init'|'loading_trace'|'ready';
+type States = 'init' | 'loading_trace' | 'ready';
 
 const METRICS = [
   'android_startup',
@@ -111,6 +112,7 @@
   'android_camera',
   'chrome_dropped_frames',
   'trace_metadata',
+  'android_trusty_workqueues',
 ];
 const FLAGGED_METRICS: Array<[Flag, string]> = METRICS.map(m => {
   const id = `forceMetric${m}`;
@@ -143,17 +145,17 @@
     switch (this.state) {
       case 'init':
         this.loadTrace()
-            .then(mode => {
-              globals.dispatch(Actions.setEngineReady({
-                engineId: this.engineId,
-                ready: true,
-                mode,
-              }));
-            })
-            .catch(err => {
-              this.updateStatus(`${err}`);
-              throw err;
-            });
+          .then(mode => {
+            globals.dispatch(Actions.setEngineReady({
+              engineId: this.engineId,
+              ready: true,
+              mode,
+            }));
+          })
+          .catch(err => {
+            this.updateStatus(`${err}`);
+            throw err;
+          });
         this.updateStatus('Opening trace');
         this.setState('loading_trace');
         break;
@@ -176,74 +178,76 @@
           if (trackCfg.engineId !== this.engineId) continue;
           if (!trackControllerRegistry.has(trackCfg.kind)) continue;
           const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
-          const trackArgs: TrackControllerArgs = {trackId, engine};
+          const trackArgs: TrackControllerArgs = { trackId, engine };
           childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
         }
 
         // Create a QueryController for each query.
         for (const queryId of Object.keys(globals.state.queries)) {
-          const queryArgs: QueryControllerArgs = {queryId, engine};
+          const queryArgs: QueryControllerArgs = { queryId, engine };
           childControllers.push(Child(queryId, QueryController, queryArgs));
         }
 
-        const selectionArgs: SelectionControllerArgs = {engine};
+        const selectionArgs: SelectionControllerArgs = { engine };
         childControllers.push(
-            Child('selection', SelectionController, selectionArgs));
+          Child('selection', SelectionController, selectionArgs));
 
-        const flowEventsArgs: FlowEventsControllerArgs = {engine};
+        const flowEventsArgs: FlowEventsControllerArgs = { engine };
         childControllers.push(
-            Child('flowEvents', FlowEventsController, flowEventsArgs));
+          Child('flowEvents', FlowEventsController, flowEventsArgs));
 
-        const cpuProfileArgs: CpuProfileControllerArgs = {engine};
+        const cpuProfileArgs: CpuProfileControllerArgs = { engine };
         childControllers.push(
-            Child('cpuProfile', CpuProfileController, cpuProfileArgs));
+          Child('cpuProfile', CpuProfileController, cpuProfileArgs));
 
-        const flamegraphArgs: FlamegraphControllerArgs = {engine};
+        const flamegraphArgs: FlamegraphControllerArgs = { engine };
         childControllers.push(
-            Child('flamegraph', FlamegraphController, flamegraphArgs));
+          Child('flamegraph', FlamegraphController, flamegraphArgs));
         childControllers.push(Child(
-            'cpu_aggregation',
-            CpuAggregationController,
-            {engine, kind: 'cpu_aggregation'}));
+          'cpu_aggregation',
+          CpuAggregationController,
+          { engine, kind: 'cpu_aggregation' }));
         childControllers.push(Child(
-            'thread_aggregation',
-            ThreadAggregationController,
-            {engine, kind: 'thread_state_aggregation'}));
+          'thread_aggregation',
+          ThreadAggregationController,
+          { engine, kind: 'thread_state_aggregation' }));
         childControllers.push(Child(
-            'cpu_process_aggregation',
-            CpuByProcessAggregationController,
-            {engine, kind: 'cpu_by_process_aggregation'}));
+          'cpu_process_aggregation',
+          CpuByProcessAggregationController,
+          { engine, kind: 'cpu_by_process_aggregation' }));
         childControllers.push(Child(
-            'slice_aggregation',
-            SliceAggregationController,
-            {engine, kind: 'slice_aggregation'}));
+          'slice_aggregation',
+          SliceAggregationController,
+          { engine, kind: 'slice_aggregation' }));
         childControllers.push(Child(
-            'counter_aggregation',
-            CounterAggregationController,
-            {engine, kind: 'counter_aggregation'}));
+          'counter_aggregation',
+          CounterAggregationController,
+          { engine, kind: 'counter_aggregation' }));
         childControllers.push(Child(
-            'frame_aggregation',
-            FrameAggregationController,
-            {engine, kind: 'frame_aggregation'}));
+          'frame_aggregation',
+          FrameAggregationController,
+          { engine, kind: 'frame_aggregation' }));
         childControllers.push(Child('search', SearchController, {
           engine,
           app: globals,
         }));
+        childControllers.push(
+            Child('pivot_table_redux', PivotTableReduxController, {engine}));
 
         childControllers.push(Child('logs', LogsController, {
           engine,
           app: globals,
         }));
         childControllers.push(
-            Child('traceError', TraceErrorController, {engine}));
-        childControllers.push(Child('metrics', MetricsController, {engine}));
+          Child('traceError', TraceErrorController, { engine }));
+        childControllers.push(Child('metrics', MetricsController, { engine }));
 
         // Create a PivotTableController for each pivot table.
         for (const pivotTableId of Object.keys(globals.state.pivotTable)) {
           const pivotTableArgs:
-              PivotTableControllerArgs = {pivotTableId, engine};
+            PivotTableControllerArgs = { pivotTableId, engine };
           childControllers.push(
-              Child(pivotTableId, PivotTableController, pivotTableArgs));
+            Child(pivotTableId, PivotTableController, pivotTableArgs));
         }
 
         return childControllers;
@@ -274,7 +278,7 @@
       engine = new HttpRpcEngine(this.engineId, LoadingManager.getInstance);
       engine.errorHandler = (err) => {
         globals.dispatch(
-            Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`}));
+          Actions.setEngineFailed({ mode: 'HTTP_RPC', failure: `${err}` }));
         throw err;
       };
     } else {
@@ -282,7 +286,7 @@
       engineMode = 'WASM';
       const enginePort = resetEngineWorker();
       engine = new WasmEngineProxy(
-          this.engineId, enginePort, LoadingManager.getInstance);
+        this.engineId, enginePort, LoadingManager.getInstance);
     }
     this.engine = engine;
 
@@ -293,7 +297,7 @@
       mode: engineMode,
     }));
     const engineCfg = assertExists(globals.state.engines[this.engineId]);
-    let traceStream: TraceStream|undefined;
+    let traceStream: TraceStream | undefined;
     if (engineCfg.source.type === 'FILE') {
       traceStream = new TraceFileStream(engineCfg.source.file);
     } else if (engineCfg.source.type === 'ARRAY_BUFFER') {
@@ -313,7 +317,7 @@
     // file/stream and we just want to jump to the loading phase.
     if (traceStream !== undefined) {
       const tStart = performance.now();
-      for (;;) {
+      for (; ;) {
         const res = await traceStream.readChunk();
         await this.engine.parse(res.data);
         const elapsed = (performance.now() - tStart) / 1000;
@@ -350,13 +354,13 @@
     const emptyOmniboxState = {
       omnibox: '',
       mode: frontendGlobals.state.frontendLocalState.omniboxState.mode ||
-          'SEARCH',
+        'SEARCH',
       lastUpdate: Date.now() / 1000
     };
 
     const actions: DeferredAction[] = [
       Actions.setOmnibox(emptyOmniboxState),
-      Actions.setTraceUuid({traceUuid}),
+      Actions.setTraceUuid({ traceUuid }),
       Actions.setTraceTime(traceTimeState)
     ];
 
@@ -365,9 +369,9 @@
     const mdTime = await this.engine.getTracingMetadataTimeBounds();
     // make sure the bounds hold
     if (Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S) <
-        Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) {
+      Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) {
       visibleStartSec =
-          Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S);
+        Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S);
       visibleEndSec = Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S);
     }
 
@@ -382,14 +386,14 @@
     }));
 
     globals.dispatchMultiple(actions);
-    Router.navigate(`#!/viewer?trace_id=${traceUuid}`);
+    Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`);
 
     // Make sure the helper views are available before we start adding tracks.
     await this.initialiseHelperViews();
 
     {
       // When we reload from a permalink don't create extra tracks:
-      const {pinnedTracks, tracks} = globals.state;
+      const { pinnedTracks, tracks } = globals.state;
       if (!pinnedTracks.length && !Object.keys(tracks).length) {
         await this.listTracks();
       }
@@ -433,11 +437,11 @@
         order by ts desc limit 1`;
     const profile = await assertExists(this.engine).query(query);
     if (profile.numRows() !== 1) return;
-    const row = profile.firstRow({ts: NUM, upid: NUM});
+    const row = profile.firstRow({ ts: NUM, upid: NUM });
     const ts = row.ts;
     const upid = row.upid;
     globals.dispatch(
-        Actions.selectPerfSamples({id: 0, upid, ts, type: 'perf'}));
+      Actions.selectPerfSamples({ id: 0, upid, ts, type: 'perf' }));
   }
 
   private async selectFirstHeapProfile() {
@@ -449,11 +453,11 @@
         heap_graph_object) order by ts limit 1`;
     const profile = await assertExists(this.engine).query(query);
     if (profile.numRows() !== 1) return;
-    const row = profile.firstRow({ts: NUM, type: STR, upid: NUM});
+    const row = profile.firstRow({ ts: NUM, type: STR, upid: NUM });
     const ts = row.ts;
     const type = row.type;
     const upid = row.upid;
-    globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
+    globals.dispatch(Actions.selectHeapProfile({ id: 0, upid, ts, type }));
   }
 
   private async listTracks() {
@@ -494,7 +498,7 @@
       const threadName = it.threadName;
       const procName = it.procName === null ? undefined : it.procName;
       const cmdline = it.cmdline === null ? undefined : it.cmdline;
-      threads.push({utid, tid, threadName, pid, procName, cmdline});
+      threads.push({ utid, tid, threadName, pid, procName, cmdline });
     }
     publishThreads(threads);
   }
@@ -506,8 +510,8 @@
     let hasSchedOverview = false;
     for (let step = 0; step < numSteps; step++) {
       this.updateStatus(
-          'Loading overview ' +
-          `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
+        'Loading overview ' +
+        `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
       const startSec = traceTime.start + step * stepSec;
       const startNs = toNsFloor(startSec);
       const endSec = startSec + stepSec;
@@ -515,15 +519,15 @@
 
       // Sched overview.
       const schedResult = await engine.query(
-          `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` +
-          `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
-          'group by cpu order by cpu');
-      const schedData: {[key: string]: QuantizedLoad} = {};
-      const it = schedResult.iter({load: NUM, cpu: NUM});
+        `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` +
+        `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
+        'group by cpu order by cpu');
+      const schedData: { [key: string]: QuantizedLoad } = {};
+      const it = schedResult.iter({ load: NUM, cpu: NUM });
       for (; it.valid(); it.next()) {
         const load = it.load;
         const cpu = it.cpu;
-        schedData[cpu] = {startSec, endSec, load};
+        schedData[cpu] = { startSec, endSec, load };
         hasSchedOverview = true;
       }
       publishOverviewData(schedData);
@@ -543,8 +547,7 @@
          from thread
          inner join (
            select
-             ifnull(cast((ts - ${traceStartNs})/${
-        stepSecNs} as int), 0) as bucket,
+             ifnull(cast((ts - ${traceStartNs})/${stepSecNs} as int), 0) as bucket,
              sum(dur) as utid_sum,
              utid
            from slice
@@ -554,8 +557,8 @@
          where upid is not null
          group by bucket, upid`);
 
-    const slicesData: {[key: string]: QuantizedLoad[]} = {};
-    const it = sliceResult.iter({bucket: NUM, upid: NUM, load: NUM});
+    const slicesData: { [key: string]: QuantizedLoad[] } = {};
+    const it = sliceResult.iter({ bucket: NUM, upid: NUM, load: NUM });
     for (; it.valid(); it.next()) {
       const bucket = it.bucket;
       const upid = it.upid;
@@ -569,7 +572,7 @@
       if (loadArray === undefined) {
         loadArray = slicesData[upidStr] = [];
       }
-      loadArray.push({startSec, endSec, load});
+      loadArray.push({ startSec, endSec, load });
     }
     publishOverviewData(slicesData);
   }
@@ -582,13 +585,14 @@
       // One of the cases covered is an empty trace.
       return '';
     }
-    const traceUuid = result.firstRow({uuid: STR}).uuid;
+    const traceUuid = result.firstRow({ uuid: STR }).uuid;
     const engineConfig = assertExists(globals.state.engines[engine.id]);
     if (!(await cacheTrace(engineConfig.source, traceUuid))) {
       // If the trace is not cacheable (cacheable means it has been opened from
-      // URL or RPC) only append '?trace_id' to the URL, without the trace_id
-      // value. Doing otherwise would cause an error if the tab is discarded or
-      // the user hits the reload button because the trace is not in the cache.
+      // URL or RPC) only append '?local_cache_key' to the URL, without the
+      // local_cache_key value. Doing otherwise would cause an error if the tab
+      // is discarded or the user hits the reload button because the trace is
+      // not in the cache.
       return '';
     }
     return traceUuid;
@@ -646,12 +650,19 @@
     `);
 
 
+    const availableMetrics = [];
+    const metricsResult = await engine.query('select name from trace_metrics');
+    for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) {
+      availableMetrics.push(it.name);
+    }
+    globals.dispatch(Actions.setAvailableMetrics({availableMetrics}));
+
+    const availableMetricsSet = new Set<string>(availableMetrics);
     for (const [flag, metric] of FLAGGED_METRICS) {
-      if (!flag.get()) {
+      if (!flag.get() || !availableMetricsSet.has(metric)) {
         continue;
       }
 
-
       this.updateStatus(`Computing ${metric} metric`);
       try {
         // We don't care about the actual result of metric here as we are just
@@ -674,7 +685,7 @@
         let hasUpid = false;
         let hasValue = false;
         let hasGroupName = false;
-        const it = result.iter({name: STR});
+        const it = result.iter({ name: STR });
         for (; it.valid(); it.next()) {
           const name = it.name;
           hasSliceName = hasSliceName || name === 'slice_name';
@@ -687,7 +698,7 @@
         const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid';
         const upidColumnWhere = hasUpid ? 'upid' : '0';
         const groupNameColumn =
-            hasGroupName ? 'group_name' : 'NULL AS group_name';
+          hasGroupName ? 'group_name' : 'NULL AS group_name';
         if (hasSliceName && hasDur) {
           await engine.query(`
             INSERT INTO annotation_slice_track(
@@ -723,7 +734,7 @@
               IFNULL(MAX(value), 0) as maxValue
             FROM ${metric}_event
             WHERE ${upidColumnWhere} != 0`);
-          const row = minMax.firstRow({minValue: NUM, maxValue: NUM});
+          const row = minMax.firstRow({ minValue: NUM, maxValue: NUM });
           await engine.query(`
             INSERT INTO annotation_counter_track(
               name, __metric_name, min_value, max_value, upid)
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index f1765fc..a82888f 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -427,7 +427,10 @@
 
   async addAnnotationTracks(): Promise<void> {
     const sliceResult = await this.engine.query(`
-    SELECT id, name, upid, group_name FROM annotation_slice_track`);
+      select id, name, upid, group_name
+      from annotation_slice_track
+      order by name
+    `);
 
     const sliceIt = sliceResult.iter({
       id: NUM,
@@ -449,7 +452,7 @@
       const upid = sliceIt.upid;
       const groupName = sliceIt.group_name;
 
-      let trackId = undefined;
+      let summaryTrackId = undefined;
       let trackGroupId =
           upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid);
 
@@ -462,16 +465,16 @@
           trackGroupId = groupIds.id;
         } else {
           trackGroupId = uuidv4();
-          trackId = uuidv4();
+          summaryTrackId = uuidv4();
           groupNameToIds.set(groupName, {
             id: trackGroupId,
-            summaryTrackId: trackId,
+            summaryTrackId,
           });
         }
       }
 
       this.tracksToAdd.push({
-        id: trackId,
+        id: summaryTrackId,
         engineId: this.engineId,
         kind: SLICE_TRACK_KIND,
         name,
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 5a98bd1..0088e33 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -37,6 +37,7 @@
 import {PivotTable} from './pivot_table';
 import {ColumnDisplay, ColumnPicker} from './pivot_table_editor';
 import {PivotTableHelper} from './pivot_table_helper';
+import {PivotTableRedux} from './pivot_table_redux';
 import {QueryTable} from './query_table';
 import {SliceDetailsPanel} from './slice_details_panel';
 import {ThreadStatePanel} from './thread_state_panel';
@@ -326,6 +327,14 @@
       });
     }
 
+    if (globals.state.pivotTableRedux.enabled) {
+      detailsPanels.push({
+        key: 'pivot_table_redux',
+        name: 'Pivot Table',
+        vnode: m(PivotTableRedux)
+      });
+    }
+
     for (const pivotTableId of Object.keys(globals.state.pivotTable)) {
       const pivotTable = globals.state.pivotTable[pivotTableId];
       const helper = globals.pivotTableHelper.get(pivotTableId);
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index b8fa46f..323d993 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -43,7 +43,7 @@
     }
 
     const flowClickHandler = (sliceId: number, trackId: number) => {
-      const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+      const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
       if (uiTrackId) {
         globals.makeSelection(
             Actions.selectChromeSlice(
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 4cde97b..1d2a719 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -97,7 +97,7 @@
 
 export class FlowEventsRenderer {
   private getTrackGroupIdByTrackId(trackId: number): string|undefined {
-    const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
     return uiTrackId ? globals.state.tracks[uiTrackId].trackGroup : undefined;
   }
 
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 9265090..c86f52b 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -146,6 +146,7 @@
     'connect-src': [
       `'self'`,
       'http://127.0.0.1:9001',  // For trace_processor_shell --httpd.
+      'ws://127.0.0.1:9001',    // Ditto, for the websocket RPC.
       'https://www.google-analytics.com',
       'https://*.googleapis.com',  // For Google Cloud Storage fetches.
       'blob:',
@@ -320,7 +321,7 @@
   }
   installFileDropHandler();
 
-  // Handles the initial ?trace_id=a0b1c2 or ?s=permalink or ?url=... cases.
+  // Handles the initial ?local_cache_key=123 or ?s=permalink or ?url=... cases.
   maybeOpenTraceFromRoute(Router.parseUrl(window.location.href));
 }
 
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 4f21346..3f46bf1 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -163,7 +163,7 @@
     if (flow.id === flowId) {
       const flowPoint = (direction === 'Backward' ? flow.begin : flow.end);
       const uiTrackId =
-          globals.state.uiTrackIdByTraceTrackId.get(flowPoint.trackId);
+          globals.state.uiTrackIdByTraceTrackId[flowPoint.trackId];
       if (uiTrackId) {
         globals.makeSelection(Actions.selectChromeSlice(
             {id: flowPoint.sliceId, trackId: uiTrackId, table: 'slice'}));
diff --git a/ui/src/frontend/pivot_table_redux.ts b/ui/src/frontend/pivot_table_redux.ts
new file mode 100644
index 0000000..c2c129a
--- /dev/null
+++ b/ui/src/frontend/pivot_table_redux.ts
@@ -0,0 +1,259 @@
+import * as m from 'mithril';
+
+import {Actions} from '../common/actions';
+import {ColumnType} from '../common/query_result';
+import {PivotTableReduxQuery, PivotTableReduxResult} from '../common/state';
+import {PivotTree} from '../controller/pivot_table_redux_controller';
+
+import {globals} from './globals';
+import {Panel} from './panel';
+import {
+  aggregationIndex,
+  ColumnSet,
+  generateQuery,
+  QueryGeneratorError,
+  sliceAggregationColumns,
+  Table,
+  TableColumn,
+  tables,
+  threadSliceAggregationColumns
+} from './pivot_table_redux_query_generator';
+
+interface ColumnSetCheckboxAttrs {
+  set: ColumnSet;
+  setKey: TableColumn;
+}
+
+interface PathItem {
+  tree: PivotTree;
+  nextKey: ColumnType;
+}
+
+// Helper component that controls whether a particular key is present in a
+// ColumnSet.
+class ColumnSetCheckbox implements m.ClassComponent<ColumnSetCheckboxAttrs> {
+  view({attrs}: m.Vnode<ColumnSetCheckboxAttrs>) {
+    return m('input[type=checkbox]', {
+      onclick: (e: InputEvent) => {
+        const target = e.target as HTMLInputElement;
+        if (target.checked) {
+          attrs.set.add(attrs.setKey);
+        } else {
+          attrs.set.delete(attrs.setKey);
+        }
+        globals.rafScheduler.scheduleFullRedraw();
+      },
+      checked: attrs.set.has(attrs.setKey)
+    });
+  }
+}
+
+export class PivotTableRedux extends Panel {
+  selectedPivotsMap = new ColumnSet();
+  selectedAggregations = new ColumnSet();
+  editMode = true;
+
+  renderCanvas(): void {}
+
+  generateQuery(): PivotTableReduxQuery {
+    return generateQuery(this.selectedPivotsMap, this.selectedAggregations);
+  }
+
+  runQuery() {
+    try {
+      const query = this.generateQuery();
+      const lastPivotTableState = globals.state.pivotTableRedux;
+      globals.dispatch(Actions.setPivotStateReduxState({
+        pivotTableState: {
+          query,
+          queryId: lastPivotTableState.queryId + 1,
+          enabled: true,
+          queryResult: null
+        }
+      }));
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  renderTablePivotColumns(t: Table) {
+    return m(
+        'li',
+        t.name,
+        m('ul',
+          t.columns.map(
+              col =>
+                  m('li',
+                    m(ColumnSetCheckbox, {
+                      set: this.selectedPivotsMap,
+                      setKey: [t.name, col],
+                    }),
+                    col))));
+  }
+
+  renderResultsView() {
+    return m(
+        '.pivot-table-redux',
+        m('button.mode-button',
+          {
+            onclick: () => {
+              this.editMode = true;
+              globals.rafScheduler.scheduleFullRedraw();
+            }
+          },
+          'Edit'),
+        this.renderResultsTable());
+  }
+
+  renderSectionRow(
+      path: PathItem[], tree: PivotTree,
+      result: PivotTableReduxResult): m.Vnode {
+    const renderedCells = [];
+    for (let j = 0; j + 1 < path.length; j++) {
+      renderedCells.push(m('td', m('span.indent', ' '), `${path[j].nextKey}`));
+    }
+
+    const treeDepth = result.metadata.pivotColumns.length;
+    const colspan = treeDepth - path.length + 1;
+    const button =
+        m('button',
+          {
+            onclick: () => {
+              tree.isCollapsed = !tree.isCollapsed;
+              globals.rafScheduler.scheduleFullRedraw();
+            }
+          },
+          m('i.material-icons',
+            tree.isCollapsed ? 'expand_more' : 'expand_less'));
+
+    renderedCells.push(
+        m('td', {colspan}, button, `${path[path.length - 1].nextKey}`));
+
+    for (const value of tree.aggregates) {
+      renderedCells.push(m('td', `${value}`));
+    }
+
+    return m('tr', renderedCells);
+  }
+
+  renderTree(
+      path: PathItem[], tree: PivotTree, result: PivotTableReduxResult,
+      sink: m.Vnode[]) {
+    if (tree.isCollapsed) {
+      sink.push(this.renderSectionRow(path, tree, result));
+      return;
+    }
+    if (tree.children.size > 0) {
+      // Avoid rendering the intermediate results row for the root of tree
+      // and in case there's only one child subtree.
+      if (!tree.isCollapsed && path.length > 0 && tree.children.size !== 1) {
+        sink.push(this.renderSectionRow(path, tree, result));
+      }
+      for (const [key, childTree] of tree.children.entries()) {
+        path.push({tree: childTree, nextKey: key});
+        this.renderTree(path, childTree, result, sink);
+        path.pop();
+      }
+      return;
+    }
+
+    // Avoid rendering the intermediate results row if it has only one leaf
+    // row.
+    if (!tree.isCollapsed && tree.rows.length > 1) {
+      sink.push(this.renderSectionRow(path, tree, result));
+    }
+    for (const row of tree.rows) {
+      const renderedCells = [];
+      const treeDepth = result.metadata.pivotColumns.length;
+      for (let j = 0; j < treeDepth; j++) {
+        if (j < path.length) {
+          renderedCells.push(m('td', m('span.indent', ' '), `${row[j]}`));
+        } else {
+          renderedCells.push(m(`td`, `${row[j]}`));
+        }
+      }
+      for (let j = 0; j < result.metadata.aggregationColumns.length; j++) {
+        const value = row[aggregationIndex(treeDepth, j, treeDepth)];
+        renderedCells.push(m('td', `${value}`));
+      }
+
+      sink.push(m('tr', renderedCells));
+    }
+  }
+
+  renderResultsTable() {
+    const state = globals.state.pivotTableRedux;
+    if (state.query !== null || state.queryResult === null) {
+      return m('div', 'Loading...');
+    }
+
+    const renderedRows: m.Vnode[] = [];
+    this.renderTree(
+        [], state.queryResult.tree, state.queryResult, renderedRows);
+
+    const allColumns = state.queryResult.metadata.pivotColumns.concat(
+        state.queryResult.metadata.aggregationColumns);
+    return m(
+        'table.query-table.pivot-table',
+        m('thead', m('tr', allColumns.map(column => m('td', column)))),
+        m('tbody', renderedRows));
+  }
+
+  renderQuery(): m.Vnode {
+    try {
+      return m(
+          'div',
+          m('pre', this.generateQuery()),
+          m('button.mode-button',
+            {
+              onclick: () => {
+                this.editMode = false;
+                this.runQuery();
+                globals.rafScheduler.scheduleFullRedraw();
+              }
+            },
+            'Execute'));
+    } catch (e) {
+      if (e instanceof QueryGeneratorError) {
+        return m('div.query-error', e.message);
+      } else {
+        throw e;
+      }
+    }
+  }
+
+  view() {
+    return this.editMode ? this.renderEditView() : this.renderResultsView();
+  }
+
+  renderEditView() {
+    return m(
+        '.pivot-table-redux.edit',
+        m('div',
+          m('h2', 'Pivots'),
+          m('ul',
+            tables.map(
+                t => this.renderTablePivotColumns(t),
+                ))),
+        m('div',
+          m('h2', 'Aggregations'),
+          m('ul',
+            ...sliceAggregationColumns.map(
+                t =>
+                    m('li',
+                      m(ColumnSetCheckbox, {
+                        set: this.selectedAggregations,
+                        setKey: ['slice', t],
+                      }),
+                      t)),
+            ...threadSliceAggregationColumns.map(
+                t =>
+                    m('li',
+                      m(ColumnSetCheckbox, {
+                        set: this.selectedAggregations,
+                        setKey: ['thread_slice', t],
+                      }),
+                      `thread_slice.${t}`)))),
+        this.renderQuery());
+  }
+}
\ No newline at end of file
diff --git a/ui/src/frontend/pivot_table_redux_query_generator.ts b/ui/src/frontend/pivot_table_redux_query_generator.ts
new file mode 100644
index 0000000..9bd62fc
--- /dev/null
+++ b/ui/src/frontend/pivot_table_redux_query_generator.ts
@@ -0,0 +1,221 @@
+import {PivotTableReduxQuery} from '../common/state';
+
+export interface Table {
+  name: string;
+  columns: string[];
+}
+
+export const sliceTable = {
+  name: 'slice',
+  columns: ['type', 'ts', 'dur', 'category', 'name']
+};
+
+// Columns of `slice` table available for aggregation.
+export const sliceAggregationColumns = ['ts', 'dur', 'depth'];
+
+// Columns of `thread_slice` table available for aggregation.
+export const threadSliceAggregationColumns = [
+  'thread_ts',
+  'thread_dur',
+  'thread_instruction_count',
+  'thread_instruction_delta'
+];
+
+// List of available tables to query, used to populate selectors of pivot
+// columns in the UI.
+export const tables: Table[] = [
+  sliceTable,
+  {
+    name: 'process',
+    columns: [
+      'type',
+      'pid',
+      'name',
+      'parent_upid',
+      'uid',
+      'android_appid',
+      'cmdline'
+    ]
+  },
+  {name: 'thread', columns: ['type', 'name', 'tid', 'upid', 'is_main_thread']},
+  {name: 'thread_track', columns: ['type', 'name', 'utid']},
+];
+
+// Pair of table name and column name.
+export type TableColumn = [string, string];
+
+// ES6 Set does not allow to reasonably store compound objects; this class
+// rectifies the problem by providing a domain-specific set of pairs of strings.
+export class ColumnSet {
+  // Should've been Set<TableColumn>, but JavaScript Set does not support custom
+  // hashing/equality predicates, so TableColumn is keyed by a string generated
+  // by columnKey method.
+  backingMap = new Map<string, TableColumn>();
+
+  private static columnKey(column: TableColumn): string {
+    // None of table and column names used in Perfetto tables contain periods,
+    // so this function should not lead to collisions.
+    return `${column[0]}.${column[1]}`;
+  }
+
+  has(column: TableColumn): boolean {
+    return this.backingMap.has(ColumnSet.columnKey(column));
+  }
+
+  add(column: TableColumn) {
+    this.backingMap.set(ColumnSet.columnKey(column), column);
+  }
+
+  delete(column: TableColumn) {
+    this.backingMap.delete(ColumnSet.columnKey(column));
+  }
+
+  values(): Iterable<[string, string]> {
+    return this.backingMap.values();
+  }
+}
+
+// Exception thrown by query generator in case incoming parameters are not
+// suitable in order to build a correct query; these are caught by the UI and
+// displayed to the user.
+export class QueryGeneratorError extends Error {}
+
+// Internal column name for different rollover levels of aggregate columns.
+function aggregationAlias(
+    aggregationIndex: number, rolloverLevel: number): string {
+  return `agg_${aggregationIndex}_level_${rolloverLevel}`;
+}
+
+function generateInnerQuery(
+    pivots: string[],
+    aggregations: string[],
+    table: string,
+    includeTrack: boolean): string {
+  const pivotColumns = pivots.concat(includeTrack ? ['track_id'] : []);
+  const aggregationColumns: string[] = [];
+
+  for (let i = 0; i < aggregations.length; i++) {
+    const agg = aggregations[i];
+    aggregationColumns.push(`SUM(${agg}) as ${aggregationAlias(i, 0)}`);
+  }
+
+  return `
+    select
+      ${pivotColumns.concat(aggregationColumns).join(',\n')}
+    from ${table}
+    group by ${pivotColumns.join(', ')}
+  `;
+}
+
+function computeSliceTableAggregations(selectedAggregations: ColumnSet):
+    {tableName: string, flatAggregations: string[]} {
+  let hasThreadSliceColumn = false;
+  const allColumns = [];
+  for (const [table, column] of selectedAggregations.values()) {
+    if (table === 'thread_slice') {
+      hasThreadSliceColumn = true;
+    }
+    allColumns.push(column);
+  }
+
+  return {
+    // If any aggregation column from `thread_slice` is present, it's going to
+    // be the base table for the pivot table query. Otherwise, `slice` is used.
+    // This later is going to be controllable by a UI element.
+    tableName: hasThreadSliceColumn ? 'thread_slice' : 'slice',
+    flatAggregations: allColumns
+  };
+}
+
+// Every aggregation in the request is contained in the result in (number of
+// pivots + 1) times for each rollover level. This helper function returs an
+// index of the necessary column in the response.
+export function aggregationIndex(
+    pivotColumns: number, aggregationNo: number, depth: number) {
+  return pivotColumns + aggregationNo * (pivotColumns + 1) +
+      (pivotColumns - depth);
+}
+
+export function generateQuery(
+    selectedPivots: ColumnSet,
+    selectedAggregations: ColumnSet): PivotTableReduxQuery {
+  const sliceTableAggregations =
+      computeSliceTableAggregations(selectedAggregations);
+  const slicePivots: string[] = [];
+  const nonSlicePivots: string[] = [];
+
+  if (sliceTableAggregations.flatAggregations.length === 0) {
+    throw new QueryGeneratorError('No aggregations selected');
+  }
+
+  for (const [table, pivot] of selectedPivots.values()) {
+    if (table === 'slice' || table === 'thread_slice') {
+      slicePivots.push(pivot);
+    } else {
+      nonSlicePivots.push(`${table}.${pivot}`);
+    }
+  }
+
+  if (slicePivots.length === 0 && nonSlicePivots.length === 0) {
+    throw new QueryGeneratorError('No pivots selected');
+  }
+
+  const outerAggregations = [];
+  const prefixedSlicePivots = slicePivots.map(p => `preaggregated.${p}`);
+  const totalPivotsArray = nonSlicePivots.concat(prefixedSlicePivots);
+  for (let i = 0; i < sliceTableAggregations.flatAggregations.length; i++) {
+    const agg = `preaggregated.${aggregationAlias(i, 0)}`;
+    outerAggregations.push(`SUM(${agg}) as ${aggregationAlias(i, 0)}`);
+
+    for (let level = 1; level < totalPivotsArray.length; level++) {
+      // Peculiar form "SUM(SUM(agg)) over (partition by columns)" here means
+      // following: inner SUM(agg) is an aggregation that is going to collapse
+      // tracks with the same pivot values, which is going to be post-aggregated
+      // by the set of columns by outer **window** SUM function.
+
+      // Need to use complicated query syntax can be avoided by having yet
+      // another nested subquery computing only aggregation values with window
+      // functions in the wrapper, but the generation code is going to be more
+      // complex; so complexity of the query is traded for complexity of the
+      // query generator.
+      outerAggregations.push(`SUM(SUM(${agg})) over (partition by ${
+          totalPivotsArray.slice(0, totalPivotsArray.length - level)
+              .join(', ')}) as ${aggregationAlias(i, level)}`);
+    }
+
+    outerAggregations.push(`SUM(SUM(${agg})) over () as ${
+        aggregationAlias(i, totalPivotsArray.length)}`);
+  }
+
+  const joins = `
+    join thread_track on thread_track.id = preaggregated.track_id
+    join thread using (utid)
+    join process using (upid)
+  `;
+
+  const text = `
+    select
+      ${
+      nonSlicePivots.concat(prefixedSlicePivots, outerAggregations).join(',\n')}
+    from (
+      ${
+      generateInnerQuery(
+          slicePivots,
+          sliceTableAggregations.flatAggregations,
+          sliceTableAggregations.tableName,
+          nonSlicePivots.length > 0)}
+    ) preaggregated
+    ${nonSlicePivots.length > 0 ? joins : ''}
+    group by ${nonSlicePivots.concat(prefixedSlicePivots).join(', ')}
+  `;
+
+  return {
+    text,
+    metadata: {
+      pivotColumns: nonSlicePivots.concat(slicePivots.map(
+          column => `${sliceTableAggregations.tableName}.${column}`)),
+      aggregationColumns: sliceTableAggregations.flatAggregations.map(
+          agg => `SUM(${sliceTableAggregations.tableName}.${agg})`)
+    }
+  };
+}
diff --git a/ui/src/frontend/post_message_handler.ts b/ui/src/frontend/post_message_handler.ts
index 5309b6b..56269e3 100644
--- a/ui/src/frontend/post_message_handler.ts
+++ b/ui/src/frontend/post_message_handler.ts
@@ -30,6 +30,7 @@
     'https://chrometto.googleplex.com',
     'https://uma.googleplex.com',
   ];
+  if (origin === window.origin) return true;
   if (TRUSTED_ORIGINS.includes(origin)) return true;
   if (new URL(origin).hostname.endsWith('corp.google.com')) return true;
   return false;
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index 8e94fd5..fc2eb7f 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -56,7 +56,7 @@
     const sliceDur = fromNs(Math.max(row.dur as number, 1));
     const sliceEnd = sliceStart + sliceDur;
     const trackId = row.track_id as number;
-    const uiTrackId = globals.state.uiTrackIdByTraceTrackId.get(trackId);
+    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
     if (uiTrackId === undefined) return;
     verticalScrollToTrack(uiTrackId, true);
     horizontalScrollAndZoomToRange(sliceStart, sliceEnd);
@@ -135,29 +135,42 @@
       rows.push(m(QueryTableRow, {row: resp.rows[i], columns: resp.columns}));
     }
 
+    const headers = [
+      m(
+          'header.overview',
+          `Query result - ${Math.round(resp.durationMs)} ms`,
+          m('span.code', resp.query),
+          resp.error ? null :
+                       m('button.query-ctrl',
+                         {
+                           onclick: () => {
+                             queryResponseToClipboard(resp);
+                           },
+                         },
+                         'Copy as .tsv'),
+          m('button.query-ctrl',
+            {
+              onclick: () => {
+                globals.queryResults.delete(queryId);
+                globals.rafScheduler.scheduleFullRedraw();
+              }
+            },
+            'Close'),
+          ),
+    ];
+
+
+    if (resp.statementWithOutputCount > 1) {
+      headers.push(
+          m('header.overview',
+            `${resp.statementWithOutputCount} out of ${resp.statementCount} ` +
+                `statements returned a result. Only the results for the last ` +
+                `statement are displayed in the table below.`));
+    }
+
     return m(
         'div',
-        m(
-            'header.overview',
-            `Query result - ${Math.round(resp.durationMs)} ms`,
-            m('span.code', resp.query),
-            resp.error ? null :
-                         m('button.query-ctrl',
-                           {
-                             onclick: () => {
-                               queryResponseToClipboard(resp);
-                             },
-                           },
-                           'Copy as .tsv'),
-            m('button.query-ctrl',
-              {
-                onclick: () => {
-                  globals.queryResults.delete(queryId);
-                  globals.rafScheduler.scheduleFullRedraw();
-                }
-              },
-              'Close'),
-            ),
+        ...headers,
         // TODO(rsavitski): the x-scrollable works for the
         // dedicated query page, but is insufficient in the case of
         // the results being presented within the bottom details
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index c288b58..5a260c1 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -21,8 +21,8 @@
 
 /*
  * A broken down representation of a route.
- * For instance: #!/record/gpu?trace_id=a0b1c2
- * becomes: {page: '/record', subpage: '/gpu', args: {trace_id: 'a0b1c2'}}
+ * For instance: #!/record/gpu?local_cache_key=a0b1
+ * becomes: {page: '/record', subpage: '/gpu', args: {local_cache_key: 'a0b1'}}
  */
 export interface Route {
   page: string;
@@ -37,19 +37,19 @@
  * Args are !== the querystring (location.search) which is sent to the
  * server. The route args are NOT sent to the HTTP server.
  * Given this URL:
- * http://host/?foo=1&bar=2#!/page/subpage?trace_id=a0b1c2&baz=3.
+ * http://host/?foo=1&bar=2#!/page/subpage?local_cache_key=a0b1&baz=3.
  *
  * location.search = 'foo=1&bar=2'.
  *   This is seen by the HTTP server. We really don't use querystrings as the
  *   perfetto UI is client only.
  *
- * location.hash = '#!/page/subpage?trace_id=a0b1c2'.
+ * location.hash = '#!/page/subpage?local_cache_key=a0b1'.
  *   This is client-only. All the routing logic in the Perfetto UI uses only
  *   this.
  */
 export interface RouteArgs {
-  // The trace_id is special and is persisted across navigations.
-  trace_id?: string;
+  // The local_cache_key is special and is persisted across navigations.
+  local_cache_key?: string;
 
   // These are transient and are really set only on startup.
   openFromAndroidBugTool?: string;
@@ -71,18 +71,18 @@
  * 2) Handles the (optional) args, e.g. #!/page?arg=1&arg2=2.
  * Route args are carry information that is orthogonal to the page (e.g. the
  * trace id).
- * trace_id has some special treatment: once a URL has a trace_id argument,
+ * local_cache_key has some special treatment: once a URL has a local_cache_key,
  * it gets automatically appended to further navigations that don't have one.
- * For instance if the current url is #!/viewer?trace_id=1234 and a later
+ * For instance if the current url is #!/viewer?local_cache_key=1234 and a later
  * action (either user-initiated or code-initited) navigates to #!/info, the
  * rotuer will automatically replace the history entry with
- * #!/info?trace_id=1234.
+ * #!/info?local_cache_key=1234.
  * This is to keep propagating the trace id across page changes, for handling
  * tab discards (b/175041881).
  *
  * This class does NOT deal with the "load a trace when the url contains ?url=
- * or ?trace_id=". That logic lives in trace_url_handler.ts, which is triggered
- * by Router.onRouteChanged().
+ * or ?local_cache_key=". That logic lives in trace_url_handler.ts, which is
+ * triggered by Router.onRouteChanged().
  */
 export class Router {
   private readonly recentChanges: number[] = [];
@@ -104,20 +104,21 @@
     const oldRoute = Router.parseUrl(e.oldURL);
     const newRoute = Router.parseUrl(e.newURL);
 
-    if (newRoute.args.trace_id === undefined && oldRoute.args.trace_id) {
-      // Propagate the trace_id across navigations. When a trace is loaded, the
-      // URL becomes #!/viewer?trace_id=a0b1c2. The ?trace_id arg allows
+    if (newRoute.args.local_cache_key === undefined &&
+        oldRoute.args.local_cache_key) {
+      // Propagate `local_cache_key across` navigations. When a trace is loaded,
+      // the URL becomes #!/viewer?local_cache_key=123. `local_cache_key` allows
       // reopening the trace from cache in the case of a reload or discard.
       // When using the UI we can hit "bare" links (e.g. just '#!/info') which
       // don't have the trace_uuid:
       // - When clicking on an <a> element from the sidebar.
       // - When the code calls Router.navigate().
       // - When the user pastes a URL from docs page.
-      // In all these cases we want to keep propagating the trace_id argument.
-      // We do so by re-setting the trace_id argument and doing a
+      // In all these cases we want to keep propagating the `local_cache_key`.
+      // We do so by re-setting the `local_cache_key` and doing a
       // location.replace which overwrites the history entry (note
       // location.replace is NOT just a String.replace operation).
-      newRoute.args.trace_id = oldRoute.args.trace_id;
+      newRoute.args.local_cache_key = oldRoute.args.local_cache_key;
     }
 
     const args = m.buildQueryString(newRoute.args);
@@ -153,9 +154,9 @@
   /*
    * Breaks down a fragment into a Route object.
    * Sample input:
-   * '#!/record/gpu?trace_id=629329-18bba4'
+   * '#!/record/gpu?local_cache_key=abcd-1234'
    * Sample output:
-   * {page: '/record', subpage: '/gpu', args: {trace_id: '629329-18bba4'}}
+   * {page: '/record', subpage: '/gpu', args: {local_cache_key: 'abcd-1234'}}
    */
   static parseFragment(hash: string): Route {
     const prefixLength = ROUTE_PREFIX.length;
@@ -176,6 +177,17 @@
     const argsStr = argsStart < 0 ? '' : hash.substr(argsStart + 1);
     const args = argsStr ? m.parseQueryString(hash.substr(argsStart)) : {};
 
+    // TODO(primiano): remove this in mid-2022. trace_id is the same concept of
+    // local_cache_key. Just at some point we renamed it to make it more obvious
+    // to people that those URLs cannot be copy-pasted in bugs. For now this
+    // handles cases of reloading pages from old version.
+    if ('trace_id' in args) {
+      if (!('local_cache_key' in args)) {
+        args['local_cache_key'] = args['trace_id'];
+      }
+      delete args['trace_id'];
+    }
+
     return {page, subpage, args};
   }
 
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 0ada41c..653ee61 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -100,7 +100,6 @@
 with first as (select started as ts from sqlstats limit 1)
 select query,
     round((max(ended - started, 0))/1e6) as runtime_ms,
-    round((max(started - queued, 0))/1e6) as latency_ms,
     round((started - first.ts)/1e6) as t_start_ms
 from sqlstats, first
 order by started desc`;
diff --git a/ui/src/frontend/time_scale.ts b/ui/src/frontend/time_scale.ts
index c5e92a7..cdf3ca2 100644
--- a/ui/src/frontend/time_scale.ts
+++ b/ui/src/frontend/time_scale.ts
@@ -15,7 +15,7 @@
 import {assertFalse, assertTrue} from '../base/logging';
 import {TimeSpan} from '../common/time';
 
-const MAX_ZOOM_SPAN_SEC = 1e-6;  // 1us.
+const MAX_ZOOM_SPAN_SEC = 1e-8;  // 10 ns.
 
 /**
  * Defines a mapping between number and seconds for the entire application.
diff --git a/ui/src/frontend/time_scale_unittest.ts b/ui/src/frontend/time_scale_unittest.ts
index c6b3822..7a1be03 100644
--- a/ui/src/frontend/time_scale_unittest.ts
+++ b/ui/src/frontend/time_scale_unittest.ts
@@ -73,5 +73,5 @@
   const scale = new TimeScale(span, [200, 300]);
   const newSpan = computeZoom(scale, span, 0.0000000001, 225);
   expect((newSpan.end - newSpan.start) / 2 + newSpan.start).toBeCloseTo(1010);
-  expect(newSpan.end - newSpan.start).toBeCloseTo(1e-6, 8);
+  expect(newSpan.end - newSpan.start).toBeCloseTo(1e-8);
 });
diff --git a/ui/src/frontend/trace_url_handler.ts b/ui/src/frontend/trace_url_handler.ts
index fcd464d..690d743 100644
--- a/ui/src/frontend/trace_url_handler.ts
+++ b/ui/src/frontend/trace_url_handler.ts
@@ -53,9 +53,9 @@
     return;
   }
 
-  if (route.args.trace_id) {
+  if (route.args.local_cache_key) {
     // Handles the case of loading traces from the cache storage.
-    maybeOpenCachedTrace(route.args.trace_id);
+    maybeOpenCachedTrace(route.args.local_cache_key);
     return;
   }
 }
@@ -68,29 +68,29 @@
  * It must take decision based on the app state, not on URL change events.
  * Fragment changes are handled by the union of Router.onHashChange() and this
  * function, as follows:
- * 1. '' -> URL without a ?trace_id=xxx arg:
+ * 1. '' -> URL without a ?local_cache_key=xxx arg:
  *  - no effect (except redrawing)
- * 2. URL without trace_id -> URL with trace_id:
+ * 2. URL without local_cache_key -> URL with local_cache_key:
  *  - Load cached trace (without prompting any dialog).
  *  - Show a (graceful) error dialog in the case of cache misses.
- * 3. '' -> URL with a ?trace_id=xxx arg:
+ * 3. '' -> URL with a ?local_cache_key=xxx arg:
  *  - Same as case 2.
- * 4. URL with trace_id=1 -> URL with trace_id=2:
+ * 4. URL with local_cache_key=1 -> URL with local_cache_key=2:
  *  a) If 2 != uuid of the trace currently loaded (globals.state.traceUuid):
  *  - Ask the user if they intend to switch trace and load 2.
  *  b) If 2 == uuid of current trace (e.g., after a new trace has loaded):
  *  - no effect (except redrawing).
- * 5. URL with trace_id -> URL without trace_id:
- *  - Redirect to ?trace_id=1234 where 1234 is the UUID of the previous URL
- *    (this might or might not match globals.state.traceUuid).
+ * 5. URL with local_cache_key -> URL without local_cache_key:
+ *  - Redirect to ?local_cache_key=1234 where 1234 is the UUID of the previous
+ *    URL (this might or might not match globals.state.traceUuid).
  *
  * Backward navigation cases:
- * 6. URL without trace_id <- URL with trace_id:
+ * 6. URL without local_cache_key <- URL with local_cache_key:
  *  - Same as case 5.
- * 7. URL with trace_id=1 <- URL with trace_id=2:
- *  - Same as case 4a: go back to trace_id=1 but ask the user for confirmation.
- * 8. landing page <- URL with trace_id:
- *  - Same as case 5: re-append the trace_id.
+ * 7. URL with local_cache_key=1 <- URL with local_cache_key=2:
+ *  - Same as case 4a: go back to local_cache_key=1 but ask the user to confirm.
+ * 8. landing page <- URL with local_cache_key:
+ *  - Same as case 5: re-append the local_cache_key.
  */
 async function maybeOpenCachedTrace(traceUuid: string) {
   if (traceUuid === globals.state.traceUuid) {
@@ -101,12 +101,12 @@
   if (traceUuid === '') {
     // This can happen if we switch from an empty UI state to an invalid UUID
     // (e.g. due to a cache miss, below). This can also happen if the user just
-    // types /#!/viewer?trace_id=.
+    // types /#!/viewer?local_cache_key=.
     return;
   }
 
   // This handles the case when a trace T1 is loaded and then the url is set to
-  // ?trace_id=T2. In that case the globals.state.traceUuid remains set to T1
+  // ?local_cache_key=T2. In that case globals.state.traceUuid remains set to T1
   // until T2 has been loaded by the trace processor (can take several seconds).
   // This early out prevents to re-trigger the openTraceFromXXX() action if the
   // URL changes (e.g. if the user navigates back/fwd) while the new trace is
@@ -122,7 +122,8 @@
   const maybeTrace = await tryGetTrace(traceUuid);
 
   const navigateToOldTraceUuid = () => {
-    Router.navigate(`#!/viewer?trace_id=${globals.state.traceUuid || ''}`);
+    Router.navigate(
+        `#!/viewer?local_cache_key=${globals.state.traceUuid || ''}`);
   };
 
   if (!maybeTrace) {
@@ -131,8 +132,8 @@
       content: m(
           'div',
           m('p',
-            'You are trying to load a cached trace by setting the ?trace_id ' +
-                'argument in the URL.'),
+            'You are trying to load a cached trace by setting the ' +
+                '?local_cache_key argument in the URL.'),
           m('p', 'Unfortunately the trace wasn\'t in the cache storage.'),
           m('p',
             'This can happen if a tab was discarded and wasn\'t opened ' +
@@ -147,7 +148,7 @@
 
   // If the UI is in a blank state (no trace has been ever opened), just load
   // the trace without showing any further dialog. This is the case of tab
-  // discarding, reloading or pasting a url with a trace_id in an empty
+  // discarding, reloading or pasting a url with a local_cache_key in an empty
   // instance.
   if (globals.state.traceUuid === undefined) {
     globals.dispatch(Actions.openTraceFromBuffer(maybeTrace));
@@ -165,7 +166,7 @@
         'div',
         m('p',
           'You are seeing this because you either pasted a URL with ' +
-              'a different ?trace_id=xxx argument or because you hit ' +
+              'a different ?local_cache_key=xxx argument or because you hit ' +
               'the history back/fwd button and reached a different trace.'),
         m('p',
           'If you continue another trace will be loaded and the UI ' +
diff --git a/ui/src/test/ui_integrationtest.ts b/ui/src/test/ui_integrationtest.ts
index 41c5107..f6a442f 100644
--- a/ui/src/test/ui_integrationtest.ts
+++ b/ui/src/test/ui_integrationtest.ts
@@ -172,7 +172,7 @@
     test('access_subpage_then_go_back', async () => {
       await waitForPerfettoIdle(page);
       await page.goto(
-          'http://localhost:10000/?testing=1/#!/metrics?trace_id=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
+          'http://localhost:10000/?testing=1/#!/metrics?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
       await page.goBack();
       await waitForPerfettoIdle(page);
     });
@@ -193,7 +193,7 @@
 
     test('open_trace ', async () => {
       await page.goto(
-          'http://localhost:10000/?testing=1#!/viewer?trace_id=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
+          'http://localhost:10000/?testing=1#!/viewer?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
       await waitForPerfettoIdle(page);
     });
 
@@ -204,7 +204,7 @@
 
     test('open_second_trace', async () => {
       await page.goto(
-          'http://localhost:10000/?testing=1#!/viewer?trace_id=00000000-0000-0000-e13c-bd7db4ff646f');
+          'http://localhost:10000/?testing=1#!/viewer?local_cache_key=00000000-0000-0000-e13c-bd7db4ff646f');
       await waitForPerfettoIdle(page);
 
       // click on the 'Continue' button in the interstitial
@@ -222,7 +222,7 @@
 
     test('open_invalid_trace', async () => {
       await page.goto(
-          'http://localhost:10000/?testing=1#!/viewer?trace_id=invalid');
+          'http://localhost:10000/?testing=1#!/viewer?local_cache_key=invalid');
       await waitForPerfettoIdle(page);
     });
   });
@@ -262,7 +262,7 @@
     const page = await getPage();
     await page.goto('http://localhost:10000/?testing=1');
     await page.goto(
-        'http://localhost:10000/?testing=1#!/viewer?trace_id=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
+        'http://localhost:10000/?testing=1#!/viewer?local_cache_key=76c25a80-25dd-1eb7-2246-d7b3c7a10f91');
     await waitForPerfettoIdle(page);
     await page.goBack();
     await waitForPerfettoIdle(page);
@@ -272,7 +272,7 @@
     const page = await getPage();
     await page.goto('about:blank');
     await page.goto(
-        'http://localhost:10000/?testing=1#!/viewer?trace_id=invalid');
+        'http://localhost:10000/?testing=1#!/viewer?local_cache_key=invalid');
     await waitForPerfettoIdle(page);
   });
 });
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index f0b4a63..5c2cc06 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -21,6 +21,8 @@
 
 import {Config, Data, SLICE_TRACK_KIND} from './common';
 
+// the lowest bucketNs gets is 2, but add some room in case of fp error
+const MIN_QUANT_NS = 3;
 
 class ChromeSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = SLICE_TRACK_KIND;
@@ -53,9 +55,16 @@
       this.maxDurNs = queryRes.firstRow({maxDur: NUM_NULL}).maxDur || 0;
     }
 
+    // Buckets are always even and positive, don't quantize once we zoom to
+    // nanosecond-scale, so that we can see exact sizes.
+    let tsq = `ts`;
+    if (bucketNs > MIN_QUANT_NS) {
+      tsq = `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+    }
+
     const query = `
       SELECT
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        ${tsq} as tsq,
         ts,
         max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
         depth,
@@ -115,15 +124,19 @@
       const durNs = it.dur;
       const endNs = startNs + durNs;
 
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
+      let endNsQ = endNs;
+      if (bucketNs > MIN_QUANT_NS) {
+        endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
+        endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
+      }
 
-      if (!it.isInstant && startNsQ === endNsQ) {
-        throw new Error(
-            'Expected startNsQ and endNsQ to differ (' +
-            `startNsQ: ${startNsQ}, startNs: ${startNs},` +
-            ` endNsQ: ${endNsQ}, durNs: ${durNs},` +
-            ` endNs: ${endNs}, bucketNs: ${bucketNs})`);
+      let isInstant = it.isInstant;
+      // Floating point rounding with large numbers of nanoseconds can mean
+      // there isn't enough precision to distinguish the start and end of a very
+      // short event so we just display the event as an instant when zoomed in
+      // rather than fail completely if the start and end time are the same.
+      if (startNsQ === endNsQ) {
+        isInstant = 1;
       }
 
       slices.starts[row] = fromNs(startNsQ);
@@ -131,11 +144,11 @@
       slices.depths[row] = it.depth;
       slices.sliceIds[row] = it.sliceId;
       slices.titles[row] = internString(it.name);
-      slices.isInstant[row] = it.isInstant;
+      slices.isInstant[row] = isInstant;
       slices.isIncomplete[row] = it.isIncomplete;
 
       let cpuTimeRatio = 1;
-      if (!it.isInstant && !it.isIncomplete) {
+      if (!isInstant && !it.isIncomplete) {
         // Rounding the CPU time ratio to two decimal places and ensuring
         // it is less than or equal to one, incase the thread duration exceeds
         // the total duration.