Merge "Trace Redaction - Convert task rename from primitive to filter" into main
diff --git a/Android.bp b/Android.bp
index dcc140c..65a914b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2377,7 +2377,6 @@
         ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
         ":perfetto_src_trace_processor_sorter_sorter",
         ":perfetto_src_trace_processor_sqlite_bindings_bindings",
-        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
@@ -12365,6 +12364,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/per_frame_metrics.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/frames/timeline.sql",
@@ -12376,6 +12376,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/services.sql",
@@ -12399,6 +12400,8 @@
         "src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql",
         "src/trace_processor/perfetto_sql/stdlib/counters/intervals.sql",
         "src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql",
         "src/trace_processor/perfetto_sql/stdlib/cpu/size.sql",
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/args.sql",
         "src/trace_processor/perfetto_sql/stdlib/deprecated/v42/common/counters.sql",
@@ -12437,6 +12440,10 @@
         "src/trace_processor/perfetto_sql/stdlib/stack_trace/jit.sql",
         "src/trace_processor/perfetto_sql/stdlib/time/conversion.sql",
         "src/trace_processor/perfetto_sql/stdlib/v8/jit.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
     ],
     cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
     out: [
@@ -12503,14 +12510,6 @@
     name: "perfetto_src_trace_processor_sqlite_bindings_bindings",
 }
 
-// GN: //src/trace_processor/sqlite:query_constraints
-filegroup {
-    name: "perfetto_src_trace_processor_sqlite_query_constraints",
-    srcs: [
-        "src/trace_processor/sqlite/query_constraints.cc",
-    ],
-}
-
 // GN: //src/trace_processor/sqlite:sqlite
 filegroup {
     name: "perfetto_src_trace_processor_sqlite_sqlite",
@@ -12519,7 +12518,6 @@
         "src/trace_processor/sqlite/sql_source.cc",
         "src/trace_processor/sqlite/sql_stats_table.cc",
         "src/trace_processor/sqlite/sqlite_engine.cc",
-        "src/trace_processor/sqlite/sqlite_table.cc",
         "src/trace_processor/sqlite/sqlite_tokenizer.cc",
         "src/trace_processor/sqlite/sqlite_utils.cc",
         "src/trace_processor/sqlite/stats_table.cc",
@@ -12531,7 +12529,6 @@
     name: "perfetto_src_trace_processor_sqlite_unittests",
     srcs: [
         "src/trace_processor/sqlite/db_sqlite_table_unittest.cc",
-        "src/trace_processor/sqlite/query_constraints_unittest.cc",
         "src/trace_processor/sqlite/sql_source_unittest.cc",
         "src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc",
         "src/trace_processor/sqlite/sqlite_utils_unittest.cc",
@@ -12851,6 +12848,7 @@
     srcs: [
         "src/trace_redaction/build_timeline.cc",
         "src/trace_redaction/filter_ftrace_using_allowlist.cc",
+        "src/trace_redaction/filter_print_events.cc",
         "src/trace_redaction/filter_sched_waking_events.cc",
         "src/trace_redaction/filter_task_rename.cc",
         "src/trace_redaction/find_package_uid.cc",
@@ -12861,6 +12859,7 @@
         "src/trace_redaction/prune_package_list.cc",
         "src/trace_redaction/redact_sched_switch.cc",
         "src/trace_redaction/scrub_ftrace_events.cc",
+        "src/trace_redaction/scrub_process_stats.cc",
         "src/trace_redaction/scrub_process_trees.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
@@ -14351,7 +14350,6 @@
         ":perfetto_src_trace_processor_sorter_sorter",
         ":perfetto_src_trace_processor_sorter_unittests",
         ":perfetto_src_trace_processor_sqlite_bindings_bindings",
-        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
         ":perfetto_src_trace_processor_sqlite_unittests",
         ":perfetto_src_trace_processor_storage_minimal",
@@ -15067,7 +15065,6 @@
         ":perfetto_src_trace_processor_rpc_stdiod",
         ":perfetto_src_trace_processor_sorter_sorter",
         ":perfetto_src_trace_processor_sqlite_bindings_bindings",
-        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
@@ -15437,7 +15434,6 @@
         ":perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_table_functions",
         ":perfetto_src_trace_processor_sorter_sorter",
         ":perfetto_src_trace_processor_sqlite_bindings_bindings",
-        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
diff --git a/BUILD b/BUILD
index 4b1e498..032036c 100644
--- a/BUILD
+++ b/BUILD
@@ -261,7 +261,6 @@
         ":src_trace_processor_rpc_rpc",
         ":src_trace_processor_sorter_sorter",
         ":src_trace_processor_sqlite_bindings_bindings",
-        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
@@ -2415,6 +2414,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/binder.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/device.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/freezer.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/garbage_collection.sql",
@@ -2424,6 +2424,7 @@
         "src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/network_packets.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/services.sql",
@@ -2468,6 +2469,8 @@
     name = "src_trace_processor_perfetto_sql_stdlib_cpu_cpu",
     srcs = [
         "src/trace_processor/perfetto_sql/stdlib/cpu/cpus.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql",
         "src/trace_processor/perfetto_sql/stdlib/cpu/size.sql",
     ],
 )
@@ -2600,6 +2603,17 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/wattson:wattson
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql",
+        "src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib:stdlib
 perfetto_cc_amalgamated_sql(
     name = "src_trace_processor_perfetto_sql_stdlib_stdlib",
@@ -2624,6 +2638,7 @@
         ":src_trace_processor_perfetto_sql_stdlib_stack_trace_stack_trace",
         ":src_trace_processor_perfetto_sql_stdlib_time_time",
         ":src_trace_processor_perfetto_sql_stdlib_v8_v8",
+        ":src_trace_processor_perfetto_sql_stdlib_wattson_wattson",
     ],
     outs = [
         "src/trace_processor/perfetto_sql/stdlib/stdlib.h",
@@ -2681,15 +2696,6 @@
     ],
 )
 
-# GN target: //src/trace_processor/sqlite:query_constraints
-perfetto_filegroup(
-    name = "src_trace_processor_sqlite_query_constraints",
-    srcs = [
-        "src/trace_processor/sqlite/query_constraints.cc",
-        "src/trace_processor/sqlite/query_constraints.h",
-    ],
-)
-
 # GN target: //src/trace_processor/sqlite:sqlite
 perfetto_filegroup(
     name = "src_trace_processor_sqlite_sqlite",
@@ -2697,7 +2703,6 @@
         "src/trace_processor/sqlite/db_sqlite_table.cc",
         "src/trace_processor/sqlite/db_sqlite_table.h",
         "src/trace_processor/sqlite/module_lifecycle_manager.h",
-        "src/trace_processor/sqlite/query_cache.h",
         "src/trace_processor/sqlite/scoped_db.h",
         "src/trace_processor/sqlite/sql_source.cc",
         "src/trace_processor/sqlite/sql_source.h",
@@ -2705,8 +2710,6 @@
         "src/trace_processor/sqlite/sql_stats_table.h",
         "src/trace_processor/sqlite/sqlite_engine.cc",
         "src/trace_processor/sqlite/sqlite_engine.h",
-        "src/trace_processor/sqlite/sqlite_table.cc",
-        "src/trace_processor/sqlite/sqlite_table.h",
         "src/trace_processor/sqlite/sqlite_tokenizer.cc",
         "src/trace_processor/sqlite/sqlite_tokenizer.h",
         "src/trace_processor/sqlite/sqlite_utils.cc",
@@ -5734,7 +5737,6 @@
         ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
         ":src_trace_processor_sorter_sorter",
         ":src_trace_processor_sqlite_bindings_bindings",
-        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
@@ -5907,7 +5909,6 @@
         ":src_trace_processor_rpc_stdiod",
         ":src_trace_processor_sorter_sorter",
         ":src_trace_processor_sqlite_bindings_bindings",
-        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
@@ -6132,7 +6133,6 @@
         ":src_trace_processor_perfetto_sql_intrinsics_table_functions_tables",
         ":src_trace_processor_sorter_sorter",
         ":src_trace_processor_sqlite_bindings_bindings",
-        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
diff --git a/CHANGELOG b/CHANGELOG
index abdf0f9..788d61f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,16 @@
   Trace Processor:
     *
   UI:
+    *
+  SDK:
+    *
+
+
+v44.0 - 2024-04-10:
+  Trace Processor:
+    * New modules added to standard library: `frames.timeline`,
+      `frame.per_frame_metrics`, `sched.time_in_state`.
+  UI:
     * Per-cpu scheduling tracks now distinguish real-time priority threads with
       a hatched pattern and name prefix. Based on priority during switch-in.
     * Added ftrace event cropping for traces recorded by perfetto v44+.
@@ -11,6 +21,19 @@
       per-cpu data streams. This should significantly improve the presentation
       of RING_BUFFER traces, removing artifacts such as never-ending slices
       starting at the beginning of the trace.
+    * Significantly improved trace load times.
+    * Improved counter track view modes, including log scale, expanded view, and
+      the ability for plugin authors to link scales of several counter tracks
+      together.
+    * Add dominated size and objects to Java heap graph view.
+      Added hotkey Q to open and close bottom drawer.
+    * Fixed bug where timeline header and tracks could become horizontally
+      misaligned when using browser zoom.
+    * Fixed crash when hot-reloading Sass during development.
+    * Fixed bug where crashed debug tracks could not be closed.
+    * Fixed missing flame graph details for area selections.
+    * Consistent reporting of durations for incomplete slices.
+    * Switch to using prettier for formatting TS & Sass instead of ESLint.
   SDK:
     * "track_event" categories are disabled by default in the C API, if they
       don't match anything in the data source config. This behavior differs from
diff --git a/docs/case-studies/memory.md b/docs/case-studies/memory.md
index b55bdce..93f67a2 100644
--- a/docs/case-studies/memory.md
+++ b/docs/case-studies/memory.md
@@ -325,7 +325,7 @@
 
 Use the `tools/heap_profile` script to profile a process. If you are having
 trouble make sure you are using the [latest version](
-https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile).
+https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile).
 See all the arguments using `tools/heap_profile -h`, or use the defaults
 and just profile a process (e.g. `system_server`):
 
@@ -390,7 +390,7 @@
 We can get a snapshot of the graph of all the Java objects that constitute the
 Java heap. We use the `tools/java_heap_dump` script. If you are having trouble
 make sure you are using the [latest version](
-https://raw.githubusercontent.com/google/perfetto/master/tools/java_heap_dump).
+https://raw.githubusercontent.com/google/perfetto/main/tools/java_heap_dump).
 
 ```bash
 $ tools/java_heap_dump -n com.android.systemui
diff --git a/docs/faq.md b/docs/faq.md
index 89491ec..02266ec 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -8,7 +8,7 @@
 This can be used as follows:
 
 ```sh
-curl -OL https://github.com/google/perfetto/raw/master/tools/open_trace_in_ui
+curl -OL https://github.com/google/perfetto/raw/main/tools/open_trace_in_ui
 chmod +x open_trace_in_ui
 ./open_trace_in_ui -i /path/to/trace
 ```
diff --git a/docs/instrumentation/heapprofd-api.md b/docs/instrumentation/heapprofd-api.md
index 0ef3f90..279f3d5 100644
--- a/docs/instrumentation/heapprofd-api.md
+++ b/docs/instrumentation/heapprofd-api.md
@@ -100,11 +100,11 @@
 ## Profile your App
 
 Then, use the [heap_profile](
-https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile)
+https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile)
 script to get a profile to generate textpb of the config.
 To convert to a binary proto, you additionally need to download
 [`perfetto_trace.proto`](
-https://raw.githubusercontent.com/google/perfetto/master/protos/perfetto/trace/perfetto_trace.proto)
+https://raw.githubusercontent.com/google/perfetto/main/protos/perfetto/trace/perfetto_trace.proto)
 and have recent version of the protoc compiler installed.
 [Learn how to install protoc](https://grpc.io/docs/protoc-installation).
 
diff --git a/docs/quickstart/android-tracing.md b/docs/quickstart/android-tracing.md
index 3a0d61a..6e8e9cb 100644
--- a/docs/quickstart/android-tracing.md
+++ b/docs/quickstart/android-tracing.md
@@ -80,7 +80,7 @@
 On Linux and Mac:
 
 ```bash
-curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
+curl -O https://raw.githubusercontent.com/google/perfetto/main/tools/record_android_trace
 chmod u+x record_android_trace
 
 # See ./record_android_trace --help for more
@@ -91,7 +91,7 @@
 On Windows:
 
 ```bash
-curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
+curl -O https://raw.githubusercontent.com/google/perfetto/main/tools/record_android_trace
 python3 record_android_trace -o trace_file.perfetto-trace -t 30s -b 64mb \
 sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
 ```
diff --git a/docs/quickstart/callstack-sampling.md b/docs/quickstart/callstack-sampling.md
index 3741c1a..30fd74a 100644
--- a/docs/quickstart/callstack-sampling.md
+++ b/docs/quickstart/callstack-sampling.md
@@ -29,7 +29,7 @@
 Download `cpu_profile` (if you don't have a Perfetto checkout):
 
 ```bash
-curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/cpu_profile
+curl -LO https://raw.githubusercontent.com/google/perfetto/main/tools/cpu_profile
 chmod +x cpu_profile
 ```
 
diff --git a/docs/quickstart/heap-profiling.md b/docs/quickstart/heap-profiling.md
index 3a58f20..114f5e6 100644
--- a/docs/quickstart/heap-profiling.md
+++ b/docs/quickstart/heap-profiling.md
@@ -29,7 +29,7 @@
 Download the `tools/heap_profile` (if you don't have a perfetto checkout):
 
 ```bash
-curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile
+curl -LO https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile
 chmod +x heap_profile
 ```
 
@@ -56,7 +56,7 @@
 ```
 
 Download the
-[heap_profile](https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile)
+[heap_profile](https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile)
 script. Then start the profile:
 
 ```bash
diff --git a/gn/write_buildflag_header.py b/gn/write_buildflag_header.py
index 30751a5..3f11a2f 100644
--- a/gn/write_buildflag_header.py
+++ b/gn/write_buildflag_header.py
@@ -82,7 +82,7 @@
   lines.append('#endif  // %s' % guard)
   lines.append('')
 
-  with open(args.out, 'w') as out:
+  with open(args.out, 'w', newline='\n') as out:
     out.write(COPYRIGHT_HEADER)
     out.write('\n'.join(lines))
 
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index f50a7c8..e46d1a7 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -140,9 +140,6 @@
   virtual std::vector<uint8_t> GetMetricDescriptors() = 0;
 };
 
-// When set, logs SQLite actions on the console.
-void PERFETTO_EXPORT_COMPONENT EnableSQLiteVtableDebugging();
-
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/protos/perfetto/trace/android/surfaceflinger_layers.proto b/protos/perfetto/trace/android/surfaceflinger_layers.proto
index d85dc7f..6640fa7 100644
--- a/protos/perfetto/trace/android/surfaceflinger_layers.proto
+++ b/protos/perfetto/trace/android/surfaceflinger_layers.proto
@@ -51,12 +51,15 @@
   // elapsed realtime in nanos since boot of when this entry was logged
   optional sfixed64 elapsed_realtime_nanos = 1;
 
-  // where the trace originated
+  // SurfaceFlinger's stage where the snapshot was triggered.
+  // Currently either "visibleRegionsDirty" or "bufferLatched".
   optional string where = 2;
 
   optional LayersProto layers = 3;
 
   // Blob for the current HWC information for all layers, reported by dumpsys.
+  // Example:
+  //   "maxDownScale: 4, maxFullWidth: 8192, HWState: 1, AssignedState: 3, ..."
   optional string hwc_blob = 4;
 
   // Excludes state sent during composition like visible region and composition
@@ -78,6 +81,7 @@
 
 message DisplayProto {
   optional uint64 id = 1;
+  // Display descriptor, e.g. "Built-In Screen"
   optional string name = 2;
   optional uint32 layer_stack = 3;
   optional SizeProto size = 4;
@@ -112,12 +116,14 @@
   // unique id per layer.
   optional int32 id = 1;
   // unique name per layer.
+  // Example: "Wallpaper".
   optional string name = 2;
   // list of children this layer may have. May be empty.
   repeated int32 children = 3 [packed = true];
   // list of layers that are z order relative to this layer.
   repeated int32 relatives = 4 [packed = true];
-  // The type of layer, ex Color, Layer
+  // The type of layer.
+  // Examples: "ContainerLayer", "BufferStateLayer".
   optional string type = 5;
   optional RegionProto transparent_region = 6;
   optional RegionProto visible_region = 7;
@@ -138,7 +144,14 @@
   optional RectProto final_crop = 15 [deprecated = true];
   optional bool is_opaque = 16;
   optional bool invalidate = 17;
+  // Composition states's dataspace.
+  // Examples: "STANDARD_BT709", "STANDARD_BT601_625".
+  // See full enum in
+  // frameworks/native/libs/nativewindow/include/android/data_space.h
   optional string dataspace = 18;
+  // Buffer's pixel format
+  // Examples: "PIXEL_FORMAT_TRANSLUCENT", "PIXEL_FORMAT_RGBA_8888".
+  // See full enum in frameworks/native/libs/ui/include/ui/PixelFormat.h
   optional string pixel_format = 19;
   // The layer's actual color.
   optional ColorProto color = 20;
diff --git a/protos/perfetto/trace/ftrace/f2fs.proto b/protos/perfetto/trace/ftrace/f2fs.proto
index 0e59384..eb8be75 100644
--- a/protos/perfetto/trace/ftrace/f2fs.proto
+++ b/protos/perfetto/trace/ftrace/f2fs.proto
@@ -288,3 +288,37 @@
   optional uint32 n_wr_s_cnt = 27;
   optional uint32 n_wr_s_peak = 28;
 }
+message F2fsBackgroundGcFtraceEvent {
+  optional uint64 dev = 1;
+  optional uint32 wait_ms = 2;
+  optional uint32 prefree = 3;
+  optional uint32 free = 4;
+}
+message F2fsGcBeginFtraceEvent {
+  optional uint64 dev = 1;
+  optional uint32 sync = 2;
+  optional uint32 background = 3;
+  optional int64 dirty_nodes = 4;
+  optional int64 dirty_dents = 5;
+  optional int64 dirty_imeta = 6;
+  optional uint32 free_sec = 7;
+  optional uint32 free_seg = 8;
+  optional int32 reserved_seg = 9;
+  optional uint32 prefree_seg = 10;
+  optional int32 gc_type = 11;
+  optional uint32 no_bg_gc = 12;
+  optional uint32 nr_free_secs = 13;
+}
+message F2fsGcEndFtraceEvent {
+  optional uint64 dev = 1;
+  optional int32 ret = 2;
+  optional int32 seg_freed = 3;
+  optional int32 sec_freed = 4;
+  optional int64 dirty_nodes = 5;
+  optional int64 dirty_dents = 6;
+  optional int64 dirty_imeta = 7;
+  optional uint32 free_sec = 8;
+  optional uint32 free_seg = 9;
+  optional int32 reserved_seg = 10;
+  optional uint32 prefree_seg = 11;
+}
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index e0b1eeb..5d5bf06 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -611,5 +611,8 @@
     DpuDsiCmdFifoStatusFtraceEvent dpu_dsi_cmd_fifo_status = 492;
     DpuDsiRxFtraceEvent dpu_dsi_rx = 493;
     DpuDsiTxFtraceEvent dpu_dsi_tx = 494;
+    F2fsBackgroundGcFtraceEvent f2fs_background_gc = 495;
+    F2fsGcBeginFtraceEvent f2fs_gc_begin = 496;
+    F2fsGcEndFtraceEvent f2fs_gc_end = 497;
   }
 }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index d5cf2d8..c8b4c9e 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -5253,12 +5253,15 @@
   // elapsed realtime in nanos since boot of when this entry was logged
   optional sfixed64 elapsed_realtime_nanos = 1;
 
-  // where the trace originated
+  // SurfaceFlinger's stage where the snapshot was triggered.
+  // Currently either "visibleRegionsDirty" or "bufferLatched".
   optional string where = 2;
 
   optional LayersProto layers = 3;
 
   // Blob for the current HWC information for all layers, reported by dumpsys.
+  // Example:
+  //   "maxDownScale: 4, maxFullWidth: 8192, HWState: 1, AssignedState: 3, ..."
   optional string hwc_blob = 4;
 
   // Excludes state sent during composition like visible region and composition
@@ -5280,6 +5283,7 @@
 
 message DisplayProto {
   optional uint64 id = 1;
+  // Display descriptor, e.g. "Built-In Screen"
   optional string name = 2;
   optional uint32 layer_stack = 3;
   optional SizeProto size = 4;
@@ -5314,12 +5318,14 @@
   // unique id per layer.
   optional int32 id = 1;
   // unique name per layer.
+  // Example: "Wallpaper".
   optional string name = 2;
   // list of children this layer may have. May be empty.
   repeated int32 children = 3 [packed = true];
   // list of layers that are z order relative to this layer.
   repeated int32 relatives = 4 [packed = true];
-  // The type of layer, ex Color, Layer
+  // The type of layer.
+  // Examples: "ContainerLayer", "BufferStateLayer".
   optional string type = 5;
   optional RegionProto transparent_region = 6;
   optional RegionProto visible_region = 7;
@@ -5340,7 +5346,14 @@
   optional RectProto final_crop = 15 [deprecated = true];
   optional bool is_opaque = 16;
   optional bool invalidate = 17;
+  // Composition states's dataspace.
+  // Examples: "STANDARD_BT709", "STANDARD_BT601_625".
+  // See full enum in
+  // frameworks/native/libs/nativewindow/include/android/data_space.h
   optional string dataspace = 18;
+  // Buffer's pixel format
+  // Examples: "PIXEL_FORMAT_TRANSLUCENT", "PIXEL_FORMAT_RGBA_8888".
+  // See full enum in frameworks/native/libs/ui/include/ui/PixelFormat.h
   optional string pixel_format = 19;
   // The layer's actual color.
   optional ColorProto color = 20;
@@ -8271,6 +8284,40 @@
   optional uint32 n_wr_s_cnt = 27;
   optional uint32 n_wr_s_peak = 28;
 }
+message F2fsBackgroundGcFtraceEvent {
+  optional uint64 dev = 1;
+  optional uint32 wait_ms = 2;
+  optional uint32 prefree = 3;
+  optional uint32 free = 4;
+}
+message F2fsGcBeginFtraceEvent {
+  optional uint64 dev = 1;
+  optional uint32 sync = 2;
+  optional uint32 background = 3;
+  optional int64 dirty_nodes = 4;
+  optional int64 dirty_dents = 5;
+  optional int64 dirty_imeta = 6;
+  optional uint32 free_sec = 7;
+  optional uint32 free_seg = 8;
+  optional int32 reserved_seg = 9;
+  optional uint32 prefree_seg = 10;
+  optional int32 gc_type = 11;
+  optional uint32 no_bg_gc = 12;
+  optional uint32 nr_free_secs = 13;
+}
+message F2fsGcEndFtraceEvent {
+  optional uint64 dev = 1;
+  optional int32 ret = 2;
+  optional int32 seg_freed = 3;
+  optional int32 sec_freed = 4;
+  optional int64 dirty_nodes = 5;
+  optional int64 dirty_dents = 6;
+  optional int64 dirty_imeta = 7;
+  optional uint32 free_sec = 8;
+  optional uint32 free_seg = 9;
+  optional int32 reserved_seg = 10;
+  optional uint32 prefree_seg = 11;
+}
 
 // End of protos/perfetto/trace/ftrace/f2fs.proto
 
@@ -10624,6 +10671,9 @@
     DpuDsiCmdFifoStatusFtraceEvent dpu_dsi_cmd_fifo_status = 492;
     DpuDsiRxFtraceEvent dpu_dsi_rx = 493;
     DpuDsiTxFtraceEvent dpu_dsi_tx = 494;
+    F2fsBackgroundGcFtraceEvent f2fs_background_gc = 495;
+    F2fsGcBeginFtraceEvent f2fs_gc_begin = 496;
+    F2fsGcEndFtraceEvent f2fs_gc_end = 497;
   }
 }
 
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index c4b4807..db13230 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -488,4 +488,7 @@
 sched/sched_migrate_task
 dpu/dsi_cmd_fifo_status
 dpu/dsi_rx
-dpu/dsi_tx
\ No newline at end of file
+dpu/dsi_tx
+f2fs/f2fs_background_gc
+f2fs/f2fs_gc_begin
+f2fs/f2fs_gc_end
diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc
index 4f4af85..17063c9 100644
--- a/src/trace_processor/db/column/arrangement_overlay.cc
+++ b/src/trace_processor/db/column/arrangement_overlay.cc
@@ -42,10 +42,7 @@
     : inner_(std::move(inner)),
       arrangement_(arrangement),
       arrangement_state_(arrangement_state),
-      does_arrangement_order_storage_(does_arrangement_order_storage) {
-  PERFETTO_DCHECK(*std::max_element(arrangement->begin(), arrangement->end()) <=
-                  inner_->size());
-}
+      does_arrangement_order_storage_(does_arrangement_order_storage) {}
 
 SingleSearchResult ArrangementOverlay::ChainImpl::SingleSearch(
     FilterOp op,
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index 46ab450..b39b4cb 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -508,9 +508,6 @@
     FilterOp op,
     SqlValue sql_val,
     const OrderedIndices& indices) const {
-  PERFETTO_DCHECK(*std::max_element(indices.data, indices.data + indices.size) <
-                  size());
-
   PERFETTO_TP_TRACE(
       metatrace::Category::DB, "NumericStorage::ChainImpl::OrderedIndexSearch",
       [indices, op](metatrace::Record* r) {
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index 0dd3aa9..51917f1 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -22,7 +22,6 @@
 #include <sys/types.h>
 #include "perfetto/base/logging.h"
 #include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/column/data_layer.h"
 #include "src/trace_processor/db/column/types.h"
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 640647b..2163b80 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -19,7 +19,6 @@
 #include <optional>
 
 #include "src/trace_processor/importers/common/args_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index f272e6d..3b846f4 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<FtraceMessageDescriptor, 492> descriptors{{
+std::array<FtraceMessageDescriptor, 498> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5423,6 +5423,84 @@
             {"load", ProtoSchemaType::kUint32},
         },
     },
+    {
+        "dpu_dsi_cmd_fifo_status",
+        2,
+        {
+            {},
+            {"header", ProtoSchemaType::kUint32},
+            {"payload", ProtoSchemaType::kUint32},
+        },
+    },
+    {
+        "dpu_dsi_rx",
+        2,
+        {
+            {},
+            {"cmd", ProtoSchemaType::kUint32},
+            {"rx_buf", ProtoSchemaType::kUint32},
+        },
+    },
+    {
+        "dpu_dsi_tx",
+        4,
+        {
+            {},
+            {"type", ProtoSchemaType::kUint32},
+            {"tx_buf", ProtoSchemaType::kUint32},
+            {"last", ProtoSchemaType::kUint32},
+            {"delay_ms", ProtoSchemaType::kUint32},
+        },
+    },
+    {
+        "f2fs_background_gc",
+        4,
+        {
+            {},
+            {"dev", ProtoSchemaType::kUint64},
+            {"wait_ms", ProtoSchemaType::kUint32},
+            {"prefree", ProtoSchemaType::kUint32},
+            {"free", ProtoSchemaType::kUint32},
+        },
+    },
+    {
+        "f2fs_gc_begin",
+        13,
+        {
+            {},
+            {"dev", ProtoSchemaType::kUint64},
+            {"sync", ProtoSchemaType::kUint32},
+            {"background", ProtoSchemaType::kUint32},
+            {"dirty_nodes", ProtoSchemaType::kInt64},
+            {"dirty_dents", ProtoSchemaType::kInt64},
+            {"dirty_imeta", ProtoSchemaType::kInt64},
+            {"free_sec", ProtoSchemaType::kUint32},
+            {"free_seg", ProtoSchemaType::kUint32},
+            {"reserved_seg", ProtoSchemaType::kInt32},
+            {"prefree_seg", ProtoSchemaType::kUint32},
+            {"gc_type", ProtoSchemaType::kInt32},
+            {"no_bg_gc", ProtoSchemaType::kUint32},
+            {"nr_free_secs", ProtoSchemaType::kUint32},
+        },
+    },
+    {
+        "f2fs_gc_end",
+        11,
+        {
+            {},
+            {"dev", ProtoSchemaType::kUint64},
+            {"ret", ProtoSchemaType::kInt32},
+            {"seg_freed", ProtoSchemaType::kInt32},
+            {"sec_freed", ProtoSchemaType::kInt32},
+            {"dirty_nodes", ProtoSchemaType::kInt64},
+            {"dirty_dents", ProtoSchemaType::kInt64},
+            {"dirty_imeta", ProtoSchemaType::kInt64},
+            {"free_sec", ProtoSchemaType::kUint32},
+            {"free_seg", ProtoSchemaType::kUint32},
+            {"reserved_seg", ProtoSchemaType::kInt32},
+            {"prefree_seg", ProtoSchemaType::kUint32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index bc832bf..b648907 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -1012,11 +1012,11 @@
         break;
       }
       case FtraceEvent::kFuncgraphEntryFieldNumber: {
-        ParseFuncgraphEntry(ts, pid, fld_bytes, seq_state);
+        ParseFuncgraphEntry(ts, cpu, pid, fld_bytes, seq_state);
         break;
       }
       case FtraceEvent::kFuncgraphExitFieldNumber: {
-        ParseFuncgraphExit(ts, pid, fld_bytes, seq_state);
+        ParseFuncgraphExit(ts, cpu, pid, fld_bytes, seq_state);
         break;
       }
       case FtraceEvent::kV4l2QbufFieldNumber:
@@ -3194,37 +3194,54 @@
 
 void FtraceParser::ParseFuncgraphEntry(
     int64_t timestamp,
+    uint32_t cpu,
     uint32_t pid,
     protozero::ConstBytes blob,
     PacketSequenceStateGeneration* seq_state) {
-  // TODO(rsavitski): remove if/when we stop collapsing all idle (swapper)
-  // threads to a single track, otherwise this breaks slice nesting.
-  if (pid == 0)
-    return;
-
   protos::pbzero::FuncgraphEntryFtraceEvent::Decoder evt(blob.data, blob.size);
   StringId name_id = InternedKernelSymbolOrFallback(evt.func(), seq_state);
 
-  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
-  TrackId track = context_->track_tracker->InternThreadTrack(utid);
+  TrackId track = {};
+  if (pid != 0) {
+    // common case: normal thread
+    UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+    track = context_->track_tracker->InternThreadTrack(utid);
+  } else {
+    // Idle threads (swapper) are implicit, and all share the same thread id 0.
+    // Therefore we cannot use a thread-scoped track because many instances
+    // of swapper might be running concurrently. Fall back onto global tracks
+    // (one per cpu).
+    base::StackString<255> track_name("swapper%" PRIu32 "-funcgraph", cpu);
+    StringId track_name_id =
+        context_->storage->InternString(track_name.string_view());
+    track = context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+  }
+
   context_->slice_tracker->Begin(timestamp, track, kNullStringId, name_id);
 }
 
 void FtraceParser::ParseFuncgraphExit(
     int64_t timestamp,
+    uint32_t cpu,
     uint32_t pid,
     protozero::ConstBytes blob,
     PacketSequenceStateGeneration* seq_state) {
-  // TODO(rsavitski): remove if/when we stop collapsing all idle (swapper)
-  // threads to a single track, otherwise this breaks slice nesting.
-  if (pid == 0)
-    return;
-
   protos::pbzero::FuncgraphExitFtraceEvent::Decoder evt(blob.data, blob.size);
   StringId name_id = InternedKernelSymbolOrFallback(evt.func(), seq_state);
 
-  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
-  TrackId track = context_->track_tracker->InternThreadTrack(utid);
+  TrackId track = {};
+  if (pid != 0) {
+    // common case: normal thread
+    UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+    track = context_->track_tracker->InternThreadTrack(utid);
+  } else {
+    // special case: see |ParseFuncgraphEntry|
+    base::StackString<255> track_name("swapper%" PRIu32 "-funcgraph", cpu);
+    StringId track_name_id =
+        context_->storage->InternString(track_name.string_view());
+    track = context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+  }
+
   context_->slice_tracker->End(timestamp, track, kNullStringId, name_id);
 }
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index 402ad73..6c84c6d 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -237,10 +237,12 @@
   void ParseSchedCpuUtilCfs(int64_t timestamp, protozero::ConstBytes);
 
   void ParseFuncgraphEntry(int64_t timestamp,
+                           uint32_t cpu,
                            uint32_t pid,
                            protozero::ConstBytes blob,
                            PacketSequenceStateGeneration* seq_state);
   void ParseFuncgraphExit(int64_t timestamp,
+                          uint32_t cpu,
                           uint32_t pid,
                           protozero::ConstBytes blob,
                           PacketSequenceStateGeneration* seq_state);
diff --git a/src/trace_processor/metrics/sql/android/android_powrails.sql b/src/trace_processor/metrics/sql/android/android_powrails.sql
index 0289350..8c2486a 100644
--- a/src/trace_processor/metrics/sql/android/android_powrails.sql
+++ b/src/trace_processor/metrics/sql/android/android_powrails.sql
@@ -14,13 +14,13 @@
 -- limitations under the License.
 --
 
+INCLUDE PERFETTO MODULE android.power_rails;
+
 -- View of Power Rail counters with ts converted from ns to ms.
 DROP VIEW IF EXISTS power_rails_counters;
 CREATE PERFETTO VIEW power_rails_counters AS
-SELECT value, ts / 1000000 AS ts, name
-FROM counter c
-JOIN counter_track t ON c.track_id = t.id
-WHERE name GLOB 'power.*';
+SELECT value, ts / 1000000 AS ts, power_rail_name AS name
+FROM android_power_rails_counters;
 
 DROP VIEW IF EXISTS avg_used_powers;
 CREATE PERFETTO VIEW avg_used_powers AS
diff --git a/src/trace_processor/perfetto_sql/engine/created_function.h b/src/trace_processor/perfetto_sql/engine/created_function.h
index c7d44b7..4d744e5 100644
--- a/src/trace_processor/perfetto_sql/engine/created_function.h
+++ b/src/trace_processor/perfetto_sql/engine/created_function.h
@@ -18,20 +18,18 @@
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_FUNCTION_H_
 
 #include <sqlite3.h>
+#include <cstddef>
 #include <memory>
-#include <unordered_map>
 
 #include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/perfetto_sql/engine/function_util.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/types/destructible.h"
 #include "src/trace_processor/util/sql_argument.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 class PerfettoSqlEngine;
 
@@ -59,7 +57,6 @@
   static base::Status EnableMemoization(Context*);
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 8959989..8f163a0 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -45,11 +45,9 @@
 #include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
-#include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/tp_metatrace.h"
 #include "src/trace_processor/util/sql_argument.h"
 #include "src/trace_processor/util/sql_modules.h"
@@ -169,7 +167,7 @@
 }  // namespace
 
 PerfettoSqlEngine::PerfettoSqlEngine(StringPool* pool)
-    : query_cache_(new QueryCache()), pool_(pool), engine_(new SqliteEngine()) {
+    : pool_(pool), engine_(new SqliteEngine()) {
   // Initialize `perfetto_tables` table, which will contain the names of all of
   // the registered tables.
   char* errmsg_raw = nullptr;
@@ -181,63 +179,75 @@
     PERFETTO_FATAL("Failed to initialize perfetto_tables: %s", errmsg_raw);
   }
 
-  engine_->RegisterVirtualTableModule<RuntimeTableFunction>(
-      "runtime_table_function", this,
-      SqliteTableLegacy::TableType::kExplicitCreate, false);
-  auto context = std::make_unique<DbSqliteTable::Context>(
-      query_cache_.get(),
-      [this](const std::string& name) {
-        auto* table = runtime_tables_.Find(name);
-        PERFETTO_CHECK(table);
-        return table->get();
-      },
-      [this](const std::string& name) {
-        bool res = runtime_tables_.Erase(name);
-        PERFETTO_CHECK(res);
-      });
-  engine_->RegisterVirtualTableModule<DbSqliteTable>(
-      "runtime_table", std::move(context),
-      SqliteTableLegacy::TableType::kExplicitCreate, false);
-}
-
-PerfettoSqlEngine::~PerfettoSqlEngine() {
-  // Destroying the sqlite engine should also destroy all the created table
-  // functions.
-  engine_.reset();
-  PERFETTO_CHECK(runtime_table_fn_states_.size() == 0);
-  PERFETTO_CHECK(runtime_tables_.size() == 0);
+  {
+    auto ctx = std::make_unique<RuntimeTableFunctionModule::Context>();
+    runtime_table_fn_context_ = ctx.get();
+    engine_->RegisterVirtualTableModule<RuntimeTableFunctionModule>(
+        "runtime_table_function", std::move(ctx));
+  }
+  {
+    auto ctx = std::make_unique<DbSqliteModule::Context>();
+    runtime_table_context_ = ctx.get();
+    engine_->RegisterVirtualTableModule<DbSqliteModule>("runtime_table",
+                                                        std::move(ctx));
+  }
+  {
+    auto ctx = std::make_unique<DbSqliteModule::Context>();
+    static_table_context_ = ctx.get();
+    engine_->RegisterVirtualTableModule<DbSqliteModule>("static_table",
+                                                        std::move(ctx));
+  }
+  {
+    auto ctx = std::make_unique<DbSqliteModule::Context>();
+    static_table_fn_context_ = ctx.get();
+    engine_->RegisterVirtualTableModule<DbSqliteModule>("static_table_function",
+                                                        std::move(ctx));
+  }
 }
 
 void PerfettoSqlEngine::RegisterStaticTable(const Table& table,
                                             const std::string& table_name,
                                             Table::Schema schema) {
-  auto context = std::make_unique<DbSqliteTable::Context>(
-      query_cache_.get(), &table, std::move(schema));
-  static_tables_.Insert(table_name, &table);
-  engine_->RegisterVirtualTableModule<DbSqliteTable>(
-      table_name, std::move(context), SqliteTableLegacy::kEponymousOnly, false);
+  // Make sure we didn't accidentally leak a state from a previous table
+  // creation.
+  PERFETTO_CHECK(!static_table_context_->temporary_create_state);
+  static_table_context_->temporary_create_state =
+      std::make_unique<DbSqliteModule::State>(&table, std::move(schema));
 
-  // Register virtual tables into an internal 'perfetto_tables' table.
-  // This is used for iterating through all the tables during a database
-  // export.
-  char* insert_sql = sqlite3_mprintf(
-      "INSERT INTO perfetto_tables(name) VALUES('%q')", table_name.c_str());
-  char* error = nullptr;
-  sqlite3_exec(engine_->db(), insert_sql, nullptr, nullptr, &error);
-  sqlite3_free(insert_sql);
-  if (error) {
-    PERFETTO_ELOG("Error adding table to perfetto_tables: %s", error);
-    sqlite3_free(error);
+  base::StackString<1024> sql(
+      R"(
+        CREATE VIRTUAL TABLE %s USING static_table;
+        INSERT INTO perfetto_tables(name) VALUES('%s');
+      )",
+      table_name.c_str(), table_name.c_str());
+  auto status =
+      Execute(SqlSource::FromTraceProcessorImplementation(sql.ToStdString()));
+  if (!status.ok()) {
+    PERFETTO_FATAL("%s", status.status().c_message());
   }
+
+  PERFETTO_CHECK(!static_table_context_->temporary_create_state);
 }
 
 void PerfettoSqlEngine::RegisterStaticTableFunction(
     std::unique_ptr<StaticTableFunction> fn) {
-  std::string table_name = fn->TableName();
-  auto context = std::make_unique<DbSqliteTable::Context>(query_cache_.get(),
-                                                          std::move(fn));
-  engine_->RegisterVirtualTableModule<DbSqliteTable>(
-      table_name, std::move(context), SqliteTableLegacy::kEponymousOnly, false);
+  std::string name = fn->TableName();
+
+  // Make sure we didn't accidentally leak a state from a previous table
+  // creation.
+  PERFETTO_CHECK(!static_table_fn_context_->temporary_create_state);
+  static_table_fn_context_->temporary_create_state =
+      std::make_unique<DbSqliteModule::State>(std::move(fn));
+
+  base::StackString<1024> sql(
+      "CREATE VIRTUAL TABLE %s USING static_table_function;", name.c_str());
+  auto status =
+      Execute(SqlSource::FromTraceProcessorImplementation(sql.ToStdString()));
+  if (!status.ok()) {
+    PERFETTO_FATAL("%s", status.status().c_message());
+  }
+
+  PERFETTO_CHECK(!static_table_fn_context_->temporary_create_state);
 }
 
 base::StatusOr<PerfettoSqlEngine::ExecutionStats> PerfettoSqlEngine::Execute(
@@ -484,24 +494,38 @@
   //
   // We would need to do with the transaction API but given we have no usage of
   // this until now, investigating that needs some proper work.
-  if (runtime_tables_.Find(create_table.name)) {
-    if (!create_table.replace) {
-      return base::ErrStatus("CREATE PERFETTO TABLE: table '%s' already exists",
-                             create_table.name.c_str());
-    }
-
-    base::StackString<1024> drop("DROP TABLE %s", create_table.name.c_str());
-    RETURN_IF_ERROR(
-        Execute(SqlSource::FromTraceProcessorImplementation(drop.ToStdString()))
-            .status());
+  if (create_table.replace) {
+    base::StackString<1024> drop("DROP TABLE IF EXISTS %s",
+                                 create_table.name.c_str());
+    auto drop_res = Execute(
+        SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
+    RETURN_IF_ERROR(drop_res.status());
   }
 
-  runtime_tables_.Insert(create_table.name, std::move(table));
   base::StackString<1024> create("CREATE VIRTUAL TABLE %s USING runtime_table",
                                  create_table.name.c_str());
-  return Execute(
-             SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
-      .status();
+
+  // Make sure we didn't accidentally leak a state from a previous function
+  // creation.
+  PERFETTO_CHECK(!runtime_table_context_->temporary_create_state);
+
+  // Move the state into the context so that it will be picked up in xCreate
+  // of RuntimeTableFunctionModule.
+  runtime_table_context_->temporary_create_state =
+      std::make_unique<DbSqliteModule::State>(std::move(table));
+  auto status =
+      Execute(SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
+          .status();
+
+  // If an error happened, it's possible that the state was not picked up.
+  // Therefore, always reset the state just in case. OTOH if the creation
+  // succeeded, the state should always have been captured.
+  if (status.ok()) {
+    PERFETTO_CHECK(!runtime_table_context_->temporary_create_state);
+  } else {
+    runtime_table_context_->temporary_create_state.reset();
+  }
+  return status;
 }
 
 base::Status PerfettoSqlEngine::ExecuteCreateView(
@@ -623,8 +647,9 @@
                                    cf.sql);
   }
 
-  std::unique_ptr<RuntimeTableFunction::State> state(
-      new RuntimeTableFunction::State{cf.sql, cf.prototype, {}, std::nullopt});
+  auto state = std::make_unique<RuntimeTableFunctionModule::State>(
+      RuntimeTableFunctionModule::State{
+          this, cf.sql, cf.prototype, {}, std::nullopt});
 
   // Parse the return type into a enum format.
   {
@@ -702,36 +727,44 @@
           state->return_values[i].name().c_str());
     }
   }
-  state->reusable_stmt = std::move(stmt);
+  state->temporary_create_stmt = std::move(stmt);
 
   // TODO(lalitm): this suffers the same non-atomic DROP/CREATE problem as
   // CREATE PERFETTO TABLE implementation above: see the comment there for
   // more info on this.
-  std::string fn_name = state->prototype.function_name;
-  std::string lower_name = base::ToLower(state->prototype.function_name);
-  if (runtime_table_fn_states_.Find(lower_name)) {
-    if (!cf.replace) {
-      return base::ErrStatus("Table function named %s already exists",
-                             state->prototype.function_name.c_str());
-    }
-    // This will cause |OnTableFunctionDestroyed| below to be executed.
-    base::StackString<1024> drop("DROP TABLE %s",
+  if (cf.replace) {
+    base::StackString<1024> drop("DROP TABLE IF EXISTS %s",
                                  state->prototype.function_name.c_str());
     auto res = Execute(
         SqlSource::FromTraceProcessorImplementation(drop.ToStdString()));
     RETURN_IF_ERROR(res.status());
   }
 
-  auto it_and_inserted =
-      runtime_table_fn_states_.Insert(lower_name, std::move(state));
-  PERFETTO_CHECK(it_and_inserted.second);
-
   base::StackString<1024> create(
-      "CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
-  return Execute(cf.sql.RewriteAllIgnoreExisting(
-                     SqlSource::FromTraceProcessorImplementation(
-                         create.ToStdString())))
-      .status();
+      "CREATE VIRTUAL TABLE %s USING runtime_table_function",
+      state->prototype.function_name.c_str());
+
+  // Make sure we didn't accidentally leak a state from a previous function
+  // creation.
+  PERFETTO_CHECK(!runtime_table_fn_context_->temporary_create_state);
+
+  // Move the state into the context so that it will be picked up in xCreate
+  // of RuntimeTableFunctionModule.
+  runtime_table_fn_context_->temporary_create_state = std::move(state);
+  auto status = Execute(cf.sql.RewriteAllIgnoreExisting(
+                            SqlSource::FromTraceProcessorImplementation(
+                                create.ToStdString())))
+                    .status();
+
+  // If an error happened, it's possible that the state was not picked up.
+  // Therefore, always reset the state just in case. OTOH if the creation
+  // succeeded, the state should always have been captured.
+  if (status.ok()) {
+    PERFETTO_CHECK(!runtime_table_fn_context_->temporary_create_state);
+  } else {
+    runtime_table_fn_context_->temporary_create_state.reset();
+  }
+  return status;
 }
 
 base::Status PerfettoSqlEngine::ExecuteCreateMacro(
@@ -782,18 +815,6 @@
   return base::OkStatus();
 }
 
-RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
-    const std::string& name) const {
-  auto* it = runtime_table_fn_states_.Find(base::ToLower(name));
-  PERFETTO_CHECK(it);
-  return it->get();
-}
-
-void PerfettoSqlEngine::OnRuntimeTableFunctionDestroyed(
-    const std::string& name) {
-  PERFETTO_CHECK(runtime_table_fn_states_.Erase(base::ToLower(name)));
-}
-
 base::StatusOr<std::vector<std::string>>
 PerfettoSqlEngine::GetColumnNamesFromSelectStatement(
     const SqliteEngine::PreparedStatement& stmt,
@@ -882,14 +903,14 @@
 
 const RuntimeTable* PerfettoSqlEngine::GetRuntimeTableOrNull(
     std::string_view name) const {
-  auto* table_ptr = runtime_tables_.Find(name.data());
-  return table_ptr ? table_ptr->get() : nullptr;
+  auto* state = runtime_table_context_->manager.FindStateByName(name);
+  return state ? state->runtime_table.get() : nullptr;
 }
 
 const Table* PerfettoSqlEngine::GetStaticTableOrNull(
     std::string_view name) const {
-  auto* table_ptr = static_tables_.Find(name.data());
-  return table_ptr ? *table_ptr : nullptr;
+  auto* state = static_table_context_->manager.FindStateByName(name);
+  return state ? state->static_table : nullptr;
 }
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index ea785b6..1d7642f 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -38,10 +38,9 @@
 #include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
 #include "src/trace_processor/sqlite/bindings/sqlite_result.h"
 #include "src/trace_processor/sqlite/bindings/sqlite_window_function.h"
-#include "src/trace_processor/sqlite/query_cache.h"
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
@@ -66,7 +65,6 @@
   };
 
   explicit PerfettoSqlEngine(StringPool* pool);
-  ~PerfettoSqlEngine();
 
   // Executes all the statements in |sql| and returns a |ExecutionResult|
   // object. The metadata will reference all the statements executed and the
@@ -173,13 +171,6 @@
   // Registers a trace processor C++ table function with SQLite.
   void RegisterStaticTableFunction(std::unique_ptr<StaticTableFunction> fn);
 
-  // Returns the state for the given table function.
-  RuntimeTableFunction::State* GetRuntimeTableFunctionState(
-      const std::string&) const;
-
-  // Should be called when a table function is destroyed.
-  void OnRuntimeTableFunctionDestroyed(const std::string&);
-
   SqliteEngine* sqlite_engine() { return engine_.get(); }
 
   // Makes new SQL module available to import.
@@ -270,7 +261,6 @@
       const std::string& key,
       const PerfettoSqlParser& parser);
 
-  std::unique_ptr<QueryCache> query_cache_;
   StringPool* pool_ = nullptr;
 
   uint64_t static_function_count_ = 0;
@@ -278,10 +268,10 @@
   uint64_t static_window_function_count_ = 0;
   uint64_t runtime_function_count_ = 0;
 
-  base::FlatHashMap<std::string, std::unique_ptr<RuntimeTableFunction::State>>
-      runtime_table_fn_states_;
-  base::FlatHashMap<std::string, const Table*> static_tables_;
-  base::FlatHashMap<std::string, std::unique_ptr<RuntimeTable>> runtime_tables_;
+  RuntimeTableFunctionModule::Context* runtime_table_fn_context_ = nullptr;
+  DbSqliteModule::Context* runtime_table_context_ = nullptr;
+  DbSqliteModule::Context* static_table_context_ = nullptr;
+  DbSqliteModule::Context* static_table_fn_context_ = nullptr;
   base::FlatHashMap<std::string, sql_modules::RegisteredModule> modules_;
   base::FlatHashMap<std::string, PerfettoSqlPreprocessor::Macro> macros_;
   std::unique_ptr<SqliteEngine> engine_;
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
index 3d0af93..12faa70 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -16,15 +16,28 @@
 
 #include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 
+#include <sqlite3.h>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
 #include <optional>
+#include <string>
 #include <utility>
+#include <vector>
 
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/perfetto_sql/engine/function_util.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
 #include "src/trace_processor/sqlite/bindings/sqlite_result.h"
-#include "src/trace_processor/util/status_macros.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/sql_argument.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 
@@ -33,217 +46,226 @@
   sqlite3_clear_bindings(stmt);
 }
 
-}  // namespace
-
-RuntimeTableFunction::RuntimeTableFunction(sqlite3*, PerfettoSqlEngine* engine)
-    : engine_(engine) {}
-
-RuntimeTableFunction::~RuntimeTableFunction() {
-  engine_->OnRuntimeTableFunctionDestroyed(name());
-}
-
-base::Status RuntimeTableFunction::Init(int,
-                                        const char* const*,
-                                        Schema* schema) {
-  state_ = engine_->GetRuntimeTableFunctionState(name());
-
-  // Now we've parsed prototype and return values, create the schema.
-  *schema = CreateSchema();
-  return base::OkStatus();
-}
-
-SqliteTableLegacy::Schema RuntimeTableFunction::CreateSchema() {
-  std::vector<Column> columns;
-  for (size_t i = 0; i < state_->return_values.size(); ++i) {
-    const auto& ret = state_->return_values[i];
-    columns.push_back(Column(columns.size(), ret.name().ToStdString(),
+auto CreateTableStrFromState(RuntimeTableFunctionModule::State* state) {
+  std::vector<std::string> columns;
+  columns.reserve(state->return_values.size());
+  for (const auto& ret : state->return_values) {
+    columns.emplace_back(ret.name().ToStdString() + " " +
+                         sqlite::utils::SqlValueTypeToString(
                              sql_argument::TypeToSqlValueType(ret.type())));
   }
-  for (size_t i = 0; i < state_->prototype.arguments.size(); ++i) {
-    const auto& arg = state_->prototype.arguments[i];
-
+  for (const auto& arg : state->prototype.arguments) {
     // Add the "in_" prefix to every argument param to avoid clashes between the
     // output and input parameters.
-    columns.push_back(Column(columns.size(), "in_" + arg.name().ToStdString(),
-                             sql_argument::TypeToSqlValueType(arg.type()),
-                             true));
+    columns.emplace_back("in_" + arg.name().ToStdString() + " " +
+                         sqlite::utils::SqlValueTypeToString(
+                             sql_argument::TypeToSqlValueType(arg.type())) +
+                         " HIDDEN");
   }
+  columns.emplace_back("_primary_key BIGINT HIDDEN");
 
-  std::vector<size_t> primary_keys;
-
-  // Add the "primary key" column. SQLite requires that we provide a column
-  // which is non-null and unique. Unfortunately, we have no restrictions on
-  // the subqueries so we cannot rely on this constraint being held there.
-  // Therefore, we create a "primary key" column which exists purely for SQLite
-  // primary key purposes and is equal to the row number.
-  columns.push_back(
-      Column(columns.size(), "_primary_key", SqlValue::kLong, true));
-  primary_keys.emplace_back(columns.size() - 1);
-
-  return SqliteTableLegacy::Schema(std::move(columns), std::move(primary_keys));
+  std::string cols = base::Join(columns, ",");
+  return base::StackString<1024>(
+      R"(CREATE TABLE x(%s, PRIMARY KEY(_primary_key)) WITHOUT ROWID)",
+      cols.c_str());
 }
 
-std::unique_ptr<SqliteTableLegacy::BaseCursor>
-RuntimeTableFunction::CreateCursor() {
-  return std::unique_ptr<Cursor>(new Cursor(this, state_));
+}  // namespace
+
+int RuntimeTableFunctionModule::Create(sqlite3* db,
+                                       void* ctx,
+                                       int,
+                                       const char* const* argv,
+                                       sqlite3_vtab** vtab,
+                                       char**) {
+  auto* context = GetContext(ctx);
+  auto state = std::move(context->temporary_create_state);
+
+  auto create_table_str = CreateTableStrFromState(state.get());
+  if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str());
+      ret != SQLITE_OK) {
+    return ret;
+  }
+  std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+  res->reusable_stmt = std::move(state->temporary_create_stmt);
+  state->temporary_create_stmt = std::nullopt;
+  res->state = context->manager.OnCreate(argv, std::move(state));
+  *vtab = res.release();
+  return SQLITE_OK;
 }
 
-int RuntimeTableFunction::BestIndex(const QueryConstraints& qc,
-                                    BestIndexInfo* info) {
-  // Only accept constraint sets where every input parameter has a value.
-  size_t seen_argument_constraints = 0;
-  for (size_t i = 0; i < qc.constraints().size(); ++i) {
-    const auto& cs = qc.constraints()[i];
-    seen_argument_constraints +=
-        state_->IsArgumentColumn(static_cast<size_t>(cs.column));
+int RuntimeTableFunctionModule::Destroy(sqlite3_vtab* vtab) {
+  std::unique_ptr<Vtab> tab(GetVtab(vtab));
+  sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDestroy(tab->state);
+  return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::Connect(sqlite3* db,
+                                        void* ctx,
+                                        int,
+                                        const char* const*,
+                                        sqlite3_vtab** vtab,
+                                        char** argv) {
+  auto* context = GetContext(ctx);
+
+  std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+  res->state = context->manager.OnConnect(argv);
+
+  auto create_table_str = CreateTableStrFromState(
+      sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+          res->state));
+  if (int ret = sqlite3_declare_vtab(db, create_table_str.c_str());
+      ret != SQLITE_OK) {
+    // If the registration happens to fail, make sure to disconnect the state
+    // again.
+    sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDisconnect(
+        res->state);
+    return ret;
   }
-  if (seen_argument_constraints < state_->prototype.arguments.size())
+  *vtab = res.release();
+  return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::Disconnect(sqlite3_vtab* vtab) {
+  std::unique_ptr<Vtab> tab(GetVtab(vtab));
+  sqlite::ModuleStateManager<RuntimeTableFunctionModule>::OnDisconnect(
+      tab->state);
+  return SQLITE_OK;
+}
+
+int RuntimeTableFunctionModule::BestIndex(sqlite3_vtab* tab,
+                                          sqlite3_index_info* info) {
+  auto* t = GetVtab(tab);
+  auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+      t->state);
+
+  // Don't deal with any constraints on the output parameters for simplicty.
+  // TODO(lalitm): reconsider this decision to allow more efficient queries:
+  // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
+  // like we do for SPAN JOIN.
+  base::Status status = sqlite::utils::ValidateFunctionArguments(
+      info, s->prototype.arguments.size(),
+      [s](size_t c) { return s->IsArgumentColumn(c); });
+  if (!status.ok()) {
     return SQLITE_CONSTRAINT;
-
-  for (size_t i = 0; i < info->sqlite_omit_constraint.size(); ++i) {
-    size_t col = static_cast<size_t>(qc.constraints()[i].column);
-    if (state_->IsArgumentColumn(col)) {
-      info->sqlite_omit_constraint[i] = true;
-    }
   }
   return SQLITE_OK;
 }
 
-RuntimeTableFunction::Cursor::Cursor(RuntimeTableFunction* table, State* state)
-    : SqliteTableLegacy::BaseCursor(table), table_(table), state_(state) {
-  if (state->reusable_stmt) {
-    stmt_ = std::move(state->reusable_stmt);
-    state->reusable_stmt = std::nullopt;
-    return_stmt_to_state_ = true;
+int RuntimeTableFunctionModule::Open(sqlite3_vtab* tab,
+                                     sqlite3_vtab_cursor** cursor) {
+  auto* t = GetVtab(tab);
+  std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+  if (t->reusable_stmt) {
+    c->stmt = std::move(t->reusable_stmt);
+    t->reusable_stmt = std::nullopt;
   }
+  *cursor = c.release();
+  return SQLITE_OK;
 }
 
-RuntimeTableFunction::Cursor::~Cursor() {
-  if (return_stmt_to_state_) {
-    ResetStatement(stmt_->sqlite_stmt());
-    state_->reusable_stmt = std::move(stmt_);
+int RuntimeTableFunctionModule::Close(sqlite3_vtab_cursor* cursor) {
+  std::unique_ptr<Cursor> c(GetCursor(cursor));
+  auto* t = GetVtab(c->pVtab);
+  if (!t->reusable_stmt && c->stmt) {
+    ResetStatement(c->stmt->sqlite_stmt());
+    t->reusable_stmt = std::move(c->stmt);
   }
+  return SQLITE_OK;
 }
 
-base::Status RuntimeTableFunction::Cursor::Filter(const QueryConstraints& qc,
-                                                  sqlite3_value** argv,
-                                                  FilterHistory) {
+int RuntimeTableFunctionModule::Filter(sqlite3_vtab_cursor* cur,
+                                       int,
+                                       const char*,
+                                       int argc,
+                                       sqlite3_value** argv) {
+  auto* c = GetCursor(cur);
+  auto* t = GetVtab(cur->pVtab);
+  auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+      t->state);
+
+  PERFETTO_CHECK(static_cast<size_t>(argc) == s->prototype.arguments.size());
   PERFETTO_TP_TRACE(metatrace::Category::FUNCTION_CALL, "TABLE_FUNCTION_CALL",
-                    [this](metatrace::Record* r) {
-                      r->AddArg("Function",
-                                state_->prototype.function_name.c_str());
+                    [s](metatrace::Record* r) {
+                      r->AddArg("Function", s->prototype.function_name.c_str());
                     });
 
-  auto col_to_arg_idx = [this](int col) {
-    return static_cast<uint32_t>(col) -
-           static_cast<uint32_t>(state_->return_values.size());
-  };
-
-  size_t seen_argument_constraints = 0;
-  for (size_t i = 0; i < qc.constraints().size(); ++i) {
-    const auto& cs = qc.constraints()[i];
-
-    // Only consider argument columns (i.e. input parameters) as we're
-    // delegating the rest to SQLite.
-    if (!state_->IsArgumentColumn(static_cast<size_t>(cs.column)))
-      continue;
-
-    // We only support equality constraints as we're expecting "input arguments"
-    // to our "function".
-    if (!sqlite::utils::IsOpEq(cs.op)) {
-      return base::ErrStatus("%s: non-equality constraint passed",
-                             state_->prototype.function_name.c_str());
-    }
-
-    const auto& arg = state_->prototype.arguments[col_to_arg_idx(cs.column)];
-    base::Status status = sqlite::utils::TypeCheckSqliteValue(
-        argv[i], sql_argument::TypeToSqlValueType(arg.type()),
-        sql_argument::TypeToHumanFriendlyString(arg.type()));
-    if (!status.ok()) {
-      return base::ErrStatus("%s: argument %s (index %zu) %s",
-                             state_->prototype.function_name.c_str(),
-                             arg.name().c_str(), i, status.c_message());
-    }
-
-    seen_argument_constraints++;
-  }
-
-  // Verify that we saw one valid constraint for every input argument.
-  if (seen_argument_constraints < state_->prototype.arguments.size()) {
-    return base::ErrStatus(
-        "%s: missing value for input argument. Saw %zu arguments but expected "
-        "%zu",
-        state_->prototype.function_name.c_str(), seen_argument_constraints,
-        state_->prototype.arguments.size());
-  }
-
   // Prepare the SQL definition as a statement using SQLite.
   // TODO(lalitm): measure and implement whether it would be a good idea to
   // forward constraints here when we build the nested query.
-  if (stmt_) {
+  if (c->stmt) {
     // Filter can be called multiple times for the same cursor, so if we
     // already have a statement, reset and reuse it. Otherwise, create a
     // new one.
-    ResetStatement(stmt_->sqlite_stmt());
+    ResetStatement(c->stmt->sqlite_stmt());
   } else {
-    auto stmt = table_->engine_->sqlite_engine()->PrepareStatement(
-        state_->sql_defn_str);
-    RETURN_IF_ERROR(stmt.status());
-    stmt_ = std::move(stmt);
+    auto stmt = s->engine->sqlite_engine()->PrepareStatement(s->sql_defn_str);
+    c->stmt = std::move(stmt);
+    if (const auto& status = c->stmt->status(); !status.ok()) {
+      return sqlite::utils::SetError(t, status.c_message());
+    }
   }
 
   // Bind all the arguments to the appropriate places in the function.
-  for (size_t i = 0; i < qc.constraints().size(); ++i) {
-    const auto& cs = qc.constraints()[i];
-
-    // Don't deal with any constraints on the output parameters for simplicty.
-    // TODO(lalitm): reconsider this decision to allow more efficient queries:
-    // we would need to wrap the query in a SELECT * FROM (...) WHERE constraint
-    // like we do for SPAN JOIN.
-    if (!state_->IsArgumentColumn(static_cast<size_t>(cs.column)))
-      continue;
-
-    uint32_t index = col_to_arg_idx(cs.column);
-    PERFETTO_DCHECK(index < state_->prototype.arguments.size());
-
-    const auto& arg = state_->prototype.arguments[index];
-    auto status = MaybeBindArgument(
-        stmt_->sqlite_stmt(), state_->prototype.function_name, arg, argv[i]);
-    RETURN_IF_ERROR(status);
+  for (uint32_t i = 0; i < static_cast<uint32_t>(argc); ++i) {
+    const auto& arg = s->prototype.arguments[i];
+    base::Status status = MaybeBindArgument(
+        c->stmt->sqlite_stmt(), s->prototype.function_name, arg, argv[i]);
+    if (!status.ok()) {
+      return sqlite::utils::SetError(t, status.c_message());
+    }
   }
 
   // Reset the next call count - this is necessary because the same cursor
   // can be used for multiple filter operations.
-  next_call_count_ = 0;
-  return Next();
+  c->next_call_count = 0;
+  return Next(cur);
 }
 
-base::Status RuntimeTableFunction::Cursor::Next() {
-  is_eof_ = !stmt_->Step();
-  next_call_count_++;
-  return stmt_->status();
+int RuntimeTableFunctionModule::Next(sqlite3_vtab_cursor* cur) {
+  auto* c = GetCursor(cur);
+  c->is_eof = !c->stmt->Step();
+  c->next_call_count++;
+  if (const auto& status = c->stmt->status(); !status.ok()) {
+    return sqlite::utils::SetError(cur->pVtab, status.c_message());
+  }
+  return SQLITE_OK;
 }
 
-bool RuntimeTableFunction::Cursor::Eof() {
-  return is_eof_;
+int RuntimeTableFunctionModule::Eof(sqlite3_vtab_cursor* cur) {
+  return GetCursor(cur)->is_eof;
 }
 
-base::Status RuntimeTableFunction::Cursor::Column(sqlite3_context* ctx, int i) {
-  size_t idx = static_cast<size_t>(i);
-  if (state_->IsReturnValueColumn(idx)) {
-    sqlite::result::Value(ctx, sqlite3_column_value(stmt_->sqlite_stmt(), i));
-  } else if (state_->IsArgumentColumn(idx)) {
+int RuntimeTableFunctionModule::Column(sqlite3_vtab_cursor* cur,
+                                       sqlite3_context* ctx,
+                                       int N) {
+  auto* c = GetCursor(cur);
+  auto* t = GetVtab(cur->pVtab);
+  auto* s = sqlite::ModuleStateManager<RuntimeTableFunctionModule>::GetState(
+      t->state);
+
+  auto idx = static_cast<size_t>(N);
+  if (PERFETTO_LIKELY(s->IsReturnValueColumn(idx))) {
+    sqlite::result::Value(ctx, sqlite3_column_value(c->stmt->sqlite_stmt(), N));
+    return SQLITE_OK;
+  }
+
+  if (PERFETTO_LIKELY(s->IsArgumentColumn(idx))) {
     // TODO(lalitm): it may be more appropriate to keep a note of the arguments
     // which we passed in and return them here. Not doing this to because it
     // doesn't seem necessary for any useful thing but something which may need
     // to be changed in the future.
     sqlite::result::Null(ctx);
-  } else {
-    PERFETTO_DCHECK(state_->IsPrimaryKeyColumn(idx));
-    sqlite::result::Long(ctx, next_call_count_);
+    return SQLITE_OK;
   }
-  return base::OkStatus();
+
+  PERFETTO_DCHECK(s->IsPrimaryKeyColumn(idx));
+  sqlite::result::Long(ctx, c->next_call_count);
+  return SQLITE_OK;
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+int RuntimeTableFunctionModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+  return SQLITE_ERROR;
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
index 80edf72..37abd2a 100644
--- a/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
@@ -17,31 +17,36 @@
 #ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
 
+#include <cstddef>
+#include <cstdint>
+#include <memory>
 #include <optional>
+#include <vector>
 
+#include "perfetto/base/logging.h"
 #include "src/trace_processor/perfetto_sql/engine/function_util.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
+#include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
+#include "src/trace_processor/util/sql_argument.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 class PerfettoSqlEngine;
 
 // The implementation of the SqliteTableLegacy interface for table functions
 // defined at runtime using SQL.
-class RuntimeTableFunction final
-    : public TypedSqliteTable<RuntimeTableFunction, PerfettoSqlEngine*> {
- public:
-  // The state of this function. This is separated from |RuntimeTableFunction|
-  // because |RuntimeTableFunction| is owned by Sqlite while |State| is owned by
-  // PerfettoSqlEngine.
+struct RuntimeTableFunctionModule
+    : public sqlite::Module<RuntimeTableFunctionModule> {
   struct State {
+    PerfettoSqlEngine* engine;
     SqlSource sql_defn_str;
 
     FunctionPrototype prototype;
     std::vector<sql_argument::ArgumentDefinition> return_values;
 
-    std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
+    std::optional<SqliteEngine::PreparedStatement> temporary_create_stmt;
 
     bool IsReturnValueColumn(size_t i) const {
       PERFETTO_DCHECK(i < TotalColumnCount());
@@ -65,44 +70,55 @@
              kPrimaryKeyColumns;
     }
   };
-  class Cursor final : public SqliteTableLegacy::BaseCursor {
-   public:
-    explicit Cursor(RuntimeTableFunction* table, State* state);
-    ~Cursor() final;
-
-    base::Status Filter(const QueryConstraints& qc,
-                        sqlite3_value**,
-                        FilterHistory);
-    base::Status Next();
-    bool Eof();
-    base::Status Column(sqlite3_context* context, int N);
-
-   private:
-    RuntimeTableFunction* table_ = nullptr;
-    State* state_ = nullptr;
-
-    std::optional<SqliteEngine::PreparedStatement> stmt_;
-    bool return_stmt_to_state_ = false;
-
-    bool is_eof_ = false;
-    int next_call_count_ = 0;
+  struct Context {
+    std::unique_ptr<State> temporary_create_state;
+    sqlite::ModuleStateManager<RuntimeTableFunctionModule> manager;
+  };
+  struct Vtab : sqlite::Module<RuntimeTableFunctionModule>::Vtab {
+    sqlite::ModuleStateManager<RuntimeTableFunctionModule>::PerVtabState* state;
+    std::optional<SqliteEngine::PreparedStatement> reusable_stmt;
+  };
+  struct Cursor : sqlite::Module<RuntimeTableFunctionModule>::Cursor {
+    std::optional<SqliteEngine::PreparedStatement> stmt;
+    bool is_eof = false;
+    int next_call_count = 0;
   };
 
-  RuntimeTableFunction(sqlite3*, PerfettoSqlEngine*);
-  ~RuntimeTableFunction() final;
+  static constexpr bool kSupportsWrites = false;
+  static constexpr bool kDoesOverloadFunctions = false;
 
-  base::Status Init(int argc, const char* const* argv, Schema*) final;
-  std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
-  int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
+  static int Create(sqlite3*,
+                    void*,
+                    int,
+                    const char* const*,
+                    sqlite3_vtab**,
+                    char**);
+  static int Destroy(sqlite3_vtab*);
 
- private:
-  Schema CreateSchema();
+  static int Connect(sqlite3*,
+                     void*,
+                     int,
+                     const char* const*,
+                     sqlite3_vtab**,
+                     char**);
+  static int Disconnect(sqlite3_vtab*);
 
-  PerfettoSqlEngine* engine_ = nullptr;
-  State* state_ = nullptr;
+  static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+  static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+  static int Close(sqlite3_vtab_cursor*);
+
+  static int Filter(sqlite3_vtab_cursor*,
+                    int,
+                    const char*,
+                    int,
+                    sqlite3_value**);
+  static int Next(sqlite3_vtab_cursor*);
+  static int Eof(sqlite3_vtab_cursor*);
+  static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+  static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h
index 5e54006..aaecb04 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h
@@ -18,14 +18,13 @@
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_FUNCTION_H_
 
 #include <sqlite3.h>
-#include <unordered_map>
+#include <cstddef>
 
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 class PerfettoSqlEngine;
 
@@ -61,7 +60,6 @@
                           Destructors&);
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
index 44782e7..f1483ea 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.cc
@@ -15,24 +15,22 @@
  */
 
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h"
-
-#include <numeric>
-#include <optional>
+#include <cstddef>
+#include <string>
+#include <utility>
 
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
 #include "src/trace_processor/perfetto_sql/engine/function_util.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
 #include "src/trace_processor/util/status_macros.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 base::Status CreateViewFunction::Run(CreateViewFunction::Context* ctx,
                                      size_t argc,
@@ -100,5 +98,4 @@
   return res.status();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h
index a4131dd..89bf9fc 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/create_view_function.h
@@ -18,12 +18,13 @@
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_VIEW_FUNCTION_H_
 
 #include <sqlite3.h>
-#include <unordered_map>
+#include <cstddef>
 
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 class PerfettoSqlEngine;
 
@@ -42,7 +43,6 @@
                           Destructors&);
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_FUNCTIONS_CREATE_VIEW_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
index a660723..df0e014 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/import.cc
@@ -16,22 +16,15 @@
 
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/import.h"
 
-#include <numeric>
+#include <cstddef>
 
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_view.h"
 #include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
-#include "src/trace_processor/util/sql_modules.h"
-#include "src/trace_processor/util/status_macros.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 base::Status Import::Run(Import::Context* ctx,
                          size_t argc,
@@ -65,5 +58,4 @@
       .status();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
index ce41792..49ad289 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/operators/span_join_operator.cc
@@ -755,7 +755,6 @@
   auto* context = GetContext(ctx);
   std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
   res->state = context->manager.OnConnect(argv);
-  *vtab = res.release();
 
   auto* state =
       sqlite::ModuleStateManager<SpanJoinOperatorModule>::GetState(res->state);
@@ -763,6 +762,7 @@
       ret != SQLITE_OK) {
     return ret;
   }
+  *vtab = res.release();
   return SQLITE_OK;
 }
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
index 23fc9f1..ef4a58a 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -62,7 +62,6 @@
     "../../../importers/proto:full",
     "../../../importers/proto:minimal",
     "../../../sqlite",
-    "../../../sqlite:query_constraints",
     "../../../storage",
     "../../../tables",
     "../../../types",
@@ -82,7 +81,6 @@
     "../../../../../include/perfetto/trace_processor:basic_types",
     "../../../../base",
     "../../../db",
-    "../../../sqlite:query_constraints",
   ]
 }
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index 73bde66..4c492d1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -36,6 +36,7 @@
     "stack_trace",
     "time",
     "v8",
+    "wattson",
   ]
   generated_header = "stdlib.h"
   namespace = "stdlib"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 6eda0bc..786882e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -27,6 +27,7 @@
     "binder.sql",
     "broadcasts.sql",
     "critical_blocking_calls.sql",
+    "device.sql",
     "dvfs.sql",
     "freezer.sql",
     "garbage_collection.sql",
@@ -36,6 +37,7 @@
     "monitor_contention.sql",
     "network_packets.sql",
     "oom_adjuster.sql",
+    "power_rails.sql",
     "process_metadata.sql",
     "screenshots.sql",
     "services.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/device.sql b/src/trace_processor/perfetto_sql/stdlib/android/device.sql
new file mode 100644
index 0000000..55e8365
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/device.sql
@@ -0,0 +1,42 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- Extract name of the device based on metadata from the trace.
+CREATE PERFETTO TABLE android_device_name(
+  -- Device name.
+  name STRING
+)
+AS
+WITH
+  -- Example str_value:
+  -- Android/aosp_raven/raven:VanillaIceCream/UDC/11197703:userdebug/test-keys
+  -- Gets substring after first slash;
+  after_first_slash(str) AS (
+    SELECT SUBSTR(str_value, INSTR(str_value, '/') + 1)
+    FROM metadata
+    WHERE name = 'android_build_fingerprint'
+  ),
+  -- Gets substring after second slash
+  after_second_slash(str) AS (
+    SELECT SUBSTR(str, INSTR(str, '/') + 1)
+    FROM after_first_slash
+  ),
+  -- Gets substring after second slash and before the colon
+  before_colon(str) AS (
+    SELECT SUBSTR(str, 0, INSTR(str, ':'))
+    FROM after_second_slash
+  )
+SELECT str AS name FROM before_colon;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
new file mode 100644
index 0000000..6630c53
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql
@@ -0,0 +1,36 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+-- Android power rails counters.
+CREATE PERFETTO TABLE android_power_rails_counters (
+    -- `counter.id`
+    id INT,
+    -- Counter timestamp.
+    ts INT,
+    -- Power rail name. From `counter_track.name`.
+    power_rail_name INT,
+    -- Power rails counter value in micro watts.
+    value DOUBLE
+)
+AS
+SELECT
+    c.id,
+    c.ts,
+    t.name AS power_rail_name,
+    c.value
+FROM counter c
+JOIN counter_track t ON c.track_id = t.id
+WHERE name GLOB 'power.*';
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
index e17e6b2..28fc5a0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/BUILD.gn
@@ -17,6 +17,8 @@
 perfetto_sql_source_set("cpu") {
   sources = [
     "cpus.sql",
+    "freq.sql",
+    "idle.sql",
     "size.sql",
   ]
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
new file mode 100644
index 0000000..c0226bf
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/freq.sql
@@ -0,0 +1,51 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- Counter information for each frequency change for each CPU. Finds each time
+-- region where a CPU frequency is constant.
+CREATE PERFETTO TABLE cpu_freq_counters(
+  -- Counter id.
+  id INT,
+  -- Joinable with 'counter_track.id'.
+  track_id INT,
+  -- Starting timestamp of the counter
+  ts LONG,
+  -- Duration in which counter is constant and frequency doesn't change.
+  dur INT,
+  -- Frequency in kHz of the CPU that corresponds to this counter. NULL if not
+  -- found or undefined.
+  freq INT,
+  -- CPU that corresponds to this counter.
+  cpu INT
+) AS
+SELECT
+  count_w_dur.id,
+  count_w_dur.track_id,
+  count_w_dur.ts,
+  count_w_dur.dur,
+  count_w_dur.value as freq,
+  cct.cpu
+FROM
+counter_leading_intervals!((
+  SELECT c.*
+  FROM counter c
+  JOIN cpu_counter_track cct
+  ON cct.id = c.track_id AND cct.name = 'cpufreq'
+)) count_w_dur
+JOIN cpu_counter_track cct
+ON track_id = cct.id;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
new file mode 100644
index 0000000..7f837a2
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/cpu/idle.sql
@@ -0,0 +1,53 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE counters.intervals;
+
+-- Counter information for each idle state change for each CPU. Finds each time
+-- region where a CPU idle state is constant.
+CREATE PERFETTO TABLE cpu_idle_counters(
+  -- Counter id.
+  id INT,
+  -- Joinable with 'counter_track.id'.
+  track_id INT,
+  -- Starting timestamp of the counter.
+  ts LONG,
+  -- Duration in which the counter is contant and idle state doesn't change.
+  dur INT,
+  -- Idle state of the CPU that corresponds to this counter. An idle state of -1
+  -- is defined to be active state for the CPU, and the larger the integer, the
+  -- deeper the idle state of the CPU. NULL if not found or undefined.
+  idle INT,
+  -- CPU that corresponds to this counter.
+  cpu INT
+)
+AS
+SELECT
+  count_w_dur.id,
+  count_w_dur.track_id,
+  count_w_dur.ts,
+  count_w_dur.dur,
+  IIF(count_w_dur.value = 4294967295, -1, count_w_dur.value) AS idle,
+  cct.cpu
+FROM
+counter_leading_intervals!((
+  SELECT c.*
+  FROM counter c
+  JOIN cpu_counter_track cct
+  ON cct.id = c.track_id and cct.name = 'cpuidle'
+)) count_w_dur
+JOIN cpu_counter_track cct
+ON track_id = cct.id;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
new file mode 100644
index 0000000..4167d8c
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("wattson") {
+  sources = [
+    "arm_dsu.sql",
+    "cpu_freq.sql",
+    "cpu_idle.sql",
+    "system_state.sql",
+  ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql
new file mode 100644
index 0000000..4f0572d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/arm_dsu.sql
@@ -0,0 +1,49 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- Converts event counter from count to rate (num of accesses per ns).
+CREATE PERFETTO FUNCTION _get_rate(event STRING)
+RETURNS TABLE(ts LONG, dur INT, access_rate INT)
+AS
+SELECT
+  ts,
+  lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur,
+  -- Rate of event accesses in a section (i.e. count / dur).
+  value / (lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts) AS access_rate
+FROM counter AS c
+JOIN counter_track AS t
+  ON c.track_id = t.id
+WHERE t.name = $event;
+
+-- The rate of L3 misses for each time slice based on the ARM DSU PMU counter's
+-- bus_access event. Units will be in number of L3 misses per ns. The number of
+-- accesses in a given duration can be calculated by multiplying the appropriate
+-- rate with the time in the window of interest.
+CREATE PERFETTO TABLE _arm_l3_miss_rate
+AS
+SELECT
+  ts, dur, access_rate AS l3_miss_rate
+FROM _get_rate("arm_dsu_0/bus_access/_cpu0");
+
+-- The rate of L3 accesses for each time slice based on the ARM DSU PMU
+-- counter's l3d_cache event. Units will be in number of DDR accesses per ns.
+-- The number of accesses in a given duration can be calculated by multiplying
+-- the appropriate rate with the time in the window of interest.
+CREATE PERFETTO TABLE _arm_l3_hit_rate
+AS
+SELECT
+  ts, dur, access_rate AS l3_hit_rate
+FROM _get_rate("arm_dsu_0/l3d_cache/_cpu0");
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
new file mode 100644
index 0000000..93738f6
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_freq.sql
@@ -0,0 +1,92 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE cpu.freq;
+
+-- Filters for CPU specific frequency slices
+CREATE PERFETTO FUNCTION _per_cpu_freq_slice(cpu_match INT)
+RETURNS TABLE(ts LONG, dur INT, freq INT)
+AS
+SELECT ts, dur, freq
+FROM cpu_freq_counters WHERE cpu = $cpu_match;
+
+-- _freq_slices_cpux has CPUx specific frequency slices.
+CREATE PERFETTO TABLE _freq_slices_cpu0
+AS
+SELECT ts, dur, freq AS freq_0 FROM _per_cpu_freq_slice(0);
+
+CREATE PERFETTO TABLE _freq_slices_cpu1
+AS
+SELECT ts, dur, freq AS freq_1 FROM _per_cpu_freq_slice(1);
+
+CREATE PERFETTO TABLE _freq_slices_cpu2
+AS
+SELECT ts, dur, freq AS freq_2 FROM _per_cpu_freq_slice(2);
+
+CREATE PERFETTO TABLE _freq_slices_cpu3
+AS
+SELECT ts, dur, freq AS freq_3 FROM _per_cpu_freq_slice(3);
+
+CREATE PERFETTO TABLE _freq_slices_cpu4
+AS
+SELECT ts, dur, freq AS freq_4 FROM _per_cpu_freq_slice(4);
+
+CREATE PERFETTO TABLE _freq_slices_cpu5
+AS
+SELECT ts, dur, freq AS freq_5 FROM _per_cpu_freq_slice(5);
+
+CREATE PERFETTO TABLE _freq_slices_cpu6
+AS
+SELECT ts, dur, freq AS freq_6 FROM _per_cpu_freq_slice(6);
+
+CREATE PERFETTO TABLE _freq_slices_cpu7
+AS
+SELECT ts, dur, freq AS freq_7 FROM _per_cpu_freq_slice(7);
+
+-- SPAN_OUTER_JOIN of all CPUs' frequency tables.
+CREATE VIRTUAL TABLE _freq_slices_cpu01
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu0, _freq_slices_cpu1);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu012
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu01, _freq_slices_cpu2);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu0123
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu012, _freq_slices_cpu3);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu01234
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu0123, _freq_slices_cpu4);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu012345
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu01234, _freq_slices_cpu5);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu0123456
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu012345, _freq_slices_cpu6);
+
+CREATE VIRTUAL TABLE _freq_slices_cpu01234567
+USING
+  SPAN_OUTER_JOIN(_freq_slices_cpu0123456, _freq_slices_cpu7);
+
+-- Table that holds time slices of the trace with the frequency transition
+-- information of every CPU in the system.
+CREATE PERFETTO TABLE _cpu_freq_all
+AS
+SELECT * FROM _freq_slices_cpu01234567;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
new file mode 100644
index 0000000..006c876
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/cpu_idle.sql
@@ -0,0 +1,160 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE android.device;
+INCLUDE PERFETTO MODULE cpu.idle;
+
+-- Device specific info for deep idle time offsets
+CREATE PERFETTO TABLE _device_cpu_deep_idle_offsets
+AS
+WITH data(device, cpu, offset_ns) AS (
+  VALUES
+  ("oriole", 6, 200000),
+  ("oriole", 7, 200000),
+  ("raven", 6, 200000),
+  ("raven", 7, 200000),
+  ("eos", 0, 450000),
+  ("eos", 1, 450000),
+  ("eos", 2, 450000),
+  ("eos", 3, 450000)
+)
+select * from data;
+
+-- Get the corresponding deep idle time offset based on device and CPU.
+CREATE PERFETTO FUNCTION _get_deep_idle_offset(cpu INT)
+RETURNS INT
+AS
+SELECT offset_ns
+FROM _device_cpu_deep_idle_offsets as offsets, android_device_name as device
+WHERE
+  offsets.device = device.name AND cpu = $cpu;
+
+-- Adjust duration of active portion to be slightly longer to account for
+-- overhead cost of transitioning out of deep idle. This is done because the
+-- device is active and consumes power for longer than the logs actually report.
+CREATE PERFETTO FUNCTION _adjust_deep_idle(cpu_match INT)
+RETURNS TABLE(ts LONG, dur INT, idle INT) AS
+WITH
+  idle_prev AS (
+    SELECT
+      ts,
+      dur,
+      idle,
+      lag(idle) OVER (PARTITION BY track_id ORDER BY ts) AS idle_prev,
+      cpu
+    FROM cpu_idle_counters
+  ),
+  offset_ns AS (
+    SELECT IFNULL(_get_deep_idle_offset($cpu_match), 0) as offset_ns
+  ),
+  -- Adjusted ts if applicable, which makes the current deep idle state
+  -- slightly shorter.
+  idle_mod AS (
+    SELECT
+      IIF(
+        idle_prev = -1 AND idle = 1,
+        IIF(dur > offset_ns, ts + offset_ns, ts + dur),
+        ts
+      ) as ts,
+      -- ts_next is the starting timestamp of the next slice (e.g. end ts of
+      -- current slice)
+      ts + dur as ts_next,
+      idle
+    FROM idle_prev, offset_ns
+    WHERE cpu = $cpu_match
+  )
+SELECT
+  ts,
+  lead(ts, 1, trace_end()) OVER (ORDER by ts) - ts as dur,
+  idle
+FROM idle_mod
+WHERE ts != ts_next;
+
+-- idle_slices_cpux has CPUx specific idle state slices.
+CREATE PERFETTO TABLE _idle_slices_cpu0
+AS
+SELECT idle as idle_0, ts, dur
+FROM _adjust_deep_idle(0);
+
+CREATE PERFETTO TABLE _idle_slices_cpu1
+AS
+SELECT idle as idle_1, ts, dur
+FROM _adjust_deep_idle(1);
+
+CREATE PERFETTO TABLE _idle_slices_cpu2
+AS
+SELECT idle as idle_2, ts, dur
+FROM _adjust_deep_idle(2);
+
+CREATE PERFETTO TABLE _idle_slices_cpu3
+AS
+SELECT idle as idle_3, ts, dur
+FROM _adjust_deep_idle(3);
+
+CREATE PERFETTO TABLE _idle_slices_cpu4
+AS
+SELECT idle as idle_4, ts, dur
+FROM _adjust_deep_idle(4);
+
+CREATE PERFETTO TABLE _idle_slices_cpu5
+AS
+SELECT idle as idle_5, ts, dur
+FROM _adjust_deep_idle(5);
+
+CREATE PERFETTO TABLE _idle_slices_cpu6
+AS
+SELECT idle as idle_6, ts, dur
+FROM _adjust_deep_idle(6);
+
+CREATE PERFETTO TABLE _idle_slices_cpu7
+AS
+SELECT idle as idle_7, ts, dur
+FROM _adjust_deep_idle(7);
+
+-- SPAN_OUTER_JOIN of all CPUs' idle state tables.
+CREATE VIRTUAL TABLE _idle_slices_cpu01
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu0, _idle_slices_cpu1);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu012
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu01, _idle_slices_cpu2);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu0123
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu012, _idle_slices_cpu3);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu01234
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu0123, _idle_slices_cpu4);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu012345
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu01234, _idle_slices_cpu5);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu0123456
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu012345, _idle_slices_cpu6);
+
+CREATE VIRTUAL TABLE _idle_slices_cpu01234567
+USING
+  SPAN_OUTER_JOIN(_idle_slices_cpu0123456, _idle_slices_cpu7);
+
+-- Table that holds time slices of the entire trace with the idle state
+-- transition information of every CPU in the system.
+CREATE PERFETTO TABLE _cpu_idle_all
+AS
+SELECT * FROM _idle_slices_cpu01234567;
+
diff --git a/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
new file mode 100644
index 0000000..63eeb7a
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/wattson/system_state.sql
@@ -0,0 +1,129 @@
+--
+-- Copyright 2024 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+INCLUDE PERFETTO MODULE time.conversion;
+INCLUDE PERFETTO MODULE wattson.arm_dsu;
+INCLUDE PERFETTO MODULE wattson.cpu_freq;
+INCLUDE PERFETTO MODULE wattson.cpu_idle;
+
+-- Combines idle and freq tables of all CPUs to create system state.
+CREATE VIRTUAL TABLE _idle_freq_slice
+USING
+  SPAN_OUTER_JOIN(_cpu_freq_all, _cpu_idle_all);
+
+-- get suspend resume state as logged by ftrace.
+CREATE PERFETTO TABLE _suspend_slice
+AS
+SELECT
+  ts, dur, TRUE AS suspended
+FROM slice
+WHERE name GLOB "timekeeping_freeze(0)";
+
+-- Combine suspend information with CPU idle and frequency system states.
+CREATE VIRTUAL TABLE _idle_freq_suspend_slice
+USING
+  SPAN_OUTER_JOIN(_idle_freq_slice, _suspend_slice);
+
+-- Add extra column indicating that idle and frequency info are present before
+-- SPAN_OUTER_JOIN with the DSU PMU counters.
+CREATE PERFETTO TABLE _idle_freq_filtered
+AS
+SELECT *, TRUE AS has_idle_freq
+FROM _idle_freq_suspend_slice
+WHERE freq_0 GLOB '*[0-9]*';
+
+-- Combine system state so that it has idle, freq, and L3 hit info.
+CREATE VIRTUAL TABLE _idle_freq_l3_hit_slice
+USING
+  SPAN_OUTER_JOIN(_idle_freq_filtered, _arm_l3_hit_rate);
+
+-- Combine system state so that it has idle, freq, L3 hit, and L3 miss info.
+CREATE VIRTUAL TABLE _idle_freq_l3_hit_l3_miss_slice
+USING
+  SPAN_OUTER_JOIN(_idle_freq_l3_hit_slice, _arm_l3_miss_rate);
+
+-- The final system state for the CPU subsystem, which has all the information
+-- needed by Wattson to estimate energy for the CPU subsystem.
+CREATE PERFETTO TABLE wattson_system_states(
+  -- Starting timestamp of the current counter where system state is constant.
+  ts LONG,
+  -- Duration of the current counter where system state is constant.
+  dur INT,
+  -- Number of L3 hits the current system state.
+  l3_hit_count INT,
+  -- Number of L3 misses in the current system state.
+  l3_miss_count INT,
+  -- Frequency of CPU0.
+  freq_0 INT,
+  -- Idle state of CPU0.
+  idle_0 INT,
+  -- Frequency of CPU1.
+  freq_1 INT,
+  -- Idle state of CPU1.
+  idle_1 INT,
+  -- Frequency of CPU2.
+  freq_2 INT,
+  -- Idle state of CPU2.
+  idle_2 INT,
+  -- Frequency of CPU3.
+  freq_3 INT,
+  -- Idle state of CPU3.
+  idle_3 INT,
+  -- Frequency of CPU4.
+  freq_4 INT,
+  -- Idle state of CPU4.
+  idle_4 INT,
+  -- Frequency of CPU5.
+  freq_5 INT,
+  -- Idle state of CPU5.
+  idle_5 INT,
+  -- Frequency of CPU6.
+  freq_6 INT,
+  -- Idle state of CPU6.
+  idle_6 INT,
+  -- Frequency of CPU7.
+  freq_7 INT,
+  -- Idle state of CPU7.
+  idle_7 INT,
+  -- Flag indicating if current system state is suspended.
+  suspended BOOL
+)
+AS
+SELECT
+  ts,
+  dur,
+  cast_int!(round(l3_hit_rate * dur, 0)) as l3_hit_count,
+  cast_int!(round(l3_miss_rate * dur, 0)) as l3_miss_count,
+  freq_0,
+  idle_0,
+  freq_1,
+  idle_1,
+  freq_2,
+  idle_2,
+  freq_3,
+  idle_3,
+  freq_4,
+  idle_4,
+  freq_5,
+  idle_5,
+  freq_6,
+  idle_6,
+  freq_7,
+  idle_7,
+  IFNULL(suspended, FALSE) as suspended
+FROM _idle_freq_l3_hit_l3_miss_slice
+-- Needs to be at least 1us to reduce inconsequential rows.
+WHERE dur > time_from_us(1) and has_idle_freq IS NOT NULL;
+
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index 8f553c1..902c9d8 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -21,7 +21,6 @@
     "db_sqlite_table.cc",
     "db_sqlite_table.h",
     "module_lifecycle_manager.h",
-    "query_cache.h",
     "scoped_db.h",
     "sql_source.cc",
     "sql_source.h",
@@ -29,8 +28,6 @@
     "sql_stats_table.h",
     "sqlite_engine.cc",
     "sqlite_engine.h",
-    "sqlite_table.cc",
-    "sqlite_table.h",
     "sqlite_tokenizer.cc",
     "sqlite_tokenizer.h",
     "sqlite_utils.cc",
@@ -40,7 +37,6 @@
     "stats_table.h",
   ]
   deps = [
-    ":query_constraints",
     "..:metatrace",
     "../../../gn:default_deps",
     "../../../gn:sqlite",
@@ -64,29 +60,15 @@
   public_deps = [ "bindings" ]
 }
 
-source_set("query_constraints") {
-  sources = [
-    "query_constraints.cc",
-    "query_constraints.h",
-  ]
-  deps = [
-    "../../../gn:default_deps",
-    "../../../gn:sqlite",
-    "../../base",
-  ]
-}
-
 perfetto_unittest_source_set("unittests") {
   testonly = true
   sources = [
     "db_sqlite_table_unittest.cc",
-    "query_constraints_unittest.cc",
     "sql_source_unittest.cc",
     "sqlite_tokenizer_unittest.cc",
     "sqlite_utils_unittest.cc",
   ]
   deps = [
-    ":query_constraints",
     ":sqlite",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
@@ -94,6 +76,7 @@
     "../../../include/perfetto/trace_processor:basic_types",
     "../../base",
     "../../base:test_support",
+    "../db",
   ]
 }
 
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 8c1d9a0..eb61280 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -21,42 +21,39 @@
 #include <cmath>
 #include <cstddef>
 #include <cstdint>
-#include <functional>
 #include <iterator>
-#include <limits>
 #include <memory>
+#include <numeric>
 #include <optional>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/small_vector.h"
 #include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/runtime_table.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/query_cache.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/tp_metatrace.h"
 #include "src/trace_processor/util/regex.h"
-#include "src/trace_processor/util/status_macros.h"
 
 #include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
 
 namespace perfetto::trace_processor {
 namespace {
 
-static constexpr uint32_t kInvalidArgumentIndex =
-    std::numeric_limits<uint32_t>::max();
-
 std::optional<FilterOp> SqliteOpToFilterOp(int sqlite_op) {
   switch (sqlite_op) {
     case SQLITE_INDEX_CONSTRAINT_EQ:
@@ -94,37 +91,6 @@
   }
 }
 
-SqlValue SqliteValueToSqlValue(sqlite3_value* sqlite_val) {
-  auto col_type = sqlite3_value_type(sqlite_val);
-  SqlValue value;
-  switch (col_type) {
-    case SQLITE_INTEGER:
-      value.type = SqlValue::kLong;
-      value.long_value = sqlite3_value_int64(sqlite_val);
-      break;
-    case SQLITE_TEXT:
-      value.type = SqlValue::kString;
-      value.string_value =
-          reinterpret_cast<const char*>(sqlite3_value_text(sqlite_val));
-      break;
-    case SQLITE_FLOAT:
-      value.type = SqlValue::kDouble;
-      value.double_value = sqlite3_value_double(sqlite_val);
-      break;
-    case SQLITE_BLOB:
-      value.type = SqlValue::kBytes;
-      value.bytes_value = sqlite3_value_blob(sqlite_val);
-      value.bytes_count = static_cast<size_t>(sqlite3_value_bytes(sqlite_val));
-      break;
-    case SQLITE_NULL:
-      value.type = SqlValue::kNull;
-      break;
-    default:
-      PERFETTO_FATAL("Unexpected sqlite3_value type");
-  }
-  return value;
-}
-
 class SafeStringWriter {
  public:
   void AppendString(const char* s) {
@@ -140,83 +106,23 @@
   }
 
   base::StringView GetStringView() const {
-    return base::StringView(buffer_.data(), buffer_.size());
+    return {buffer_.data(), buffer_.size()};
   }
 
  private:
   base::SmallVector<char, 2048> buffer_;
 };
 
-base::Status ValidateTableFunctionArguments(const std::string& name,
-                                            const Table::Schema& schema,
-                                            const QueryConstraints& qc) {
-  for (uint32_t i = 0; i < schema.columns.size(); ++i) {
-    if (!schema.columns[i].is_hidden) {
-      continue;
+std::string CreateTableStatementFromSchema(const Table::Schema& schema,
+                                           const char* table_name) {
+  std::string stmt = "CREATE TABLE x(";
+  for (const auto& col : schema.columns) {
+    std::string c =
+        col.name + " " + sqlite::utils::SqlValueTypeToString(col.type);
+    if (col.is_hidden) {
+      c += " HIDDEN";
     }
-    auto pred = [i](const QueryConstraints::Constraint& c) {
-      return i == static_cast<uint32_t>(c.column);
-    };
-    auto it =
-        std::find_if(qc.constraints().begin(), qc.constraints().end(), pred);
-    if (it == qc.constraints().end()) {
-      return base::ErrStatus(
-          "Failed to find constraint on column '%u' in function %s", i,
-          name.c_str());
-    }
-
-    // Arguments should always use equality constraints.
-    if (it->op != SQLITE_INDEX_CONSTRAINT_EQ) {
-      return base::ErrStatus(
-          "Only equality constraints supported on column '%u'", i);
-    }
-
-    // Disallow multiple constraints on an argument column.
-    auto count = std::count_if(it + 1, qc.constraints().end(), pred);
-    if (count > 0) {
-      return base::ErrStatus(
-          "Found multiple constraints on column '%u' in function %s", i,
-          name.c_str());
-    }
-  }
-  return base::OkStatus();
-}
-
-}  // namespace
-
-DbSqliteTable::DbSqliteTable(sqlite3*, Context* context) : context_(context) {}
-DbSqliteTable::~DbSqliteTable() {
-  if (context_->computation == DbSqliteTableContext::Computation::kRuntime) {
-    context_->erase_runtime_table(name());
-  }
-}
-
-base::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) {
-  switch (context_->computation) {
-    case TableComputation::kStatic:
-      schema_ = context_->static_schema;
-      break;
-    case TableComputation::kRuntime:
-      runtime_table_ = context_->get_runtime_table(name());
-      PERFETTO_CHECK(runtime_table_);
-      PERFETTO_CHECK(!runtime_table_->columns().empty());
-      schema_ = runtime_table_->schema();
-      break;
-    case TableComputation::kTableFunction:
-      schema_ = context_->static_table_function->CreateSchema();
-      break;
-  }
-  *schema = ComputeSchema(schema_, name().c_str());
-  return base::OkStatus();
-}
-
-SqliteTableLegacy::Schema DbSqliteTable::ComputeSchema(
-    const Table::Schema& schema,
-    const char* table_name) {
-  std::vector<SqliteTableLegacy::Column> schema_cols;
-  for (uint32_t i = 0; i < schema.columns.size(); ++i) {
-    const auto& col = schema.columns[i];
-    schema_cols.emplace_back(i, col.name, col.type, col.is_hidden);
+    stmt += c + ",";
   }
 
   auto it =
@@ -227,418 +133,118 @@
         "id column not found in %s. All tables need to contain an id column;",
         table_name);
   }
-
-  std::vector<size_t> primary_keys;
-  primary_keys.emplace_back(std::distance(schema.columns.begin(), it));
-  return Schema(std::move(schema_cols), std::move(primary_keys));
+  stmt += "PRIMARY KEY(" + it->name + ")";
+  stmt += ") WITHOUT ROWID;";
+  return stmt;
 }
 
-int DbSqliteTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
-  switch (context_->computation) {
-    case TableComputation::kStatic:
-      BestIndex(schema_, context_->static_table->row_count(), qc, info);
-      break;
-    case TableComputation::kRuntime:
-      BestIndex(schema_, runtime_table_->row_count(), qc, info);
-      break;
-    case TableComputation::kTableFunction:
-      base::Status status = ValidateTableFunctionArguments(name(), schema_, qc);
-      if (!status.ok()) {
-        // TODO(lalitm): instead of returning SQLITE_CONSTRAINT which shows the
-        // user a very cryptic error message, consider instead SQLITE_OK but
-        // with a very high (~infinite) cost. If SQLite still chose the query
-        // plan after that, we can throw a proper error message in xFilter.
-        return SQLITE_CONSTRAINT;
+base::StatusOr<SqlValue> SqliteValueToSqlValueChecked(sqlite3_value* value,
+                                                      const Constraint& cs) {
+  SqlValue v = sqlite::utils::SqliteValueToSqlValue(value);
+  if constexpr (regex::IsRegexSupported()) {
+    if (cs.op == FilterOp::kRegex) {
+      if (cs.value.type != SqlValue::kString) {
+        return base::ErrStatus("Value has to be a string");
       }
-      BestIndex(schema_, context_->static_table_function->EstimateRowCount(),
-                qc, info);
-      break;
+      if (auto st = regex::Regex::Create(cs.value.AsString()); !st.ok()) {
+        return st.status();
+      }
+    }
+  }
+  return v;
+}
+
+int UpdateConstraintsAndOrderByFromIndex(DbSqliteModule::Cursor* c,
+                                         const char* idx_str,
+                                         sqlite3_value** argv) {
+  base::StringSplitter splitter(idx_str, ',');
+  PERFETTO_CHECK(splitter.Next());
+  PERFETTO_DCHECK(splitter.cur_token_size() >= 2);
+  PERFETTO_DCHECK(splitter.cur_token()[0] == 'C');
+
+  uint32_t cs_count = *base::CStringToUInt32(splitter.cur_token() + 1);
+
+  // We reuse this vector to reduce memory allocations on nested subqueries.
+  uint32_t c_offset = 0;
+  c->constraints.resize(cs_count);
+  for (auto& cs : c->constraints) {
+    PERFETTO_CHECK(splitter.Next());
+    cs.col_idx = *base::CStringToUInt32(splitter.cur_token());
+    PERFETTO_CHECK(splitter.Next());
+    cs.op = static_cast<FilterOp>(*base::CStringToUInt32(splitter.cur_token()));
+
+    auto value_or = SqliteValueToSqlValueChecked(argv[c_offset++], cs);
+    if (!value_or.ok()) {
+      return sqlite::utils::SetError(c->pVtab, value_or.status().c_message());
+    }
+    cs.value = *value_or;
+  }
+
+  PERFETTO_CHECK(splitter.Next());
+  PERFETTO_DCHECK(splitter.cur_token_size() >= 2);
+  PERFETTO_DCHECK(splitter.cur_token()[0] == 'O');
+
+  uint32_t ob_count = *base::CStringToUInt32(splitter.cur_token() + 1);
+
+  // We reuse this vector to reduce memory allocations on nested subqueries.
+  c->orders.resize(ob_count);
+  for (auto& ob : c->orders) {
+    PERFETTO_CHECK(splitter.Next());
+    ob.col_idx = *base::CStringToUInt32(splitter.cur_token());
+    PERFETTO_CHECK(splitter.Next());
+    ob.desc = *base::CStringToUInt32(splitter.cur_token());
   }
   return SQLITE_OK;
 }
 
-void DbSqliteTable::BestIndex(const Table::Schema& schema,
-                              uint32_t row_count,
-                              const QueryConstraints& qc,
-                              BestIndexInfo* info) {
-  auto cost_and_rows = EstimateCost(schema, row_count, qc);
-  info->estimated_cost = cost_and_rows.cost;
-  info->estimated_rows = cost_and_rows.rows;
-
-  const auto& cs = qc.constraints();
-  for (uint32_t i = 0; i < cs.size(); ++i) {
-    // SqliteOpToFilterOp will return std::nullopt for any constraint which we
-    // don't support filtering ourselves. Only omit filtering by SQLite when we
-    // can handle filtering.
-    std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs[i].op);
-    info->sqlite_omit_constraint[i] = opt_op.has_value();
-  }
-
-  // We can sort on any column correctly.
-  info->sqlite_omit_order_by = true;
-}
-
-base::Status DbSqliteTable::ModifyConstraints(QueryConstraints* qc) {
-  ModifyConstraints(schema_, qc);
-  return base::OkStatus();
-}
-
-void DbSqliteTable::ModifyConstraints(const Table::Schema& schema,
-                                      QueryConstraints* qc) {
-  using C = QueryConstraints::Constraint;
-
-  // Reorder constraints to consider the constraints on columns which are
-  // cheaper to filter first.
-  auto* cs = qc->mutable_constraints();
-  std::sort(cs->begin(), cs->end(), [&schema](const C& a, const C& b) {
-    uint32_t a_idx = static_cast<uint32_t>(a.column);
-    uint32_t b_idx = static_cast<uint32_t>(b.column);
-    const auto& a_col = schema.columns[a_idx];
-    const auto& b_col = schema.columns[b_idx];
-
-    // Id columns are always very cheap to filter on so try and get them
-    // first.
-    if (a_col.is_id || b_col.is_id)
-      return a_col.is_id && !b_col.is_id;
-
-    // Set id columns are always very cheap to filter on so try and get them
-    // second.
-    if (a_col.is_set_id || b_col.is_set_id)
-      return a_col.is_set_id && !b_col.is_set_id;
-
-    // Sorted columns are also quite cheap to filter so order them after
-    // any id/set id columns.
-    if (a_col.is_sorted || b_col.is_sorted)
-      return a_col.is_sorted && !b_col.is_sorted;
-
-    // TODO(lalitm): introduce more orderings here based on empirical data.
-    return false;
-  });
-
-  // Remove any order by constraints which also have an equality constraint.
-  auto* ob = qc->mutable_order_by();
-  {
-    auto p = [&cs](const QueryConstraints::OrderBy& o) {
-      auto inner_p = [&o](const QueryConstraints::Constraint& c) {
-        return c.column == o.iColumn && sqlite::utils::IsOpEq(c.op);
-      };
-      return std::any_of(cs->begin(), cs->end(), inner_p);
-    };
-    auto remove_it = std::remove_if(ob->begin(), ob->end(), p);
-    ob->erase(remove_it, ob->end());
-  }
-
-  // Go through the order by constraints in reverse order and eliminate
-  // constraints until the first non-sorted column or the first order by in
-  // descending order.
-  {
-    auto p = [&schema](const QueryConstraints::OrderBy& o) {
-      const auto& col = schema.columns[static_cast<uint32_t>(o.iColumn)];
-      return o.desc || !col.is_sorted;
-    };
-    auto first_non_sorted_it = std::find_if(ob->rbegin(), ob->rend(), p);
-    auto pop_count = std::distance(ob->rbegin(), first_non_sorted_it);
-    ob->resize(ob->size() - static_cast<uint32_t>(pop_count));
-  }
-}
-
-DbSqliteTable::QueryCost DbSqliteTable::EstimateCost(
+PERFETTO_ALWAYS_INLINE void TryCacheCreateSortedTable(
+    DbSqliteModule::Cursor* cursor,
     const Table::Schema& schema,
-    uint32_t row_count,
-    const QueryConstraints& qc) {
-  // Currently our cost estimation algorithm is quite simplistic but is good
-  // enough for the simplest cases.
-  // TODO(lalitm): replace hardcoded constants with either more heuristics
-  // based on the exact type of constraint or profiling the queries themselves.
-
-  // We estimate the fixed cost of set-up and tear-down of a query in terms of
-  // the number of rows scanned.
-  constexpr double kFixedQueryCost = 1000.0;
-
-  // Setup the variables for estimating the number of rows we will have at the
-  // end of filtering. Note that |current_row_count| should always be at least 1
-  // unless we are absolutely certain that we will return no rows as otherwise
-  // SQLite can make some bad choices.
-  uint32_t current_row_count = row_count;
-
-  // If the table is empty, any constraint set only pays the fixed cost. Also we
-  // can return 0 as the row count as we are certain that we will return no
-  // rows.
-  if (current_row_count == 0)
-    return QueryCost{kFixedQueryCost, 0};
-
-  // Setup the variables for estimating the cost of filtering.
-  double filter_cost = 0.0;
-  const auto& cs = qc.constraints();
-  for (const auto& c : cs) {
-    if (current_row_count < 2)
-      break;
-    const auto& col_schema = schema.columns[static_cast<uint32_t>(c.column)];
-    if (sqlite::utils::IsOpEq(c.op) && col_schema.is_id) {
-      // If we have an id equality constraint, we can very efficiently filter
-      // down to a single row in C++. However, if we're joining with another
-      // table, SQLite will do this once per row which can be extremely
-      // expensive because of all the virtual table (which is implemented using
-      // virtual function calls) machinery. Indicate this by saying that an
-      // entire filter call is ~10x the cost of iterating a single row.
-      filter_cost += 10;
-      current_row_count = 1;
-    } else if (sqlite::utils::IsOpEq(c.op)) {
-      // If there is only a single equality constraint, we have special logic
-      // to sort by that column and then binary search if we see the constraint
-      // set often. Model this by dividing by the log of the number of rows as
-      // a good approximation. Otherwise, we'll need to do a full table scan.
-      // Alternatively, if the column is sorted, we can use the same binary
-      // search logic so we have the same low cost (even better because we don't
-      // have to sort at all).
-      filter_cost += cs.size() == 1 || col_schema.is_sorted
-                         ? log2(current_row_count)
-                         : current_row_count;
-
-      // As an extremely rough heuristic, assume that an equalty constraint will
-      // cut down the number of rows by approximately double log of the number
-      // of rows.
-      double estimated_rows = current_row_count / (2 * log2(current_row_count));
-      current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
-    } else if (col_schema.is_sorted &&
-               (sqlite::utils::IsOpLe(c.op) || sqlite::utils::IsOpLt(c.op) ||
-                sqlite::utils::IsOpGt(c.op) || sqlite::utils::IsOpGe(c.op))) {
-      // On a sorted column, if we see any partition constraints, we can do this
-      // filter very efficiently. Model this using the log of the  number of
-      // rows as a good approximation.
-      filter_cost += log2(current_row_count);
-
-      // As an extremely rough heuristic, assume that an partition constraint
-      // will cut down the number of rows by approximately double log of the
-      // number of rows.
-      double estimated_rows = current_row_count / (2 * log2(current_row_count));
-      current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
-    } else {
-      // Otherwise, we will need to do a full table scan and we estimate we will
-      // maybe (at best) halve the number of rows.
-      filter_cost += current_row_count;
-      current_row_count = std::max(current_row_count / 2u, 1u);
-    }
-  }
-
-  // Now, to figure out the cost of sorting, multiply the final row count
-  // by |qc.order_by().size()| * log(row count). This should act as a crude
-  // estimation of the cost.
-  double sort_cost =
-      static_cast<double>(qc.order_by().size() * current_row_count) *
-      log2(current_row_count);
-
-  // The cost of iterating rows is more expensive than just filtering the rows
-  // so multiply by an appropriate factor.
-  double iteration_cost = current_row_count * 2.0;
-
-  // To get the final cost, add up all the individual components.
-  double final_cost =
-      kFixedQueryCost + filter_cost + sort_cost + iteration_cost;
-  return QueryCost{final_cost, current_row_count};
-}
-
-std::unique_ptr<SqliteTableLegacy::BaseCursor> DbSqliteTable::CreateCursor() {
-  return std::make_unique<Cursor>(this, context_->cache);
-}
-
-DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
-    : SqliteTableLegacy::BaseCursor(sqlite_table),
-      db_sqlite_table_(sqlite_table),
-      cache_(cache) {
-  switch (db_sqlite_table_->context_->computation) {
-    case TableComputation::kStatic:
-      upstream_table_ = db_sqlite_table_->context_->static_table;
-      argument_index_per_column_.resize(sqlite_table->schema_.columns.size(),
-                                        kInvalidArgumentIndex);
-      break;
-    case TableComputation::kRuntime:
-      upstream_table_ = db_sqlite_table_->runtime_table_;
-      argument_index_per_column_.resize(sqlite_table->schema_.columns.size(),
-                                        kInvalidArgumentIndex);
-      break;
-    case TableComputation::kTableFunction: {
-      uint32_t argument_index = 0;
-      for (const auto& col : sqlite_table->schema_.columns) {
-        argument_index_per_column_.emplace_back(
-            col.is_hidden ? argument_index++ : kInvalidArgumentIndex);
-      }
-      table_function_arguments_.resize(argument_index);
-      break;
-    }
-  }
-}
-DbSqliteTable::Cursor::~Cursor() = default;
-
-void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
-    const QueryConstraints& qc,
-    FilterHistory history) {
-  // Check if we have a cache. Some subclasses (e.g. the flamegraph table) may
-  // pass nullptr to disable caching.
-  if (!cache_)
-    return;
-
-  if (history == FilterHistory::kDifferent) {
-    repeated_cache_count_ = 0;
-
-    // Check if the new constraint set is cached by another cursor.
-    sorted_cache_table_ =
-        cache_->GetIfCached(upstream_table_, qc.constraints());
+    bool is_same_idx) {
+  if (!is_same_idx) {
+    cursor->repeated_cache_count = 0;
     return;
   }
 
-  PERFETTO_DCHECK(history == FilterHistory::kSame);
-
-  // TODO(lalitm): all of the caching policy below should live in QueryCache and
-  // not here. This is only here temporarily to allow migration of sched without
-  // regressing UI performance and should be removed ASAP.
-
-  // Only try and create the cached table on exactly the third time we see this
-  // constraint set.
+  // Only try and create the cached table on exactly the third time we see
+  // this constraint set.
   constexpr uint32_t kRepeatedThreshold = 3;
-  if (sorted_cache_table_ || repeated_cache_count_++ != kRepeatedThreshold)
+  if (cursor->sorted_cache_table ||
+      cursor->repeated_cache_count++ != kRepeatedThreshold) {
     return;
+  }
 
   // If we have more than one constraint, we can't cache the table using
   // this method.
-  if (qc.constraints().size() != 1)
+  if (cursor->constraints.size() != 1) {
     return;
+  }
 
   // If the constraing is not an equality constraint, there's little
   // benefit to caching
-  const auto& c = qc.constraints().front();
-  if (!sqlite::utils::IsOpEq(c.op))
+  const auto& c = cursor->constraints.front();
+  if (c.op != FilterOp::kEq) {
     return;
+  }
 
   // If the column is already sorted, we don't need to cache at all.
-  auto col = static_cast<uint32_t>(c.column);
-  if (db_sqlite_table_->schema_.columns[col].is_sorted)
+  if (schema.columns[c.col_idx].is_sorted) {
     return;
+  }
 
   // Try again to get the result or start caching it.
-  sorted_cache_table_ =
-      cache_->GetOrCache(upstream_table_, qc.constraints(), [this, col]() {
-        return upstream_table_->Sort({Order{col, false}});
-      });
+  cursor->sorted_cache_table =
+      cursor->upstream_table->Sort({Order{c.col_idx, false}});
 }
 
-base::Status DbSqliteTable::Cursor::Filter(const QueryConstraints& qc,
-                                           sqlite3_value** argv,
-                                           FilterHistory history) {
-  // Clear out the iterator before filtering to ensure the destructor is run
-  // before the table's destructor.
-  iterator_ = std::nullopt;
-
-  RETURN_IF_ERROR(PopulateConstraintsAndArguments(qc, argv));
-  PopulateOrderBys(qc);
-
-  // Setup the upstream table based on the computation state.
-  switch (db_sqlite_table_->context_->computation) {
-    case TableComputation::kStatic:
-    case TableComputation::kRuntime:
-      // Tries to create a sorted cached table which can be used to speed up
-      // filters below.
-      TryCacheCreateSortedTable(qc, history);
-      break;
-    case TableComputation::kTableFunction: {
-      PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
-                        "TABLE_FUNCTION_CALL", [this](metatrace::Record* r) {
-                          r->AddArg("Name", db_sqlite_table_->name());
-                        });
-      base::StatusOr<std::unique_ptr<Table>> table =
-          db_sqlite_table_->context_->static_table_function->ComputeTable(
-              table_function_arguments_);
-      if (!table.ok()) {
-        return base::ErrStatus("%s: %s", db_sqlite_table_->name().c_str(),
-                               table.status().c_message());
-      }
-      dynamic_table_ = std::move(*table);
-      upstream_table_ = dynamic_table_.get();
-      break;
-    }
-  }
-
-  PERFETTO_TP_TRACE(
-      metatrace::Category::QUERY_DETAILED, "DB_TABLE_FILTER_AND_SORT",
-      [this](metatrace::Record* r) { FilterAndSortMetatrace(r); });
-
-  RowMap filter_map = SourceTable()->QueryToRowMap(constraints_, orders_);
-  if (filter_map.IsRange() && filter_map.size() <= 1) {
-    // Currently, our criteria where we have a special fast path is if it's
-    // a single ranged row. We have this fast path for joins on id columns
-    // where we get repeated queries filtering down to a single row. The
-    // other path performs allocations when creating the new table as well
-    // as the iterator on the new table whereas this path only uses a single
-    // number and lives entirely on the stack.
-
-    // TODO(lalitm): investigate some other criteria where it is beneficial
-    // to have a fast path and expand to them.
-    mode_ = Mode::kSingleRow;
-    single_row_ = filter_map.size() == 1 ? std::make_optional(filter_map.Get(0))
-                                         : std::nullopt;
-    eof_ = !single_row_.has_value();
-  } else {
-    mode_ = Mode::kTable;
-    iterator_ = SourceTable()->ApplyAndIterateRows(std::move(filter_map));
-    eof_ = !*iterator_;
-  }
-  return base::OkStatus();
-}
-
-base::Status DbSqliteTable::Cursor::PopulateConstraintsAndArguments(
-    const QueryConstraints& qc,
-    sqlite3_value** argv) {
-  // We reuse this vector to reduce memory allocations on nested subqueries.
-  constraints_.resize(qc.constraints().size());
-  uint32_t constraints_pos = 0;
-  for (size_t i = 0; i < qc.constraints().size(); ++i) {
-    const auto& cs = qc.constraints()[i];
-    auto col = static_cast<uint32_t>(cs.column);
-
-    // If we get a std::nullopt FilterOp, that means we should allow SQLite
-    // to handle the constraint.
-    std::optional<FilterOp> opt_op = SqliteOpToFilterOp(cs.op);
-    if (!opt_op)
-      continue;
-
-    SqlValue value = SqliteValueToSqlValue(argv[i]);
-    if constexpr (regex::IsRegexSupported()) {
-      if (*opt_op == FilterOp::kRegex) {
-        if (value.type != SqlValue::kString)
-          return base::ErrStatus("Value has to be a string");
-
-        if (auto regex_status = regex::Regex::Create(value.AsString());
-            !regex_status.ok())
-          return regex_status.status();
-      }
-    }
-
-    uint32_t argument_index = argument_index_per_column_[col];
-    if (argument_index == kInvalidArgumentIndex) {
-      constraints_[constraints_pos++] = Constraint{col, *opt_op, value};
-    } else {
-      table_function_arguments_[argument_index] = value;
-    }
-  }
-  constraints_.resize(constraints_pos);
-  return base::OkStatus();
-}
-
-void DbSqliteTable::Cursor::PopulateOrderBys(const QueryConstraints& qc) {
-  // We reuse this vector to reduce memory allocations on nested subqueries.
-  orders_.resize(qc.order_by().size());
-  for (size_t i = 0; i < qc.order_by().size(); ++i) {
-    const auto& ob = qc.order_by()[i];
-    auto col = static_cast<uint32_t>(ob.iColumn);
-    orders_[i] = Order{col, static_cast<bool>(ob.desc)};
-  }
-}
-
-void DbSqliteTable::Cursor::FilterAndSortMetatrace(metatrace::Record* r) {
-  r->AddArg("Table", db_sqlite_table_->name());
-  for (const Constraint& c : constraints_) {
+void FilterAndSortMetatrace(const std::string& table_name,
+                            const Table::Schema& schema,
+                            DbSqliteModule::Cursor* cursor,
+                            metatrace::Record* r) {
+  r->AddArg("Table", table_name);
+  for (const Constraint& c : cursor->constraints) {
     SafeStringWriter writer;
-    writer.AppendString(db_sqlite_table_->schema_.columns[c.col_idx].name);
+    writer.AppendString(schema.columns[c.col_idx].name);
 
     writer.AppendString(" ");
     switch (c.op) {
@@ -697,37 +303,517 @@
     r->AddArg("Constraint", writer.GetStringView());
   }
 
-  for (const auto& o : orders_) {
+  for (const auto& o : cursor->orders) {
     SafeStringWriter writer;
-    writer.AppendString(db_sqlite_table_->schema_.columns[o.col_idx].name);
+    writer.AppendString(schema.columns[o.col_idx].name);
     if (o.desc)
       writer.AppendString(" desc");
     r->AddArg("Order by", writer.GetStringView());
   }
 }
 
-DbSqliteTableContext::DbSqliteTableContext(QueryCache* query_cache,
-                                           const Table* table,
-                                           Table::Schema schema)
-    : cache(query_cache),
-      computation(Computation::kStatic),
-      static_table(table),
-      static_schema(std::move(schema)) {}
+}  // namespace
 
-DbSqliteTableContext::DbSqliteTableContext(
-    QueryCache* query_cache,
-    std::function<RuntimeTable*(std::string)> get_table,
-    std::function<void(std::string)> erase_table)
-    : cache(query_cache),
-      computation(Computation::kRuntime),
-      get_runtime_table(std::move(get_table)),
-      erase_runtime_table(std::move(erase_table)) {}
+int DbSqliteModule::Create(sqlite3* db,
+                           void* ctx,
+                           int argc,
+                           const char* const* argv,
+                           sqlite3_vtab** vtab,
+                           char**) {
+  PERFETTO_CHECK(argc == 3);
+  auto* context = GetContext(ctx);
+  auto state = std::move(context->temporary_create_state);
+  PERFETTO_CHECK(state);
 
-DbSqliteTableContext::DbSqliteTableContext(
-    QueryCache* query_cache,
-    std::unique_ptr<StaticTableFunction> table)
-    : cache(query_cache),
-      computation(Computation::kTableFunction),
-      static_table_function(std::move(table)) {}
+  std::string sql = CreateTableStatementFromSchema(state->schema, argv[2]);
+  if (int ret = sqlite3_declare_vtab(db, sql.c_str()); ret != SQLITE_OK) {
+    return ret;
+  }
+  std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+  res->state = context->manager.OnCreate(argv, std::move(state));
+  res->table_name = argv[2];
+  *vtab = res.release();
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Destroy(sqlite3_vtab* vtab) {
+  auto* t = GetVtab(vtab);
+  auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+  if (s->computation == TableComputation::kStatic) {
+    // SQLite does not read error messages returned from xDestroy so just pick
+    // the closest appropriate error code.
+    return SQLITE_READONLY;
+  }
+  std::unique_ptr<Vtab> tab(GetVtab(vtab));
+  sqlite::ModuleStateManager<DbSqliteModule>::OnDestroy(tab->state);
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Connect(sqlite3* db,
+                            void* ctx,
+                            int argc,
+                            const char* const* argv,
+                            sqlite3_vtab** vtab,
+                            char**) {
+  PERFETTO_CHECK(argc == 3);
+  auto* context = GetContext(ctx);
+
+  std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+  res->state = context->manager.OnConnect(argv);
+  res->table_name = argv[2];
+
+  auto* state =
+      sqlite::ModuleStateManager<DbSqliteModule>::GetState(res->state);
+  std::string sql = CreateTableStatementFromSchema(state->schema, argv[2]);
+  if (int ret = sqlite3_declare_vtab(db, sql.c_str()); ret != SQLITE_OK) {
+    // If the registration happens to fail, make sure to disconnect the state
+    // again.
+    sqlite::ModuleStateManager<DbSqliteModule>::OnDisconnect(res->state);
+    return ret;
+  }
+  *vtab = res.release();
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Disconnect(sqlite3_vtab* vtab) {
+  std::unique_ptr<Vtab> tab(GetVtab(vtab));
+  sqlite::ModuleStateManager<DbSqliteModule>::OnDisconnect(tab->state);
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::BestIndex(sqlite3_vtab* vtab, sqlite3_index_info* info) {
+  auto* t = GetVtab(vtab);
+  auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+
+  uint32_t row_count;
+  int argv_index;
+  switch (s->computation) {
+    case TableComputation::kStatic:
+      row_count = s->static_table->row_count();
+      argv_index = 1;
+      break;
+    case TableComputation::kRuntime:
+      row_count = s->runtime_table->row_count();
+      argv_index = 1;
+      break;
+    case TableComputation::kTableFunction:
+      base::Status status = sqlite::utils::ValidateFunctionArguments(
+          info, static_cast<size_t>(s->argument_count),
+          [s](uint32_t i) { return s->schema.columns[i].is_hidden; });
+      if (!status.ok()) {
+        // TODO(lalitm): instead of returning SQLITE_CONSTRAINT which shows the
+        // user a very cryptic error message, consider instead SQLITE_OK but
+        // with a very high (~infinite) cost. If SQLite still chose the query
+        // plan after that, we can throw a proper error message in xFilter.
+        return SQLITE_CONSTRAINT;
+      }
+      row_count = s->static_table_function->EstimateRowCount();
+      argv_index = 1 + s->argument_count;
+      break;
+  }
+
+  std::vector<int> cs_idxes;
+  cs_idxes.reserve(static_cast<uint32_t>(info->nConstraint));
+  for (int i = 0; i < info->nConstraint; ++i) {
+    const auto& c = info->aConstraint[i];
+    if (!c.usable || info->aConstraintUsage[i].omit) {
+      continue;
+    }
+    if (std::optional<FilterOp> opt_op = SqliteOpToFilterOp(c.op); !opt_op) {
+      continue;
+    }
+    cs_idxes.push_back(i);
+  }
+
+  std::vector<int> ob_idxes(static_cast<uint32_t>(info->nOrderBy));
+  std::iota(ob_idxes.begin(), ob_idxes.end(), 0);
+
+  // Reorder constraints to consider the constraints on columns which are
+  // cheaper to filter first.
+  {
+    std::sort(cs_idxes.begin(), cs_idxes.end(), [s, info](int a, int b) {
+      auto a_idx = static_cast<uint32_t>(info->aConstraint[a].iColumn);
+      auto b_idx = static_cast<uint32_t>(info->aConstraint[b].iColumn);
+      const auto& a_col = s->schema.columns[a_idx];
+      const auto& b_col = s->schema.columns[b_idx];
+
+      // Id columns are always very cheap to filter on so try and get them
+      // first.
+      if (a_col.is_id || b_col.is_id)
+        return a_col.is_id && !b_col.is_id;
+
+      // Set id columns are always very cheap to filter on so try and get them
+      // second.
+      if (a_col.is_set_id || b_col.is_set_id)
+        return a_col.is_set_id && !b_col.is_set_id;
+
+      // Sorted columns are also quite cheap to filter so order them after
+      // any id/set id columns.
+      if (a_col.is_sorted || b_col.is_sorted)
+        return a_col.is_sorted && !b_col.is_sorted;
+
+      // TODO(lalitm): introduce more orderings here based on empirical data.
+      return false;
+    });
+  }
+
+  // Remove any order by constraints which also have an equality constraint.
+  {
+    auto p = [info, &cs_idxes](int o_idx) {
+      auto& o = info->aOrderBy[o_idx];
+      auto inner_p = [info, &o](int c_idx) {
+        auto& c = info->aConstraint[c_idx];
+        return c.iColumn == o.iColumn && sqlite::utils::IsOpEq(c.op);
+      };
+      return std::any_of(cs_idxes.begin(), cs_idxes.end(), inner_p);
+    };
+    ob_idxes.erase(std::remove_if(ob_idxes.begin(), ob_idxes.end(), p),
+                   ob_idxes.end());
+  }
+
+  // Go through the order by constraints in reverse order and eliminate
+  // constraints until the first non-sorted column or the first order by in
+  // descending order.
+  {
+    auto p = [info, s](int o_idx) {
+      auto& o = info->aOrderBy[o_idx];
+      const auto& col = s->schema.columns[static_cast<uint32_t>(o.iColumn)];
+      return o.desc || !col.is_sorted;
+    };
+    auto first_non_sorted_it =
+        std::find_if(ob_idxes.rbegin(), ob_idxes.rend(), p);
+    auto pop_count = std::distance(ob_idxes.rbegin(), first_non_sorted_it);
+    ob_idxes.resize(ob_idxes.size() - static_cast<uint32_t>(pop_count));
+  }
+
+  std::string cs_idx_str;
+  for (int i : cs_idxes) {
+    const auto& c = info->aConstraint[i];
+    auto& o = info->aConstraintUsage[i];
+    o.omit = true;
+    o.argvIndex = argv_index++;
+
+    auto op = SqliteOpToFilterOp(c.op);
+    PERFETTO_DCHECK(op);
+
+    cs_idx_str += ',';
+    cs_idx_str += std::to_string(c.iColumn);
+    cs_idx_str += ',';
+    cs_idx_str += std::to_string(static_cast<uint32_t>(*op));
+  }
+
+  std::string idx_str = "C";
+  idx_str += std::to_string(cs_idxes.size());
+  idx_str += cs_idx_str;
+  idx_str += ",";
+  idx_str += "O";
+  idx_str += std::to_string(ob_idxes.size());
+  for (int i : ob_idxes) {
+    idx_str += ',';
+    idx_str += std::to_string(info->aOrderBy[i].iColumn);
+    idx_str += ',';
+    idx_str += std::to_string(info->aOrderBy[i].desc);
+  }
+
+  info->idxNum = t->best_index_num++;
+  info->idxStr = sqlite3_mprintf("%s", idx_str.c_str());
+  info->needToFreeIdxStr = true;
+
+  // We can sort on any column correctly.
+  info->orderByConsumed = true;
+
+  auto cost_and_rows =
+      EstimateCost(s->schema, row_count, info, cs_idxes, ob_idxes);
+  info->estimatedCost = cost_and_rows.cost;
+  info->estimatedRows = cost_and_rows.rows;
+
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Open(sqlite3_vtab* tab, sqlite3_vtab_cursor** cursor) {
+  auto* t = GetVtab(tab);
+  auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+  std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+  switch (s->computation) {
+    case TableComputation::kStatic:
+      c->upstream_table = s->static_table;
+      break;
+    case TableComputation::kRuntime:
+      c->upstream_table = s->runtime_table.get();
+      break;
+    case TableComputation::kTableFunction:
+      c->table_function_arguments.resize(
+          static_cast<size_t>(s->argument_count));
+      break;
+  }
+  *cursor = c.release();
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Close(sqlite3_vtab_cursor* cursor) {
+  std::unique_ptr<Cursor> c(GetCursor(cursor));
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Filter(sqlite3_vtab_cursor* cursor,
+                           int idx_num,
+                           const char* idx_str,
+                           int,
+                           sqlite3_value** argv) {
+  auto* c = GetCursor(cursor);
+  auto* t = GetVtab(cursor->pVtab);
+  auto* s = sqlite::ModuleStateManager<DbSqliteModule>::GetState(t->state);
+
+  // Clear out the iterator before filtering to ensure the destructor is run
+  // before the table's destructor.
+  c->iterator = std::nullopt;
+
+  size_t offset = c->table_function_arguments.size();
+  bool is_same_idx = idx_num == c->last_idx_num;
+  if (PERFETTO_LIKELY(is_same_idx)) {
+    for (auto& cs : c->constraints) {
+      auto value_or = SqliteValueToSqlValueChecked(argv[offset++], cs);
+      if (!value_or.ok()) {
+        return sqlite::utils::SetError(c->pVtab, value_or.status().c_message());
+      }
+      cs.value = *value_or;
+    }
+  } else {
+    if (int r = UpdateConstraintsAndOrderByFromIndex(c, idx_str, argv + offset);
+        r != SQLITE_OK) {
+      return r;
+    }
+    c->last_idx_num = idx_num;
+  }
+
+  // Setup the upstream table based on the computation state.
+  switch (s->computation) {
+    case TableComputation::kStatic:
+    case TableComputation::kRuntime:
+      // Tries to create a sorted cached table which can be used to speed up
+      // filters below.
+      TryCacheCreateSortedTable(c, s->schema, is_same_idx);
+      break;
+    case TableComputation::kTableFunction: {
+      PERFETTO_TP_TRACE(
+          metatrace::Category::QUERY_DETAILED, "TABLE_FUNCTION_CALL",
+          [t](metatrace::Record* r) { r->AddArg("Name", t->table_name); });
+      for (uint32_t i = 0; i < c->table_function_arguments.size(); ++i) {
+        c->table_function_arguments[i] =
+            sqlite::utils::SqliteValueToSqlValue(argv[i]);
+      }
+      base::StatusOr<std::unique_ptr<Table>> table =
+          s->static_table_function->ComputeTable(c->table_function_arguments);
+      if (!table.ok()) {
+        base::StackString<1024> err("%s: %s", t->table_name.c_str(),
+                                    table.status().c_message());
+        return sqlite::utils::SetError(t, err.c_str());
+      }
+      c->dynamic_table = std::move(*table);
+      c->upstream_table = c->dynamic_table.get();
+      break;
+    }
+  }
+
+  PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
+                    "DB_TABLE_FILTER_AND_SORT",
+                    [s, t, c](metatrace::Record* r) {
+                      FilterAndSortMetatrace(t->table_name, s->schema, c, r);
+                    });
+
+  const auto* source_table =
+      c->sorted_cache_table ? &*c->sorted_cache_table : c->upstream_table;
+  RowMap filter_map = source_table->QueryToRowMap(c->constraints, c->orders);
+  if (filter_map.IsRange() && filter_map.size() <= 1) {
+    // Currently, our criteria where we have a special fast path is if it's
+    // a single ranged row. We have this fast path for joins on id columns
+    // where we get repeated queries filtering down to a single row. The
+    // other path performs allocations when creating the new table as well
+    // as the iterator on the new table whereas this path only uses a single
+    // number and lives entirely on the stack.
+
+    // TODO(lalitm): investigate some other criteria where it is beneficial
+    // to have a fast path and expand to them.
+    c->mode = Cursor::Mode::kSingleRow;
+    c->single_row = filter_map.size() == 1
+                        ? std::make_optional(filter_map.Get(0))
+                        : std::nullopt;
+    c->eof = !c->single_row.has_value();
+  } else {
+    c->mode = Cursor::Mode::kTable;
+    c->iterator = source_table->ApplyAndIterateRows(std::move(filter_map));
+    c->eof = !*c->iterator;
+  }
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Next(sqlite3_vtab_cursor* cursor) {
+  auto* c = GetCursor(cursor);
+  if (c->mode == Cursor::Mode::kSingleRow) {
+    c->eof = true;
+  } else {
+    c->eof = !++*c->iterator;
+  }
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Eof(sqlite3_vtab_cursor* cursor) {
+  return GetCursor(cursor)->eof;
+}
+
+int DbSqliteModule::Column(sqlite3_vtab_cursor* cursor,
+                           sqlite3_context* ctx,
+                           int N) {
+  Cursor* c = GetCursor(cursor);
+  auto idx = static_cast<uint32_t>(N);
+  const auto* source_table =
+      c->sorted_cache_table ? &*c->sorted_cache_table : c->upstream_table;
+  SqlValue value = c->mode == Cursor::Mode::kSingleRow
+                       ? source_table->columns()[idx].Get(*c->single_row)
+                       : c->iterator->Get(idx);
+  // We can say kSqliteStatic for strings because all strings are expected
+  // to come from the string pool. Thus they will be valid for the lifetime
+  // of trace processor. Similarily, for bytes, we can also use
+  // kSqliteStatic because for our iterator will hold onto the pointer as
+  // long as we don't call Next(). However, that only happens when Next() is
+  // called on the Cursor itself, at which point SQLite no longer cares
+  // about the bytes pointer.
+  sqlite::utils::ReportSqlValue(ctx, value, sqlite::utils::kSqliteStatic,
+                                sqlite::utils::kSqliteStatic);
+  return SQLITE_OK;
+}
+
+int DbSqliteModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+  return SQLITE_ERROR;
+}
+
+DbSqliteModule::QueryCost DbSqliteModule::EstimateCost(
+    const Table::Schema& schema,
+    uint32_t row_count,
+    sqlite3_index_info* info,
+    const std::vector<int>& cs_idxes,
+    const std::vector<int>& ob_idxes) {
+  // Currently our cost estimation algorithm is quite simplistic but is good
+  // enough for the simplest cases.
+  // TODO(lalitm): replace hardcoded constants with either more heuristics
+  // based on the exact type of constraint or profiling the queries
+  // themselves.
+
+  // We estimate the fixed cost of set-up and tear-down of a query in terms of
+  // the number of rows scanned.
+  constexpr double kFixedQueryCost = 1000.0;
+
+  // Setup the variables for estimating the number of rows we will have at the
+  // end of filtering. Note that |current_row_count| should always be at least
+  // 1 unless we are absolutely certain that we will return no rows as
+  // otherwise SQLite can make some bad choices.
+  uint32_t current_row_count = row_count;
+
+  // If the table is empty, any constraint set only pays the fixed cost. Also
+  // we can return 0 as the row count as we are certain that we will return no
+  // rows.
+  if (current_row_count == 0) {
+    return QueryCost{kFixedQueryCost, 0};
+  }
+
+  // Setup the variables for estimating the cost of filtering.
+  double filter_cost = 0.0;
+  for (int i : cs_idxes) {
+    if (current_row_count < 2) {
+      break;
+    }
+    const auto& c = info->aConstraint[i];
+    PERFETTO_DCHECK(c.usable);
+    PERFETTO_DCHECK(info->aConstraintUsage[i].omit);
+    PERFETTO_DCHECK(info->aConstraintUsage[i].argvIndex > 0);
+    const auto& col_schema = schema.columns[static_cast<uint32_t>(c.iColumn)];
+    if (sqlite::utils::IsOpEq(c.op) && col_schema.is_id) {
+      // If we have an id equality constraint, we can very efficiently filter
+      // down to a single row in C++. However, if we're joining with another
+      // table, SQLite will do this once per row which can be extremely
+      // expensive because of all the virtual table (which is implemented
+      // using virtual function calls) machinery. Indicate this by saying that
+      // an entire filter call is ~10x the cost of iterating a single row.
+      filter_cost += 10;
+      current_row_count = 1;
+    } else if (sqlite::utils::IsOpEq(c.op)) {
+      // If there is only a single equality constraint, we have special logic
+      // to sort by that column and then binary search if we see the
+      // constraint set often. Model this by dividing by the log of the number
+      // of rows as a good approximation. Otherwise, we'll need to do a full
+      // table scan. Alternatively, if the column is sorted, we can use the
+      // same binary search logic so we have the same low cost (even
+      // better because we don't // have to sort at all).
+      filter_cost += cs_idxes.size() == 1 || col_schema.is_sorted
+                         ? log2(current_row_count)
+                         : current_row_count;
+
+      // As an extremely rough heuristic, assume that an equalty constraint
+      // will cut down the number of rows by approximately double log of the
+      // number of rows.
+      double estimated_rows = current_row_count / (2 * log2(current_row_count));
+      current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
+    } else if (col_schema.is_sorted &&
+               (sqlite::utils::IsOpLe(c.op) || sqlite::utils::IsOpLt(c.op) ||
+                sqlite::utils::IsOpGt(c.op) || sqlite::utils::IsOpGe(c.op))) {
+      // On a sorted column, if we see any partition constraints, we can do
+      // this filter very efficiently. Model this using the log of the  number
+      // of rows as a good approximation.
+      filter_cost += log2(current_row_count);
+
+      // As an extremely rough heuristic, assume that an partition constraint
+      // will cut down the number of rows by approximately double log of the
+      // number of rows.
+      double estimated_rows = current_row_count / (2 * log2(current_row_count));
+      current_row_count = std::max(static_cast<uint32_t>(estimated_rows), 1u);
+    } else {
+      // Otherwise, we will need to do a full table scan and we estimate we
+      // will maybe (at best) halve the number of rows.
+      filter_cost += current_row_count;
+      current_row_count = std::max(current_row_count / 2u, 1u);
+    }
+  }
+
+  // Now, to figure out the cost of sorting, multiply the final row count
+  // by |qc.order_by().size()| * log(row count). This should act as a crude
+  // estimation of the cost.
+  double sort_cost =
+      static_cast<double>(static_cast<uint32_t>(ob_idxes.size()) *
+                          current_row_count) *
+      log2(current_row_count);
+
+  // The cost of iterating rows is more expensive than just filtering the rows
+  // so multiply by an appropriate factor.
+  double iteration_cost = current_row_count * 2.0;
+
+  // To get the final cost, add up all the individual components.
+  double final_cost =
+      kFixedQueryCost + filter_cost + sort_cost + iteration_cost;
+  return QueryCost{final_cost, current_row_count};
+}
+
+DbSqliteModule::State::State(const Table* _table, Table::Schema _schema)
+    : State(TableComputation::kStatic, std::move(_schema)) {
+  static_table = _table;
+}
+
+DbSqliteModule::State::State(std::unique_ptr<RuntimeTable> _table)
+    : State(TableComputation::kRuntime, _table->schema()) {
+  runtime_table = std::move(_table);
+}
+
+DbSqliteModule::State::State(
+    std::unique_ptr<StaticTableFunction> _static_function)
+    : State(TableComputation::kTableFunction,
+            _static_function->CreateSchema()) {
+  static_table_function = std::move(_static_function);
+  for (const auto& c : schema.columns) {
+    argument_count += c.is_hidden;
+  }
+}
+
+DbSqliteModule::State::State(TableComputation _computation,
+                             Table::Schema _schema)
+    : computation(_computation), schema(std::move(_schema)) {}
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index ded0f84..401c370 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -17,215 +17,149 @@
 #ifndef SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
 #define SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
 
+#include <sqlite3.h>
 #include <cstdint>
-#include <functional>
-#include <limits>
 #include <memory>
 #include <optional>
 #include <string>
 #include <vector>
 
-#include <sqlite3.h>
-
-#include "perfetto/base/compiler.h"
-#include "perfetto/base/status.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/runtime_table.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
-#include "src/trace_processor/sqlite/query_cache.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+#include "src/trace_processor/sqlite/module_lifecycle_manager.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
-struct DbSqliteTableContext {
-  enum class Computation {
-    // Table is statically defined.
-    kStatic,
+enum class TableComputation {
+  // Table is statically defined.
+  kStatic,
 
-    // Table is defined as a function.
-    kTableFunction,
+  // Table is defined as a function.
+  kTableFunction,
 
-    // Table is defined in runtime.
-    kRuntime
-  };
-  DbSqliteTableContext(QueryCache* query_cache,
-                       const Table* table,
-                       Table::Schema schema);
-  DbSqliteTableContext(QueryCache* query_cache,
-                       std::function<RuntimeTable*(std::string)> get_table,
-                       std::function<void(std::string)> erase_table);
-  DbSqliteTableContext(QueryCache* query_cache,
-                       std::unique_ptr<StaticTableFunction> table);
-
-  QueryCache* cache;
-  Computation computation;
-
-  // Only valid when computation == TableComputation::kStatic.
-  const Table* static_table = nullptr;
-  Table::Schema static_schema;
-
-  // Only valid when computation == TableComputation::kRuntime.
-  // Those functions implement the interactions with
-  // PerfettoSqlEngine::runtime_tables_ to get the |runtime_table_| and erase it
-  // from the map when |this| is destroyed.
-  std::function<RuntimeTable*(std::string)> get_runtime_table;
-  std::function<void(std::string)> erase_runtime_table;
-
-  // Only valid when computation == TableComputation::kTableFunction.
-  std::unique_ptr<StaticTableFunction> static_table_function;
+  // Table is defined in runtime.
+  kRuntime
 };
 
 // Implements the SQLite table interface for db tables.
-class DbSqliteTable final
-    : public TypedSqliteTable<DbSqliteTable,
-                              std::unique_ptr<DbSqliteTableContext>> {
- public:
-  using Context = DbSqliteTableContext;
-  using TableComputation = Context::Computation;
+struct DbSqliteModule : public sqlite::Module<DbSqliteModule> {
+  struct State {
+    State(const Table*, Table::Schema);
+    explicit State(std::unique_ptr<RuntimeTable>);
+    explicit State(std::unique_ptr<StaticTableFunction>);
 
-  class Cursor final : public SqliteTableLegacy::BaseCursor {
-   public:
-    Cursor(DbSqliteTable*, QueryCache*);
-    ~Cursor() final;
+    TableComputation computation;
+    Table::Schema schema;
+    int argument_count = 0;
 
-    Cursor(const Cursor&) = delete;
-    Cursor& operator=(const Cursor&) = delete;
+    // Only valid when computation == TableComputation::kStatic.
+    const Table* static_table = nullptr;
 
-    Cursor(Cursor&&) noexcept = delete;
-    Cursor& operator=(Cursor&&) = delete;
+    // Only valid when computation == TableComputation::kRuntime.
+    std::unique_ptr<RuntimeTable> runtime_table;
 
-    // Implementation of SqliteTableLegacy::Cursor.
-    base::Status Filter(const QueryConstraints& qc,
-                        sqlite3_value** argv,
-                        FilterHistory);
-
-    PERFETTO_ALWAYS_INLINE void Next() {
-      if (mode_ == Mode::kSingleRow) {
-        eof_ = true;
-      } else {
-        eof_ = !++*iterator_;
-      }
-    }
-
-    PERFETTO_ALWAYS_INLINE bool Eof() const { return eof_; }
-
-    PERFETTO_ALWAYS_INLINE void Column(sqlite3_context* ctx,
-                                       int raw_col) const {
-      auto column = static_cast<uint32_t>(raw_col);
-      SqlValue value = mode_ == Mode::kSingleRow
-                           ? SourceTable()->columns()[column].Get(*single_row_)
-                           : iterator_->Get(column);
-      // We can say kSqliteStatic for strings because all strings are expected
-      // to come from the string pool. Thus they will be valid for the lifetime
-      // of trace processor. Similarily, for bytes, we can also use
-      // kSqliteStatic because for our iterator will hold onto the pointer as
-      // long as we don't call Next(). However, that only happens when Next() is
-      // called on the Cursor itself, at which point SQLite no longer cares
-      // about the bytes pointer.
-      sqlite::utils::ReportSqlValue(ctx, value, sqlite::utils::kSqliteStatic,
-                                    sqlite::utils::kSqliteStatic);
-    }
+    // Only valid when computation == TableComputation::kTableFunction.
+    std::unique_ptr<StaticTableFunction> static_table_function;
 
    private:
+    State(TableComputation, Table::Schema);
+  };
+  struct Context {
+    std::unique_ptr<State> temporary_create_state;
+    sqlite::ModuleStateManager<DbSqliteModule> manager;
+  };
+  struct Vtab : public sqlite::Module<DbSqliteModule>::Vtab {
+    sqlite::ModuleStateManager<DbSqliteModule>::PerVtabState* state;
+    int best_index_num = 0;
+    std::string table_name;
+  };
+  struct Cursor : public sqlite::Module<DbSqliteModule>::Cursor {
     enum class Mode {
       kSingleRow,
       kTable,
     };
 
-    // Tries to create a sorted table to cache in |sorted_cache_table_| if the
-    // constraint set matches the requirements.
-    void TryCacheCreateSortedTable(const QueryConstraints&, FilterHistory);
-
-    const Table* SourceTable() const {
-      // Try and use the sorted cache table (if it exists) to speed up the
-      // sorting. Otherwise, just use the original table.
-      return sorted_cache_table_ ? &*sorted_cache_table_ : upstream_table_;
-    }
-
-    base::Status PopulateConstraintsAndArguments(const QueryConstraints& qc,
-                                                 sqlite3_value** argv);
-
-    void PopulateOrderBys(const QueryConstraints& qc);
-
-    void FilterAndSortMetatrace(metatrace::Record* record);
-
-    DbSqliteTable* db_sqlite_table_ = nullptr;
-    QueryCache* cache_ = nullptr;
-    std::vector<uint32_t> argument_index_per_column_;
-
-    const Table* upstream_table_ = nullptr;
+    const Table* upstream_table = nullptr;
 
     // Only valid for |db_sqlite_table_->computation_| ==
     // TableComputation::kDynamic.
-    std::unique_ptr<Table> dynamic_table_;
+    std::unique_ptr<Table> dynamic_table;
 
     // Only valid for Mode::kSingleRow.
-    std::optional<uint32_t> single_row_;
+    std::optional<uint32_t> single_row;
 
     // Only valid for Mode::kTable.
-    std::optional<Table::Iterator> iterator_;
+    std::optional<Table::Iterator> iterator;
 
-    bool eof_ = true;
+    bool eof = true;
 
-    // Stores a sorted version of |db_table_| sorted on a repeated equals
+    // Stores a sorted version of |db_table| sorted on a repeated equals
     // constraint. This allows speeding up repeated subqueries in joins
     // significantly.
-    std::shared_ptr<Table> sorted_cache_table_;
+    std::optional<Table> sorted_cache_table;
 
     // Stores the count of repeated equality queries to decide whether it is
-    // wortwhile to sort |db_table_| to create |sorted_cache_table_|.
-    uint32_t repeated_cache_count_ = 0;
+    // wortwhile to sort |db_table| to create |sorted_cache_table|.
+    uint32_t repeated_cache_count = 0;
 
-    Mode mode_ = Mode::kSingleRow;
+    Mode mode = Mode::kSingleRow;
 
-    std::vector<Constraint> constraints_;
-    std::vector<Order> orders_;
-    std::vector<SqlValue> table_function_arguments_;
+    int last_idx_num = -1;
+    std::vector<Constraint> constraints;
+    std::vector<Order> orders;
+    std::vector<SqlValue> table_function_arguments;
   };
   struct QueryCost {
     double cost;
     uint32_t rows;
   };
 
-  DbSqliteTable(sqlite3*, Context* context);
-  virtual ~DbSqliteTable() final;
+  static constexpr bool kSupportsWrites = false;
+  static constexpr bool kDoesOverloadFunctions = false;
 
-  // Table implementation.
-  base::Status Init(int, const char* const*, SqliteTableLegacy::Schema*) final;
-  std::unique_ptr<SqliteTableLegacy::BaseCursor> CreateCursor() final;
-  base::Status ModifyConstraints(QueryConstraints*) final;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
+  static int Create(sqlite3*,
+                    void*,
+                    int,
+                    const char* const*,
+                    sqlite3_vtab**,
+                    char**);
+  static int Destroy(sqlite3_vtab*);
 
-  // These static functions are useful to allow other callers to make use
-  // of them.
-  static SqliteTableLegacy::Schema ComputeSchema(const Table::Schema&,
-                                                 const char* table_name);
-  static void ModifyConstraints(const Table::Schema&, QueryConstraints*);
-  static void BestIndex(const Table::Schema&,
-                        uint32_t row_count,
-                        const QueryConstraints&,
-                        BestIndexInfo*);
+  static int Connect(sqlite3*,
+                     void*,
+                     int,
+                     const char* const*,
+                     sqlite3_vtab**,
+                     char**);
+  static int Disconnect(sqlite3_vtab*);
+
+  static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+  static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+  static int Close(sqlite3_vtab_cursor*);
+
+  static int Filter(sqlite3_vtab_cursor*,
+                    int,
+                    const char*,
+                    int,
+                    sqlite3_value**);
+  static int Next(sqlite3_vtab_cursor*);
+  static int Eof(sqlite3_vtab_cursor*);
+  static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+  static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
 
   // static for testing.
   static QueryCost EstimateCost(const Table::Schema&,
                                 uint32_t row_count,
-                                const QueryConstraints& qc);
-
- private:
-  Context* context_ = nullptr;
-
-  // Only valid after Init has completed.
-  Table::Schema schema_;
-  RuntimeTable* runtime_table_;
+                                sqlite3_index_info* info,
+                                const std::vector<int>&,
+                                const std::vector<int>&);
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
diff --git a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
index 891ba35..29c4e65 100644
--- a/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table_unittest.cc
@@ -17,10 +17,15 @@
 
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
 
+#include <sqlite3.h>
+#include <array>
+#include <cstdint>
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/db/table.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
 Table::Schema CreateSchema() {
@@ -43,56 +48,85 @@
   return schema;
 }
 
-TEST(DbSqliteTable, IdEqCheaperThanOtherEq) {
+sqlite3_index_info::sqlite3_index_constraint CreateConstraint(int col,
+                                                              uint8_t op) {
+  return {col, op, true, 0};
+}
+
+sqlite3_index_info::sqlite3_index_constraint_usage CreateUsage() {
+  return {1, true};
+}
+
+sqlite3_index_info CreateCsIndexInfo(
+    int cs_count,
+    sqlite3_index_info::sqlite3_index_constraint* c,
+    sqlite3_index_info::sqlite3_index_constraint_usage* u) {
+  return {cs_count, c, 0, nullptr, u, 0, nullptr, false, 0, 0, 0, 0, 0};
+}
+
+sqlite3_index_info CreateObIndexInfo(
+    int ob_count,
+    sqlite3_index_info::sqlite3_index_orderby* o) {
+  return {0, nullptr, ob_count, o, nullptr, 0, nullptr, false, 0, 0, 0, 0, 0};
+}
+
+TEST(DbSqliteModule, IdEqCheaperThanOtherEq) {
   auto schema = CreateSchema();
   constexpr uint32_t kRowCount = 1234;
 
-  QueryConstraints id_eq;
-  id_eq.AddConstraint(0u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+  auto c = CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ);
+  auto u = CreateUsage();
+  auto info = CreateCsIndexInfo(1, &c, &u);
 
-  auto id_cost = DbSqliteTable::EstimateCost(schema, kRowCount, id_eq);
+  auto id_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
 
-  QueryConstraints a_eq;
-  a_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_EQ, 1u);
-
-  auto a_cost = DbSqliteTable::EstimateCost(schema, kRowCount, a_eq);
+  c.iColumn = 1;
+  auto a_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
 
   ASSERT_LT(id_cost.cost, a_cost.cost);
   ASSERT_LT(id_cost.rows, a_cost.rows);
 }
 
-TEST(DbSqliteTable, IdEqCheaperThatOtherConstraint) {
+TEST(DbSqliteModule, IdEqCheaperThatOtherConstraint) {
   auto schema = CreateSchema();
   constexpr uint32_t kRowCount = 1234;
 
-  QueryConstraints id_eq;
-  id_eq.AddConstraint(0u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+  auto c = CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ);
+  auto u = CreateUsage();
+  auto info = CreateCsIndexInfo(1, &c, &u);
 
-  auto id_cost = DbSqliteTable::EstimateCost(schema, kRowCount, id_eq);
+  auto id_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
 
-  QueryConstraints a_eq;
-  a_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_LT, 1u);
-
-  auto a_cost = DbSqliteTable::EstimateCost(schema, kRowCount, a_eq);
+  c.iColumn = 1;
+  c.op = SQLITE_INDEX_CONSTRAINT_LT;
+  auto a_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
 
   ASSERT_LT(id_cost.cost, a_cost.cost);
   ASSERT_LT(id_cost.rows, a_cost.rows);
 }
 
-TEST(DbSqliteTable, SingleEqCheaperThanMultipleConstraint) {
+TEST(DbSqliteModule, SingleEqCheaperThanMultipleConstraint) {
   auto schema = CreateSchema();
   constexpr uint32_t kRowCount = 1234;
 
-  QueryConstraints single_eq;
-  single_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+  auto c = CreateConstraint(1, SQLITE_INDEX_CONSTRAINT_EQ);
+  auto u = CreateUsage();
+  auto info = CreateCsIndexInfo(1, &c, &u);
 
-  auto single_cost = DbSqliteTable::EstimateCost(schema, kRowCount, single_eq);
+  auto single_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info, {0u}, {});
 
-  QueryConstraints multi_eq;
-  multi_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
-  multi_eq.AddConstraint(2u, SQLITE_INDEX_CONSTRAINT_EQ, 1u);
+  std::array c2{CreateConstraint(1, SQLITE_INDEX_CONSTRAINT_EQ),
+                CreateConstraint(2, SQLITE_INDEX_CONSTRAINT_EQ)};
+  std::array u2{CreateUsage(), CreateUsage()};
+  auto info2 = CreateCsIndexInfo(c2.size(), c2.data(), u2.data());
 
-  auto multi_cost = DbSqliteTable::EstimateCost(schema, kRowCount, multi_eq);
+  auto multi_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {0u, 1u}, {});
 
   // The cost of the single filter should be cheaper (because of our special
   // handling of single equality). But the number of rows should be greater.
@@ -100,22 +134,25 @@
   ASSERT_GT(single_cost.rows, multi_cost.rows);
 }
 
-TEST(DbSqliteTable, MultiSortedEqCheaperThanMultiUnsortedEq) {
+TEST(DbSqliteModule, MultiSortedEqCheaperThanMultiUnsortedEq) {
   auto schema = CreateSchema();
   constexpr uint32_t kRowCount = 1234;
 
-  QueryConstraints sorted_eq;
-  sorted_eq.AddConstraint(2u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
-  sorted_eq.AddConstraint(3u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+  std::array c1{CreateConstraint(1, SQLITE_INDEX_CONSTRAINT_EQ),
+                CreateConstraint(2, SQLITE_INDEX_CONSTRAINT_EQ)};
+  std::array u1{CreateUsage(), CreateUsage()};
+  auto info1 = CreateCsIndexInfo(c1.size(), c1.data(), u1.data());
 
-  auto sorted_cost = DbSqliteTable::EstimateCost(schema, kRowCount, sorted_eq);
+  auto sorted_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info1, {0u, 1u}, {});
 
-  QueryConstraints unsorted_eq;
-  unsorted_eq.AddConstraint(3u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
-  unsorted_eq.AddConstraint(4u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+  std::array c2{CreateConstraint(3, SQLITE_INDEX_CONSTRAINT_EQ),
+                CreateConstraint(4, SQLITE_INDEX_CONSTRAINT_EQ)};
+  std::array u2{CreateUsage(), CreateUsage()};
+  auto info2 = CreateCsIndexInfo(c2.size(), c2.data(), u2.data());
 
   auto unsorted_cost =
-      DbSqliteTable::EstimateCost(schema, kRowCount, unsorted_eq);
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {0u, 1u}, {});
 
   // The number of rows should be the same but the cost of the sorted
   // query should be less.
@@ -123,41 +160,49 @@
   ASSERT_EQ(sorted_cost.rows, unsorted_cost.rows);
 }
 
-TEST(DbSqliteTable, EmptyTableCosting) {
+TEST(DbSqliteModule, EmptyTableCosting) {
   auto schema = CreateSchema();
+  constexpr uint32_t kRowCount = 0;
 
-  QueryConstraints id_eq;
-  id_eq.AddConstraint(0u, SQLITE_INDEX_CONSTRAINT_EQ, 0u);
+  std::array c1{CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ)};
+  std::array u1{CreateUsage()};
+  auto info1 = CreateCsIndexInfo(c1.size(), c1.data(), u1.data());
 
-  auto id_cost = DbSqliteTable::EstimateCost(schema, 0, id_eq);
+  auto id_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info1, {0u}, {});
 
-  QueryConstraints a_eq;
-  a_eq.AddConstraint(1u, SQLITE_INDEX_CONSTRAINT_LT, 1u);
+  std::array c2{CreateConstraint(0, SQLITE_INDEX_CONSTRAINT_EQ)};
+  std::array u2{CreateUsage()};
+  auto info2 = CreateCsIndexInfo(c2.size(), c2.data(), u2.data());
 
-  auto a_cost = DbSqliteTable::EstimateCost(schema, 0, a_eq);
+  auto a_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {0u}, {});
 
   ASSERT_DOUBLE_EQ(id_cost.cost, a_cost.cost);
   ASSERT_EQ(id_cost.rows, a_cost.rows);
 }
 
-TEST(DbSqliteTable, OrderByOnSortedCheaper) {
+TEST(DbSqliteModule, OrderByOnSortedCheaper) {
   auto schema = CreateSchema();
   constexpr uint32_t kRowCount = 1234;
 
-  QueryConstraints a_qc;
-  a_qc.AddOrderBy(1u, false);
+  sqlite3_index_info::sqlite3_index_orderby ob1{1u, false};
+  auto info1 = CreateObIndexInfo(1, &ob1);
 
-  auto a_cost = DbSqliteTable::EstimateCost(schema, kRowCount, a_qc);
+  auto a_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info1, {}, {0u});
+
+  sqlite3_index_info::sqlite3_index_orderby ob2{2u, false};
+  auto info2 = CreateObIndexInfo(1, &ob2);
 
   // On an ordered column, the constraint for sorting would get pruned so
   // we would end up with an empty constraint set.
-  QueryConstraints sorted_qc;
-  auto sorted_cost = DbSqliteTable::EstimateCost(schema, kRowCount, sorted_qc);
+  auto sorted_cost =
+      DbSqliteModule::EstimateCost(schema, kRowCount, &info2, {}, {});
 
   ASSERT_LT(sorted_cost.cost, a_cost.cost);
   ASSERT_EQ(sorted_cost.rows, a_cost.rows);
 }
 
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/module_lifecycle_manager.h b/src/trace_processor/sqlite/module_lifecycle_manager.h
index 0e44a6d..66054d5 100644
--- a/src/trace_processor/sqlite/module_lifecycle_manager.h
+++ b/src/trace_processor/sqlite/module_lifecycle_manager.h
@@ -19,6 +19,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/flat_hash_map.h"
@@ -117,6 +118,16 @@
     return s->state.get();
   }
 
+  // Looks up the state of a module by name. This function should only be called
+  // for speculative lookups from outside the module implementation: use
+  // |GetState| inside the sqlite::Module implementation.
+  typename Module::State* FindStateByName(std::string_view name) {
+    if (auto ptr = state_by_name_.Find(std::string(name)); ptr) {
+      return GetState(ptr->get());
+    }
+    return nullptr;
+  }
+
  private:
   base::FlatHashMap<std::string, std::unique_ptr<PerVtabState>> state_by_name_;
 };
diff --git a/src/trace_processor/sqlite/query_cache.h b/src/trace_processor/sqlite/query_cache.h
deleted file mode 100644
index 30f6045..0000000
--- a/src/trace_processor/sqlite/query_cache.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
-
-#include <algorithm>
-#include <functional>
-#include <memory>
-#include <vector>
-
-#include "src/trace_processor/db/table.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-namespace perfetto::trace_processor {
-
-// Implements a simple caching strategy for commonly executed queries.
-// TODO(lalitm): the design of this class is very experimental. It was mainly
-// introduced to solve a specific problem (slow process summary tracks in the
-// Perfetto UI) and should not be modified without a full design discussion.
-class QueryCache {
- public:
-  using Constraint = QueryConstraints::Constraint;
-
-  // Returns a cached table if the passed query set are currenly cached or
-  // nullptr otherwise.
-  std::shared_ptr<Table> GetIfCached(const Table* source,
-                                     const std::vector<Constraint>& cs) const {
-    if (cached_.source != source || cs.size() != cached_.constraints.size())
-      return nullptr;
-
-    auto p = [](const Constraint& a, const Constraint& b) {
-      return a.column == b.column && a.op == b.op;
-    };
-    bool same_cs =
-        std::equal(cs.begin(), cs.end(), cached_.constraints.begin(), p);
-    return same_cs ? cached_.table : nullptr;
-  }
-
-  // Caches the table with the given source, constraint and order set. Returns
-  // a pointer to the newly cached table.
-  std::shared_ptr<Table> GetOrCache(
-      const Table* source,
-      const std::vector<QueryConstraints::Constraint>& cs,
-      std::function<Table()> fn) {
-    std::shared_ptr<Table> cached = GetIfCached(source, cs);
-    if (cached)
-      return cached;
-
-    cached_.source = source;
-    cached_.constraints = cs;
-    cached_.table.reset(new Table(fn()));
-    return cached_.table;
-  }
-
- private:
-  struct CachedTable {
-    std::shared_ptr<Table> table;
-
-    const Table* source = nullptr;
-    std::vector<Constraint> constraints;
-  };
-
-  CachedTable cached_;
-};
-
-}  // namespace perfetto::trace_processor
-
-#endif  // SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
diff --git a/src/trace_processor/sqlite/query_constraints.cc b/src/trace_processor/sqlite/query_constraints.cc
deleted file mode 100644
index 67f1812..0000000
--- a/src/trace_processor/sqlite/query_constraints.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-#include <sqlite3.h>
-
-#include <string>
-
-#include "perfetto/ext/base/string_splitter.h"
-#include "perfetto/ext/base/string_utils.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-QueryConstraints::QueryConstraints(uint64_t cols_used)
-    : cols_used_(cols_used) {}
-QueryConstraints::~QueryConstraints() = default;
-QueryConstraints::QueryConstraints(QueryConstraints&&) noexcept = default;
-QueryConstraints& QueryConstraints::operator=(QueryConstraints&&) noexcept =
-    default;
-
-int QueryConstraints::FreeSqliteString(char* resource) {
-  sqlite3_free(resource);
-  return 0;
-}
-
-bool QueryConstraints::operator==(const QueryConstraints& other) const {
-  if ((other.constraints().size() != constraints().size()) ||
-      (other.order_by().size() != order_by().size()) ||
-      other.cols_used() != cols_used()) {
-    return false;
-  }
-
-  for (size_t i = 0; i < constraints().size(); ++i) {
-    if ((constraints()[i].column != other.constraints()[i].column) ||
-        (constraints()[i].op != other.constraints()[i].op)) {
-      return false;
-    }
-  }
-
-  for (size_t i = 0; i < order_by().size(); ++i) {
-    if ((order_by()[i].iColumn != other.order_by()[i].iColumn) ||
-        (order_by()[i].desc != other.order_by()[i].desc)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-void QueryConstraints::AddConstraint(int column,
-                                     unsigned char op,
-                                     int aconstraint_idx) {
-  Constraint c{};
-  c.column = column;
-  c.op = op;
-  c.a_constraint_idx = aconstraint_idx;
-  constraints_.emplace_back(c);
-}
-
-void QueryConstraints::AddOrderBy(int column, unsigned char desc) {
-  OrderBy ob{};
-  ob.iColumn = column;
-  ob.desc = desc;
-  order_by_.emplace_back(ob);
-}
-
-QueryConstraints::SqliteString QueryConstraints::ToNewSqlite3String() const {
-  std::string str_result;
-  str_result.reserve(512);
-
-  // Add all the constraints.
-  str_result.append("C");
-  str_result.append(std::to_string(constraints_.size()));
-  str_result.append(",");
-  for (const auto& cs : constraints_) {
-    str_result.append(std::to_string(cs.column));
-    str_result.append(",");
-    str_result.append(std::to_string(cs.op));
-    str_result.append(",");
-  }
-  str_result.back() = ';';
-
-  // Add all the clauses.
-  str_result.append("O");
-  str_result.append(std::to_string(order_by_.size()));
-  str_result.append(",");
-  for (const auto& ob : order_by_) {
-    str_result.append(std::to_string(ob.iColumn));
-    str_result.append(",");
-    str_result.append(std::to_string(ob.desc));
-    str_result.append(",");
-  }
-  str_result.back() = ';';
-
-  // Add the columns used.
-  str_result.append("U");
-  str_result.append(std::to_string(cols_used_));
-
-  SqliteString result(static_cast<char*>(
-      sqlite3_malloc(static_cast<int>(str_result.size() + 1))));
-  base::StringCopy(result.get(), str_result.c_str(), str_result.size() + 1);
-  return result;
-}
-
-QueryConstraints QueryConstraints::FromString(const char* idxStr) {
-  QueryConstraints qc;
-
-  base::StringSplitter outer_splitter(std::string(idxStr), ';');
-
-  // Handle the CONSTRAINT section of the string.
-  PERFETTO_CHECK(outer_splitter.Next() && outer_splitter.cur_token_size() > 1);
-  {
-    base::StringSplitter splitter(&outer_splitter, ',');
-    PERFETTO_CHECK(splitter.Next() && splitter.cur_token_size() > 1);
-
-    // The '[1]' skips the letter 'C' in the first token.
-    int64_t num_constraints = *base::CStringToInt64(&splitter.cur_token()[1]);
-    for (int i = 0; i < num_constraints; ++i) {
-      PERFETTO_CHECK(splitter.Next());
-      int col = static_cast<int>(*base::CStringToInt32(splitter.cur_token()));
-      PERFETTO_CHECK(splitter.Next());
-      unsigned char op = static_cast<unsigned char>(
-          *base::CStringToUInt32(splitter.cur_token()));
-      qc.AddConstraint(col, op, 0);
-    }
-  }
-
-  // Handle the ORDER BY section of the string.
-  PERFETTO_CHECK(outer_splitter.Next() && outer_splitter.cur_token_size() > 1);
-  {
-    base::StringSplitter splitter(&outer_splitter, ',');
-    PERFETTO_CHECK(splitter.Next() && splitter.cur_token_size() > 1);
-
-    // The '[1]' skips the letter 'O' in the current token.
-    int64_t num_order_by = *base::CStringToInt64(&splitter.cur_token()[1]);
-    for (int i = 0; i < num_order_by; ++i) {
-      PERFETTO_CHECK(splitter.Next());
-      int col = static_cast<int>(*base::CStringToInt32(splitter.cur_token()));
-      PERFETTO_CHECK(splitter.Next());
-      unsigned char desc = static_cast<unsigned char>(
-          *base::CStringToUInt32(splitter.cur_token()));
-      qc.AddOrderBy(col, desc);
-    }
-  }
-
-  // Handle the COLS USED section of the string.
-  PERFETTO_CHECK(outer_splitter.Next() && outer_splitter.cur_token_size() > 1);
-  {
-    // The '[1]' skips the letter 'U' in the current token.
-    qc.cols_used_ = *base::CStringToUInt64(&outer_splitter.cur_token()[1]);
-  }
-
-  PERFETTO_DCHECK(!outer_splitter.Next());
-  return qc;
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/query_constraints.h b/src/trace_processor/sqlite/query_constraints.h
deleted file mode 100644
index 8fc08e7..0000000
--- a/src/trace_processor/sqlite/query_constraints.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2018 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_SQLITE_QUERY_CONSTRAINTS_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
-
-#include <cstdint>
-#include <limits>
-#include <vector>
-
-#include "perfetto/ext/base/scoped_file.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// This class stores the constraints (including the order-by information) for
-// a query on a sqlite3 virtual table and handles their de/serialization into
-// strings.
-// This is because the constraint columns and the order-by clauses are passed
-// to the xBestIndex method but the constraint values are available only in the
-// xFilter method. Unfortunately sqlite vtable API don't give any hint about
-// the validity of the constraints (i.e. constraints passed to xBestIndex can
-// be used by future xFilter calls in the far future). The only mechanism
-// offered by sqlite is the idxStr string which is returned by the vtable
-// in the xBestIndex call and passed to each corresponding xFilter call.
-class QueryConstraints {
- public:
-  struct Constraint {
-    // Column this constraint refers to.
-    int column;
-
-    // SQLite op for the constraint.
-    int op;
-
-    // The original index of this constraint in the aConstraint array.
-    // Used internally by SqliteTableLegacy for xBestIndex - this should not be
-    // read or modified by subclasses of SqliteTableLegacy.
-    int a_constraint_idx;
-  };
-  struct OrderBy {
-    int iColumn;
-    unsigned char desc;
-  };
-
-  static int FreeSqliteString(char* resource);
-
-  using SqliteString = base::ScopedResource<char*, FreeSqliteString, nullptr>;
-
-  explicit QueryConstraints(
-      uint64_t cols_used = std::numeric_limits<uint64_t>::max());
-  ~QueryConstraints();
-  QueryConstraints(QueryConstraints&&) noexcept;
-  QueryConstraints& operator=(QueryConstraints&&) noexcept;
-
-  // Two QueryConstraints with the same constraint and orderby vectors
-  // are equal.
-  bool operator==(const QueryConstraints& other) const;
-
-  void AddConstraint(int column, unsigned char op, int aconstraint_idx);
-
-  void AddOrderBy(int column, unsigned char desc);
-
-  void ClearOrderBy() { order_by_.clear(); }
-
-  // Converts the constraints and order by information to a string for
-  // use by sqlite.
-  SqliteString ToNewSqlite3String() const;
-
-  // Deserializes the string into QueryConstraints. String given is in the form
-  // C{# of constraints},col1,op1,col2,op2...,O{# of order by},col1,desc1...
-  // For example C1,0,3,O2,1,0,4,1
-  static QueryConstraints FromString(const char* idxStr);
-
-  const std::vector<OrderBy>& order_by() const { return order_by_; }
-
-  const std::vector<Constraint>& constraints() const { return constraints_; }
-
-  std::vector<OrderBy>* mutable_order_by() { return &order_by_; }
-
-  std::vector<Constraint>* mutable_constraints() { return &constraints_; }
-
-  uint64_t cols_used() const { return cols_used_; }
-
- private:
-  QueryConstraints(const QueryConstraints&) = delete;
-  QueryConstraints& operator=(const QueryConstraints&) = delete;
-
-  std::vector<OrderBy> order_by_;
-  std::vector<Constraint> constraints_;
-
-  // Stores information about which column is used by this query.
-  // If the lowest bit of is set, the first column is used. The second lowest
-  // bit corresponds to the second column etc. If the most significant bit is
-  // set, that means that any column after the first 63 columns could be used.
-  uint64_t cols_used_ = std::numeric_limits<uint64_t>::max();
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_SQLITE_QUERY_CONSTRAINTS_H_
diff --git a/src/trace_processor/sqlite/query_constraints_unittest.cc b/src/trace_processor/sqlite/query_constraints_unittest.cc
deleted file mode 100644
index 830931e..0000000
--- a/src/trace_processor/sqlite/query_constraints_unittest.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-#include <sqlite3.h>
-
-#include "perfetto/base/logging.h"
-#include "test/gtest_and_gmock.h"
-
-using testing::ElementsAreArray;
-using testing::Field;
-using testing::Matcher;
-using testing::Matches;
-using testing::Pointwise;
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-
-class QueryConstraintsTest : public ::testing::Test {
- public:
-  QueryConstraintsTest() { PERFETTO_CHECK(sqlite3_initialize() == SQLITE_OK); }
-};
-
-TEST_F(QueryConstraintsTest, ConvertToAndFromSqlString) {
-  QueryConstraints qc(0);
-  qc.AddConstraint(12, 0, 0);
-
-  QueryConstraints::SqliteString only_constraint = qc.ToNewSqlite3String();
-  ASSERT_TRUE(strcmp(only_constraint.get(), "C1,12,0;O0;U0") == 0);
-
-  QueryConstraints qc_constraint =
-      QueryConstraints::FromString(only_constraint.get());
-  ASSERT_EQ(qc, qc_constraint);
-
-  qc.AddOrderBy(1, false);
-  qc.AddOrderBy(21, true);
-
-  QueryConstraints::SqliteString result = qc.ToNewSqlite3String();
-  ASSERT_TRUE(strcmp(result.get(), "C1,12,0;O2,1,0,21,1;U0") == 0);
-
-  QueryConstraints qc_result = QueryConstraints::FromString(result.get());
-  ASSERT_EQ(qc, qc_result);
-}
-
-TEST_F(QueryConstraintsTest, CheckEmptyConstraints) {
-  QueryConstraints qc(0);
-
-  QueryConstraints::SqliteString string_result = qc.ToNewSqlite3String();
-  ASSERT_TRUE(strcmp(string_result.get(), "C0;O0;U0") == 0);
-
-  QueryConstraints qc_result =
-      QueryConstraints::FromString(string_result.get());
-  ASSERT_EQ(qc_result.constraints().size(), 0u);
-  ASSERT_EQ(qc_result.order_by().size(), 0u);
-}
-
-TEST_F(QueryConstraintsTest, OnlyOrderBy) {
-  QueryConstraints qc(0);
-  qc.AddOrderBy(3, true);
-
-  QueryConstraints::SqliteString string_result = qc.ToNewSqlite3String();
-  ASSERT_TRUE(strcmp(string_result.get(), "C0;O1,3,1;U0") == 0);
-
-  QueryConstraints qc_result =
-      QueryConstraints::FromString(string_result.get());
-  ASSERT_EQ(qc, qc_result);
-}
-
-TEST_F(QueryConstraintsTest, ColsUsed) {
-  ASSERT_EQ(QueryConstraints(0), QueryConstraints::FromString("C0;O0;U0"));
-
-  ASSERT_EQ(QueryConstraints(4), QueryConstraints::FromString("C0;O0;U4"));
-
-  ASSERT_EQ(QueryConstraints(1ull << 63),
-            QueryConstraints::FromString("C0;O0;U9223372036854775808"));
-
-  ASSERT_EQ(QueryConstraints(9223372036854775807ull),
-            QueryConstraints::FromString("C0;O0;U9223372036854775807"));
-
-  ASSERT_EQ(QueryConstraints(),
-            QueryConstraints::FromString("C0;O0;U18446744073709551615"));
-
-  auto str = QueryConstraints(0xFFFFFFFFFFFFFFFF).ToNewSqlite3String();
-  ASSERT_STREQ(str.get(), "C0;O0;U18446744073709551615");
-}
-
-}  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index 00defc2..cfd73dc 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -16,21 +16,20 @@
 
 #include "src/trace_processor/sqlite/sqlite_engine.h"
 
-#include <memory>
+#include <cstdint>
 #include <optional>
-#include <unordered_set>
+#include <string>
 #include <utility>
-#include <vector>
 
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/public/compiler.h"
-#include "src/trace_processor/sqlite/db_sqlite_table.h"
-#include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/tp_metatrace.h"
+
+#include "protos/perfetto/trace_processor/metatrace_categories.pbzero.h"
 
 // In Android and Chromium tree builds, we don't have the percentile module.
 // Just don't include it.
@@ -41,14 +40,13 @@
                                        const sqlite3_api_routines* api);
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
 void EnsureSqliteInitialized() {
-  // sqlite3_initialize isn't actually thread-safe despite being documented
-  // as such; we need to make sure multiple TraceProcessorImpl instances don't
-  // call it concurrently and only gets called once per process, instead.
+  // sqlite3_initialize isn't actually thread-safe in standalone builds because
+  // we build with SQLITE_THREADSAFE=0. Ensure it's only called from a single
+  // thread.
   static bool init_once = [] { return sqlite3_initialize() == SQLITE_OK; }();
   PERFETTO_CHECK(init_once);
 }
@@ -60,7 +58,6 @@
     PERFETTO_FATAL("Error setting pragma temp_store: %s", error);
   }
 // In Android tree builds, we don't have the percentile module.
-// Just don't include it.
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
   sqlite3_percentile_init(db, &error, nullptr);
   if (error) {
@@ -91,15 +88,10 @@
   PERFETTO_CHECK(sqlite3_open_v2(":memory:", &db, kSqliteOpenFlags, nullptr) ==
                  SQLITE_OK);
   InitializeSqlite(db);
-  db_.reset(std::move(db));
+  db_.reset(db);
 }
 
 SqliteEngine::~SqliteEngine() {
-  // IMPORTANT: the order of operations in this destructor is very sensitive and
-  // should not be changed without careful consideration of the consequences.
-  // Thankfully, because we are very aggressive with PERFETTO_CHECK, mistakes
-  // will usually manifest as crashes, but this is not guaranteed.
-
   // It is important to unregister any functions that have been registered with
   // the database before destroying it. This is because functions can hold onto
   // prepared statements, which must be finalized before database destruction.
@@ -112,52 +104,6 @@
     }
   }
   fn_ctx_.Clear();
-
-  // Drop any explicitly created virtual tables before destroying the database
-  // so that any prepared statements are correctly finalized. Note that we need
-  // to do this in two steps (first create all the SQLs before then executing
-  // them) because |OnSqliteTableDestroyed| will be called as each DROP is
-  // executed.
-  std::vector<std::string> drop_stmts;
-  std::unordered_set<std::string> dropped_tables;
-  for (auto it = all_created_sqlite_tables_.rbegin();
-       it != all_created_sqlite_tables_.rend(); it++) {
-    if (auto* type = sqlite_tables_.Find(*it);
-        !type || *type != SqliteTableLegacy::TableType::kExplicitCreate) {
-      continue;
-    }
-    if (auto it_and_ins = dropped_tables.insert(*it); !it_and_ins.second) {
-      continue;
-    }
-    base::StackString<1024> drop("DROP TABLE %s", it->c_str());
-    drop_stmts.emplace_back(drop.ToStdString());
-  }
-  for (const auto& drop : drop_stmts) {
-    int ret = sqlite3_exec(db(), drop.c_str(), nullptr, nullptr, nullptr);
-    if (PERFETTO_UNLIKELY(ret != SQLITE_OK)) {
-      PERFETTO_FATAL("Failed to execute statement: '%s'", drop.c_str());
-    }
-  }
-
-  // SQLite will not pick saved tables back up when destroyed as, from it's
-  // perspective, it has called xDisconnect. Make sure to do that ourselves.
-  saved_tables_.Clear();
-
-  // Reset the database itself. We need to do this after clearing the saved
-  // tables as the saved tables could hold onto prepared statements.
-  db_.reset();
-
-  // The above operations should have cleared all the tables.
-  if (PERFETTO_UNLIKELY(sqlite_tables_.size() != 0)) {
-    std::vector<std::string> tables;
-    for (auto it = sqlite_tables_.GetIterator(); it; ++it) {
-      tables.push_back(it.key());
-    }
-    std::string joined = base::Join(tables, ",");
-    PERFETTO_FATAL(
-        "SqliteTable instances still exist: count='%zu', tables='[%s]'",
-        sqlite_tables_.size(), joined.c_str());
-  }
 }
 
 SqliteEngine::PreparedStatement SqliteEngine::PrepareStatement(SqlSource sql) {
@@ -256,30 +202,6 @@
   return base::OkStatus();
 }
 
-base::Status SqliteEngine::SaveSqliteTable(
-    const std::string& table_name,
-    std::unique_ptr<SqliteTableLegacy> table) {
-  auto res = saved_tables_.Insert(table_name, {});
-  if (!res.second) {
-    return base::ErrStatus("Table with name %s already is saved",
-                           table_name.c_str());
-  }
-  *res.first = std::move(table);
-  return base::OkStatus();
-}
-
-base::StatusOr<std::unique_ptr<SqliteTableLegacy>>
-SqliteEngine::RestoreSqliteTable(const std::string& table_name) {
-  auto* res = saved_tables_.Find(table_name);
-  if (!res) {
-    return base::ErrStatus("Table with name %s does not exist in saved state",
-                           table_name.c_str());
-  }
-  std::unique_ptr<SqliteTableLegacy> table = std::move(*res);
-  PERFETTO_CHECK(saved_tables_.Erase(table_name));
-  return std::move(table);
-}
-
 void* SqliteEngine::GetFunctionContext(const std::string& name, int argc) {
   auto* res = fn_ctx_.Find(std::make_pair(name, argc));
   return res ? *res : nullptr;
@@ -289,17 +211,6 @@
   return GetErrorOffsetDb(db_.get());
 }
 
-void SqliteEngine::OnSqliteTableCreated(const std::string& name,
-                                        SqliteTableLegacy::TableType type) {
-  auto it_and_inserted = sqlite_tables_.Insert(name, type);
-  PERFETTO_CHECK(it_and_inserted.second);
-  all_created_sqlite_tables_.push_back(name);
-}
-
-void SqliteEngine::OnSqliteTableDestroyed(const std::string& name) {
-  PERFETTO_CHECK(sqlite_tables_.Erase(name));
-}
-
 SqliteEngine::PreparedStatement::PreparedStatement(ScopedStmt stmt,
                                                    SqlSource source)
     : stmt_(std::move(stmt)),
@@ -342,5 +253,4 @@
   return expanded_sql_.get();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index 55e991f..6d6ad13 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -18,29 +18,23 @@
 #define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
 
 #include <sqlite3.h>
-#include <stdint.h>
-#include <functional>
+#include <cstddef>
+#include <cstdint>
 #include <memory>
 #include <optional>
 #include <string>
 #include <type_traits>
-#include <vector>
+#include <utility>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
-#include "src/trace_processor/db/table.h"
 #include "src/trace_processor/sqlite/bindings/sqlite_module.h"
-#include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // Wrapper class around SQLite C API.
 //
@@ -93,6 +87,9 @@
   SqliteEngine();
   ~SqliteEngine();
 
+  SqliteEngine(SqliteEngine&&) noexcept = delete;
+  SqliteEngine& operator=(SqliteEngine&&) = delete;
+
   // Prepares a SQLite statement for the given SQL.
   PreparedStatement PrepareStatement(SqlSource);
 
@@ -137,34 +134,12 @@
   void RegisterVirtualTableModule(const std::string& module_name,
                                   std::unique_ptr<typename Module::Context>);
 
-  // Registers a SQLite virtual table module with the given name.
-  template <typename Vtab, typename Context>
-  void RegisterVirtualTableModule(const std::string& module_name,
-                                  Context ctx,
-                                  SqliteTableLegacy::TableType table_type,
-                                  bool updatable);
-
   // Declares a virtual table with SQLite.
   base::Status DeclareVirtualTable(const std::string& create_stmt);
 
-  // Saves a SQLite table across a pair of xDisconnect/xConnect callbacks.
-  base::Status SaveSqliteTable(const std::string& table_name,
-                               std::unique_ptr<SqliteTableLegacy>);
-
-  // Restores a SQLite table across a pair of xDisconnect/xConnect callbacks.
-  base::StatusOr<std::unique_ptr<SqliteTableLegacy>> RestoreSqliteTable(
-      const std::string& table_name);
-
   // Gets the context for a registered SQL function.
   void* GetFunctionContext(const std::string& name, int argc);
 
-  // Should be called when a SqliteTableLegacy instance is created.
-  void OnSqliteTableCreated(const std::string& name,
-                            SqliteTableLegacy::TableType);
-
-  // Should be called when a SqliteTableLegacy instance is destroyed.
-  void OnSqliteTableDestroyed(const std::string& name);
-
   sqlite3* db() const { return db_.get(); }
 
  private:
@@ -179,20 +154,11 @@
 
   std::optional<uint32_t> GetErrorOffset() const;
 
-  SqliteEngine(SqliteEngine&&) noexcept = delete;
-  SqliteEngine& operator=(SqliteEngine&&) = delete;
-
-  base::FlatHashMap<std::string, SqliteTableLegacy::TableType> sqlite_tables_;
-  std::vector<std::string> all_created_sqlite_tables_;
-  base::FlatHashMap<std::string, std::unique_ptr<SqliteTableLegacy>>
-      saved_tables_;
   base::FlatHashMap<std::pair<std::string, int>, void*, FnHasher> fn_ctx_;
-
   ScopedDb db_;
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 // The rest of this file is just implementation details which we need
 // in the header file because it is templated code. We separate it out
@@ -222,24 +188,6 @@
   PERFETTO_CHECK(res == SQLITE_OK);
 }
 
-template <typename Vtab, typename Context>
-void SqliteEngine::RegisterVirtualTableModule(
-    const std::string& module_name,
-    Context ctx,
-    SqliteTableLegacy::TableType table_type,
-    bool updatable) {
-  static_assert(std::is_base_of_v<SqliteTableLegacy, Vtab>,
-                "Must subclass TypedSqliteTable");
-
-  auto module_arg =
-      Vtab::CreateModuleArg(this, std::move(ctx), table_type, updatable);
-  sqlite3_module* module = &module_arg->module;
-  int res = sqlite3_create_module_v2(
-      db_.get(), module_name.c_str(), module, module_arg.release(),
-      [](void* arg) { delete static_cast<typename Vtab::ModuleArg*>(arg); });
-  PERFETTO_CHECK(res == SQLITE_OK);
-}
-
 }  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
diff --git a/src/trace_processor/sqlite/sqlite_table.cc b/src/trace_processor/sqlite/sqlite_table.cc
deleted file mode 100644
index a56fa6e..0000000
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/sqlite/sqlite_table.h"
-
-#include <string.h>
-#include <algorithm>
-#include <cinttypes>
-#include <map>
-#include <memory>
-
-#include "perfetto/base/logging.h"
-#include "perfetto/base/status.h"
-#include "perfetto/ext/base/status_or.h"
-#include "perfetto/ext/base/string_view.h"
-#include "sqlite3.h"
-#include "src/trace_processor/sqlite/sqlite_engine.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-#include "src/trace_processor/tp_metatrace.h"
-#include "src/trace_processor/util/status_macros.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-namespace {
-
-std::string OpToDebugString(int op) {
-  switch (op) {
-    case SQLITE_INDEX_CONSTRAINT_EQ:
-      return "=";
-    case SQLITE_INDEX_CONSTRAINT_NE:
-      return "!=";
-    case SQLITE_INDEX_CONSTRAINT_GE:
-      return ">=";
-    case SQLITE_INDEX_CONSTRAINT_GT:
-      return ">";
-    case SQLITE_INDEX_CONSTRAINT_LE:
-      return "<=";
-    case SQLITE_INDEX_CONSTRAINT_LT:
-      return "<";
-    case SQLITE_INDEX_CONSTRAINT_LIKE:
-      return "like";
-    case SQLITE_INDEX_CONSTRAINT_ISNULL:
-      return "is null";
-    case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
-      return "is not null";
-    case SQLITE_INDEX_CONSTRAINT_IS:
-      return "is";
-    case SQLITE_INDEX_CONSTRAINT_ISNOT:
-      return "is not";
-    case SQLITE_INDEX_CONSTRAINT_GLOB:
-      return "glob";
-    case SQLITE_INDEX_CONSTRAINT_LIMIT:
-      return "limit";
-    case SQLITE_INDEX_CONSTRAINT_OFFSET:
-      return "offset";
-    case SqliteTableLegacy::CustomFilterOpcode::kSourceGeqOpCode:
-      return "source_geq";
-    default:
-      PERFETTO_FATAL("Operator to string conversion not impemented for %d", op);
-  }
-}
-
-void ConstraintsToString(const QueryConstraints& qc,
-                         const SqliteTableLegacy::Schema& schema,
-                         std::string& out) {
-  bool is_first = true;
-  for (const auto& cs : qc.constraints()) {
-    if (!is_first) {
-      out.append(",");
-    }
-    out.append(schema.columns()[static_cast<size_t>(cs.column)].name());
-    out.append(" ");
-    out.append(OpToDebugString(cs.op));
-    is_first = false;
-  }
-}
-
-void OrderByToString(const QueryConstraints& qc,
-                     const SqliteTableLegacy::Schema& schema,
-                     std::string& out) {
-  bool is_first = true;
-  for (const auto& ob : qc.order_by()) {
-    if (!is_first) {
-      out.append(",");
-    }
-    out.append(schema.columns()[static_cast<size_t>(ob.iColumn)].name());
-    out.append(" ");
-    out.append(std::to_string(ob.desc));
-    is_first = false;
-  }
-}
-
-std::string QcDebugStr(const QueryConstraints& qc,
-                       const SqliteTableLegacy::Schema& schema) {
-  std::string str_result;
-  str_result.reserve(512);
-
-  str_result.append("C");
-  str_result.append(std::to_string(qc.constraints().size()));
-  str_result.append(",");
-  ConstraintsToString(qc, schema, str_result);
-  str_result.append(";");
-
-  str_result.append("O");
-  str_result.append(std::to_string(qc.order_by().size()));
-  str_result.append(",");
-  OrderByToString(qc, schema, str_result);
-  str_result.append(";");
-
-  str_result.append("U");
-  str_result.append(std::to_string(qc.cols_used()));
-
-  return str_result;
-}
-
-void WriteQueryConstraintsToMetatrace(metatrace::Record* r,
-                                      const QueryConstraints& qc,
-                                      const SqliteTableLegacy::Schema& schema) {
-  r->AddArg("constraint_count", std::to_string(qc.constraints().size()));
-  std::string constraints;
-  ConstraintsToString(qc, schema, constraints);
-  r->AddArg("constraints", constraints);
-  r->AddArg("order_by_count", std::to_string(qc.order_by().size()));
-  std::string order_by;
-  OrderByToString(qc, schema, order_by);
-  r->AddArg("order_by", order_by);
-  r->AddArg("columns_used", std::to_string(qc.cols_used()));
-}
-
-}  // namespace
-
-// static
-bool SqliteTableLegacy::debug = false;
-
-SqliteTableLegacy::SqliteTableLegacy() = default;
-SqliteTableLegacy::~SqliteTableLegacy() = default;
-
-base::Status SqliteTableLegacy::ModifyConstraints(QueryConstraints*) {
-  return base::OkStatus();
-}
-
-int SqliteTableLegacy::FindFunction(const char*, FindFunctionFn*, void**) {
-  return 0;
-}
-
-base::Status SqliteTableLegacy::Update(int, sqlite3_value**, sqlite3_int64*) {
-  return base::ErrStatus("Updating not supported");
-}
-
-bool SqliteTableLegacy::ReadConstraints(int idxNum,
-                                        const char* idxStr,
-                                        int argc) {
-  bool cache_hit = true;
-  if (idxNum != qc_hash_) {
-    qc_cache_ = QueryConstraints::FromString(idxStr);
-    qc_hash_ = idxNum;
-    cache_hit = false;
-  }
-
-  PERFETTO_TP_TRACE(metatrace::Category::QUERY_DETAILED,
-                    "SQLITE_TABLE_READ_CONSTRAINTS", [&](metatrace::Record* r) {
-                      r->AddArg("cache_hit", std::to_string(cache_hit));
-                      r->AddArg("name", name_);
-                      WriteQueryConstraintsToMetatrace(r, qc_cache_, schema_);
-                      r->AddArg("raw_constraints", idxStr);
-                      r->AddArg("argc", std::to_string(argc));
-                    });
-
-  // Logging this every ReadConstraints just leads to log spam on joins making
-  // it unusable. Instead, only print this out when we miss the cache (which
-  // happens precisely when the constraint set from SQLite changes.)
-  if (SqliteTableLegacy::debug && !cache_hit) {
-    PERFETTO_LOG("[%s::ParseConstraints] constraints=%s argc=%d", name_.c_str(),
-                 QcDebugStr(qc_cache_, schema_).c_str(), argc);
-  }
-  return cache_hit;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// SqliteTableLegacy::BaseCursor implementation
-////////////////////////////////////////////////////////////////////////////////
-
-SqliteTableLegacy::BaseCursor::BaseCursor(SqliteTableLegacy* table)
-    : table_(table) {
-  // This is required to prevent us from leaving this field uninitialised if
-  // we ever move construct the Cursor.
-  pVtab = table;
-}
-SqliteTableLegacy::BaseCursor::~BaseCursor() = default;
-
-////////////////////////////////////////////////////////////////////////////////
-// SqliteTableLegacy::Column implementation
-////////////////////////////////////////////////////////////////////////////////
-
-SqliteTableLegacy::Column::Column(size_t index,
-                                  std::string name,
-                                  SqlValue::Type type,
-                                  bool hidden)
-    : index_(index), name_(name), type_(type), hidden_(hidden) {}
-
-////////////////////////////////////////////////////////////////////////////////
-// SqliteTableLegacy::Schema implementation
-////////////////////////////////////////////////////////////////////////////////
-
-SqliteTableLegacy::Schema::Schema() = default;
-
-SqliteTableLegacy::Schema::Schema(std::vector<Column> columns,
-                                  std::vector<size_t> primary_keys)
-    : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
-  for (size_t i = 0; i < columns_.size(); i++) {
-    PERFETTO_CHECK(columns_[i].index() == i);
-  }
-  for (auto key : primary_keys_) {
-    PERFETTO_CHECK(key < columns_.size());
-  }
-}
-
-SqliteTableLegacy::Schema::Schema(const Schema&) = default;
-SqliteTableLegacy::Schema& SqliteTableLegacy::Schema::operator=(const Schema&) =
-    default;
-
-std::string SqliteTableLegacy::Schema::ToCreateTableStmt() const {
-  std::string stmt = "CREATE TABLE x(";
-  for (size_t i = 0; i < columns_.size(); ++i) {
-    const Column& col = columns_[i];
-    stmt += " " + col.name();
-
-    if (col.type() != SqlValue::Type::kNull) {
-      stmt += " " + sqlite::utils::SqlValueTypeToString(col.type());
-    } else if (std::find(primary_keys_.begin(), primary_keys_.end(), i) !=
-               primary_keys_.end()) {
-      PERFETTO_FATAL("Unknown type for primary key column %s",
-                     col.name().c_str());
-    }
-    if (col.hidden()) {
-      stmt += " HIDDEN";
-    }
-    stmt += ",";
-  }
-  stmt += " PRIMARY KEY(";
-  for (size_t i = 0; i < primary_keys_.size(); i++) {
-    if (i != 0)
-      stmt += ", ";
-    stmt += columns_[primary_keys_[i]].name();
-  }
-  stmt += ")) WITHOUT ROWID;";
-  return stmt;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TypedSqliteTableBase implementation
-////////////////////////////////////////////////////////////////////////////////
-
-TypedSqliteTableBase::~TypedSqliteTableBase() = default;
-
-base::Status TypedSqliteTableBase::DeclareAndAssignVtab(
-    std::unique_ptr<SqliteTableLegacy> table,
-    sqlite3_vtab** tab) {
-  auto create_stmt = table->schema().ToCreateTableStmt();
-  PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str());
-  RETURN_IF_ERROR(table->engine_->DeclareVirtualTable(create_stmt));
-  *tab = table.release();
-  return base::OkStatus();
-}
-
-int TypedSqliteTableBase::xDestroy(sqlite3_vtab* t) {
-  auto* table = static_cast<SqliteTableLegacy*>(t);
-  table->engine_->OnSqliteTableDestroyed(table->name_);
-  delete table;
-  return SQLITE_OK;
-}
-
-int TypedSqliteTableBase::xDestroyFatal(sqlite3_vtab*) {
-  PERFETTO_FATAL("xDestroy should not be called");
-}
-
-int TypedSqliteTableBase::xConnectRestoreTable(sqlite3*,
-                                               void* arg,
-                                               int,
-                                               const char* const* argv,
-                                               sqlite3_vtab** tab,
-                                               char** pzErr) {
-  auto* xArg = static_cast<BaseModuleArg*>(arg);
-
-  // SQLite guarantees that argv[2] contains the name of the table.
-  std::string table_name = argv[2];
-  base::StatusOr<std::unique_ptr<SqliteTableLegacy>> table =
-      xArg->engine->RestoreSqliteTable(table_name);
-  if (!table.status().ok()) {
-    *pzErr = sqlite3_mprintf("%s", table.status().c_message());
-    return SQLITE_ERROR;
-  }
-  base::Status status = DeclareAndAssignVtab(std::move(table.value()), tab);
-  if (!status.ok()) {
-    *pzErr = sqlite3_mprintf("%s", status.c_message());
-    return SQLITE_ERROR;
-  }
-  return SQLITE_OK;
-}
-
-int TypedSqliteTableBase::xDisconnectSaveTable(sqlite3_vtab* t) {
-  auto* table = static_cast<TypedSqliteTableBase*>(t);
-  base::Status status = table->engine_->SaveSqliteTable(
-      table->name(), std::unique_ptr<SqliteTableLegacy>(table));
-  return table->SetStatusAndReturn(status);
-}
-
-base::Status TypedSqliteTableBase::InitInternal(SqliteEngine* engine,
-                                                int argc,
-                                                const char* const* argv) {
-  // Set the engine to allow saving into it later.
-  engine_ = engine;
-
-  // SQLite guarantees that argv[0] will be the "module" name: this is the
-  // same as |table_name| passed to the Register function.
-  module_name_ = argv[0];
-
-  // SQLite guarantees that argv[2] contains the name of the table: for
-  // non-arg taking tables, this will be the same as |table_name| but for
-  // arg-taking tables, this will be the table name as defined by the
-  // user in the CREATE VIRTUAL TABLE call.
-  name_ = argv[2];
-
-  Schema schema;
-  RETURN_IF_ERROR(Init(argc, argv, &schema));
-  schema_ = std::move(schema);
-  return base::OkStatus();
-}
-
-int TypedSqliteTableBase::xOpen(sqlite3_vtab* t,
-                                sqlite3_vtab_cursor** ppCursor) {
-  auto* table = static_cast<TypedSqliteTableBase*>(t);
-  *ppCursor =
-      static_cast<sqlite3_vtab_cursor*>(table->CreateCursor().release());
-  return SQLITE_OK;
-}
-
-int TypedSqliteTableBase::xBestIndex(sqlite3_vtab* t, sqlite3_index_info* idx) {
-  auto* table = static_cast<TypedSqliteTableBase*>(t);
-
-  QueryConstraints qc(idx->colUsed);
-
-  for (int i = 0; i < idx->nConstraint; i++) {
-    const auto& cs = idx->aConstraint[i];
-    if (!cs.usable)
-      continue;
-    qc.AddConstraint(cs.iColumn, cs.op, i);
-  }
-
-  for (int i = 0; i < idx->nOrderBy; i++) {
-    int column = idx->aOrderBy[i].iColumn;
-    bool desc = idx->aOrderBy[i].desc;
-    qc.AddOrderBy(column, desc);
-  }
-
-  int ret = table->SetStatusAndReturn(table->ModifyConstraints(&qc));
-  if (ret != SQLITE_OK)
-    return ret;
-
-  BestIndexInfo info;
-  info.estimated_cost = idx->estimatedCost;
-  info.estimated_rows = idx->estimatedRows;
-  info.sqlite_omit_constraint.resize(qc.constraints().size());
-
-  ret = table->BestIndex(qc, &info);
-
-  if (ret != SQLITE_OK)
-    return ret;
-
-  idx->orderByConsumed = qc.order_by().empty() || info.sqlite_omit_order_by;
-  idx->estimatedCost = info.estimated_cost;
-  idx->estimatedRows = info.estimated_rows;
-
-  // First pass: mark all constraints as omitted to ensure that any pruned
-  // constraints are not checked for by SQLite.
-  for (int i = 0; i < idx->nConstraint; ++i) {
-    auto& u = idx->aConstraintUsage[i];
-    u.omit = true;
-  }
-
-  // Second pass: actually set the correct omit and index values for all
-  // retained constraints.
-  for (uint32_t i = 0; i < qc.constraints().size(); ++i) {
-    auto& u = idx->aConstraintUsage[qc.constraints()[i].a_constraint_idx];
-    u.omit = info.sqlite_omit_constraint[i];
-    u.argvIndex = static_cast<int>(i) + 1;
-  }
-
-  PERFETTO_TP_TRACE(
-      metatrace::Category::QUERY_TIMELINE, "SQLITE_TABLE_BEST_INDEX",
-      [&](metatrace::Record* r) {
-        r->AddArg("name", table->name());
-        WriteQueryConstraintsToMetatrace(r, qc, table->schema());
-        r->AddArg("order_by_consumed", std::to_string(idx->orderByConsumed));
-        r->AddArg("estimated_cost", std::to_string(idx->estimatedCost));
-        r->AddArg("estimated_rows",
-                  std::to_string(static_cast<int64_t>(idx->estimatedRows)));
-      });
-
-  auto out_qc_str = qc.ToNewSqlite3String();
-  if (SqliteTableLegacy::debug) {
-    PERFETTO_LOG(
-        "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%f "
-        "estimatedRows=%" PRId64,
-        table->name().c_str(), QcDebugStr(qc, table->schema()).c_str(),
-        idx->orderByConsumed, idx->estimatedCost,
-        static_cast<int64_t>(idx->estimatedRows));
-  }
-
-  idx->idxStr = out_qc_str.release();
-  idx->needToFreeIdxStr = true;
-  idx->idxNum = ++table->best_index_num_;
-
-  return SQLITE_OK;
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
deleted file mode 100644
index d3b1c32..0000000
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * Copyright (C) 2018 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_SQLITE_SQLITE_TABLE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TABLE_H_
-
-#include <sqlite3.h>
-#include <memory>
-#include <string>
-#include <type_traits>
-#include <vector>
-
-#include "perfetto/base/status.h"
-#include "perfetto/trace_processor/basic_types.h"
-#include "src/trace_processor/sqlite/query_constraints.h"
-
-namespace perfetto::trace_processor {
-
-class SqliteEngine;
-class TypedSqliteTableBase;
-
-// Abstract base class representing a SQLite virtual table. Implements the
-// common bookeeping required across all tables and allows subclasses to
-// implement a friendlier API than that required by SQLite.
-class SqliteTableLegacy : public sqlite3_vtab {
- public:
-  // Custom opcodes used by subclasses of SqliteTableLegacy.
-  // Stored here as we need a central repository of opcodes to prevent clashes
-  // between different sub-classes.
-  enum CustomFilterOpcode {
-    kSourceGeqOpCode = SQLITE_INDEX_CONSTRAINT_FUNCTION + 1,
-  };
-  // Describes a column of this table.
-  class Column {
-   public:
-    Column(size_t idx,
-           std::string name,
-           SqlValue::Type type,
-           bool hidden = false);
-
-    size_t index() const { return index_; }
-    const std::string& name() const { return name_; }
-    SqlValue::Type type() const { return type_; }
-
-    bool hidden() const { return hidden_; }
-    void set_hidden(bool hidden) { hidden_ = hidden; }
-
-   private:
-    size_t index_ = 0;
-    std::string name_;
-    SqlValue::Type type_ = SqlValue::Type::kNull;
-    bool hidden_ = false;
-  };
-
-  // Abstract base class representing an SQLite Cursor. Presents a friendlier
-  // API for subclasses to implement.
-  class BaseCursor : public sqlite3_vtab_cursor {
-   public:
-    // Enum for the history of calls to Filter.
-    enum class FilterHistory : uint32_t {
-      // Indicates that constraint set passed is the different to the
-      // previous Filter call.
-      kDifferent = 0,
-
-      // Indicates that the constraint set passed is the same as the previous
-      // Filter call.
-      // This can be useful for subclasses to perform optimizations on repeated
-      // nested subqueries.
-      kSame = 1,
-    };
-
-    explicit BaseCursor(SqliteTableLegacy* table);
-    virtual ~BaseCursor();
-
-    // Methods to be implemented by derived table classes.
-    // Note: these methods are intentionally not virtual for performance
-    // reasons. As these methods are not defined, there will be compile errors
-    // thrown if any of these methods are missing.
-
-    // Called to intialise the cursor with the constraints of the query.
-    base::Status Filter(const QueryConstraints& qc,
-                        sqlite3_value**,
-                        FilterHistory);
-
-    // Called to forward the cursor to the next row in the table.
-    void Next();
-
-    // Called to check if the cursor has reached eof. Column will be called iff
-    // this method returns true.
-    bool Eof();
-
-    // Used to extract the value from the column at index |N|.
-    void Column(sqlite3_context* context, int N);
-
-    SqliteTableLegacy* table() const { return table_; }
-
-   protected:
-    BaseCursor(BaseCursor&) = delete;
-    BaseCursor& operator=(const BaseCursor&) = delete;
-
-    BaseCursor(BaseCursor&&) noexcept = default;
-    BaseCursor& operator=(BaseCursor&&) = default;
-
-   private:
-    SqliteTableLegacy* table_ = nullptr;
-  };
-
-  // The schema of the table. Created by subclasses to allow the table class to
-  // do filtering and inform SQLite about the CREATE table statement.
-  class Schema {
-   public:
-    Schema();
-    Schema(std::vector<Column>, std::vector<size_t> primary_keys);
-
-    // This class is explicitly copiable.
-    Schema(const Schema&);
-    Schema& operator=(const Schema& t);
-
-    std::string ToCreateTableStmt() const;
-
-    const std::vector<Column>& columns() const { return columns_; }
-    std::vector<Column>* mutable_columns() { return &columns_; }
-
-    const std::vector<size_t> primary_keys() { return primary_keys_; }
-
-   private:
-    // The names and types of the columns of the table.
-    std::vector<Column> columns_;
-
-    // The primary keys of the table given by an offset into |columns|.
-    std::vector<size_t> primary_keys_;
-  };
-
-  enum TableType {
-    // A table which automatically exists in the main schema and cannot be
-    // created with CREATE VIRTUAL TABLE.
-    // Note: the name value here matches the naming in the vtable docs of
-    // SQLite.
-    kEponymousOnly,
-
-    // A table which must be explicitly created using a CREATE VIRTUAL TABLE
-    // statement (i.e. does exist automatically).
-    kExplicitCreate,
-  };
-
-  // Public for unique_ptr destructor calls.
-  virtual ~SqliteTableLegacy();
-
-  // When set it logs all BestIndex and Filter actions on the console.
-  static bool debug;
-
- protected:
-  // Populated by a BestIndex call to allow subclasses to tweak SQLite's
-  // handling of sets of constraints.
-  struct BestIndexInfo {
-    // Contains bools which indicate whether SQLite should omit double checking
-    // the constraint at that index.
-    //
-    // If there are no constraints, SQLite will be told it can omit checking for
-    // the whole query.
-    std::vector<bool> sqlite_omit_constraint;
-
-    // Indicates that SQLite should not double check the result of the order by
-    // clause.
-    //
-    // If there are no order by clauses, this value will be ignored and SQLite
-    // will be told that it can omit double checking (i.e. this value will
-    // implicitly be taken to be true).
-    bool sqlite_omit_order_by = false;
-
-    // Stores the estimated cost of this query.
-    double estimated_cost = 0;
-
-    // Estimated row count.
-    int64_t estimated_rows = 0;
-  };
-
-  SqliteTableLegacy();
-
-  // Methods to be implemented by derived table classes.
-  virtual base::Status Init(int argc, const char* const* argv, Schema*) = 0;
-  virtual std::unique_ptr<BaseCursor> CreateCursor() = 0;
-  virtual int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) = 0;
-
-  // Optional metods to implement.
-  using FindFunctionFn = void (*)(sqlite3_context*, int, sqlite3_value**);
-  virtual base::Status ModifyConstraints(QueryConstraints* qc);
-  virtual int FindFunction(const char* name, FindFunctionFn* fn, void** args);
-
-  // At registration time, the function should also pass true for |read_write|.
-  virtual base::Status Update(int, sqlite3_value**, sqlite3_int64*);
-
-  bool ReadConstraints(int idxNum, const char* idxStr, int argc);
-
-  const Schema& schema() const { return schema_; }
-  const std::string& module_name() const { return module_name_; }
-  const std::string& name() const { return name_; }
-
- private:
-  template <typename, typename>
-  friend class TypedSqliteTable;
-  friend class TypedSqliteTableBase;
-
-  SqliteTableLegacy(const SqliteTableLegacy&) = delete;
-  SqliteTableLegacy& operator=(const SqliteTableLegacy&) = delete;
-
-  // The engine class this table is registered with. Used for restoring/saving
-  // the table.
-  SqliteEngine* engine_ = nullptr;
-
-  // This name of the table. For tables created using CREATE VIRTUAL TABLE, this
-  // will be the name of the table specified by the query. For automatically
-  // created tables, this will be the same as the module name registered.
-  std::string name_;
-
-  // The module name is the name that will be registered. This is
-  // differs from the table name (|name_|) where the table was created using
-  // CREATE VIRTUAL TABLE.
-  std::string module_name_;
-
-  Schema schema_;
-
-  QueryConstraints qc_cache_;
-  int qc_hash_ = 0;
-  int best_index_num_ = 0;
-};
-
-class TypedSqliteTableBase : public SqliteTableLegacy {
- protected:
-  struct BaseModuleArg {
-    sqlite3_module module;
-    SqliteEngine* engine;
-    TableType table_type;
-  };
-
-  ~TypedSqliteTableBase() override;
-
-  static int xDestroy(sqlite3_vtab*);
-  static int xDestroyFatal(sqlite3_vtab*);
-
-  static int xConnectRestoreTable(sqlite3* xdb,
-                                  void* arg,
-                                  int argc,
-                                  const char* const* argv,
-                                  sqlite3_vtab** tab,
-                                  char** pzErr);
-  static int xDisconnectSaveTable(sqlite3_vtab*);
-
-  static int xOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
-  static int xBestIndex(sqlite3_vtab*, sqlite3_index_info*);
-
-  static base::Status DeclareAndAssignVtab(
-      std::unique_ptr<SqliteTableLegacy> table,
-      sqlite3_vtab** tab);
-
-  base::Status InitInternal(SqliteEngine* engine,
-                            int argc,
-                            const char* const* argv);
-
-  int SetStatusAndReturn(base::Status status) {
-    if (!status.ok()) {
-      sqlite3_free(zErrMsg);
-      zErrMsg = sqlite3_mprintf("%s", status.c_message());
-      return SQLITE_ERROR;
-    }
-    return SQLITE_OK;
-  }
-};
-
-template <typename SubTable, typename Context>
-class TypedSqliteTable : public TypedSqliteTableBase {
- public:
-  struct ModuleArg : public BaseModuleArg {
-    Context context;
-  };
-
-  static std::unique_ptr<ModuleArg> CreateModuleArg(SqliteEngine* engine,
-                                                    Context ctx,
-                                                    TableType table_type,
-                                                    bool updatable) {
-    auto arg = std::make_unique<ModuleArg>();
-    arg->module = CreateModule(table_type, updatable);
-    arg->engine = engine;
-    arg->table_type = table_type;
-    arg->context = std::move(ctx);
-    return arg;
-  }
-
- private:
-  static constexpr sqlite3_module CreateModule(TableType table_type,
-                                               bool updatable) {
-    sqlite3_module module;
-    memset(&module, 0, sizeof(sqlite3_module));
-    switch (table_type) {
-      case TableType::kEponymousOnly:
-        // Neither xCreate nor xDestroy should ever be called for
-        // eponymous-only tables.
-        module.xCreate = nullptr;
-        module.xDestroy = &xDestroyFatal;
-
-        // xConnect and xDisconnect will automatically be called with
-        // |module_name| == |name|.
-        module.xConnect = &xCreate;
-        module.xDisconnect = &xDestroy;
-        break;
-      case TableType::kExplicitCreate:
-        // xCreate and xDestroy will be called when the table is CREATE-ed and
-        // DROP-ed respectively.
-        module.xCreate = &xCreate;
-        module.xDestroy = &xDestroy;
-
-        // xConnect and xDisconnect can be called at any time.
-        module.xConnect = &xConnectRestoreTable;
-        module.xDisconnect = &xDisconnectSaveTable;
-        break;
-    }
-    module.xOpen = &xOpen;
-    module.xClose = &xClose;
-    module.xBestIndex = &xBestIndex;
-    module.xFindFunction = &xFindFunction;
-    module.xFilter = &xFilter;
-    module.xNext = &xNext;
-    module.xEof = &xEof;
-    module.xColumn = &xColumn;
-    module.xRowid = &xRowid;
-    if (updatable) {
-      module.xUpdate = &xUpdate;
-    }
-    return module;
-  }
-
-  static int xCreate(sqlite3* xdb,
-                     void* arg,
-                     int argc,
-                     const char* const* argv,
-                     sqlite3_vtab** tab,
-                     char** pzErr) {
-    auto* xdesc = static_cast<ModuleArg*>(arg);
-    std::unique_ptr<SubTable> table(new SubTable(xdb, &*xdesc->context));
-    SubTable* table_ptr = table.get();
-    base::Status status = table->InitInternal(xdesc->engine, argc, argv);
-    if (!status.ok()) {
-      *pzErr = sqlite3_mprintf("%s", status.c_message());
-      return SQLITE_ERROR;
-    }
-    status = DeclareAndAssignVtab(std::move(table), tab);
-    if (!status.ok()) {
-      *pzErr = sqlite3_mprintf("%s", status.c_message());
-      return SQLITE_ERROR;
-    }
-    xdesc->engine->OnSqliteTableCreated(table_ptr->name(), xdesc->table_type);
-    return SQLITE_OK;
-  }
-  static int xClose(sqlite3_vtab_cursor* c) {
-    delete static_cast<typename SubTable::Cursor*>(c);
-    return SQLITE_OK;
-  }
-  static int xFindFunction(sqlite3_vtab* t,
-                           int,
-                           const char* name,
-                           void (**fn)(sqlite3_context*, int, sqlite3_value**),
-                           void** args) {
-    return static_cast<SubTable*>(t)->FindFunction(name, fn, args);
-  }
-  static int xFilter(sqlite3_vtab_cursor* vc,
-                     int i,
-                     const char* s,
-                     int a,
-                     sqlite3_value** v) {
-    auto* cursor = static_cast<typename SubTable::Cursor*>(vc);
-    bool is_cached = cursor->table()->ReadConstraints(i, s, a);
-    auto history = is_cached ? BaseCursor::FilterHistory::kSame
-                             : BaseCursor::FilterHistory::kDifferent;
-    auto* table = static_cast<SubTable*>(cursor->table());
-    return table->SetStatusAndReturn(
-        cursor->Filter(cursor->table()->qc_cache_, v, history));
-  }
-  static int xNext(sqlite3_vtab_cursor* c) {
-    auto* cursor = static_cast<typename SubTable::Cursor*>(c);
-    using NextType = decltype(&SubTable::Cursor::Next);
-    using ReturnType =
-        std::invoke_result_t<NextType, typename SubTable::Cursor*>;
-    if constexpr (std::is_same_v<ReturnType, void>) {
-      cursor->Next();
-      return SQLITE_OK;
-    } else {
-      auto* table = static_cast<SubTable*>(cursor->table());
-      return table->SetStatusAndReturn(cursor->Next());
-    }
-  }
-  static int xEof(sqlite3_vtab_cursor* c) {
-    return static_cast<int>(static_cast<typename SubTable::Cursor*>(c)->Eof());
-  }
-  static int xColumn(sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
-    auto* cursor = static_cast<typename SubTable::Cursor*>(c);
-    using ColumnType = decltype(&SubTable::Cursor::Column);
-    using ReturnType =
-        std::invoke_result_t<ColumnType, typename SubTable::Cursor*,
-                             sqlite3_context*, int>;
-    if constexpr (std::is_same_v<ReturnType, void>) {
-      cursor->Column(a, b);
-      return SQLITE_OK;
-    } else {
-      auto* table = static_cast<SubTable*>(cursor->table());
-      return table->SetStatusAndReturn(cursor->Column(a, b));
-    }
-  }
-  static int xRowid(sqlite3_vtab_cursor*, sqlite3_int64*) {
-    return SQLITE_ERROR;
-  }
-  static int xUpdate(sqlite3_vtab* t,
-                     int a,
-                     sqlite3_value** v,
-                     sqlite3_int64* r) {
-    auto* table = static_cast<SubTable*>(t);
-    return table->SetStatusAndReturn(table->Update(a, v, r));
-  }
-};
-
-}  // namespace perfetto::trace_processor
-
-#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TABLE_H_
diff --git a/src/trace_processor/sqlite/sqlite_utils.cc b/src/trace_processor/sqlite/sqlite_utils.cc
index 0de36f6..b9141df 100644
--- a/src/trace_processor/sqlite/sqlite_utils.cc
+++ b/src/trace_processor/sqlite/sqlite_utils.cc
@@ -31,7 +31,6 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 
 namespace perfetto::trace_processor::sqlite::utils {
 namespace internal {
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index ba1eed6..77654e3 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -22,6 +22,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
+#include <functional>
 #include <optional>
 #include <string>
 #include <utility>
@@ -32,7 +33,6 @@
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/sqlite/bindings/sqlite_result.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 
 namespace perfetto::trace_processor::sqlite::utils {
 
@@ -156,6 +156,54 @@
                                 status.c_message()));
 }
 
+// For a given |sqlite3_index_info| struct received in a BestIndex call, returns
+// whether all |arg_count| arguments (with |is_arg_column| indicating whether a
+// given column is a function argument) have exactly one equaltiy constraint
+// associated with them.
+//
+// If so, the associated constraint is omitted and the argvIndex is mapped to
+// the corresponding argument's index.
+inline base::Status ValidateFunctionArguments(
+    sqlite3_index_info* info,
+    size_t arg_count,
+    const std::function<bool(size_t)>& is_arg_column) {
+  std::vector<bool> present;
+  size_t present_count = 0;
+  for (int i = 0; i < info->nConstraint; ++i) {
+    const auto& in = info->aConstraint[i];
+    if (!in.usable) {
+      continue;
+    }
+    auto cs_col = static_cast<size_t>(in.iColumn);
+    if (!is_arg_column(cs_col)) {
+      continue;
+    }
+    if (!IsOpEq(in.op)) {
+      return base::ErrStatus(
+          "Unexpected non equality constraints for column %zu", cs_col);
+    }
+    if (cs_col >= present.size()) {
+      present.resize(cs_col + 1);
+    }
+    if (present[cs_col]) {
+      return base::ErrStatus("Unexpected multiple constraints for column %zu",
+                             cs_col);
+    }
+    present[cs_col] = true;
+    present_count++;
+
+    auto& out = info->aConstraintUsage[i];
+    out.argvIndex = static_cast<int>(present_count);
+    out.omit = true;
+  }
+  if (present_count != arg_count) {
+    return base::ErrStatus(
+        "Unexpected missing argument: expected %zu, actual %zu", arg_count,
+        present_count);
+  }
+  return base::OkStatus();
+}
+
 // Converts the given SqlValue type to the type string SQLite understands.
 inline std::string SqlValueTypeToString(SqlValue::Type type) {
   switch (type) {
diff --git a/src/trace_processor/trace_processor.cc b/src/trace_processor/trace_processor.cc
index 4d022f7..060c16d 100644
--- a/src/trace_processor/trace_processor.cc
+++ b/src/trace_processor/trace_processor.cc
@@ -16,11 +16,12 @@
 
 #include "perfetto/trace_processor/trace_processor.h"
 
-#include "src/trace_processor/sqlite/sqlite_table.h"
+#include <memory>
+
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/trace_processor_impl.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 TraceProcessor::MetatraceConfig::MetatraceConfig() = default;
 
@@ -32,12 +33,4 @@
 
 TraceProcessor::~TraceProcessor() = default;
 
-// static
-void EnableSQLiteVtableDebugging() {
-  // This level of indirection is required to avoid clients to depend on table.h
-  // which in turn requires sqlite headers.
-  SqliteTableLegacy::debug = true;
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 9f53c22..b36a63a 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -103,7 +103,6 @@
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sql_stats_table.h"
-#include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/sqlite/stats_table.h"
 #include "src/trace_processor/storage/metadata.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -799,7 +798,6 @@
 
   RegisterStaticTable(storage->slice_table());
   RegisterStaticTable(storage->flow_table());
-  RegisterStaticTable(storage->slice_table());
   RegisterStaticTable(storage->sched_slice_table());
   RegisterStaticTable(storage->spurious_sched_wakeup_table());
   RegisterStaticTable(storage->thread_state_table());
@@ -866,9 +864,6 @@
   RegisterStaticTable(storage->jit_code_table());
   RegisterStaticTable(storage->jit_frame_table());
 
-  RegisterStaticTable(storage->jit_code_table());
-  RegisterStaticTable(storage->jit_frame_table());
-
   RegisterStaticTable(storage->surfaceflinger_layers_snapshot_table());
   RegisterStaticTable(storage->surfaceflinger_layer_table());
   RegisterStaticTable(storage->surfaceflinger_transactions_table());
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 692b105..faabf21 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -813,7 +813,6 @@
   static const option long_options[] = {
       {"help", no_argument, nullptr, 'h'},
       {"version", no_argument, nullptr, 'v'},
-      {"debug", no_argument, nullptr, 'd'},
       {"wide", no_argument, nullptr, 'W'},
       {"perf-file", required_argument, nullptr, 'p'},
       {"query-file", required_argument, nullptr, 'q'},
@@ -859,11 +858,6 @@
       exit(0);
     }
 
-    if (option == 'd') {
-      EnableSQLiteVtableDebugging();
-      continue;
-    }
-
     if (option == 'W') {
       command_line_options.wide = true;
       continue;
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 4e93f6a..bd2e925 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -32,6 +32,8 @@
     "build_timeline.h",
     "filter_ftrace_using_allowlist.cc",
     "filter_ftrace_using_allowlist.h",
+    "filter_print_events.cc",
+    "filter_print_events.h",
     "filter_sched_waking_events.cc",
     "filter_sched_waking_events.h",
     "filter_task_rename.cc",
@@ -52,6 +54,8 @@
     "redact_sched_switch.h",
     "scrub_ftrace_events.cc",
     "scrub_ftrace_events.h",
+    "scrub_process_stats.cc",
+    "scrub_process_stats.h",
     "scrub_process_trees.cc",
     "scrub_process_trees.h",
     "scrub_trace_packet.cc",
@@ -83,6 +87,7 @@
     "filter_task_rename_integrationtest.cc",
     "redact_sched_switch_integrationtest.cc",
     "scrub_ftrace_events_integrationtest.cc",
+    "scrub_process_stats_integrationtest.cc",
     "scrub_process_trees_integrationtest.cc",
     "trace_redaction_integration_fixture.cc",
     "trace_redaction_integration_fixture.h",
diff --git a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
index c0abf3f..89946ae 100644
--- a/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
+++ b/src/trace_redaction/filter_ftrace_using_allowlist_integrationtest.cc
@@ -170,6 +170,7 @@
   ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kTimestampFieldNumber));
 
   // These are events.
+  ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber));
   ASSERT_TRUE(
       events.count(protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber));
   ASSERT_TRUE(events.count(protos::pbzero::FtraceEvent::kCpuIdleFieldNumber));
@@ -197,7 +198,6 @@
   // These are events.
   ASSERT_FALSE(
       events.count(protos::pbzero::FtraceEvent::kOomScoreAdjUpdateFieldNumber));
-  ASSERT_FALSE(events.count(protos::pbzero::FtraceEvent::kPrintFieldNumber));
   ASSERT_FALSE(
       events.count(protos::pbzero::FtraceEvent::kSchedProcessExitFieldNumber));
   ASSERT_FALSE(
diff --git a/src/trace_redaction/filter_print_events.cc b/src/trace_redaction/filter_print_events.cc
new file mode 100644
index 0000000..6c1843b
--- /dev/null
+++ b/src/trace_redaction/filter_print_events.cc
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/filter_print_events.h"
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status FilterPrintEvents::VerifyContext(const Context& context) const {
+  if (!context.package_uid.has_value()) {
+    return base::ErrStatus("FilterPrintEvents: missing packet uid.");
+  }
+
+  if (!context.timeline) {
+    return base::ErrStatus("FilterPrintEvents: missing timeline.");
+  }
+
+  return base::OkStatus();
+}
+
+bool FilterPrintEvents::KeepEvent(const Context& context,
+                                  protozero::ConstBytes bytes) const {
+  PERFETTO_DCHECK(context.timeline);
+  PERFETTO_DCHECK(context.package_uid.has_value());
+
+  const auto* timeline = context.timeline.get();
+  auto package_uid = context.package_uid;
+
+  protozero::ProtoDecoder event(bytes);
+
+  // This is not a print packet. Keep the packet.
+  if (!event.FindField(protos::pbzero::FtraceEvent::kPrintFieldNumber)
+           .valid()) {
+    return true;
+  }
+
+  auto time =
+      event.FindField(protos::pbzero::FtraceEvent::kTimestampFieldNumber);
+  auto pid = event.FindField(protos::pbzero::FtraceEvent::kPidFieldNumber);
+
+  // Pid + Time --> UID, if the uid matches the target package, keep the event.
+  return pid.valid() && time.valid() &&
+         timeline->Search(time.as_uint64(), pid.as_int32()).uid == package_uid;
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/filter_print_events.h b/src/trace_redaction/filter_print_events.h
new file mode 100644
index 0000000..36ef92b
--- /dev/null
+++ b/src/trace_redaction/filter_print_events.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
+#define SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
+
+#include "perfetto/protozero/field.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// event {
+//   timestamp: 6702093749982230
+//   pid: 7947                    <-- target
+//   print {
+//     buf: "B|7105|virtual void
+//     swappy::ChoreographerThread::onChoreographer()\n"
+//   }
+// }
+//
+// If the target pid doesn't belong to the target package (context.package_uid),
+// then the event will be marked as "don't keep".
+class FilterPrintEvents : public FtraceEventFilter {
+ public:
+  base::Status VerifyContext(const Context& context) const override;
+  bool KeepEvent(const Context& context,
+                 protozero::ConstBytes bytes) const override;
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_FILTER_PRINT_EVENTS_H_
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 5a55943..961e337 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -18,6 +18,7 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/build_timeline.h"
 #include "src/trace_redaction/filter_ftrace_using_allowlist.h"
+#include "src/trace_redaction/filter_print_events.h"
 #include "src/trace_redaction/filter_sched_waking_events.h"
 #include "src/trace_redaction/filter_task_rename.h"
 #include "src/trace_redaction/find_package_uid.h"
@@ -26,6 +27,7 @@
 #include "src/trace_redaction/prune_package_list.h"
 #include "src/trace_redaction/redact_sched_switch.h"
 #include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_process_stats.h"
 #include "src/trace_redaction/scrub_process_trees.h"
 #include "src/trace_redaction/scrub_trace_packet.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
@@ -55,11 +57,13 @@
   // number of events they need to iterate over.
   auto scrub_ftrace_events = redactor.emplace_transform<ScrubFtraceEvents>();
   scrub_ftrace_events->emplace_back<FilterFtraceUsingAllowlist>();
+  scrub_ftrace_events->emplace_back<FilterPrintEvents>();
   scrub_ftrace_events->emplace_back<FilterSchedWakingEvents>();
   scrub_ftrace_events->emplace_back<FilterTaskRename>();
 
   redactor.emplace_transform<ScrubProcessTrees>();
   redactor.emplace_transform<RedactSchedSwitch>();
+  redactor.emplace_transform<ScrubProcessStats>();
 
   Context context;
   context.package_name = package_name;
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 2ba81b6..7a5b48a 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -77,6 +77,7 @@
       protos::pbzero::FtraceEvent::kIonBufferDestroyFieldNumber,
       protos::pbzero::FtraceEvent::kDmaHeapStatFieldNumber,
       protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber,
+      protos::pbzero::FtraceEvent::kPrintFieldNumber,
   };
 
   // TODO: Some ftrace fields should be retained, but they carry too much risk
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
index 3260c71..c3d1abf 100644
--- a/src/trace_redaction/scrub_ftrace_events.cc
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -94,7 +94,9 @@
 bool ScrubFtraceEvents::KeepEvent(const Context& context,
                                   protozero::ConstBytes bytes) const {
   for (const auto& filter : filters_) {
-    if (!filter->KeepEvent(context, bytes)) {
+    auto keep = filter->KeepEvent(context, bytes);
+
+    if (!keep) {
       return false;
     }
   }
diff --git a/src/trace_redaction/scrub_process_stats.cc b/src/trace_redaction/scrub_process_stats.cc
new file mode 100644
index 0000000..991c02f
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_redaction/scrub_process_stats.h"
+
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
+#include "src/trace_redaction/proto_util.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status ScrubProcessStats::Transform(const Context& context,
+                                          std::string* packet) const {
+  if (!context.package_uid.has_value()) {
+    return base::ErrStatus("FilterProcessStats: missing package uid.");
+  }
+
+  if (!context.timeline) {
+    return base::ErrStatus("FilterProcessStats: missing timeline.");
+  }
+
+  protozero::ProtoDecoder packet_decoder(*packet);
+
+  // Very few packets will have process stats. It's best to avoid
+  // reserialization whenever possible.
+  if (!packet_decoder
+           .FindField(protos::pbzero::TracePacket::kProcessStatsFieldNumber)
+           .valid()) {
+    return base::OkStatus();
+  }
+
+  protozero::HeapBuffered<protos::pbzero::TracePacket> message;
+
+  // TODO(vaage): Add primitive to drop all packets that don't have a
+  // timestamp, allowing all other packets assume there are timestamps.
+  auto time_field = packet_decoder.FindField(
+      protos::pbzero::TracePacket::kTimestampFieldNumber);
+  PERFETTO_DCHECK(time_field.valid());
+  auto time = time_field.as_uint64();
+
+  auto* timeline = context.timeline.get();
+  auto uid = context.package_uid.value();
+
+  for (auto packet_field = packet_decoder.ReadField(); packet_field.valid();
+       packet_field = packet_decoder.ReadField()) {
+    if (packet_field.id() !=
+        protos::pbzero::TracePacket::kProcessStatsFieldNumber) {
+      proto_util::AppendField(packet_field, message.get());
+      continue;
+    }
+
+    auto process_stats = std::move(packet_field);
+    protozero::ProtoDecoder process_stats_decoder(process_stats.as_bytes());
+
+    auto* process_stats_message = message->set_process_stats();
+
+    for (auto process_stats_field = process_stats_decoder.ReadField();
+         process_stats_field.valid();
+         process_stats_field = process_stats_decoder.ReadField()) {
+      bool keep_field;
+
+      if (process_stats_field.id() ==
+          protos::pbzero::ProcessStats::kProcessesFieldNumber) {
+        protozero::ProtoDecoder process_decoder(process_stats_field.as_bytes());
+        auto pid = process_decoder.FindField(
+            protos::pbzero::ProcessStats::Process::kPidFieldNumber);
+        keep_field =
+            pid.valid() && timeline->Search(time, pid.as_int32()).uid == uid;
+      } else {
+        keep_field = true;
+      }
+
+      if (keep_field) {
+        proto_util::AppendField(process_stats_field, process_stats_message);
+      }
+    }
+  }
+
+  packet->assign(message.SerializeAsString());
+
+  return base::OkStatus();
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_process_stats.h b/src/trace_redaction/scrub_process_stats.h
new file mode 100644
index 0000000..99b6697
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
+#define SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
+
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+class ScrubProcessStats : public TransformPrimitive {
+ public:
+  base::Status Transform(const Context& context,
+                         std::string* packet) const override;
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_SCRUB_PROCESS_STATS_H_
diff --git a/src/trace_redaction/scrub_process_stats_integrationtest.cc b/src/trace_redaction/scrub_process_stats_integrationtest.cc
new file mode 100644
index 0000000..01c61b1
--- /dev/null
+++ b/src/trace_redaction/scrub_process_stats_integrationtest.cc
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <string>
+
+#include "perfetto/base/status.h"
+#include "src/base/test/status_matchers.h"
+#include "src/trace_redaction/build_timeline.h"
+#include "src/trace_redaction/optimize_timeline.h"
+#include "src/trace_redaction/scrub_process_stats.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ps/process_stats.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+class ScrubProcessStatsTest : public testing::Test,
+                              protected TraceRedactionIntegrationFixure {
+ protected:
+  void SetUp() override {
+    trace_redactor()->emplace_collect<BuildTimeline>();
+    trace_redactor()->emplace_build<OptimizeTimeline>();
+    trace_redactor()->emplace_transform<ScrubProcessStats>();
+
+    // Package "com.Unity.com.unity.multiplayer.samples.coop";
+    context()->package_uid = 10252;
+  }
+
+  // Gets pids from all process_stats messages in the trace (bytes).
+  base::FlatSet<int32_t> GetAllPids(const std::string& bytes) const {
+    base::FlatSet<int32_t> pids;
+
+    protos::pbzero::Trace::Decoder decoder(bytes);
+
+    for (auto packet = decoder.packet(); packet; ++packet) {
+      protos::pbzero::TracePacket::Decoder trace_packet(packet->as_bytes());
+
+      if (!trace_packet.has_process_stats()) {
+        continue;
+      }
+
+      protos::pbzero::ProcessStats::Decoder process_stats(
+          trace_packet.process_stats());
+
+      for (auto process = process_stats.processes(); process; ++process) {
+        protos::pbzero::ProcessStats::Process::Decoder p(process->as_bytes());
+        PERFETTO_DCHECK(p.has_pid());
+        pids.insert(p.pid());
+      }
+    }
+
+    return pids;
+  }
+};
+
+// This test is a canary for changes to the test data. If the test data was to
+// change, every test in this file would fail.
+//
+//  SELECT DISTINCT pid
+//  FROM process
+//  WHERE upid IN (
+//    SELECT DISTINCT upid
+//    FROM counter
+//      JOIN process_counter_track ON counter.track_id=process_counter_track.id
+//    WHERE name!='oom_score_adj'
+//  )
+//  ORDER BY pid
+//
+//  NOTE: WHERE name!='oom_score_adj' is used because there are two sources for
+//  oom_score_adj values and we only want process stats here.
+TEST_F(ScrubProcessStatsTest, VerifyTraceStats) {
+  base::FlatSet<int32_t> expected = {
+      1,     578,   581,   696,   697,   698,   699,   700,   701,   704,
+      709,   710,   718,   728,   749,   750,   751,   752,   756,   760,
+      761,   762,   873,   874,   892,   1046,  1047,  1073,  1074,  1091,
+      1092,  1093,  1101,  1103,  1104,  1105,  1106,  1107,  1110,  1111,
+      1112,  1113,  1115,  1116,  1118,  1119,  1120,  1121,  1123,  1124,
+      1125,  1126,  1127,  1129,  1130,  1131,  1133,  1140,  1145,  1146,
+      1147,  1151,  1159,  1163,  1164,  1165,  1166,  1167,  1168,  1175,
+      1177,  1205,  1206,  1235,  1237,  1238,  1248,  1251,  1254,  1255,
+      1295,  1296,  1298,  1300,  1301,  1303,  1304,  1312,  1317,  1325,
+      1339,  1340,  1363,  1374,  1379,  1383,  1388,  1392,  1408,  1409,
+      1410,  1413,  1422,  1426,  1427,  1428,  1429,  1433,  1436,  1448,
+      1450,  1451,  1744,  1774,  1781,  1814,  2262,  2268,  2286,  2392,
+      2456,  2502,  2510,  2518,  2528,  2569,  3171,  3195,  3262,  3286,
+      3310,  3338,  3442,  3955,  4386,  4759,  5935,  6034,  6062,  6167,
+      6547,  6573,  6720,  6721,  6725,  6944,  6984,  7105,  7207,  7557,
+      7636,  7786,  7874,  7958,  7960,  7967,  15449, 15685, 15697, 16453,
+      19683, 21124, 21839, 23150, 23307, 23876, 24317, 25017, 25126, 25450,
+      25474, 27271, 30604, 32289,
+  };
+
+  auto original = LoadOriginal();
+  ASSERT_OK(original) << original.status().c_message();
+
+  auto actual = GetAllPids(*original);
+
+  for (auto pid : expected) {
+    ASSERT_TRUE(actual.count(pid))
+        << "pid " << pid << " was not found in the trace";
+  }
+
+  for (auto pid : actual) {
+    ASSERT_TRUE(expected.count(pid))
+        << "pid " << pid << " was found in the trace";
+  }
+}
+
+// Package name: "com.Unity.com.unity.multiplayer.samples.coop"
+// Package pid: 7105
+TEST_F(ScrubProcessStatsTest, OnlyKeepsStatsForPackage) {
+  auto result = Redact();
+  ASSERT_OK(result) << result.c_message();
+
+  auto redacted = LoadRedacted();
+  ASSERT_OK(redacted) << redacted.status().c_message();
+
+  auto actual = GetAllPids(*redacted);
+  ASSERT_EQ(actual.size(), 1u);
+  ASSERT_TRUE(actual.count(7105));
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 3778987..e472d06 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -4637,6 +4637,111 @@
        kUnsetFtraceId,
        420,
        kUnsetSize},
+      {"f2fs_background_gc",
+       "f2fs",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dev", 1, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "wait_ms", 2, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prefree", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "free", 4, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       495,
+       kUnsetSize},
+      {"f2fs_gc_begin",
+       "f2fs",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dev", 1, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "sync", 2, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "background", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dirty_nodes", 4, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dirty_dents", 5, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dirty_imeta", 6, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "free_sec", 7, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "free_seg", 8, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "reserved_seg", 9, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prefree_seg", 10, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "gc_type", 11, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "no_bg_gc", 12, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "nr_free_secs", 13, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       496,
+       kUnsetSize},
+      {"f2fs_gc_end",
+       "f2fs",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dev", 1, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ret", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "seg_freed", 3, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "sec_freed", 4, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dirty_nodes", 5, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dirty_dents", 6, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "dirty_imeta", 7, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "free_sec", 8, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "free_seg", 9, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "reserved_seg", 10, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "prefree_seg", 11, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       497,
+       kUnsetSize},
       {"fastrpc_dma_stat",
        "fastrpc",
        {
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_background_gc/format b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_background_gc/format
new file mode 100644
index 0000000..f6aa901
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_background_gc/format
@@ -0,0 +1,14 @@
+name: f2fs_background_gc
+ID: 519
+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:dev_t dev;	offset:8;	size:4;	signed:0;
+	field:unsigned int wait_ms;	offset:12;	size:4;	signed:0;
+	field:unsigned int prefree;	offset:16;	size:4;	signed:0;
+	field:unsigned int free;	offset:20;	size:4;	signed:0;
+
+print fmt: "dev = (%d,%d), wait_ms = %u, prefree = %u, free = %u", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), REC->wait_ms, REC->prefree, REC->free
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_begin/format b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_begin/format
new file mode 100644
index 0000000..502deca
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_begin/format
@@ -0,0 +1,21 @@
+name: f2fs_gc_begin
+ID: 520
+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:dev_t dev;	offset:8;	size:4;	signed:0;
+	field:int gc_type;	offset:12;	size:4;	signed:1;
+	field:bool no_bg_gc;	offset:16;	size:1;	signed:0;
+	field:unsigned int nr_free_secs;	offset:20;	size:4;	signed:0;
+	field:long long dirty_nodes;	offset:24;	size:8;	signed:1;
+	field:long long dirty_dents;	offset:32;	size:8;	signed:1;
+	field:long long dirty_imeta;	offset:40;	size:8;	signed:1;
+	field:unsigned int free_sec;	offset:48;	size:4;	signed:0;
+	field:unsigned int free_seg;	offset:52;	size:4;	signed:0;
+	field:int reserved_seg;	offset:56;	size:4;	signed:1;
+	field:unsigned int prefree_seg;	offset:60;	size:4;	signed:0;
+
+print fmt: "dev = (%d,%d), gc_type = %s, no_background_GC = %d, nr_free_secs = %u, nodes = %lld, dents = %lld, imeta = %lld, free_sec:%u, free_seg:%u, rsv_seg:%d, prefree_seg:%u", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), __print_symbolic(REC->gc_type, { 1, "Foreground GC" }, { 0, "Background GC" }), (REC->gc_type == 0) ? REC->no_bg_gc : -1, REC->nr_free_secs, REC->dirty_nodes, REC->dirty_dents, REC->dirty_imeta, REC->free_sec, REC->free_seg, REC->reserved_seg, REC->prefree_seg
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_end/format b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_end/format
new file mode 100644
index 0000000..14870b3
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/f2fs/f2fs_gc_end/format
@@ -0,0 +1,21 @@
+name: f2fs_gc_end
+ID: 521
+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:dev_t dev;	offset:8;	size:4;	signed:0;
+	field:int ret;	offset:12;	size:4;	signed:1;
+	field:int seg_freed;	offset:16;	size:4;	signed:1;
+	field:int sec_freed;	offset:20;	size:4;	signed:1;
+	field:long long dirty_nodes;	offset:24;	size:8;	signed:1;
+	field:long long dirty_dents;	offset:32;	size:8;	signed:1;
+	field:long long dirty_imeta;	offset:40;	size:8;	signed:1;
+	field:unsigned int free_sec;	offset:48;	size:4;	signed:0;
+	field:unsigned int free_seg;	offset:52;	size:4;	signed:0;
+	field:int reserved_seg;	offset:56;	size:4;	signed:1;
+	field:unsigned int prefree_seg;	offset:60;	size:4;	signed:0;
+
+print fmt: "dev = (%d,%d), ret = %d, seg_freed = %d, sec_freed = %d, nodes = %lld, dents = %lld, imeta = %lld, free_sec:%u, free_seg:%u, rsv_seg:%d, prefree_seg:%u", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), REC->ret, REC->seg_freed, REC->sec_freed, REC->dirty_nodes, REC->dirty_dents, REC->dirty_imeta, REC->free_sec, REC->free_seg, REC->reserved_seg, REC->prefree_seg
diff --git a/test/data/android_cpu_eos.pb.sha256 b/test/data/android_cpu_eos.pb.sha256
new file mode 100644
index 0000000..15f2572
--- /dev/null
+++ b/test/data/android_cpu_eos.pb.sha256
@@ -0,0 +1 @@
+68377df0bb4fc55a26e2da4285aa441b6d1d3c4e99a192a7bc80876834954c2b
\ No newline at end of file
diff --git a/test/data/wattson_dsu_pmu.pb.sha256 b/test/data/wattson_dsu_pmu.pb.sha256
new file mode 100644
index 0000000..e547cee
--- /dev/null
+++ b/test/data/wattson_dsu_pmu.pb.sha256
@@ -0,0 +1 @@
+848d01bf930bd5b05a9d13c921a38b0101f6f8ec0b69e74ea2a6f408e118b995
\ No newline at end of file
diff --git a/test/data/wattson_eos_suspend.pb.sha256 b/test/data/wattson_eos_suspend.pb.sha256
new file mode 100644
index 0000000..52b8eef
--- /dev/null
+++ b/test/data/wattson_eos_suspend.pb.sha256
@@ -0,0 +1 @@
+dcae89e16d14152cd2cd19bcd7c800981041be74d002b3ab99dc469618d84401
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 1edf16f..1f711b8 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -97,6 +97,7 @@
 from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.counters.tests import StdlibCounterIntervals
+from diff_tests.stdlib.cpu.tests import CpuStdlib
 from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
 from diff_tests.stdlib.graphs.dominator_tree_tests import DominatorTree
 from diff_tests.stdlib.graphs.partition_tests import GraphPartitionTests
@@ -118,6 +119,7 @@
 from diff_tests.stdlib.span_join.tests_smoke import SpanJoinSmoke
 from diff_tests.stdlib.tests import StdlibSmoke
 from diff_tests.stdlib.timestamps.tests import Timestamps
+from diff_tests.stdlib.wattson.tests import WattsonStdlib
 from diff_tests.syntax.filtering_tests import PerfettoFiltering
 from diff_tests.syntax.function_tests import PerfettoFunction
 from diff_tests.syntax.include_tests import PerfettoInclude
@@ -243,6 +245,7 @@
 
   stdlib_tests = [
       *AndroidStdlib(index_path, 'stdlib/android', 'AndroidStdlib').fetch(),
+      *CpuStdlib(index_path, 'stdlib/cpu', 'CpuStdlib').fetch(),
       *DominatorTree(index_path, 'stdlib/graphs', 'DominatorTree').fetch(),
       *Frames(index_path, 'stdlib/android', 'Frames').fetch(),
       *GraphSearchTests(index_path, 'stdlib/graphs',
@@ -280,6 +283,7 @@
       *IntervalsIntersect(index_path, 'stdlib/intervals',
                           'StdlibIntervalsIntersect').fetch(),
       *Timestamps(index_path, 'stdlib/timestamps', 'Timestamps').fetch(),
+      *WattsonStdlib(index_path, 'stdlib/wattson', 'WattsonStdlib').fetch(),
   ] + chrome_stdlib_tests
 
   syntax_tests = [
diff --git a/test/trace_processor/diff_tests/parser/power/tests_power_rails.py b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
index 5085cd2..d08bbbc 100644
--- a/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
+++ b/test/trace_processor/diff_tests/parser/power/tests_power_rails.py
@@ -21,6 +21,23 @@
 
 class PowerPowerRails(TestSuite):
 
+  def test_android_power_rails_counters(self):
+    return DiffTestBlueprint(
+        trace=DataPath('power_rails.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE android.power_rails;
+        SELECT
+        power_rail_name, AVG(value), COUNT(*)
+        FROM android_power_rails_counters
+        GROUP BY 1
+        LIMIT 20;
+      """,
+        out=Csv("""
+        "power_rail_name","AVG(value)","COUNT(*)"
+        "power.PPVAR_VPH_PWR_ABH_uws",7390700.360656,61
+        "power.PPVAR_VPH_PWR_OLED_uws",202362991.655738,61
+        """))
+
   def test_power_rails_power_rails(self):
     return DiffTestBlueprint(
         trace=DataPath('power_rails.pb'),
diff --git a/test/trace_processor/diff_tests/stdlib/cpu/tests.py b/test/trace_processor/diff_tests/stdlib/cpu/tests.py
new file mode 100644
index 0000000..0c32ed3
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/cpu/tests.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class CpuStdlib(TestSuite):
+  # Test CPU frequency counter grouping.
+  def test_cpu_eos_counters_freq(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query=("""
+             INCLUDE PERFETTO MODULE cpu.freq;
+             select
+               track_id,
+               freq,
+               cpu,
+               sum(dur) as dur
+             from cpu_freq_counters
+             GROUP BY freq, cpu
+             """),
+        out=Csv("""
+            "track_id","freq","cpu","dur"
+            33,614400,0,4755967239
+            34,614400,1,4755971561
+            35,614400,2,4755968228
+            36,614400,3,4755964320
+            33,864000,0,442371195
+            34,864000,1,442397134
+            35,864000,2,442417916
+            36,864000,3,442434530
+            33,1363200,0,897122398
+            34,1363200,1,897144167
+            35,1363200,2,897180154
+            36,1363200,3,897216772
+            33,1708800,0,2553979530
+            34,1708800,1,2553923073
+            35,1708800,2,2553866772
+            36,1708800,3,2553814688
+            """))
+
+  # Test CPU idle state counter grouping.
+  def test_cpu_eos_counters_idle(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query=("""
+             INCLUDE PERFETTO MODULE cpu.idle;
+             select
+               track_id,
+               idle,
+               cpu,
+               sum(dur) as dur
+             from cpu_idle_counters
+             GROUP BY idle, cpu
+             """),
+        out=Csv("""
+             "track_id","idle","cpu","dur"
+             0,-1,0,2839828332
+             37,-1,1,1977033843
+             32,-1,2,1800498713
+             1,-1,3,1884366297
+             0,0,0,1833971336
+             37,0,1,2285260950
+             32,0,2,1348416182
+             1,0,3,1338508968
+             0,1,0,4013820433
+             37,1,1,4386917600
+             32,1,2,5532102915
+             1,1,3,5462026920
+            """))
diff --git a/test/trace_processor/diff_tests/stdlib/wattson/tests.py b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
new file mode 100644
index 0000000..94c9bd7
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/wattson/tests.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License a
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from python.generators.diff_tests.testing import Csv, Path, DataPath
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class WattsonStdlib(TestSuite):
+  consolidate_tables_template = ('''
+      SELECT
+        sum(dur) as duration,
+        SUM(l3_hit_count) AS l3_hit_count,
+        SUM(l3_miss_count) AS l3_miss_count,
+        freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+        freq_4, idle_4, freq_5, idle_5, freq_6, idle_6, freq_7, idle_7,
+        suspended
+      FROM SYSTEM_STATE_TABLE
+      GROUP BY
+        freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+        freq_4, idle_4, freq_5, idle_5, freq_6, idle_6, freq_7, idle_7,
+        suspended
+      ORDER BY duration desc
+      LIMIT 20;
+      ''')
+
+  # Test fixup of deep idle offset and time marker window.
+  def test_wattson_time_window(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_dsu_pmu.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.system_state;
+
+        CREATE PERFETTO TABLE wattson_time_window
+        AS
+        SELECT 362426061658 AS ts, 5067704349 AS dur;
+
+        -- Final table that is cut off to fit within the requested time window.
+        CREATE VIRTUAL TABLE time_window_intersect
+          USING SPAN_JOIN(wattson_system_states, wattson_time_window);
+        """ + self.consolidate_tables_template.replace("SYSTEM_STATE_TABLE",
+                                                       "time_window_intersect"),
+        out=Csv("""
+            "duration","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended"
+            58982760,2787779,1229222,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,1,500000,0,0
+            50664181,2364802,1133322,574000,0,574000,1,574000,1,574000,0,553000,1,553000,0,500000,0,500000,0,0
+            41743544,2013295,917107,574000,0,574000,0,574000,1,574000,1,553000,0,553000,0,500000,0,500000,1,0
+            33801135,1479851,684489,300000,0,300000,0,300000,1,300000,1,400000,0,400000,0,500000,0,500000,1,0
+            32703489,1428203,690001,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+            29062133,1597555,719900,574000,0,574000,0,574000,1,574000,0,553000,1,553000,0,500000,1,500000,0,0
+            28310872,1211262,566873,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+            26754474,1224826,569901,300000,0,300000,1,300000,0,300000,0,400000,1,400000,0,500000,0,500000,0,0
+            25107621,1059311,473161,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,1,500000,0,0
+            23771603,987803,450930,300000,0,300000,1,300000,1,300000,0,400000,1,400000,0,500000,0,500000,0,0
+            23721891,963220,409196,300000,0,300000,0,300000,1,300000,0,400000,1,400000,0,500000,1,500000,0,0
+            22988523,984240,473025,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,1,0
+            21790436,987511,449348,300000,0,300000,1,300000,1,300000,0,400000,0,400000,0,500000,0,500000,1,0
+            21673975,1034856,445803,574000,0,574000,0,574000,1,574000,0,553000,0,553000,0,500000,0,500000,1,0
+            20665650,974100,442861,300000,0,300000,0,300000,1,300000,0,400000,0,400000,1,500000,0,500000,0,0
+            18024891,823424,339250,300000,0,300000,1,300000,0,300000,0,400000,0,400000,0,500000,1,500000,0,0
+            17669272,826030,346995,574000,0,574000,0,574000,0,574000,0,553000,1,553000,0,500000,1,500000,0,0
+            16774291,762738,348469,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,0,500000,1,0
+            16191449,689792,316923,300000,0,300000,0,300000,1,300000,0,400000,1,400000,0,500000,0,500000,0,0
+            15918895,742531,325426,574000,0,574000,1,574000,0,574000,1,553000,0,553000,0,500000,1,500000,0,0
+            """))
+
+  # Test on Raven for checking system states and the DSU PMU counts.
+  def test_wattson_dsu_pmu(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_dsu_pmu.pb'),
+        query=("INCLUDE PERFETTO MODULE wattson.system_state;\n" +
+               self.consolidate_tables_template.replace(
+                   "SYSTEM_STATE_TABLE", "wattson_system_states")),
+        out=Csv("""
+            "duration","l3_hit_count","l3_miss_count","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","freq_4","idle_4","freq_5","idle_5","freq_6","idle_6","freq_7","idle_7","suspended"
+            1279130692,1318302,419159,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            165854009,118369,42037,300000,-1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            121156343,5846554,2343180,574000,0,574000,1,574000,0,574000,0,553000,0,553000,0,500000,1,500000,1,0
+            72876331,133532,57759,300000,1,300000,1,300000,-1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            71060865,69029,22056,300000,1,300000,-1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            64501262,276098,309757,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,-1,500000,1,0
+            58982760,2787779,1229222,574000,0,574000,1,574000,1,574000,0,553000,0,553000,0,500000,1,500000,0,0
+            51127388,50724,18075,300000,1,300000,1,300000,1,300000,-1,400000,1,400000,1,500000,1,500000,1,0
+            50664181,2364802,1133322,574000,0,574000,1,574000,1,574000,0,553000,1,553000,0,500000,0,500000,0,0
+            49948122,2216740,934893,300000,0,300000,1,300000,0,300000,0,400000,0,400000,0,500000,1,500000,1,0
+            41743544,2013295,917107,574000,0,574000,0,574000,1,574000,1,553000,0,553000,0,500000,0,500000,1,0
+            40606558,"[NULL]","[NULL]",1401000,1,1401000,1,1401000,1,1401000,1,400000,1,400000,1,2802000,1,2802000,1,0
+            39887541,14272,1252,300000,0,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,1,0
+            38159789,1428203,690001,300000,0,300000,0,300000,1,300000,0,400000,0,400000,0,500000,0,500000,0,0
+            33801135,1479851,684489,300000,0,300000,0,300000,1,300000,1,400000,0,400000,0,500000,0,500000,1,0
+            31543574,34702,18036,300000,1,300000,1,300000,1,300000,1,400000,-1,400000,1,500000,1,500000,1,0
+            31470669,163778,200331,1098000,1,1098000,1,1098000,1,1098000,1,400000,1,400000,1,500000,1,500000,-1,0
+            30993650,39579,48376,300000,1,300000,1,300000,1,300000,1,400000,1,400000,1,500000,1,500000,-1,0
+            30144287,1396131,585581,574000,0,574000,1,574000,0,574000,0,553000,0,553000,0,500000,1,500000,0,0
+            30009881,"[NULL]","[NULL]",1328000,1,1328000,1,1328000,1,1328000,1,2253000,1,2253000,1,500000,1,500000,1,0
+            """))
+
+  # Test on eos to check that suspend states are being calculated appropriately.
+  def test_wattson_suspend(self):
+    return DiffTestBlueprint(
+        trace=DataPath('wattson_eos_suspend.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.system_state;
+        SELECT
+          sum(dur) as duration,
+          freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+          suspended
+        FROM wattson_system_states
+        GROUP BY
+          freq_0, idle_0, freq_1, idle_1, freq_2, idle_2, freq_3, idle_3,
+          suspended
+        ORDER BY duration desc
+        LIMIT 20;
+        """,
+        out=Csv("""
+            "duration","freq_0","idle_0","freq_1","idle_1","freq_2","idle_2","freq_3","idle_3","suspended"
+            16606175990,614400,1,614400,1,614400,1,614400,1,0
+            10648392546,1708800,-1,1708800,-1,1708800,-1,1708800,-1,1
+            6933558399,1708800,-1,1708800,-1,1708800,-1,1708800,-1,0
+            1649400745,614400,0,614400,0,614400,0,614400,0,0
+            1199187488,614400,-1,614400,1,614400,1,614400,1,0
+            945900007,1708800,0,1708800,0,1708800,0,1708800,0,0
+            936351409,1363200,0,1363200,0,1363200,0,1363200,1,0
+            708490325,1708800,0,1708800,0,1708800,0,1708800,1,0
+            706695995,1708800,1,1708800,1,1708800,1,1708800,1,0
+            656873956,1363200,1,1363200,1,1363200,1,1363200,1,0
+            633440914,1363200,0,1363200,0,1363200,0,1363200,0,0
+            627708654,1708800,-1,1708800,0,1708800,0,1708800,0,0
+            620315547,1708800,-1,1708800,1,1708800,1,1708800,1,0
+            578173274,1708800,-1,1708800,0,1708800,0,1708800,-1,0
+            530967964,1708800,-1,1708800,-1,1708800,0,1708800,-1,0
+            516281990,1708800,0,1708800,0,1708800,0,1708800,-1,0
+            473910837,1363200,-1,1363200,0,1363200,0,1363200,1,0
+            461831724,1708800,0,1708800,-1,1708800,0,1708800,0,0
+            402233299,1708800,-1,1708800,-1,1708800,-1,1708800,1,0
+            375051979,864000,1,864000,1,864000,1,864000,1,0
+            """))
+
+  # Test cpu_idle.sql module to make sure final CPU idle system state is proper.
+  def test_wattson_cpu_idle(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.cpu_idle;
+        SELECT ts, dur, idle_0, idle_1, idle_2, idle_3
+        FROM _cpu_idle_all
+        ORDER BY ts DESC
+        LIMIT 20;
+        """,
+        out=Csv("""
+            "ts","dur","idle_0","idle_1","idle_2","idle_3"
+            183589213730,322865,-1,-1,-1,-1
+            183589083105,130625,-1,0,-1,-1
+            183587834095,1249010,-1,-1,-1,-1
+            183586743417,1090678,0,-1,-1,-1
+            183584248209,2495208,0,-1,-1,0
+            183584002220,245989,-1,-1,-1,0
+            183580262740,3739480,0,-1,-1,0
+            183580247324,15416,0,-1,-1,-1
+            183580135813,111511,-1,-1,-1,-1
+            183580002792,133021,-1,-1,-1,0
+            183576199459,3803333,0,-1,-1,0
+            183576002376,197083,-1,-1,-1,0
+            183575781386,220990,0,-1,-1,0
+            183575764355,17031,0,-1,-1,-1
+            183575719824,44531,-1,-1,-1,-1
+            183575706490,13334,-1,-1,-1,0
+            183575630865,75625,-1,-1,-1,-1
+            183575503886,126979,0,-1,-1,-1
+            183573856542,1647344,0,-1,-1,0
+            183573750032,106510,-1,-1,-1,0
+            """))
+
+  # Test cpu_freq.sql module to make sure final CPU frequency states are proper.
+  def test_wattson_cpu_freq(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query="""
+        INCLUDE PERFETTO MODULE wattson.cpu_freq;
+        SELECT ts, dur, freq_0, freq_1, freq_2, freq_3
+        FROM _cpu_freq_all
+        ORDER BY ts DESC
+        LIMIT 20;
+        """,
+        out=Csv("""
+            "ts","dur","freq_0","freq_1","freq_2","freq_3"
+            183219975813,369560782,1708800,1708800,1708800,1708800
+            183219971595,4218,1708800,1708800,1708800,1363200
+            183219967324,4271,1708800,1708800,1363200,1363200
+            183219961334,5990,1708800,1363200,1363200,1363200
+            183217603313,2358021,1363200,1363200,1363200,1363200
+            183217599147,4166,1363200,1363200,1363200,614400
+            183217594720,4427,1363200,1363200,614400,614400
+            183217584459,10261,1363200,614400,614400,614400
+            183099692324,117892135,614400,614400,614400,614400
+            183099688001,4323,614400,614400,614400,1708800
+            183099683418,4583,614400,614400,1708800,1708800
+            183099677584,5834,614400,1708800,1708800,1708800
+            183000228105,99449479,1708800,1708800,1708800,1708800
+            183000223938,4167,1708800,1708800,1708800,864000
+            183000219303,4635,1708800,1708800,864000,864000
+            183000213313,5990,1708800,864000,864000,864000
+            182984137428,16075885,864000,864000,864000,864000
+            182984133522,3906,864000,864000,864000,1363200
+            182984129355,4167,864000,864000,1363200,1363200
+            182984123678,5677,864000,1363200,1363200,1363200
+            """))
+
+  # Test that the device name can be extracted from the trace's metadata.
+  def test_wattson_device_name(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_cpu_eos.pb'),
+        query=("""
+            INCLUDE PERFETTO MODULE android.device;
+            select name from android_device_name
+            """),
+        out=Csv("""
+            "name"
+            "eos"
+            """))
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 049cf40..6036809 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -284,6 +284,7 @@
     gn = gn_utils.GnParser(gn_desc)
     project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
     tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
+    tool_name = tool_name.replace('\\', '/')
     res = '''
 # Copyright (C) 2022 The Android Open Source Project
 #
@@ -746,6 +747,7 @@
   gn = gn_utils.GnParser(gn_desc)
   project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
   tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
+  tool_name = tool_name.replace('\\', '/')
   res = '''
 # Copyright (C) 2019 The Android Open Source Project
 #
@@ -915,14 +917,14 @@
 
   contents = generate_build(desc, args.targets or default_targets, extras)
   out_files.append(args.output + '.swp')
-  with open(out_files[-1], 'w') as out_f:
+  with open(out_files[-1], 'w', newline='\n') as out_f:
     out_f.write(contents)
 
   # Generate the python BUILD file.
   python_gen = PythonBuildGenerator()
   python_contents = python_gen.generate(desc)
   out_files.append(args.output_python + '.swp')
-  with open(out_files[-1], 'w') as out_f:
+  with open(out_files[-1], 'w', newline='\n') as out_f:
     out_f.write(python_contents)
 
   # Generate the build flags file.
diff --git a/tools/gn_utils.py b/tools/gn_utils.py
index 65c0ff0..d0417d7 100644
--- a/tools/gn_utils.py
+++ b/tools/gn_utils.py
@@ -123,7 +123,7 @@
     source files in the amalgamated result.
     """
   targets = [t.replace('//', '') for t in targets]
-  with open(os.devnull, 'w') as devnull:
+  with open(os.devnull, 'w', newline='\n') as devnull:
     stdout = devnull if quiet else None
     cmd = _tool_path('ninja', system_buildtools) + targets
     subprocess.check_call(cmd, cwd=os.path.abspath(out), stdout=stdout)
@@ -211,7 +211,7 @@
         res = 1
       os.unlink(tmp_file)
     else:
-      os.rename(tmp_file, target_file)
+      os.replace(tmp_file, target_file)
   return res
 
 
diff --git a/ui/PRESUBMIT.py b/ui/PRESUBMIT.py
index 068f9de..be0ad24 100644
--- a/ui/PRESUBMIT.py
+++ b/ui/PRESUBMIT.py
@@ -99,7 +99,7 @@
 
   def file_filter(x):
     return input_api.FilterSourceFile(
-        x, files_to_check=[r'.*\.ts$', r'.*\.js$'])
+        x, files_to_check=[r'.*\.ts$', r'.*\.js$', r'.*\.scss$'])
 
   files = input_api.AffectedSourceFiles(file_filter)
 
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index f3e529a..d09690a 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -173,43 +173,6 @@
         width: 50%;
       }
     }
-    &.flamegraph-profile {
-      display: flex;
-      justify-content: space-between;
-      align-content: center;
-      height: 30px;
-      padding: 0;
-      font-size: 12px;
-      * {
-        align-self: center;
-      }
-      .options {
-        display: inline-flex;
-        justify-content: space-around;
-      }
-      .details {
-        display: inline-flex;
-        justify-content: flex-end;
-      }
-      .title {
-        justify-self: start;
-        margin-left: 5px;
-        font-size: 14px;
-        margin-right: 10px;
-      }
-      .time {
-        justify-self: end;
-        margin-right: 10px;
-      }
-      .selected {
-        justify-self: end;
-        margin-right: 10px;
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        width: 200px;
-      }
-    }
   }
 
   table {
@@ -709,3 +672,35 @@
 .pf-noselection {
   height: 100%;
 }
+
+.flamegraph-profile {
+  height: 100%;
+  // This is required to position locally-scoped (i.e. non-full-screen) modal
+  // dialogs within the panel, as they use position: absolute.
+  position: relative;
+
+  .time {
+    justify-self: end;
+    margin-right: 10px;
+  }
+  .selected {
+    justify-self: end;
+    margin-right: 10px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: 200px;
+  }
+  .flamegraph-content {
+    overflow: auto;
+    height: 100%;
+
+    .loading-container {
+      font-size: larger;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 100%;
+    }
+  }
+}
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
index 7226e88..7f8e16a 100644
--- a/ui/src/assets/record.scss
+++ b/ui/src/assets/record.scss
@@ -689,6 +689,10 @@
       max-height: 0;
     }
 
+    .extended-desc {
+      grid-column: span 2;
+    }
+
     &.enabled {
       .probe-config {
         opacity: 1;
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index dff19c2..334ad25 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -23,6 +23,7 @@
   BufferConfig,
   ChromeConfig,
   DataSourceConfig,
+  EtwConfig,
   FtraceConfig,
   HeapprofdConfig,
   JavaContinuousDumpConfig,
@@ -649,31 +650,35 @@
   }
 
   // Keep these last. The stages above can enrich them.
+  if (
+    targetInfo.targetType !== 'WINDOWS' &&
+    targetInfo.targetType !== 'CHROME'
+  ) {
+    if (sysStatsCfg !== undefined) {
+      const ds = new TraceConfig.DataSource();
+      ds.config = new DataSourceConfig();
+      ds.config.name = 'linux.sys_stats';
+      ds.config.sysStatsConfig = sysStatsCfg;
+      protoCfg.dataSources.push(ds);
+    }
 
-  if (sysStatsCfg !== undefined && targetInfo.targetType !== 'CHROME') {
-    const ds = new TraceConfig.DataSource();
-    ds.config = new DataSourceConfig();
-    ds.config.name = 'linux.sys_stats';
-    ds.config.sysStatsConfig = sysStatsCfg;
-    protoCfg.dataSources.push(ds);
-  }
+    if (heapprofd !== undefined) {
+      const ds = new TraceConfig.DataSource();
+      ds.config = new DataSourceConfig();
+      ds.config.targetBuffer = 0;
+      ds.config.name = 'android.heapprofd';
+      ds.config.heapprofdConfig = heapprofd;
+      protoCfg.dataSources.push(ds);
+    }
 
-  if (heapprofd !== undefined && targetInfo.targetType !== 'CHROME') {
-    const ds = new TraceConfig.DataSource();
-    ds.config = new DataSourceConfig();
-    ds.config.targetBuffer = 0;
-    ds.config.name = 'android.heapprofd';
-    ds.config.heapprofdConfig = heapprofd;
-    protoCfg.dataSources.push(ds);
-  }
-
-  if (javaHprof !== undefined && targetInfo.targetType !== 'CHROME') {
-    const ds = new TraceConfig.DataSource();
-    ds.config = new DataSourceConfig();
-    ds.config.targetBuffer = 0;
-    ds.config.name = 'android.java_hprof';
-    ds.config.javaHprofConfig = javaHprof;
-    protoCfg.dataSources.push(ds);
+    if (javaHprof !== undefined) {
+      const ds = new TraceConfig.DataSource();
+      ds.config = new DataSourceConfig();
+      ds.config.targetBuffer = 0;
+      ds.config.name = 'android.java_hprof';
+      ds.config.javaHprofConfig = javaHprof;
+      protoCfg.dataSources.push(ds);
+    }
   }
 
   if (
@@ -757,6 +762,28 @@
     }
   }
 
+  if (
+    targetInfo.targetType === 'WINDOWS' ||
+    uiCfg.etwCSwitch ||
+    uiCfg.etwThreadState
+  ) {
+    const ds = new TraceConfig.DataSource();
+    ds.config = new DataSourceConfig();
+    ds.config.name = 'windows.etw';
+    ds.config.etwConfig = new EtwConfig();
+
+    const kernelFlags: EtwConfig.KernelFlag[] = [];
+
+    if (uiCfg.etwCSwitch) {
+      kernelFlags.push(EtwConfig.KernelFlag.CSWITCH);
+    }
+    if (uiCfg.etwThreadState) {
+      kernelFlags.push(EtwConfig.KernelFlag.DISPATCHER);
+    }
+    ds.config.etwConfig.kernelFlags = kernelFlags;
+    protoCfg.dataSources.push(ds);
+  }
+
   return protoCfg;
 }
 
diff --git a/ui/src/common/recordingV2/recording_interfaces_v2.ts b/ui/src/common/recordingV2/recording_interfaces_v2.ts
index ed5fdbf..805def8 100644
--- a/ui/src/common/recordingV2/recording_interfaces_v2.ts
+++ b/ui/src/common/recordingV2/recording_interfaces_v2.ts
@@ -79,7 +79,7 @@
 }
 
 export interface HostOsTargetInfo extends TargetInfoBase {
-  targetType: 'LINUX' | 'MACOS';
+  targetType: 'LINUX' | 'MACOS' | 'WINDOWS';
 }
 
 // Holds information about a target. It's used by the UI and the logic which
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 92efee5..43f205e 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -580,7 +580,16 @@
   | 'LONG_TRACE';
 
 // 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
-export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
+export declare type TargetOs =
+  | 'S'
+  | 'R'
+  | 'Q'
+  | 'P'
+  | 'O'
+  | 'C'
+  | 'L'
+  | 'CrOS'
+  | 'Win';
 
 export function isAndroidP(target: RecordingTarget) {
   return target.os === 'P';
@@ -602,6 +611,10 @@
   return target.os === 'L';
 }
 
+export function isWindowsTarget(target: RecordingTarget) {
+  return target.os === 'Win';
+}
+
 export function isAdbTarget(
   target: RecordingTarget,
 ): target is AdbRecordingTarget {
@@ -638,6 +651,7 @@
     {os: 'C', name: 'Chrome'},
     {os: 'CrOS', name: 'Chrome OS (system trace)'},
     {os: 'L', name: 'Linux desktop'},
+    {os: 'Win', name: 'Windows desktop'},
   ];
 }
 
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index a10252c..b1c00a4 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -300,6 +300,7 @@
         await this.args.engine.query(`select value from stats
        where severity = 'error' and name = 'heap_graph_non_finalized_graph'`)
       ).firstRow({value: NUM}).value > 0;
+    flamegraphDetails.graphLoading = false;
     publishFlamegraphDetails(flamegraphDetails);
   }
 
@@ -317,8 +318,10 @@
     if (this.flamegraphDatasets.has(key)) {
       currentData = this.flamegraphDatasets.get(key)!;
     } else {
-      // TODO(b/330703412): Show loading state.
-
+      publishFlamegraphDetails({
+        ...globals.flamegraphDetails,
+        graphLoading: true,
+      });
       // Collecting data for drawing flamegraph for selected profile.
       // Data needs to be in following format:
       // id, name, parent_id, depth, total_size
diff --git a/ui/src/controller/record_config_types.ts b/ui/src/controller/record_config_types.ts
index 61e05e2..beb52d9 100644
--- a/ui/src/controller/record_config_types.ts
+++ b/ui/src/controller/record_config_types.ts
@@ -107,6 +107,9 @@
   audio: bool(),
   video: bool(),
 
+  etwCSwitch: bool(),
+  etwThreadState: bool(),
+
   symbolizeKsyms: bool(),
 
   // Enabling stack sampling
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 2ce0a0b..1398581 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -64,7 +64,7 @@
   uiCfg: RecordConfig,
   target: RecordingTarget,
 ): TraceConfig {
-  let targetType: 'ANDROID' | 'CHROME' | 'CHROME_OS' | 'LINUX';
+  let targetType: 'ANDROID' | 'CHROME' | 'CHROME_OS' | 'LINUX' | 'WINDOWS';
   let androidApiLevel!: number;
   switch (target.os) {
     case 'L':
@@ -76,6 +76,9 @@
     case 'CrOS':
       targetType = 'CHROME_OS';
       break;
+    case 'Win':
+      targetType = 'WINDOWS';
+      break;
     case 'S':
       androidApiLevel = 31;
       targetType = 'ANDROID';
@@ -105,14 +108,12 @@
       dataSources: [],
       name: '',
     };
-  } else if (targetType === 'CHROME' || targetType === 'CHROME_OS') {
+  } else {
     targetInfo = {
       targetType,
       dataSources: [],
       name: '',
     };
-  } else {
-    targetInfo = {targetType, dataSources: [], name: ''};
   }
 
   return genTraceConfig(uiCfg, targetInfo);
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index fea7fb2..0f4cba2 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -30,6 +30,8 @@
 import {Icon} from '../widgets/icon';
 import {Modal, ModalAttrs} from '../widgets/modal';
 import {Popup} from '../widgets/popup';
+import {EmptyState} from '../widgets/empty_state';
+import {Spinner} from '../widgets/spinner';
 
 import {Flamegraph, NodeRendering} from './flamegraph';
 import {globals} from './globals';
@@ -37,7 +39,9 @@
 import {Router} from './router';
 import {getCurrentTrace} from './sidebar';
 import {convertTraceToPprofAndDownload} from './trace_converter';
+import {ButtonBar} from '../widgets/button';
 import {DurationWidget} from './widgets/duration';
+import {DetailsShell} from '../widgets/details_shell';
 
 const HEADER_HEIGHT = 30;
 
@@ -90,33 +94,31 @@
         ? this.flamegraph.getHeight() + HEADER_HEIGHT
         : 0;
       return m(
-        '.details-panel',
+        '.flamegraph-profile',
         this.maybeShowModal(flamegraphDetails.graphIncomplete),
         m(
-          '.details-panel-heading.flamegraph-profile',
-          {onclick: (e: MouseEvent) => e.stopPropagation()},
-          [
-            m('div.options', [
-              m(
-                'div.title',
-                this.getTitle(),
-                this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+          DetailsShell,
+          {
+            fillParent: true,
+            title: m(
+              'div.title',
+              this.getTitle(),
+              this.profileType === ProfileType.MIXED_HEAP_PROFILE &&
+                m(
+                  Popup,
+                  {
+                    trigger: m(Icon, {icon: 'warning'}),
+                  },
                   m(
-                    Popup,
-                    {
-                      trigger: m(Icon, {icon: 'warning'}),
-                    },
-                    m(
-                      '',
-                      {style: {width: '300px'}},
-                      'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
-                    ),
+                    '',
+                    {style: {width: '300px'}},
+                    'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.',
                   ),
-                ':',
-              ),
-              this.getViewingOptionButtons(),
-            ]),
-            m('div.details', [
+                ),
+              ':',
+            ),
+            description: this.getViewingOptionButtons(),
+            buttons: [
               m(
                 'div.selected',
                 `Selected function: ${toSelectedCallsite(
@@ -145,23 +147,39 @@
                     this.downloadPprof();
                   },
                 }),
-            ]),
-          ],
+            ],
+          },
+          m(
+            '.flamegraph-content',
+            flamegraphDetails.graphLoading
+              ? m(
+                  '.loading-container',
+                  m(
+                    EmptyState,
+                    {
+                      icon: 'bar_chart',
+                      title: 'Computing graph ...',
+                      className: 'flamegraph-loading',
+                    },
+                    m(Spinner, {easing: true}),
+                  ),
+                )
+              : m(`canvas[ref=canvas]`, {
+                  style: `height:${height}px; width:100%`,
+                  onmousemove: (e: MouseEvent) => {
+                    const {offsetX, offsetY} = e;
+                    this.onMouseMove({x: offsetX, y: offsetY});
+                  },
+                  onmouseout: () => {
+                    this.onMouseOut();
+                  },
+                  onclick: (e: MouseEvent) => {
+                    const {offsetX, offsetY} = e;
+                    this.onMouseClick({x: offsetX, y: offsetY});
+                  },
+                }),
+          ),
         ),
-        m(`canvas[ref=canvas]`, {
-          style: `height:${height}px; width:100%`,
-          onmousemove: (e: MouseEvent) => {
-            const {offsetX, offsetY} = e;
-            this.onMouseMove({x: offsetX, y: offsetY});
-          },
-          onmouseout: () => {
-            this.onMouseOut();
-          },
-          onclick: (e: MouseEvent) => {
-            const {offsetX, offsetY} = e;
-            this.onMouseClick({x: offsetX, y: offsetY});
-          },
-        }),
       );
     } else {
       return m(
@@ -260,7 +278,7 @@
 
   getViewingOptionButtons(): m.Children {
     return m(
-      'div',
+      ButtonBar,
       ...FlamegraphDetailsPanel.selectViewingOptions(
         assertExists(this.profileType),
       ),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 1af1399..5cecd7e 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -160,6 +160,8 @@
   // When heap_graph_non_finalized_graph has a count >0, we mark the graph
   // as incomplete.
   graphIncomplete?: boolean;
+  // About to show a new graph whose data is not ready yet.
+  graphLoading?: boolean;
 }
 
 export interface CpuProfileDetails {
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 631f814..ad59ea8 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -25,6 +25,7 @@
   isChromeTarget,
   isCrOSTarget,
   isLinuxTarget,
+  isWindowsTarget,
   LoadedConfig,
   MAX_TIME,
   RecordingTarget,
@@ -55,6 +56,7 @@
 import {PowerSettings} from './recording/power_settings';
 import {RecordingSectionAttrs} from './recording/recording_sections';
 import {RecordingSettings} from './recording/recording_settings';
+import {EtwSettings} from './recording/etw_settings';
 
 export const PERSIST_CONFIG_FLAG = featureFlags.register({
   id: 'persistConfigsUI',
@@ -68,6 +70,7 @@
   'instructions',
   'config',
   'cpu',
+  'etw',
   'gpu',
   'power',
   'memory',
@@ -807,6 +810,15 @@
       m('.sub', 'Lightweight stack polling'),
     ),
   );
+  const etwProbe = m(
+    'a[href="#!/record/etw"]',
+    m(
+      `li${routePage === 'etw' ? '.active' : ''}`,
+      m('i.material-icons', 'subtitles'),
+      m('.title', 'ETW Tracing Config'),
+      m('.sub', 'Context switch, Thread state'),
+    ),
+  );
   const recInProgress = globals.state.recordingInProgress;
 
   const probes = [];
@@ -814,6 +826,8 @@
     probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe);
   } else if (isChromeTarget(target)) {
     probes.push(chromeProbe);
+  } else if (isWindowsTarget(target)) {
+    probes.push(chromeProbe, etwProbe);
   } else {
     probes.push(
       cpuProbe,
@@ -908,6 +922,7 @@
       ['chrome', ChromeSettings],
       ['tracePerf', LinuxPerfSettings],
       ['advanced', AdvancedSettings],
+      ['etw', EtwSettings],
     ]);
     for (const [section, component] of settingsSections.entries()) {
       pages.push(
diff --git a/ui/src/frontend/record_page_v2.ts b/ui/src/frontend/record_page_v2.ts
index 7e6e4f0..db87cfa 100644
--- a/ui/src/frontend/record_page_v2.ts
+++ b/ui/src/frontend/record_page_v2.ts
@@ -48,6 +48,7 @@
 import {AndroidSettings} from './recording/android_settings';
 import {ChromeSettings} from './recording/chrome_settings';
 import {CpuSettings} from './recording/cpu_settings';
+import {EtwSettings} from './recording/etw_settings';
 import {GpuSettings} from './recording/gpu_settings';
 import {LinuxPerfSettings} from './recording/linux_perf_settings';
 import {MemorySettings} from './recording/memory_settings';
@@ -488,6 +489,15 @@
       m('.sub', 'Lightweight stack polling'),
     ),
   );
+  const etwProbe = m(
+    'a[href="#!/record/etw"]',
+    m(
+      `li${routePage === 'etw' ? '.active' : ''}`,
+      m('i.material-icons', 'subtitles'),
+      m('.title', 'ETW Tracing Config'),
+      m('.sub', 'Context switch, Thread state'),
+    ),
+  );
 
   // We only display the probes when we have a valid target, so it's not
   // possible for the target to be undefined here.
@@ -495,6 +505,8 @@
   const probes = [];
   if (targetType === 'CHROME_OS' || targetType === 'LINUX') {
     probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe);
+  } else if (targetType === 'WINDOWS') {
+    probes.push(chromeProbe, etwProbe);
   } else if (targetType === 'CHROME') {
     probes.push(chromeProbe);
   } else {
@@ -618,6 +630,7 @@
     ['chrome', ChromeSettings],
     ['tracePerf', LinuxPerfSettings],
     ['advanced', AdvancedSettings],
+    ['etw', EtwSettings],
   ]);
   for (const [section, component] of settingsSections.entries()) {
     pages.push(
diff --git a/ui/src/frontend/record_widgets.ts b/ui/src/frontend/record_widgets.ts
index 3385655..fc9d2f4 100644
--- a/ui/src/frontend/record_widgets.ts
+++ b/ui/src/frontend/record_widgets.ts
@@ -91,7 +91,11 @@
       ),
       attrs.compact
         ? ''
-        : m('div', m('div', attrs.descr), m('.probe-config', children)),
+        : m(
+            `div${attrs.img ? '' : '.extended-desc'}`,
+            m('div', attrs.descr),
+            m('.probe-config', children),
+          ),
     );
   }
 }
diff --git a/ui/src/frontend/recording/etw_settings.ts b/ui/src/frontend/recording/etw_settings.ts
new file mode 100644
index 0000000..64ffc51
--- /dev/null
+++ b/ui/src/frontend/recording/etw_settings.ts
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+
+import {Probe, ProbeAttrs} from '../record_widgets';
+
+import {RecordingSectionAttrs} from './recording_sections';
+
+export class EtwSettings implements m.ClassComponent<RecordingSectionAttrs> {
+  view({attrs}: m.CVnode<RecordingSectionAttrs>) {
+    return m(
+      `.record-section${attrs.cssClass}`,
+      m(Probe, {
+        title: 'CSwitch',
+        img: null,
+        descr: `Enables to recording of context switches.`,
+        setEnabled: (cfg, val) => (cfg.etwCSwitch = val),
+        isEnabled: (cfg) => cfg.etwCSwitch,
+      } as ProbeAttrs),
+      m(Probe, {
+        title: 'Dispatcher',
+        img: null,
+        descr: 'Enables to get thread state.',
+        setEnabled: (cfg, val) => (cfg.etwThreadState = val),
+        isEnabled: (cfg) => cfg.etwThreadState,
+      } as ProbeAttrs),
+    );
+  }
+}
diff --git a/ui/src/protos/index.ts b/ui/src/protos/index.ts
index 77e8b5f..378fcfb 100644
--- a/ui/src/protos/index.ts
+++ b/ui/src/protos/index.ts
@@ -33,6 +33,7 @@
 import EnableMetatraceArgs = protos.perfetto.protos.EnableMetatraceArgs;
 import EnableTracingRequest = protos.perfetto.protos.EnableTracingRequest;
 import EnableTracingResponse = protos.perfetto.protos.EnableTracingResponse;
+import EtwConfig = protos.perfetto.protos.EtwConfig;
 import FreeBuffersRequest = protos.perfetto.protos.FreeBuffersRequest;
 import FreeBuffersResponse = protos.perfetto.protos.FreeBuffersResponse;
 import FtraceConfig = protos.perfetto.protos.FtraceConfig;
@@ -101,6 +102,7 @@
   EnableMetatraceArgs,
   EnableTracingRequest,
   EnableTracingResponse,
+  EtwConfig,
   FreeBuffersRequest,
   FreeBuffersResponse,
   FtraceConfig,