Merge changes Ief462c4e,I59e158ce

* changes:
  Add TP support for SurfaceFlinger's transactions data source
  Add TP support for SurfaceFlinger's layers data source
diff --git a/docs/faq.md b/docs/faq.md
index 3978a26..df41ff3 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -20,12 +20,26 @@
 tools/open_trace_in_ui -i /path/to/trace
 ```
 
-## Incorrectly displayed overlapping events in JSON trace
+## Why does Perfetto not support <some obscure JSON format feature>?
 
-NOTE: JSON is considered a legacy trace format and is supported on a best-effort
-basis.
+The JSON trace format is considered a legacy trace format and is supported on a
+best-effort basis. While we try our best to maintain compatibility with the
+chrome://tracing UI and the [format spec](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.nso4gcezn7n1)
+in how events are parsed and displayed, this is not always possible.
+This is especially the case for traces which are programmatically generated
+outside of Chrome and depend on the implementation details of chrome://tracing.
 
-The Perfetto UI and trace processor do support overlapping B/E/X events, in
+If supporting a feature would introduce a misproportional amount of technical
+debt, we generally make the choice not to support that feature. Users
+are recommended to emit [TrackEvent](/docs/instrumentation/track-events.md)
+instead, Perfetto's native trace format. See
+[this guide](/docs/reference/synthetic-track-event.md) for how common JSON
+events can be represented using
+TrackEvent.
+
+## Why are overlapping events in JSON traces not displayed correctly?
+
+The Perfetto UI and trace processor do not support overlapping B/E/X events, in
 compliance with the
 [JSON spec](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.nso4gcezn7n1).
 As stated in the spec, events are only allowed to perfecty nest.
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index af6438a..19c8029 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -261,9 +261,7 @@
   } else if (current_cpu == "riscv64") {
     if (!is_clang) {
       # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104338
-      libs += [
-        "atomic",
-      ]
+      libs += [ "atomic" ]
     }
   } else if (current_cpu == "x86") {
     asmflags += [ "-m32" ]
diff --git a/include/perfetto/tracing/data_source.h b/include/perfetto/tracing/data_source.h
index 879e59e..91e29fe 100644
--- a/include/perfetto/tracing/data_source.h
+++ b/include/perfetto/tracing/data_source.h
@@ -93,6 +93,9 @@
     // be retained.
     const DataSourceConfig* config = nullptr;
 
+    // Backend type.
+    BackendType backend_type = kUnspecifiedBackend;
+
     // The index of this data source instance (0..kMaxDataSourceInstances - 1).
     uint32_t internal_instance_index = 0;
   };
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index 0a3e9e9..c5e842f 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -37,9 +37,9 @@
 perfetto_proto_library("winscope_deps") {
   proto_generators = [ "source_set" ]
   sources = [
+    "surfaceflinger_common.proto",
     "surfaceflinger_layers.proto",
     "surfaceflinger_transactions.proto",
-    "surfaceflinger_common.proto",
   ]
 }
 
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 347b36e..2547a8e 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -46,10 +46,16 @@
 }
 
 message ChromeTaskAnnotator {
+  enum DelayPolicy {
+    FLEXIBLE_NO_SOONER = 0;
+    FLEXIBLE_PREFER_EARLY = 1;
+    PRECISE = 2;
+  }
   optional uint32 ipc_hash = 1;
   // The delay in microseconds that was specified, if any, when this task was
   // posted. This is only valid for delayed tasks.
   optional uint64 task_delay_us = 2;
+  optional DelayPolicy delay_policy = 3;
 }
 
 message ChromeBrowserContext {
@@ -1208,6 +1214,35 @@
   repeated float segregated_predicted_deltas_in_gpu_frame_y = 14;
 }
 
+// Debug information for system layer of audio rendering on Windows.
+message WinRenderAudioFromSource {
+  // Buffer size used by the platform.
+  optional uint32 iaudioclient_buffer_size_frames = 1;
+  // Number of available frames in the platform buffer.
+  optional uint32 iaudioclient_buffer_unfilled_frames = 2;
+  // Number of frames requested from the audio stream client.
+  optional uint32 packet_size_frames = 3;
+  // Total number of frames requested from the client and written to the
+  // platform buffer during the stream lifetime.
+  optional uint64 num_written_frames = 4;
+  // Total number of frames played by the platform during the stream lifetime.
+  optional uint64 num_played_out_frames = 5;
+  // Device frequency reported by the platform; used to convert the stream
+  // position to frames.
+  optional uint64 iaudioclock_device_frequency = 6;
+  // Stream position reported by the platform.
+  optional uint64 iaudioclock_stream_position = 7;
+  // Performance counter position reported by the platform.
+  optional uint64 iaudioclock_qpc_position = 8;
+  // Stream position increase since the last call, converted to milliseconds.
+  optional int64 iaudioclock_stream_position_increase_ms = 9;
+  // Performance counter position since the last call, converted to
+  // milliseconds.
+  optional int64 iaudioclock_qpc_position_increase_ms = 10;
+  // Estimated playout delay, milliseconds.
+  optional int64 playout_delay_ms = 11;
+};
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
   // Next ID: 1048
@@ -1312,5 +1347,7 @@
     optional TabSwitchMeasurement tab_switch_measurement = 1046;
 
     optional ScrollDeltas scroll_deltas = 1047;
+
+    optional WinRenderAudioFromSource win_render_audio_from_source = 1048;
   }
 }
diff --git a/python/generators/stdlib_docs/utils.py b/python/generators/stdlib_docs/utils.py
index d635b08..52ddfac 100644
--- a/python/generators/stdlib_docs/utils.py
+++ b/python/generators/stdlib_docs/utils.py
@@ -22,34 +22,34 @@
 ANY_NON_QUOTE = r'[^\']*.*'
 TYPE = r'[A-Z]+'
 SQL = r'[\s\S]*?'
+WS = r'\s*'
 
 CREATE_TABLE_VIEW_PATTERN = (
     # Match create table/view and catch type
-    r'CREATE (?:VIRTUAL )?(TABLE|VIEW)?(?:IF NOT EXISTS)?\s*'
+    fr'CREATE{WS}(?:VIRTUAL )?{WS}(TABLE|VIEW){WS}(?:IF NOT EXISTS)?{WS}'
     # Catch the name
-    fr'({LOWER_NAME})\s*(?:AS|USING)?.*')
+    fr'{WS}({LOWER_NAME}){WS}(?:AS|USING)?{WS}.*')
 
 CREATE_FUNCTION_PATTERN = (
-    r"SELECT\s*CREATE_FUNCTION\(\s*"
     # Function name: we are matching everything [A-Z]* between ' and ).
-    fr"'\s*({UPPER_NAME})\s*\("
-    # Args: anything before closing bracket with '.
-    fr"({ANY_WORDS})\)',\s*"
+    fr"CREATE{WS}PERFETTO{WS}FUNCTION{WS}({UPPER_NAME}){WS}"
+    # Args: anything before closing bracket.
+    fr"{WS}\({WS}({ANY_WORDS}){WS}\){WS}"
     # Type: [A-Z]* between two '.
-    fr"'({TYPE})',\s*"
+    fr"{WS}RETURNS{WS}({TYPE}){WS}AS{WS}"
     # Sql: Anything between ' and ');. We are catching \'.
-    fr"'({SQL})'\s*\);")
+    fr"{WS}({SQL});")
 
 CREATE_VIEW_FUNCTION_PATTERN = (
-    r"SELECT\s*CREATE_VIEW_FUNCTION\(\s*"
+    fr"SELECT{WS}CREATE_VIEW_FUNCTION\({WS}"
     # Function name: we are matching everything [A-Z]* between ' and ).
-    fr"'({UPPER_NAME})\s*\("
+    fr"{WS}'{WS}({UPPER_NAME}){WS}\({WS}"
     # Args: anything before closing bracket with '.
-    fr"({ANY_WORDS})\)',\s*"
+    fr"{WS}({ANY_WORDS}){WS}\){WS}'{WS},{WS}"
     # Return columns: anything between two '.
-    fr"'\s*({ANY_NON_QUOTE})\s*',\s*"
+    fr"'{WS}({ANY_NON_QUOTE}){WS}',{WS}"
     # Sql: Anything between ' and ');. We are catching \'.
-    fr"'({SQL})'\s*\);")
+    fr"{WS}'{WS}({SQL}){WS}'{WS}\){WS};")
 
 
 class ObjKind(str, Enum):
diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py
index 1f609d0..649cf4d 100644
--- a/python/test/stdlib_unittest.py
+++ b/python/test/stdlib_unittest.py
@@ -73,11 +73,10 @@
 {DESC}
 {ARGS_STR}
 {RET_STR}
-SELECT CREATE_FUNCTION(
-  'FOO_FN({ARGS_SQL_STR})',
-  '{RET_SQL_STR}',
-  '{SQL_STR}'
-);
+CREATE PERFETTO FUNCTION FOO_FN({ARGS_SQL_STR})
+RETURNS {RET_SQL_STR}
+AS
+{SQL_STR};
     '''.strip())
     assert isinstance(res, dict)
 
@@ -183,11 +182,10 @@
 -- @arg utid2 INT              Uint.
 -- @arg name STRING           String name.
 {RET_STR}
-SELECT CREATE_FUNCTION(
-  'FOO_FN({ARGS_SQL_STR})',
-  '{RET_SQL_STR}',
-  '{SQL_STR}'
-);
+CREATE PERFETTO FUNCTION FOO_FN({ARGS_SQL_STR})
+RETURNS {RET_SQL_STR}
+AS
+{SQL_STR};
     '''.strip())
     assert isinstance(res, list)
 
@@ -199,11 +197,10 @@
 -- @arg utid INT
 -- @arg name STRING           String name.
 {RET_STR}
-SELECT CREATE_FUNCTION(
-  'FOO_FN({ARGS_SQL_STR})',
-  '{RET_SQL_STR}',
-  '{SQL_STR}'
-);
+CREATE PERFETTO FUNCTION FOO_FN({ARGS_SQL_STR})
+RETURNS {RET_SQL_STR}
+AS
+{SQL_STR};
     '''.strip())
     assert isinstance(res, list)
 
@@ -214,11 +211,10 @@
 {ARGS_STR}
 --
 -- @ret BOOL
-SELECT CREATE_FUNCTION(
-  'FOO_FN({ARGS_SQL_STR})',
-  '{RET_SQL_STR}',
-  '{SQL_STR}'
-);
+CREATE PERFETTO FUNCTION FOO_FN({ARGS_SQL_STR})
+RETURNS {RET_SQL_STR}
+AS
+{SQL_STR};
     '''.strip())
     assert isinstance(res, list)
 
@@ -236,11 +232,10 @@
 -- description.
 {ARGS_STR}
 {RET_STR}
-SELECT CREATE_FUNCTION(
-  'FOO_FN({ARGS_SQL_STR})',
-  '{RET_SQL_STR}',
-  '{SQL_STR}'
-);
+CREATE PERFETTO FUNCTION FOO_FN({ARGS_SQL_STR})
+RETURNS {RET_SQL_STR}
+AS
+{SQL_STR};
     '''.strip())
     assert isinstance(res, dict)
 
@@ -260,11 +255,10 @@
 --                             which spans across multiple lines
 -- inconsistently.
 {RET_STR}
-SELECT CREATE_FUNCTION(
-  'FOO_FN({ARGS_SQL_STR})',
-  '{RET_SQL_STR}',
-  '{SQL_STR}'
-);
+CREATE PERFETTO FUNCTION FOO_FN({ARGS_SQL_STR})
+RETURNS {RET_SQL_STR}
+AS
+{SQL_STR};
     '''.strip())
     assert isinstance(res, dict)
 
diff --git a/src/trace_processor/importers/proto/android_probes_module.cc b/src/trace_processor/importers/proto/android_probes_module.cc
index fece007..d26b6ef 100644
--- a/src/trace_processor/importers/proto/android_probes_module.cc
+++ b/src/trace_processor/importers/proto/android_probes_module.cc
@@ -46,7 +46,7 @@
     return "cpu.big";
   } else if (raw == "S5M_VDD_INT") {
     return "system.fabric";
-  } else if (raw == "S10M_VDD_TPU") {
+  } else if (raw == "S10M_VDD_TPU" || raw == "S7M_VDD_TPU") {
     return "tpu";
   } else if (raw == "PPVAR_VSYS_PWR_DISP" || raw == "VSYS_PWR_DISPLAY") {
     return "display";
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index d61d0e3..8884dd8 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -22,7 +22,6 @@
     "android_batt.sql",
     "android_binder.sql",
     "android_blocking_calls_cuj_metric.sql",
-    "android_sysui_notifications_blocking_calls_metric.sql",
     "android_camera.sql",
     "android_camera_unagg.sql",
     "android_cpu.sql",
@@ -55,6 +54,7 @@
     "android_simpleperf.sql",
     "android_startup.sql",
     "android_surfaceflinger.sql",
+    "android_sysui_notifications_blocking_calls_metric.sql",
     "android_task_names.sql",
     "android_trace_quality.sql",
     "android_trusty_workqueues.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_batt.sql b/src/trace_processor/metrics/sql/android/android_batt.sql
index a5cfd5a..acd256f 100644
--- a/src/trace_processor/metrics/sql/android/android_batt.sql
+++ b/src/trace_processor/metrics/sql/android/android_batt.sql
@@ -191,10 +191,10 @@
          dur,
          value_name AS slice_name,
          CASE track_name
-         WHEN 'battery_stats.mobile_radio' THEN 'Cellular radio'
+         WHEN 'battery_stats.mobile_radio' THEN 'Cellular interface'
          WHEN 'battery_stats.data_conn' THEN 'Cellular connection'
          WHEN 'battery_stats.phone_signal_strength' THEN 'Cellular strength'
-         WHEN 'battery_stats.wifi_radio' THEN 'WiFi radio'
+         WHEN 'battery_stats.wifi_radio' THEN 'WiFi interface'
          WHEN 'battery_stats.wifi_suppl' THEN 'Wifi supplicant state'
          WHEN 'battery_stats.wifi_signal_strength' THEN 'WiFi strength'
          ELSE NULL
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index ab69d2b..d619612 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -59,27 +59,23 @@
 );
 
 -- Returns the fully drawn slice proto given a launch id.
-SELECT CREATE_FUNCTION(
-  'REPORT_FULLY_DRAWN_FOR_LAUNCH(startup_id INT)',
-  'PROTO',
-  '
-    SELECT
-      STARTUP_SLICE_PROTO(report_fully_drawn_ts - launch_ts)
-    FROM (
-      SELECT
-        launches.ts AS launch_ts,
-        min(slice.ts) AS report_fully_drawn_ts
-      FROM android_startups launches
-      JOIN android_startup_processes ON (launches.startup_id = android_startup_processes.startup_id)
-      JOIN thread USING (upid)
-      JOIN thread_track USING (utid)
-      JOIN slice ON (slice.track_id = thread_track.id)
-      WHERE
-        slice.name GLOB "reportFullyDrawn*" AND
-        slice.ts >= launches.ts AND
-        launches.startup_id = $startup_id
-    )
-  '
+CREATE PERFETTO FUNCTION REPORT_FULLY_DRAWN_FOR_LAUNCH(startup_id INT)
+RETURNS PROTO AS
+SELECT
+  STARTUP_SLICE_PROTO(report_fully_drawn_ts - launch_ts)
+FROM (
+  SELECT
+    launches.ts AS launch_ts,
+    min(slice.ts) AS report_fully_drawn_ts
+  FROM android_startups launches
+  JOIN android_startup_processes ON (launches.startup_id = android_startup_processes.startup_id)
+  JOIN thread USING (upid)
+  JOIN thread_track USING (utid)
+  JOIN slice ON (slice.track_id = thread_track.id)
+  WHERE
+    slice.name GLOB "reportFullyDrawn*" AND
+    slice.ts >= launches.ts AND
+    launches.startup_id = $startup_id
 );
 
 -- Given a launch id and GLOB for a slice name, returns the N longest slice name and duration.
diff --git a/src/trace_processor/metrics/sql/android/counter_span_view_merged.sql b/src/trace_processor/metrics/sql/android/counter_span_view_merged.sql
index 01b7079..2a89f21 100644
--- a/src/trace_processor/metrics/sql/android/counter_span_view_merged.sql
+++ b/src/trace_processor/metrics/sql/android/counter_span_view_merged.sql
@@ -14,7 +14,7 @@
 -- limitations under the License.
 --
 
--- Creates a span view for counters that may be global or associated with a 
+-- Creates a span view for counters that may be global or associated with a
 -- process, assuming that in the latter case we don't actually care about the
 -- process (probably because it's always system_server). We may want to erase
 -- this distinction for example when merging system properties and atrace
diff --git a/src/trace_processor/metrics/sql/android/jank/internal/counters.sql b/src/trace_processor/metrics/sql/android/jank/internal/counters.sql
index d04229d..9f197fa 100644
--- a/src/trace_processor/metrics/sql/android/jank/internal/counters.sql
+++ b/src/trace_processor/metrics/sql/android/jank/internal/counters.sql
@@ -36,20 +36,19 @@
 FROM counter
 JOIN cuj_counter_track ON counter.track_id = cuj_counter_track.track_id;
 
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CUJ_COUNTER_VALUE(cuj_name STRING, counter_name STRING, ts_min INT, ts_max INT)',
-  'INT',
-  '
-  SELECT value
-  FROM android_jank_cuj_counter
-  WHERE
-    cuj_name = $cuj_name
-    AND counter_name = $counter_name
-    AND ts >= $ts_min
-    AND ($ts_max IS NULL OR ts <= $ts_max)
-  ORDER BY ts ASC LIMIT 1
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_JANK_CUJ_COUNTER_VALUE(cuj_name STRING,
+                                                        counter_name STRING,
+                                                        ts_min INT,
+                                                        ts_max INT)
+RETURNS INT AS
+SELECT value
+FROM android_jank_cuj_counter
+WHERE
+  cuj_name = $cuj_name
+  AND counter_name = $counter_name
+  AND ts >= $ts_min
+  AND ($ts_max IS NULL OR ts <= $ts_max)
+ORDER BY ts ASC LIMIT 1;
 
 DROP TABLE IF EXISTS cuj_marker_missed_callback;
 CREATE TABLE cuj_marker_missed_callback AS
@@ -61,19 +60,21 @@
 JOIN track marker_track on  marker_track.id = marker.track_id
 WHERE marker.name GLOB '*FT#Missed*';
 
-SELECT CREATE_FUNCTION(
-   'ANDROID_MISSED_VSYNCS_FOR_CALLBACK(cuj_slice_name STRING, ts_min INT, ts_max INT, callback_missed STRING)',
-   'INT',
-   '
-   SELECT IFNULL(SUM(marker_name GLOB $callback_missed), 0)
-   FROM cuj_marker_missed_callback
-   WHERE
-     cuj_slice_name = $cuj_slice_name
-     AND ts >= $ts_min
-     AND ($ts_max IS NULL OR ts <= $ts_max)
-   ORDER BY ts ASC LIMIT 1
-   '
-);
+CREATE PERFETTO FUNCTION ANDROID_MISSED_VSYNCS_FOR_CALLBACK(
+  cuj_slice_name STRING,
+  ts_min INT,
+  ts_max INT,
+  callback_missed STRING
+)
+RETURNS INT AS
+SELECT IFNULL(SUM(marker_name GLOB $callback_missed), 0)
+FROM cuj_marker_missed_callback
+WHERE
+  cuj_slice_name = $cuj_slice_name
+  AND ts >= $ts_min
+  AND ($ts_max IS NULL OR ts <= $ts_max)
+ORDER BY ts ASC
+LIMIT 1;
 
 DROP TABLE IF EXISTS android_jank_cuj_counter_metrics;
 CREATE TABLE android_jank_cuj_counter_metrics AS
diff --git a/src/trace_processor/metrics/sql/android/jank/internal/query_base.sql b/src/trace_processor/metrics/sql/android/jank/internal/query_base.sql
index b205f79..76bcec6 100644
--- a/src/trace_processor/metrics/sql/android/jank/internal/query_base.sql
+++ b/src/trace_processor/metrics/sql/android/jank/internal/query_base.sql
@@ -64,38 +64,43 @@
 
 -- Functions below retrieve specific columns for a given table set.
 
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CUJ_TABLE_SET_SLICE(table_set STRING)',
-  'STRING',
-  'SELECT slice_table_name FROM android_jank_cuj_table_set ts WHERE ts.name = $table_set'
-);
+CREATE PERFETTO FUNCTION ANDROID_JANK_CUJ_TABLE_SET_SLICE(table_set STRING)
+RETURNS STRING AS
+SELECT slice_table_name
+FROM android_jank_cuj_table_set ts
+WHERE ts.name = $table_set;
 
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CUJ_TABLE_SET_FRAME_BOUNDARY(table_set STRING)',
-  'STRING',
-  'SELECT frame_boundary_table_name FROM android_jank_cuj_table_set ts WHERE ts.name = $table_set'
-);
+CREATE PERFETTO FUNCTION ANDROID_JANK_CUJ_TABLE_SET_FRAME_BOUNDARY(
+  table_set STRING
+)
+RETURNS STRING AS
+SELECT frame_boundary_table_name
+FROM android_jank_cuj_table_set ts
+WHERE ts.name = $table_set;
 
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CUJ_TABLE_SET_CUJ_BOUNDARY(table_set STRING)',
-  'STRING',
-  'SELECT cuj_boundary_table_name FROM android_jank_cuj_table_set ts WHERE ts.name = $table_set'
-);
+CREATE PERFETTO FUNCTION ANDROID_JANK_CUJ_TABLE_SET_CUJ_BOUNDARY(
+  table_set STRING
+)
+RETURNS STRING AS
+SELECT cuj_boundary_table_name
+FROM android_jank_cuj_table_set ts
+WHERE ts.name = $table_set;
 
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CUJ_TABLE_SET_FRAME(table_set STRING)',
-  'STRING',
-  'SELECT frame_table_name FROM android_jank_cuj_table_set ts WHERE ts.name = $table_set'
-);
+CREATE PERFETTO FUNCTION ANDROID_JANK_CUJ_TABLE_SET_FRAME(table_set STRING)
+RETURNS STRING AS
+SELECT frame_table_name
+FROM android_jank_cuj_table_set ts
+WHERE ts.name = $table_set;
 
 -- Checks if two slices, described by ts and dur, ts_second and dur_second, overlap.
 -- Does not handle cases where slices are unfinished (dur = -1).
+CREATE PERFETTO FUNCTION ANDROID_JANK_CUJ_SLICE_OVERLAPS(ts LONG,
+                                                        dur LONG,
+                                                        ts_second LONG,
+                                                        dur_second LONG)
+RETURNS BOOL AS
 SELECT
-  CREATE_FUNCTION(
-    'ANDROID_JANK_CUJ_SLICE_OVERLAPS(ts LONG, dur LONG, ts_second LONG, dur_second LONG)',
-    'BOOL',
-    'SELECT
-      -- A starts before B ends and A ends after B starts
-      ($ts < $ts_second + $dur_second AND $ts + $dur > $ts_second)
-      -- or A starts after B starts and A ends before B ends
-      OR ($ts > $ts_second AND $ts < $ts_second + $ts_dur)');
+  -- A starts before B ends and A ends after B starts
+  ($ts < $ts_second + $dur_second AND $ts + $dur > $ts_second)
+  -- or A starts after B starts and A ends before B ends
+  OR ($ts > $ts_second AND $ts < $ts_second + $ts_dur);
diff --git a/src/trace_processor/metrics/sql/android/jank/query_functions.sql b/src/trace_processor/metrics/sql/android/jank/query_functions.sql
index e96b587..c37dc42 100644
--- a/src/trace_processor/metrics/sql/android/jank/query_functions.sql
+++ b/src/trace_processor/metrics/sql/android/jank/query_functions.sql
@@ -13,7 +13,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-
 -- Consider calling ANDROID_JANK_CORRELATE_FRAME_SLICE which passes a default value for
 -- `table_name_prefix`.
 --
@@ -28,40 +27,52 @@
 --
 -- CREATE VIEW example_table AS
 -- SELECT * FROM android_jank_cuj_slice WHERE name = 'binder transaction';
--- SELECT ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL('MainThread', 'example_table', 'jank_query');
+-- SELECT ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL('MainThread',
+--                                                'example_table',
+--                                                'jank_query');
 -- SELECT * FROM jank_query_slice_in_frame_agg;
 --
 -- Function arguments:
 --
 -- table_set - Name of a set of tables from `android_jank_cuj_table_set`.
---             Groups releated tables to simplify passing them as arguments to functions.
+--             Groups releated tables to simplify passing them as arguments to
+--             functions.
 --
--- relevant_slice_table_name - Table or View which selects slices for analysis from the
---                             `android_jank_cuj_slice` table.
+-- relevant_slice_table_name - Table or View which selects slices for analysis
+--                             from the `android_jank_cuj_slice` table.
 --
--- table_name_prefix - Running the function will create multiple tables. This value will be used
---                     as a prefx for their names to avoid name collisions with other tables.
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL(table_set STRING, relevant_slice_table_name STRING, table_name_prefix STRING)',
-  'STRING',
-  '
-      SELECT COALESCE( -- COALESCE to return the text with table names to the caller instead of NULL
-          RUN_METRIC(
-            "android/jank/internal/query_frame_slice.sql",
-            "table_name_prefix", $table_name_prefix,
-            "relevant_slice_table_name", $relevant_slice_table_name,
-            "slice_table_name", (SELECT ANDROID_JANK_CUJ_TABLE_SET_SLICE($table_set)),
-            "frame_boundary_table_name", (SELECT ANDROID_JANK_CUJ_TABLE_SET_FRAME_BOUNDARY($table_set)),
-            "frame_table_name", (SELECT ANDROID_JANK_CUJ_TABLE_SET_FRAME($table_set))),
-          "Query results in `" || $table_name_prefix || "_slice_in_frame_agg` and `" || $table_name_prefix || "_slice_in_frame`.")
-   '
+-- table_name_prefix - Running the function will create multiple tables. This
+--                     value will be used as a prefx for their names to avoid
+--                     name collisions with other tables.
+CREATE PERFETTO FUNCTION ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL(
+  table_set STRING,
+  relevant_slice_table_name STRING,
+  table_name_prefix STRING
+)
+RETURNS STRING AS
+-- COALESCE to return the text with table names to the caller instead of NULL
+SELECT COALESCE(
+  RUN_METRIC(
+    "android/jank/internal/query_frame_slice.sql",
+    "table_name_prefix", $table_name_prefix,
+    "relevant_slice_table_name", $relevant_slice_table_name,
+    "slice_table_name", (SELECT ANDROID_JANK_CUJ_TABLE_SET_SLICE($table_set)),
+    "frame_boundary_table_name", (SELECT ANDROID_JANK_CUJ_TABLE_SET_FRAME_BOUNDARY($table_set)),
+    "frame_table_name", (SELECT ANDROID_JANK_CUJ_TABLE_SET_FRAME($table_set))
+  ),
+  "Query results in `" || $table_name_prefix || "_slice_in_frame_agg` and `" || $table_name_prefix || "_slice_in_frame`."
 );
 
-
--- Provides a default value for table_name_prefix in ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL.
+-- Provides a default value for table_name_prefix in
+-- ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL.
 -- See documentation for ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL.
-SELECT CREATE_FUNCTION(
-  'ANDROID_JANK_CORRELATE_FRAME_SLICE(table_set STRING, relevant_slice_table_name STRING)',
-  'STRING',
-  'SELECT ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL($table_set, $relevant_slice_table_name, "jank_query")'
+CREATE PERFETTO FUNCTION ANDROID_JANK_CORRELATE_FRAME_SLICE(
+  table_set STRING,
+  relevant_slice_table_name STRING
+)
+RETURNS STRING AS
+SELECT ANDROID_JANK_CORRELATE_FRAME_SLICE_IMPL(
+  $table_set,
+  $relevant_slice_table_name,
+  "jank_query"
 );
diff --git a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
index bd72bd2..5894e64 100644
--- a/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
+++ b/src/trace_processor/metrics/sql/android/jank/relevant_slices.sql
@@ -13,41 +13,36 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-SELECT CREATE_FUNCTION(
-  'VSYNC_FROM_NAME(slice_name STRING)',
-  'STRING',
-  'SELECT CAST(STR_SPLIT($slice_name, " ", 1) AS INTEGER)'
-);
+CREATE PERFETTO FUNCTION VSYNC_FROM_NAME(slice_name STRING)
+RETURNS STRING AS
+SELECT CAST(STR_SPLIT($slice_name, " ", 1) AS INTEGER);
 
-SELECT CREATE_FUNCTION(
-  'GPU_COMPLETION_FENCE_ID_FROM_NAME(slice_name STRING)',
-  'STRING',
-  'SELECT
-    CASE
-      WHEN
-        $slice_name GLOB "GPU completion fence *"
-      THEN
-        CAST(STR_SPLIT($slice_name, " ", 3) AS INTEGER)
-      WHEN
-        $slice_name GLOB "Trace GPU completion fence *"
-      THEN
-        CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
-      WHEN
-        $slice_name GLOB "waiting for GPU completion *"
-      THEN
-        CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
-      WHEN
-        $slice_name GLOB "Trace HWC release fence *"
-      THEN
-        CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
-      WHEN
-        $slice_name GLOB "waiting for HWC release *"
-      THEN
-        CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
-      ELSE NULL
-    END
-  '
-);
+CREATE PERFETTO FUNCTION GPU_COMPLETION_FENCE_ID_FROM_NAME(slice_name STRING)
+RETURNS STRING AS
+SELECT
+  CASE
+    WHEN
+      $slice_name GLOB "GPU completion fence *"
+    THEN
+      CAST(STR_SPLIT($slice_name, " ", 3) AS INTEGER)
+    WHEN
+      $slice_name GLOB "Trace GPU completion fence *"
+    THEN
+      CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
+    WHEN
+      $slice_name GLOB "waiting for GPU completion *"
+    THEN
+      CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
+    WHEN
+      $slice_name GLOB "Trace HWC release fence *"
+    THEN
+      CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
+    WHEN
+      $slice_name GLOB "waiting for HWC release *"
+    THEN
+      CAST(STR_SPLIT($slice_name, " ", 4) AS INTEGER)
+    ELSE NULL
+  END;
 
 -- Find Choreographer#doFrame slices that are between the CUJ markers.
 -- We extract vsync IDs from doFrame slice names and use these as the source
diff --git a/src/trace_processor/metrics/sql/android/process_metadata.sql b/src/trace_processor/metrics/sql/android/process_metadata.sql
index 5ccf2b1..3f6b9e8 100644
--- a/src/trace_processor/metrics/sql/android/process_metadata.sql
+++ b/src/trace_processor/metrics/sql/android/process_metadata.sql
@@ -56,13 +56,9 @@
 LEFT JOIN upid_packages USING (upid);
 
 -- Given a process name, return if it is debuggable.
-SELECT CREATE_FUNCTION(
-  'IS_PROCESS_DEBUGGABLE(process_name STRING)',
-  'BOOL',
-  '
-    SELECT p.debuggable
-    FROM process_metadata_table p
-    WHERE p.process_name = $process_name
-    LIMIT 1
-  '
-);
+CREATE PERFETTO FUNCTION IS_PROCESS_DEBUGGABLE(process_name STRING)
+RETURNS BOOL AS
+SELECT p.debuggable
+FROM process_metadata_table p
+WHERE p.process_name = $process_name
+LIMIT 1;
diff --git a/src/trace_processor/metrics/sql/android/startup/launches.sql b/src/trace_processor/metrics/sql/android/startup/launches.sql
index 8387cfe..e8441b9 100644
--- a/src/trace_processor/metrics/sql/android/startup/launches.sql
+++ b/src/trace_processor/metrics/sql/android/startup/launches.sql
@@ -13,7 +13,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
--- This metric will be deprecated soon. All of the tables have been 
+-- This metric will be deprecated soon. All of the tables have been
 -- migrated to SQL standard library and can be imported from:
 SELECT IMPORT('android.startup.startups');
 
diff --git a/src/trace_processor/metrics/sql/android/startup/mcycles_per_launch.sql b/src/trace_processor/metrics/sql/android/startup/mcycles_per_launch.sql
index b47381e..6944625 100644
--- a/src/trace_processor/metrics/sql/android/startup/mcycles_per_launch.sql
+++ b/src/trace_processor/metrics/sql/android/startup/mcycles_per_launch.sql
@@ -55,15 +55,11 @@
 
 -- Given a launch id and core type, returns the number of mcycles consumed
 -- on CPUs of that core type during the launch.
-SELECT CREATE_FUNCTION(
-  'MCYCLES_FOR_LAUNCH_AND_CORE_TYPE(startup_id INT, core_type STRING)',
-  'INT',
-  '
-    SELECT mcycles
-    FROM mcycles_per_core_type_per_launch m
-    WHERE m.startup_id = $startup_id AND m.core_type = $core_type
-  '
-);
+CREATE PERFETTO FUNCTION MCYCLES_FOR_LAUNCH_AND_CORE_TYPE(startup_id INT, core_type STRING)
+RETURNS INT AS
+SELECT mcycles
+FROM mcycles_per_core_type_per_launch m
+WHERE m.startup_id = $startup_id AND m.core_type = $core_type;
 
 -- Contains the process using the most mcycles during the launch
 -- *excluding the process being started*.
@@ -97,30 +93,23 @@
 
 -- Given a launch id, returns the name of the processes consuming the most
 -- mcycles during the launch excluding the process being started.
-SELECT CREATE_FUNCTION(
-  'N_MOST_ACTIVE_PROCESS_NAMES_FOR_LAUNCH(startup_id INT)',
-  'STRING',
-  '
-    SELECT RepeatedField(process_name)
-    FROM (
-      SELECT IFNULL(process.name, "[NULL]") AS process_name
-      FROM top_mcyles_process_excluding_started_per_launch
-      JOIN process USING (upid)
-      WHERE startup_id = $startup_id
-      ORDER BY mcycles DESC
-    );
-  '
+CREATE PERFETTO FUNCTION N_MOST_ACTIVE_PROCESS_NAMES_FOR_LAUNCH(startup_id INT)
+RETURNS STRING AS
+SELECT RepeatedField(process_name)
+FROM (
+  SELECT IFNULL(process.name, "[NULL]") AS process_name
+  FROM top_mcyles_process_excluding_started_per_launch
+  JOIN process USING (upid)
+  WHERE startup_id = $startup_id
+  ORDER BY mcycles DESC
 );
 
 -- Given a launch id, returns the most active process name.
-SELECT CREATE_FUNCTION(
-  'MOST_ACTIVE_PROCESS_FOR_LAUNCH(startup_id INT)',
-  'STRING',
-  '
-    SELECT process.name AS process_name
-    FROM top_mcyles_process_excluding_started_per_launch
-    JOIN process USING (upid)
-    WHERE startup_id = $startup_id
-    ORDER BY mcycles DESC LIMIT 1;
-  '
-);
+CREATE PERFETTO FUNCTION MOST_ACTIVE_PROCESS_FOR_LAUNCH(startup_id INT)
+RETURNS STRING AS
+SELECT process.name AS process_name
+FROM top_mcyles_process_excluding_started_per_launch
+JOIN process USING (upid)
+WHERE startup_id = $startup_id
+ORDER BY mcycles DESC
+LIMIT 1;
diff --git a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
index 0939e0d..6c4eb25 100644
--- a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
+++ b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
@@ -17,12 +17,12 @@
 SELECT IMPORT('android.startup.startups');
 
 -- Helper function to build a Slice proto from a duration.
-SELECT CREATE_FUNCTION('STARTUP_SLICE_PROTO(dur INT)', 'PROTO', '
-  SELECT AndroidStartupMetric_Slice(
-    "dur_ns", $dur,
-    "dur_ms", $dur / 1e6
-  )
-');
+CREATE PERFETTO FUNCTION STARTUP_SLICE_PROTO(dur INT)
+RETURNS PROTO AS
+SELECT AndroidStartupMetric_Slice(
+  "dur_ns", $dur,
+  "dur_ms", $dur / 1e6
+);
 
 -- View containing all the slices for all launches. Generally, this view
 -- should not be used. Instead, one of the helper functions below which wrap
@@ -31,149 +31,112 @@
 CREATE VIEW thread_slices_for_all_launches AS
 SELECT * FROM android_thread_slices_for_all_startups;
 
-
--- Given a launch id and GLOB for a slice name,
--- summing the slice durations across the whole startup.
-SELECT CREATE_FUNCTION(
-  'ANDROID_SUM_DUR_FOR_STARTUP_AND_SLICE(startup_id LONG, slice_name STRING)',
-  'INT',
-  '
-    SELECT SUM(slice_dur)
-    FROM android_thread_slices_for_all_startups
-    WHERE startup_id = $startup_id AND slice_name GLOB $slice_name
-  '
-);
-
 -- Given a launch id and GLOB for a slice name, returns the startup slice proto,
 -- summing the slice durations across the whole startup.
-SELECT CREATE_FUNCTION(
-  'DUR_SUM_SLICE_PROTO_FOR_LAUNCH(startup_id LONG, slice_name STRING)',
-  'PROTO',
-  '
-    SELECT NULL_IF_EMPTY(
-      STARTUP_SLICE_PROTO(
-        ANDROID_SUM_DUR_FOR_STARTUP_AND_SLICE($startup_id, $slice_name)
-      )
-    )
-  '
+CREATE PERFETTO FUNCTION DUR_SUM_SLICE_PROTO_FOR_LAUNCH(startup_id LONG, slice_name STRING)
+RETURNS PROTO AS
+SELECT NULL_IF_EMPTY(
+  STARTUP_SLICE_PROTO(
+    ANDROID_SUM_DUR_FOR_STARTUP_AND_SLICE($startup_id, $slice_name)
+  )
 );
 
 -- Same as |DUR_SUM_SLICE_PROTO_FOR_LAUNCH| except only counting slices happening
 -- on the main thread.
-SELECT CREATE_FUNCTION(
-  'DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(startup_id LONG, slice_name STRING)',
-  'PROTO',
-  '
-    SELECT NULL_IF_EMPTY(
-      STARTUP_SLICE_PROTO(
-        ANDROID_SUM_DUR_ON_MAIN_THREAD_FOR_STARTUP_AND_SLICE($startup_id, $slice_name)
-      )
-    )
-  '
+CREATE PERFETTO FUNCTION DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(startup_id LONG, slice_name STRING)
+RETURNS PROTO AS
+SELECT NULL_IF_EMPTY(
+  STARTUP_SLICE_PROTO(
+    ANDROID_SUM_DUR_ON_MAIN_THREAD_FOR_STARTUP_AND_SLICE($startup_id, $slice_name)
+  )
 );
 
 -- Given a launch id and GLOB for a slice name, returns the startup slice proto by
 -- taking the duration between the start of the launch and start of the slice.
 -- If multiple slices match, picks the latest one which started during the launch.
-SELECT CREATE_FUNCTION(
-  'LAUNCH_TO_MAIN_THREAD_SLICE_PROTO(startup_id INT, slice_name STRING)',
-  'PROTO',
-  '
-    SELECT NULL_IF_EMPTY(STARTUP_SLICE_PROTO(MAX(slice_ts) - startup_ts))
-    FROM android_thread_slices_for_all_startups s
-    JOIN thread t USING (utid)
-    WHERE
-      s.slice_name GLOB $slice_name AND
-      s.startup_id = $startup_id AND
-      s.is_main_thread AND
-      (t.end_ts IS NULL OR t.end_ts >= s.startup_ts_end)
-  '
-);
+CREATE PERFETTO FUNCTION LAUNCH_TO_MAIN_THREAD_SLICE_PROTO(startup_id INT, slice_name STRING)
+RETURNS PROTO AS
+SELECT NULL_IF_EMPTY(STARTUP_SLICE_PROTO(MAX(slice_ts) - startup_ts))
+FROM android_thread_slices_for_all_startups s
+JOIN thread t USING (utid)
+WHERE
+  s.slice_name GLOB $slice_name AND
+  s.startup_id = $startup_id AND
+  s.is_main_thread AND
+  (t.end_ts IS NULL OR t.end_ts >= s.startup_ts_end);
 
 -- Given a lauch id, returns the total time spent in GC
-SELECT CREATE_FUNCTION(
-  'TOTAL_GC_TIME_BY_LAUNCH(startup_id LONG)',
-  'INT',
-  '
-    SELECT SUM(slice_dur)
-        FROM android_thread_slices_for_all_startups slice
-        WHERE
-          slice.startup_id = $startup_id AND
-          (
-            slice_name GLOB "*semispace GC" OR
-            slice_name GLOB "*mark sweep GC" OR
-            slice_name GLOB "*concurrent copying GC"
-          )
-  '
-);
+CREATE PERFETTO FUNCTION TOTAL_GC_TIME_BY_LAUNCH(startup_id LONG)
+RETURNS INT AS
+SELECT SUM(slice_dur)
+FROM android_thread_slices_for_all_startups slice
+WHERE
+  slice.startup_id = $startup_id AND
+  (
+    slice_name GLOB "*semispace GC" OR
+    slice_name GLOB "*mark sweep GC" OR
+    slice_name GLOB "*concurrent copying GC"
+  );
 
 -- Given a launch id and package name, returns if baseline or cloud profile is missing.
-SELECT CREATE_FUNCTION(
-  'MISSING_BASELINE_PROFILE_FOR_LAUNCH(startup_id LONG, pkg_name STRING)',
-  'BOOL',
-  '
-    SELECT (COUNT(slice_name) > 0)
-    FROM (
-      SELECT *
-      FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(
-        $startup_id,
-        "location=* status=* filter=* reason=*"
-      )
-      ORDER BY slice_name
-    )
-    WHERE
-      -- when location is the package odex file and the reason is "install" or "install-dm",
-      -- if the compilation filter is not "speed-profile", baseline/cloud profile is missing.
-      SUBSTR(STR_SPLIT(slice_name, " status=", 0), LENGTH("location=") + 1)
-        GLOB ("*" || $pkg_name || "*odex")
-      AND (STR_SPLIT(slice_name, " reason=", 1) = "install"
-        OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm")
-      AND STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0) != "speed-profile"
-  '
-);
+CREATE PERFETTO FUNCTION MISSING_BASELINE_PROFILE_FOR_LAUNCH(startup_id LONG, pkg_name STRING)
+RETURNS BOOL AS
+SELECT (COUNT(slice_name) > 0)
+FROM (
+  SELECT *
+  FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(
+    $startup_id,
+    "location=* status=* filter=* reason=*"
+  )
+  ORDER BY slice_name
+)
+WHERE
+  -- when location is the package odex file and the reason is "install" or "install-dm",
+  -- if the compilation filter is not "speed-profile", baseline/cloud profile is missing.
+  SUBSTR(STR_SPLIT(slice_name, " status=", 0), LENGTH("location=") + 1)
+    GLOB ("*" || $pkg_name || "*odex")
+  AND (STR_SPLIT(slice_name, " reason=", 1) = "install"
+    OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm")
+  AND STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0) != "speed-profile";
 
 -- Given a launch id, returns if there is a main thread run-from-apk slice.
 -- Add an exception if "split_config" is in parent slice's name(b/277809828).
 -- TODO: remove the exception after Sep 2023 (b/78772867)
-SELECT CREATE_FUNCTION(
-  'RUN_FROM_APK_FOR_LAUNCH(launch_id LONG)',
-  'BOOL',
-  '
-    SELECT EXISTS(
-      SELECT slice_name, startup_id, is_main_thread
-      FROM android_thread_slices_for_all_startups s
-      JOIN slice ON slice.id = s.slice_id
-      LEFT JOIN slice parent ON slice.parent_id = parent.id
-      WHERE
-        startup_id = $launch_id AND is_main_thread AND
-        slice_name GLOB "location=* status=* filter=* reason=*" AND
-        STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0)
-          GLOB ("*" || "run-from-apk" || "*") AND
-        (parent.name IS NULL OR
-          parent.name NOT GLOB ("OpenDexFilesFromOat(*split_config*apk)"))
-    )
-  '
+CREATE PERFETTO FUNCTION RUN_FROM_APK_FOR_LAUNCH(launch_id LONG)
+RETURNS BOOL AS
+SELECT EXISTS(
+  SELECT slice_name, startup_id, is_main_thread
+  FROM android_thread_slices_for_all_startups s
+  JOIN slice ON slice.id = s.slice_id
+  LEFT JOIN slice parent ON slice.parent_id = parent.id
+  WHERE
+    startup_id = $launch_id AND is_main_thread AND
+    slice_name GLOB "location=* status=* filter=* reason=*" AND
+    STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0)
+      GLOB ("*" || "run-from-apk" || "*") AND
+    (parent.name IS NULL OR
+      parent.name NOT GLOB ("OpenDexFilesFromOat(*split_config*apk)"))
 );
 
-SELECT CREATE_FUNCTION(
-  'SUMMARY_FOR_OPTIMIZATION_STATUS(loc STRING, status STRING, filter STRING, reason STRING)',
-  'STRING',
-  '
-    SELECT
-      CASE
-        WHEN
-          $loc GLOB "*/base.odex" AND $loc GLOB "*==/*-*"
-        THEN STR_SPLIT(STR_SPLIT($loc, "==/", 1), "-", 0) || "/.../"
-        ELSE ""
-      END ||
-      CASE
-        WHEN $loc GLOB "*/*"
-          THEN REVERSE(STR_SPLIT(REVERSE($loc), "/", 0))
-        ELSE $loc
-      END ||
-      ": " || $status || "/" || $filter || "/" || $reason
-  '
-);
+CREATE PERFETTO FUNCTION SUMMARY_FOR_OPTIMIZATION_STATUS(
+  loc STRING,
+  status STRING,
+  filter_str STRING,
+  reason STRING
+)
+RETURNS STRING AS
+SELECT
+CASE
+  WHEN
+    $loc GLOB "*/base.odex" AND $loc GLOB "*==/*-*"
+  THEN STR_SPLIT(STR_SPLIT($loc, "==/", 1), "-", 0) || "/.../"
+  ELSE ""
+END ||
+CASE
+  WHEN $loc GLOB "*/*"
+    THEN REVERSE(STR_SPLIT(REVERSE($loc), "/", 0))
+  ELSE $loc
+END || ": " || $status || "/" || $filter_str || "/" || $reason;
 
 SELECT CREATE_VIEW_FUNCTION(
   'BINDER_TRANSACTION_REPLY_SLICES_FOR_LAUNCH(startup_id INT, threshold DOUBLE)',
@@ -188,21 +151,17 @@
 );
 
 -- Given a launch id, return if unlock is running by systemui during the launch.
-SELECT CREATE_FUNCTION(
-  'IS_UNLOCK_RUNNING_DURING_LAUNCH(startup_id LONG)',
-  'BOOL',
-  '
-    SELECT EXISTS(
-      SELECT slice.name
-      FROM slice, android_startups launches
-      JOIN thread_track ON slice.track_id = thread_track.id
-      JOIN thread USING(utid)
-      JOIN process USING(upid)
-      WHERE launches.startup_id = $startup_id
-      AND slice.name = "KeyguardUpdateMonitor#onAuthenticationSucceeded"
-      AND process.name = "com.android.systemui"
-      AND slice.ts >= launches.ts
-      AND (slice.ts + slice.dur) <= launches.ts_end
-    )
-  '
+CREATE PERFETTO FUNCTION IS_UNLOCK_RUNNING_DURING_LAUNCH(startup_id LONG)
+RETURNS BOOL AS
+SELECT EXISTS(
+  SELECT slice.name
+  FROM slice, android_startups launches
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN thread USING(utid)
+  JOIN process USING(upid)
+  WHERE launches.startup_id = $startup_id
+  AND slice.name = "KeyguardUpdateMonitor#onAuthenticationSucceeded"
+  AND process.name = "com.android.systemui"
+  AND slice.ts >= launches.ts
+  AND (slice.ts + slice.dur) <= launches.ts_end
 );
diff --git a/src/trace_processor/metrics/sql/android/startup/system_state.sql b/src/trace_processor/metrics/sql/android/startup/system_state.sql
index 5b49ace..214ac3f 100644
--- a/src/trace_processor/metrics/sql/android/startup/system_state.sql
+++ b/src/trace_processor/metrics/sql/android/startup/system_state.sql
@@ -21,40 +21,35 @@
 
 -- Given a launch id and process name glob, returns the sched.dur if a process with
 -- that name was running on a CPU concurrent to that launch.
-SELECT CREATE_FUNCTION(
-  'DUR_OF_PROCESS_RUNNING_CONCURRENT_TO_LAUNCH(startup_id INT, process_glob STRING)',
-  'INT',
-  '
-      SELECT IFNULL(SUM(sched.dur), 0)
-      FROM sched
-      JOIN thread USING (utid)
-      JOIN process USING (upid)
-      JOIN (
-        SELECT ts, ts_end
-        FROM android_startups
-        WHERE startup_id = $startup_id
-      ) launch
-      WHERE
-        process.name GLOB $process_glob AND
-        sched.ts BETWEEN launch.ts AND launch.ts_end
-  '
-);
+CREATE PERFETTO FUNCTION DUR_OF_PROCESS_RUNNING_CONCURRENT_TO_LAUNCH(
+  startup_id INT,
+  process_glob STRING
+)
+RETURNS INT AS
+SELECT IFNULL(SUM(sched.dur), 0)
+FROM sched
+JOIN thread USING (utid)
+JOIN process USING (upid)
+JOIN (
+  SELECT ts, ts_end
+  FROM android_startups
+  WHERE startup_id = $startup_id
+) launch
+WHERE
+  process.name GLOB $process_glob AND
+  sched.ts BETWEEN launch.ts AND launch.ts_end;
 
 -- Given a launch id and slice name glob, returns the number of slices with that
 -- name which start concurrent to that launch.
-SELECT CREATE_FUNCTION(
-  'COUNT_SLICES_CONCURRENT_TO_LAUNCH(startup_id INT, slice_glob STRING)',
-  'INT',
-  '
-    SELECT COUNT(1)
-    FROM slice
-    JOIN (
-      SELECT ts, ts_end
-      FROM android_startups
-      WHERE startup_id = $startup_id
-    ) launch
-    WHERE
-      slice.name GLOB $slice_glob AND
-      slice.ts BETWEEN launch.ts AND launch.ts_end
-  '
-);
+CREATE PERFETTO FUNCTION COUNT_SLICES_CONCURRENT_TO_LAUNCH(startup_id INT, slice_glob STRING)
+RETURNS INT AS
+SELECT COUNT(1)
+FROM slice
+JOIN (
+  SELECT ts, ts_end
+  FROM android_startups
+  WHERE startup_id = $startup_id
+) launch
+WHERE
+  slice.name GLOB $slice_glob AND
+  slice.ts BETWEEN launch.ts AND launch.ts_end;
diff --git a/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql b/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql
index 4ef1fc4..0ed863d 100644
--- a/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql
+++ b/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql
@@ -58,51 +58,35 @@
 
 -- Given a launch id and thread state value, returns the aggregate sum
 -- of time spent in that state by the main thread of the process being started up.
-SELECT CREATE_FUNCTION(
-  'MAIN_THREAD_TIME_FOR_LAUNCH_AND_STATE(startup_id INT, state STRING)',
-  'INT',
-  '
-    SELECT SUM(dur)
-    FROM launch_thread_state_dur_sum l
-    WHERE l.startup_id = $startup_id AND state GLOB $state AND is_main_thread;
-  '
-);
+CREATE PERFETTO FUNCTION MAIN_THREAD_TIME_FOR_LAUNCH_AND_STATE(startup_id INT, state STRING)
+RETURNS INT AS
+SELECT SUM(dur)
+FROM launch_thread_state_dur_sum l
+WHERE l.startup_id = $startup_id AND state GLOB $state AND is_main_thread;
 
 -- Given a launch id, returns the aggregate sum of time spent in runnable state
 -- by the main thread of the process being started up.
-SELECT CREATE_FUNCTION(
-  'MAIN_THREAD_TIME_FOR_LAUNCH_IN_RUNNABLE_STATE(startup_id INT)',
-  'INT',
-  '
-    SELECT IFNULL(MAIN_THREAD_TIME_FOR_LAUNCH_AND_STATE($startup_id, "R"), 0)
+CREATE PERFETTO FUNCTION MAIN_THREAD_TIME_FOR_LAUNCH_IN_RUNNABLE_STATE(startup_id INT)
+RETURNS INT AS
+SELECT IFNULL(MAIN_THREAD_TIME_FOR_LAUNCH_AND_STATE($startup_id, "R"), 0)
       + IFNULL(MAIN_THREAD_TIME_FOR_LAUNCH_AND_STATE($startup_id, "R+"), 0);
-  '
-);
 
 -- Given a launch id, thread state  and io_wait value, returns the aggregate sum
 -- of time spent in that state by the main thread of the process being started up.
-SELECT CREATE_FUNCTION(
-  'MAIN_THREAD_TIME_FOR_LAUNCH_STATE_AND_IO_WAIT(startup_id INT, state STRING, io_wait BOOL)',
-  'INT',
-  '
-    SELECT SUM(dur)
-    FROM launch_thread_state_io_wait_dur_sum l
-    WHERE l.startup_id = $startup_id AND state GLOB $state
-      AND is_main_thread AND l.io_wait = $io_wait;
-  '
-);
+CREATE PERFETTO FUNCTION MAIN_THREAD_TIME_FOR_LAUNCH_STATE_AND_IO_WAIT(startup_id INT, state STRING, io_wait BOOL)
+RETURNS INT AS
+SELECT SUM(dur)
+FROM launch_thread_state_io_wait_dur_sum l
+WHERE l.startup_id = $startup_id AND state GLOB $state
+  AND is_main_thread AND l.io_wait = $io_wait;
 
 
 -- Given a launch id, thread state value and name of a thread, returns the aggregate sum
 -- of time spent in that state by that thread. Note: only threads of the processes
 -- being started are considered by this function - if a thread from a different name
 -- happens to match the name passed, it will *not* be included.
-SELECT CREATE_FUNCTION(
-  'THREAD_TIME_FOR_LAUNCH_STATE_AND_THREAD(startup_id INT, state STRING, thread_name STRING)',
-  'INT',
-  '
-    SELECT SUM(dur)
-    FROM launch_thread_state_dur_sum l
-    WHERE l.startup_id = $startup_id AND state GLOB $state AND thread_name = $thread_name;
-  '
-);
+CREATE PERFETTO FUNCTION THREAD_TIME_FOR_LAUNCH_STATE_AND_THREAD(startup_id INT, state STRING, thread_name STRING)
+RETURNS INT AS
+SELECT SUM(dur)
+FROM launch_thread_state_dur_sum l
+WHERE l.startup_id = $startup_id AND state GLOB $state AND thread_name = $thread_name;
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql b/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql
index 9d39948..a43903c 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_input_to_browser_intervals_base.sql
@@ -17,44 +17,43 @@
 --          active development and the values & meaning might change without
 --          notice.
 
-SELECT CREATE_FUNCTION(
-  -- Given a slice id of an event, get timestamp of the most recent flow
-  -- event on the Chrome IO thread that preceded this slice.
-  -- This helps us identify the last flow event on the IO thread before
-  -- letting the browser main thread be aware of input.
-  -- We need this for flings (generated by the GPU process) and blocked
-  -- touch moves that are forwarded from the renderer.
-  '{{function_prefix}}PRECEDING_IO_THREAD_EVENT_FLOW_ID(id LONG)',
-  -- Returning the slice id for the flow_out on the chrome IO thread.
-  'LONG',
-  'SELECT MAX(flow.slice_out) AS id
-  FROM
-    PRECEDING_FLOW(($id)) flow');
+-- Given a slice id of an event, get timestamp of the most recent flow
+-- event on the Chrome IO thread that preceded this slice.
+-- This helps us identify the last flow event on the IO thread before
+-- letting the browser main thread be aware of input.
+-- We need this for flings (generated by the GPU process) and blocked
+-- touch moves that are forwarded from the renderer.
+-- Returning the slice id for the flow_out on the chrome IO thread.
+CREATE PERFETTO FUNCTION {{function_prefix}}PRECEDING_IO_THREAD_EVENT_FLOW_ID(id LONG)
+RETURNS LONG AS
+SELECT MAX(flow.slice_out) AS id
+FROM PRECEDING_FLOW(($id)) flow;
 
 -- Returns a Chrome task which contains the given slice.
-SELECT CREATE_FUNCTION(
-  '{{function_prefix}}GET_ENCLOSING_CHROME_TASK_NAME(slice_id LONG)',
-  'STRING',
-  'SELECT
-    task.name
-    FROM ancestor_slice($slice_id)
-    JOIN chrome_tasks task USING (id)
-    LIMIT 1
-  '
-);
+CREATE PERFETTO FUNCTION {{function_prefix}}GET_ENCLOSING_CHROME_TASK_NAME(
+  slice_id LONG
+)
+RETURNS STRING AS
+SELECT
+  task.name
+FROM ancestor_slice($slice_id)
+JOIN chrome_tasks task USING (id)
+LIMIT 1;
 
-SELECT CREATE_FUNCTION(
-  '{{function_prefix}}GET_SCROLL_TYPE(blocked_gesture BOOL, task_name STRING)',
-  'STRING',
-  'SELECT
-    CASE WHEN ($blocked_gesture)
-    THEN (SELECT
-            CASE WHEN ($task_name) glob "viz.mojom.BeginFrameObserver *"
-            THEN "fling"
-            WHEN ($task_name) glob "blink.mojom.WidgetInputHandler *"
-            THEN "blocking_touch_move"
-            ELSE "unknown" END)
-    ELSE "regular" END AS delay_type');
+CREATE PERFETTO FUNCTION {{function_prefix}}GET_SCROLL_TYPE(
+  blocked_gesture BOOL,
+  task_name STRING
+)
+RETURNS STRING AS
+SELECT
+  CASE WHEN ($blocked_gesture)
+  THEN (SELECT
+          CASE WHEN ($task_name) glob "viz.mojom.BeginFrameObserver *"
+          THEN "fling"
+          WHEN ($task_name) glob "blink.mojom.WidgetInputHandler *"
+          THEN "blocking_touch_move"
+          ELSE "unknown" END)
+  ELSE "regular" END AS delay_type;
 
 -- Get all InputLatency::GestureScrollUpdate events, to use their
 -- flows later on to decide how much time we waited from queueing the event
@@ -161,8 +160,6 @@
 WHERE
   flow_order = 1;
 
-
-
 -- The decision for processing on the browser main thread for a frame can be
 -- instant, or delayed by the renderer in cases where the renderer needs to
 -- decide whether the touch move is an ScrollUpdate or not, and in other cases
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql b/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql
index d1487c1..6557694 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_long_tasks.sql
@@ -48,11 +48,9 @@
   '
 );
 
-SELECT CREATE_FUNCTION(
-  'IS_LONG_CHOREOGRAPHER_TASK(dur LONG)',
-  'BOOL',
-  'SELECT $dur >= 4 * 1e6'
-);
+CREATE PERFETTO FUNCTION IS_LONG_CHOREOGRAPHER_TASK(dur LONG)
+RETURNS BOOL AS
+SELECT $dur >= 4 * 1e6;
 
 -- Note that not all slices will be mojo slices; filter on interface_name IS
 -- NOT NULL for mojo slices specifically.
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql b/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql
index f971cbb..eb04fa2 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_stack_samples_for_task.sql
@@ -23,18 +23,15 @@
 -- @task_name: a task name following chrome_tasks.sql naming convention to
 -- find stack samples on.
 
-
 SELECT IMPORT('chrome.tasks');
 
-SELECT CREATE_FUNCTION(
-  'DescribeSymbol(symbol STRING, frame_name STRING)',
-  'STRING',
-  'SELECT COALESCE($symbol,
-   CASE WHEN demangle($frame_name) IS NULL
-    THEN $frame_name
-    ELSE demangle($frame_name)
-   END)'
-);
+CREATE PERFETTO FUNCTION DescribeSymbol(symbol STRING, frame_name STRING)
+RETURNS STRING AS
+SELECT COALESCE($symbol,
+  CASE WHEN demangle($frame_name) IS NULL
+  THEN $frame_name
+  ELSE demangle($frame_name)
+  END);
 
 -- Get all Chrome tasks that match a specific name on a specific thread.
 -- The timestamps for those tasks are going to be used later on to gather
diff --git a/src/trace_processor/metrics/sql/chrome/event_latency_to_breakdowns.sql b/src/trace_processor/metrics/sql/chrome/event_latency_to_breakdowns.sql
index da02110..2d448e5 100644
--- a/src/trace_processor/metrics/sql/chrome/event_latency_to_breakdowns.sql
+++ b/src/trace_processor/metrics/sql/chrome/event_latency_to_breakdowns.sql
@@ -45,43 +45,41 @@
   ON slice.parent_id = event_latency.slice_id;
 
 -- The function takes a breakdown name and checks if the breakdown name is known or not.
-SELECT CREATE_FUNCTION(
-  'InvalidNameOrNull(name STRING)',
-  -- Returns the input breakdown name if it's an unknown breakdown, NULL otherwise.
-  'STRING',
-  'SELECT
-    CASE
-      WHEN
-      $name not in (
-        "GenerationToBrowserMain", "GenerationToRendererCompositor",
-        "BrowserMainToRendererCompositor", "RendererCompositorQueueingDelay",
-        "RendererCompositorToMain", "RendererCompositorProcessing",
-        "RendererMainProcessing", "EndActivateToSubmitCompositorFrame",
-        "SubmitCompositorFrameToPresentationCompositorFrame",
-        "ArrivedInRendererCompositorToTermination",
-        "RendererCompositorStartedToTermination",
-        "RendererMainFinishedToTermination",
-        "RendererCompositorFinishedToTermination",
-        "RendererMainStartedToTermination",
-        "RendererCompositorFinishedToBeginImplFrame",
-        "RendererCompositorFinishedToCommit",
-        "RendererCompositorFinishedToEndCommit",
-        "RendererCompositorFinishedToActivation",
-        "RendererCompositorFinishedToEndActivate", 
-        "RendererCompositorFinishedToSubmitCompositorFrame",
-        "RendererMainFinishedToBeginImplFrame",
-        "RendererMainFinishedToSendBeginMainFrame",
-        "RendererMainFinishedToCommit", "RendererMainFinishedToEndCommit",
-        "RendererMainFinishedToActivation", "RendererMainFinishedToEndActivate",
-        "RendererMainFinishedToSubmitCompositorFrame",
-        "BeginImplFrameToSendBeginMainFrame",
-        "RendererCompositorFinishedToSendBeginMainFrame",
-        "SendBeginMainFrameToCommit", "Commit",
-        "EndCommitToActivation", "Activation")
-        THEN $name
-      ELSE NULL
-    END'
-);
+-- Returns the input breakdown name if it's an unknown breakdown, NULL otherwise.
+CREATE PERFETTO FUNCTION InvalidNameOrNull(name STRING)
+RETURNS STRING AS
+SELECT
+  CASE
+    WHEN
+    $name not in (
+      "GenerationToBrowserMain", "GenerationToRendererCompositor",
+      "BrowserMainToRendererCompositor", "RendererCompositorQueueingDelay",
+      "RendererCompositorToMain", "RendererCompositorProcessing",
+      "RendererMainProcessing", "EndActivateToSubmitCompositorFrame",
+      "SubmitCompositorFrameToPresentationCompositorFrame",
+      "ArrivedInRendererCompositorToTermination",
+      "RendererCompositorStartedToTermination",
+      "RendererMainFinishedToTermination",
+      "RendererCompositorFinishedToTermination",
+      "RendererMainStartedToTermination",
+      "RendererCompositorFinishedToBeginImplFrame",
+      "RendererCompositorFinishedToCommit",
+      "RendererCompositorFinishedToEndCommit",
+      "RendererCompositorFinishedToActivation",
+      "RendererCompositorFinishedToEndActivate",
+      "RendererCompositorFinishedToSubmitCompositorFrame",
+      "RendererMainFinishedToBeginImplFrame",
+      "RendererMainFinishedToSendBeginMainFrame",
+      "RendererMainFinishedToCommit", "RendererMainFinishedToEndCommit",
+      "RendererMainFinishedToActivation", "RendererMainFinishedToEndActivate",
+      "RendererMainFinishedToSubmitCompositorFrame",
+      "BeginImplFrameToSendBeginMainFrame",
+      "RendererCompositorFinishedToSendBeginMainFrame",
+      "SendBeginMainFrameToCommit", "Commit",
+      "EndCommitToActivation", "Activation")
+      THEN $name
+    ELSE NULL
+  END;
 
 -- Creates a view where each row contains information about one EventLatency event. Columns are duration of breakdowns.
 -- In the result it will be something like this:
diff --git a/src/trace_processor/metrics/sql/chrome/jank_utilities.sql b/src/trace_processor/metrics/sql/chrome/jank_utilities.sql
index 0d94fad..e1c3a45 100644
--- a/src/trace_processor/metrics/sql/chrome/jank_utilities.sql
+++ b/src/trace_processor/metrics/sql/chrome/jank_utilities.sql
@@ -25,16 +25,19 @@
 -- ratio should increase with increments more than minimal value in numerator
 -- (1ns) divided by maximum value in denominator, giving 1e-9.
 
-SELECT CREATE_FUNCTION(
-  -- Function : function takes scroll ids of frames to verify it's from
-  -- the same scroll, and makes sure the frame ts occured within the scroll
-  -- timestamp of the neighbour and computes whether the frame was janky or not.
-  'IsJankyFrame(cur_gesture_id LONG,neighbour_gesture_id LONG,neighbour_ts LONG,'
-  || 'cur_gesture_begin_ts LONG,cur_gesture_end_ts LONG,cur_frame_exact FLOAT,'
-  || 'neighbour_frame_exact FLOAT)',
-  -- Returns true if the frame was janky, false otherwise
-  'BOOL',
-  'SELECT
+-- Function : function takes scroll ids of frames to verify it's from
+-- the same scroll, and makes sure the frame ts occured within the scroll
+-- timestamp of the neighbour and computes whether the frame was janky or not.
+CREATE PERFETTO FUNCTION IsJankyFrame(cur_gesture_id LONG,
+                                      neighbour_gesture_id LONG,
+                                      neighbour_ts LONG,
+                                      cur_gesture_begin_ts LONG,
+                                      cur_gesture_end_ts LONG,
+                                      cur_frame_exact FLOAT,
+                                      neighbour_frame_exact FLOAT)
+-- Returns true if the frame was janky, false otherwise
+RETURNS BOOL AS
+SELECT
     CASE WHEN
       $cur_gesture_id != $neighbour_gesture_id OR
       $neighbour_ts IS NULL OR
@@ -42,42 +45,64 @@
       $neighbour_ts > $cur_gesture_end_ts THEN
         FALSE ELSE
         $cur_frame_exact > $neighbour_frame_exact + 0.5 + 1e-9
-    END'
-);
+    END;
 
-SELECT CREATE_FUNCTION(
-  -- Function : function takes the cur_frame_exact, prev_frame_exact and
-  -- next_frame_exact and returns the value of the jank budget of the current
-  -- frame.
-  --
-  -- JankBudget is the minimum amount of frames/time we need to reduce the frame
-  -- duration by for it to be no longer considered janky.
-  'JankBudget(cur_frame_exact FLOAT, prev_frame_exact FLOAT, '
-  || ' next_frame_exact FLOAT)',
-  -- Returns the jank budget in percentage (i.e. 0.75) of vsync interval
-  -- percentage.
-  --
-  -- We determine the difference between the frame count of the current frame
-  -- and its consecutive frames by subtracting with the frame_exact values. We
-  -- null check for cases when the neighbor frame count can be null for the
-  -- first and last frames.
-  --
-  -- Since a frame is considered janky, if the difference in the frame count
-  -- with its adjacent frame is greater than 0.5 (half a vsync) which means we
-  -- need to reduce the frame count by a value less than 0.5 of maximum
-  -- difference in frame count for it to be no longer janky. We subtract 1e-9 as
-  -- we want to output minimum amount required.
-  'FLOAT',
-  'SELECT
-    COALESCE(
-      -- Could be null if next or previous is null.
-      MAX(
-        ($cur_frame_exact - $prev_frame_exact),
-        ($cur_frame_exact - $next_frame_exact)
-      ),
-      -- If one of them is null output the first non-null.
+-- Function : function takes the cur_frame_exact, prev_frame_exact and
+-- next_frame_exact and returns the value of the jank budget of the current
+-- frame.
+--
+-- JankBudget is the minimum amount of frames/time we need to reduce the frame
+-- duration by for it to be no longer considered janky.
+--
+-- Returns the jank budget in percentage (i.e. 0.75) of vsync interval
+-- percentage.
+CREATE PERFETTO FUNCTION JankBudget(
+  cur_frame_exact FLOAT,
+  prev_frame_exact FLOAT,
+  next_frame_exact FLOAT
+)
+RETURNS FLOAT AS
+-- We determine the difference between the frame count of the current frame
+-- and its consecutive frames by subtracting with the frame_exact values. We
+-- null check for cases when the neighbor frame count can be null for the
+-- first and last frames.
+--
+-- Since a frame is considered janky, if the difference in the frame count
+-- with its adjacent frame is greater than 0.5 (half a vsync) which means we
+-- need to reduce the frame count by a value less than 0.5 of maximum
+-- difference in frame count for it to be no longer janky. We subtract 1e-9 as
+-- we want to output minimum amount required.
+SELECT
+  COALESCE(
+    -- Could be null if next or previous is null.
+    MAX(
       ($cur_frame_exact - $prev_frame_exact),
       ($cur_frame_exact - $next_frame_exact)
-      -- Otherwise return null
-    ) - 0.5 - 1e-9'
-);
\ No newline at end of file
+    ),
+    -- If one of them is null output the first non-null.
+    ($cur_frame_exact - $prev_frame_exact),
+    ($cur_frame_exact - $next_frame_exact)
+    -- Otherwise return null
+  ) - 0.5 - 1e-9;
+
+-- Extract mojo information for the long-task-tracking scenario for specific
+-- names. For example, LongTaskTracker slices may have associated IPC
+-- metadata, or InterestingTask slices for input may have associated IPC to
+-- determine whether the task is fling/etc.
+SELECT CREATE_VIEW_FUNCTION(
+  'SELECT_LONG_TASK_SLICES(name STRING)',
+  'interface_name STRING, ipc_hash INT, message_type STRING, id INT',
+  'SELECT
+      EXTRACT_ARG(s.arg_set_id, "chrome_mojo_event_info.mojo_interface_tag") AS interface_name,
+      EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash") AS ipc_hash,
+      CASE
+        WHEN EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.is_reply") THEN "reply"
+        ELSE "message"
+      END AS message_type,
+      s.id
+    FROM slice s
+    WHERE
+      category GLOB "*scheduler.long_tasks*"
+      AND name = $name
+  '
+);
diff --git a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_get_bitmap.sql b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_get_bitmap.sql
index fd57296..be66e44 100644
--- a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_get_bitmap.sql
+++ b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_get_bitmap.sql
@@ -55,7 +55,7 @@
 --------------------------------------------------------------------------------
 
 -- Keeping only the GestureScrollUpdates join the maximum flows on the browser
--- thread. 
+-- thread.
 DROP VIEW IF EXISTS scroll_with_browser_flows;
 CREATE VIEW scroll_with_browser_flows AS
 SELECT
diff --git a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
index 1e2ecd2..b683959 100644
--- a/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
+++ b/src/trace_processor/metrics/sql/chrome/scroll_jank_cause_queuing_delay.sql
@@ -322,127 +322,122 @@
 GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
 ORDER BY descendant_cpu_percentage DESC;
 
-SELECT CREATE_FUNCTION(
-  -- Function prototype: takes a '-' separated list of slice names (formed by
-  -- the GROUP_CONCAT above) and returns the first slice if any or NULL
-  -- otherwise.
-  'GetFirstSliceNameOrNull(name STRING)',
-  -- Returns the first slice name or NULL
-  'STRING',
-  -- Preforms the actual string modification, takes the either the whole string
-  -- if there is no '-' or up to the first '-'. SUBSTR returns NULL if $name is
-  -- NULL.
-  'SELECT SUBSTR($name, 0,
-    CASE WHEN INSTR($name, "-") = 0 THEN
-      LENGTH($name)+1 ELSE
-      INSTR($name, "-")
-    END)'
-);
+-- Function prototype: takes a - separated list of slice names (formed by
+-- the GROUP_CONCAT above) and returns the first slice if any or NULL
+-- otherwise.
+CREATE PERFETTO FUNCTION GetFirstSliceNameOrNull(name STRING)
+-- Returns the first slice name or NULL
+RETURNS STRING AS
+-- Performs the actual string modification, takes the either the whole string
+-- if there is no - or up to the first '-'. SUBSTR returns NULL if $name is
+-- NULL.
+SELECT SUBSTR($name, 0,
+  CASE WHEN INSTR($name, "-") = 0 THEN
+    LENGTH($name)+1 ELSE
+    INSTR($name, "-")
+  END);
 
-SELECT CREATE_FUNCTION(
-  -- Function prototype: takes a '-' separated list of slice names (formed by
-  -- the GROUP_CONCAT above) and checks for certain important view names and
-  -- falls back on GetFirstSliceNameOrNull if it can't find one.
-  'GetJavaSliceSummaryOrNull(name STRING)',
-  -- Returns the summary of the provided list of java slice names.
-  'STRING',
-  -- Performs a bunch of GLOB matches in an order, now there could be multiple
-  -- matches (both Toolbar & TabList could be true) so the order matters in
-  -- tagging since we don't support multiple tagging of values. Ideally we would
-  -- determine which one was the longest duration, but this should be sufficient
-  -- for now.
-  'SELECT
-    CASE WHEN $name GLOB "*ToolbarControlContainer*" THEN
-      "ToolbarControlContainer"
-    WHEN $name GLOB "*ToolbarProgressBar*" THEN
-      "ToolbarProgressBar"
-    WHEN $name GLOB "*TabGroupUiToolbarView*" THEN
-      "TabGroupUiToolbarView"
-    WHEN $name GLOB "*TabGridThumbnailView*" THEN
-      "TabGridThumbnailView"
-    WHEN $name GLOB "*TabGridDialogView*" THEN
-      "TabGridDialogView"
-    WHEN $name GLOB "*BottomContainer*" THEN
-      "BottomContainer"
-    WHEN $name GLOB "*FeedSwipeRefreshLayout*" THEN
-      "FeedSwipeRefreshLayout"
-    WHEN $name GLOB "*AutocompleteEditText*" THEN
-      "AutocompleteEditText"
-    WHEN $name GLOB "*HomeButton*" THEN
-      "HomeButton"
-    WHEN $name GLOB "*ToggleTabStackButton*" THEN
-      "ToggleTabStackButton"
-    WHEN $name GLOB "*ListMenuButton*" THEN
-      "ListMenuButton"
-    WHEN $name GLOB "*ScrimView*" THEN
-      "ScrimView"
-    WHEN $name GLOB "*ChromeImageView*" THEN
-      "ChromeImageView"
-    WHEN $name GLOB "*AppCompatImageView*" THEN
-      "AppCompatImageView"
-    WHEN $name GLOB "*ChromeImageButton*" THEN
-      "ChromeImageButton"
-    WHEN $name GLOB "*AppCompatImageButton*" THEN
-      "AppCompatImageButton"
-    WHEN $name GLOB "*TabListRecyclerView*" THEN
-      "TabListRecyclerView"
-    ELSE
-      GetFirstSliceNameOrNull($name)
-    END'
-);
+-- Function prototype: takes a - separated list of slice names (formed by
+-- the GROUP_CONCAT above) and checks for certain important view names and
+-- falls back on GetFirstSliceNameOrNull if it can't find one.
+CREATE PERFETTO FUNCTION GetJavaSliceSummaryOrNull(name STRING)
+-- Returns the summary of the provided list of java slice names.
+RETURNS STRING AS
+-- Performs a bunch of GLOB matches in an order, now there could be multiple
+-- matches (both Toolbar & TabList could be true) so the order matters in
+-- tagging since we dont support multiple tagging of values. Ideally we would
+-- determine which one was the longest duration, but this should be sufficient
+-- for now.
+SELECT
+  CASE WHEN $name GLOB "*ToolbarControlContainer*" THEN
+    "ToolbarControlContainer"
+  WHEN $name GLOB "*ToolbarProgressBar*" THEN
+    "ToolbarProgressBar"
+  WHEN $name GLOB "*TabGroupUiToolbarView*" THEN
+    "TabGroupUiToolbarView"
+  WHEN $name GLOB "*TabGridThumbnailView*" THEN
+    "TabGridThumbnailView"
+  WHEN $name GLOB "*TabGridDialogView*" THEN
+    "TabGridDialogView"
+  WHEN $name GLOB "*BottomContainer*" THEN
+    "BottomContainer"
+  WHEN $name GLOB "*FeedSwipeRefreshLayout*" THEN
+    "FeedSwipeRefreshLayout"
+  WHEN $name GLOB "*AutocompleteEditText*" THEN
+    "AutocompleteEditText"
+  WHEN $name GLOB "*HomeButton*" THEN
+    "HomeButton"
+  WHEN $name GLOB "*ToggleTabStackButton*" THEN
+    "ToggleTabStackButton"
+  WHEN $name GLOB "*ListMenuButton*" THEN
+    "ListMenuButton"
+  WHEN $name GLOB "*ScrimView*" THEN
+    "ScrimView"
+  WHEN $name GLOB "*ChromeImageView*" THEN
+    "ChromeImageView"
+  WHEN $name GLOB "*AppCompatImageView*" THEN
+    "AppCompatImageView"
+  WHEN $name GLOB "*ChromeImageButton*" THEN
+    "ChromeImageButton"
+  WHEN $name GLOB "*AppCompatImageButton*" THEN
+    "AppCompatImageButton"
+  WHEN $name GLOB "*TabListRecyclerView*" THEN
+    "TabListRecyclerView"
+  ELSE
+    GetFirstSliceNameOrNull($name)
+  END;
 
-SELECT CREATE_FUNCTION(
-  -- Function prototype: takes slice name, category and descendant_name and
-  -- determines if this event should be classified as unknown or not.
-  'UnknownEventOrEmptyString(name STRING, cat STRING, has_descendant STRING)',
-  -- Returns either "-UnknownEvent" or "".
-  'STRING',
-  -- If our current event has a posted from we consider it already categorized
-  -- even if we don't have events underneath it. If its java often we won't have
-  -- sub events, and finally if its a single event we just use its name there
-  -- isn't anything under to use so just leave it at that.
-  'SELECT
-    CASE WHEN
-      $name = "ThreadControllerImpl::RunTask" OR
-      $cat = "Java" OR
-      $has_descendant IS NULL THEN
-        "" ELSE
-        "-UnknownEvent"
-      END'
-);
+-- Function prototype: takes slice name, category and descendant_name and
+-- determines if this event should be classified as unknown or not.
+--
+-- Returns either "-UnknownEvent" or "".
+CREATE PERFETTO FUNCTION UnknownEventOrEmptyString(name STRING,
+                                                   cat STRING,
+                                                   has_descendant STRING)
+RETURNS STRING AS
+-- If our current event has a posted from we consider it already categorized
+-- even if we dont have events underneath it. If its java often we wont have
+-- sub events, and finally if its a single event we just use its name there
+-- isn't anything under to use so just leave it at that.
+SELECT
+  CASE WHEN
+    $name = "ThreadControllerImpl::RunTask" OR
+    $cat = "Java" OR
+    $has_descendant IS NULL THEN
+      "" ELSE
+      "-UnknownEvent"
+    END;
 
-SELECT CREATE_FUNCTION(
-  -- Function prototype: Takes a slice name, function, and file, and determines
-  -- if we should use the slice name, or if its a RunTask event uses the
-  -- function & file name, however if the RunTask posted from is one of the
-  -- simple_watcher paths we collapse them for attributation.
-  'TopLevelName(name STRING, function STRING, file STRING)',
-  'STRING',
-  -- The difference for the mojom functions are:
-  --  1) PostDispatchNextMessageFromPipe:
-  --         We knew that there is a message in the pipe, didn't try to set up a
-  --         SimpleWatcher to monitor when a new one arrives.
-  --  2) ArmOrNotify:
-  --         We tried to set up SimpleWatcher, but the setup failed as the
-  --         message arrived as we were setting this up, so we posted a task
-  --         instead.
-  --  3) Notify:
-  --         SimpleWatcher was set up and after a period of monitoring detected
-  --         a new message.
-  -- For our jank use case this distinction isn't very useful so we group them
-  -- together.
-  'SELECT
-     CASE WHEN $name = "ThreadControllerImpl::RunTask" THEN
-       CASE WHEN $function IN
-           ("PostDispatchNextMessageFromPipe", "ArmOrNotify", "Notify") THEN
-         "posted-from-mojo-pipe"
-        ELSE
-         "posted-from-" || $function || "()-in-" || $file
-        END
-    ELSE
-      $name
-    END'
-);
+-- Function prototype: Takes a slice name, function, and file, and determines
+-- if we should use the slice name, or if its a RunTask event uses the
+-- function & file name, however if the RunTask posted from is one of the
+-- simple_watcher paths we collapse them for attributation.
+CREATE PERFETTO FUNCTION TopLevelName(name STRING, function STRING, file STRING)
+RETURNS STRING AS
+-- The difference for the mojom functions are:
+--  1) PostDispatchNextMessageFromPipe:
+--         We knew that there is a message in the pipe, didnt try to set up a
+--         SimpleWatcher to monitor when a new one arrives.
+--  2) ArmOrNotify:
+--         We tried to set up SimpleWatcher, but the setup failed as the
+--         message arrived as we were setting this up, so we posted a task
+--         instead.
+--  3) Notify:
+--         SimpleWatcher was set up and after a period of monitoring detected
+--         a new message.
+-- For our jank use case this distinction isnt very useful so we group them
+-- together.
+SELECT
+    CASE WHEN $name = "ThreadControllerImpl::RunTask" THEN
+      CASE WHEN $function IN
+          ("PostDispatchNextMessageFromPipe", "ArmOrNotify", "Notify") THEN
+        "posted-from-mojo-pipe"
+      ELSE
+        "posted-from-" || $function || "()-in-" || $file
+      END
+  ELSE
+    $name
+  END;
 
 -- Create a common name for each "cause" based on the slice stack we found.
 DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_temp;
diff --git a/src/trace_processor/metrics/sql/chrome/scroll_jank_v3.sql b/src/trace_processor/metrics/sql/chrome/scroll_jank_v3.sql
index a126354..72211a3 100644
--- a/src/trace_processor/metrics/sql/chrome/scroll_jank_v3.sql
+++ b/src/trace_processor/metrics/sql/chrome/scroll_jank_v3.sql
@@ -29,17 +29,40 @@
 -- respective scroll ids and start/end timestamps.
 DROP VIEW IF EXISTS chrome_presented_gesture_scrolls;
 CREATE VIEW chrome_presented_gesture_scrolls AS
+WITH
+  chrome_gesture_scrolls AS (
+    SELECT
+      ts AS start_ts,
+      ts + dur AS end_ts,
+      id,
+      -- TODO(b/250089570) Add trace_id to EventLatency and update this script to use it.
+      EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') AS scroll_update_id,
+      EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id,
+      EXTRACT_ARG(arg_set_id, 'chrome_latency_info.is_coalesced') AS is_coalesced
+    FROM slice
+    WHERE name = "InputLatency::GestureScrollUpdate"
+          AND dur != -1),
+  updates_with_coalesce_info AS (
+    SELECT
+      chrome_updates.*,
+      (
+        SELECT
+          MAX(id)
+        FROM chrome_gesture_scrolls updates
+        WHERE updates.is_coalesced = false
+          AND updates.start_ts <= chrome_updates.start_ts) AS coalesced_inside_id
+        FROM
+          chrome_gesture_scrolls chrome_updates)
 SELECT
-  ts AS start_ts,
-  ts + dur AS end_ts,
-  id,
-  -- TODO(b/250089570) Add trace_id to EventLatency and update this script to use it.
-  EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') AS scroll_update_id,
-  EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id
-FROM slice
-WHERE name = "InputLatency::GestureScrollUpdate"
-      AND EXTRACT_ARG(arg_set_id, 'chrome_latency_info.is_coalesced') = FALSE
-      AND dur != -1;
+  MIN(id) AS id,
+  MIN(start_ts) AS start_ts,
+  MAX(end_ts) AS end_ts,
+  MAX(start_ts) AS last_coalesced_input_ts,
+  scroll_update_id,
+  MIN(scroll_id) AS scroll_id
+FROM updates_with_coalesce_info
+GROUP BY coalesced_inside_id;
+
 
 
 -- Associate every trace_id with it's perceived delta_y on the screen after
@@ -82,6 +105,7 @@
 SELECT
   frames.id,
   frames.start_ts,
+  frames.last_coalesced_input_ts,
   frames.scroll_id,
   frames.scroll_update_id,
   events.event_latency_id,
@@ -99,6 +123,7 @@
   frames.start_ts,
   frames.scroll_id,
   frames.scroll_update_id,
+  frames.last_coalesced_input_ts,
   deltas.delta_y,
   frames.event_latency_id,
   frames.dur,
@@ -125,7 +150,7 @@
 CREATE VIEW chrome_merged_frame_view_with_jank AS
 SELECT
   id,
-  MAX(start_ts) AS max_start_ts,
+  MAX(last_coalesced_input_ts) AS max_start_ts,
   MIN(start_ts) AS min_start_ts,
   scroll_id,
   scroll_update_id,
diff --git a/src/trace_processor/metrics/sql/chrome/vsync_intervals.sql b/src/trace_processor/metrics/sql/chrome/vsync_intervals.sql
index 3434056..c77e387 100644
--- a/src/trace_processor/metrics/sql/chrome/vsync_intervals.sql
+++ b/src/trace_processor/metrics/sql/chrome/vsync_intervals.sql
@@ -15,28 +15,29 @@
 WHERE name = "VSync"
 ORDER BY track_id, ts;
 
-SELECT CREATE_FUNCTION(
-  -- Function: compute the average Vysnc interval of the
-  -- gesture (hopefully this would be either 60 FPS for the whole gesture or 90
-  -- FPS but that isn't always the case) on the given time segment.
-  -- If the trace doesn't contain the VSync TraceEvent we just fall back on
-  -- assuming its 60 FPS (this is the 1.6e+7 in the COALESCE which
-  -- corresponds to 16 ms or 60 FPS).
-  --
-  -- begin_ts: segment start time
-  -- end_ts: segment end time
-  'CalculateAvgVsyncInterval(begin_ts LONG, end_ts LONG)',
-  -- Returns: the average Vysnc interval on this time segment
-  -- or 1.6e+7, if trace doesn't contain the VSync TraceEvent.
-  'FLOAT',
-  'SELECT
-    COALESCE((
-      SELECT
-        CAST(AVG(time_to_next_vsync) AS FLOAT)
-      FROM vsync_intervals in_query
-      WHERE
-        time_to_next_vsync IS NOT NULL AND
-        in_query.ts > $begin_ts AND
-        in_query.ts < $end_ts
-    ), 1e+9 / 60)'
-);
+-- Function: compute the average Vysnc interval of the
+-- gesture (hopefully this would be either 60 FPS for the whole gesture or 90
+-- FPS but that isnt always the case) on the given time segment.
+-- If the trace doesnt contain the VSync TraceEvent we just fall back on
+-- assuming its 60 FPS (this is the 1.6e+7 in the COALESCE which
+-- corresponds to 16 ms or 60 FPS).
+--
+-- begin_ts: segment start time
+-- end_ts: segment end time
+CREATE PERFETTO FUNCTION CalculateAvgVsyncInterval(
+  begin_ts LONG,
+  end_ts LONG
+)
+-- Returns: the average Vysnc interval on this time segment
+-- or 1.6e+7, if trace doesnt contain the VSync TraceEvent.
+RETURNS FLOAT AS
+SELECT
+  COALESCE((
+    SELECT
+      CAST(AVG(time_to_next_vsync) AS FLOAT)
+    FROM vsync_intervals in_query
+    WHERE
+      time_to_next_vsync IS NOT NULL AND
+      in_query.ts > $begin_ts AND
+      in_query.ts < $end_ts
+  ), 1e+9 / 60);
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
index 0f51ab1..295a385 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
@@ -166,6 +166,14 @@
     if (tok.token_type == SqliteTokenType::TK_RP) {
       break;
     }
+    if (tok.token_type == SqliteTokenType::TK_GENERIC_KEYWORD) {
+      base::StackString<1024> err(
+          "Malformed function prototype: %.*s is a SQL keyword so cannot "
+          "appear in a function prototype",
+          static_cast<int>(tok.str.size()), tok.str.data());
+      return ErrorAtToken(tok, err.c_str());
+    }
+
     // TODO(lalitm): add a link to create function documentation.
     return ErrorAtToken(
         tok, "Malformed function prototype: ')', ',', name or type expected");
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql b/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql
index 30cea54..46ba36d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql
@@ -20,104 +20,99 @@
 -- @arg track STRING  The counter track name (e.g. 'battery_stats.audio').
 -- @arg value FLOAT   The counter value.
 -- @ret STRING        The human-readable name for the counter value.
-SELECT CREATE_FUNCTION(
-  'ANDROID_BATTERY_STATS_COUNTER_TO_STRING(track STRING, value FLOAT)',
-  'STRING',
-  '
-  SELECT
-    CASE
-      WHEN ($track = "battery_stats.wifi_scan" OR
-            $track = "battery_stats.wifi_radio" OR
-            $track = "battery_stats.mobile_radio" OR
-            $track = "battery_stats.audio" OR
-            $track = "battery_stats.video" OR
-            $track = "battery_stats.camera" OR
-            $track = "battery_stats.power_save" OR
-            $track = "battery_stats.phone_in_call")
-        THEN
-          CASE $value
-            WHEN 0 THEN "inactive"
-            WHEN 1 THEN "active"
-            ELSE "unknown"
-          END
-      WHEN $track = "battery_stats.wifi"
-        THEN
-          CASE $value
-            WHEN 0 THEN "off"
-            WHEN 1 THEN "on"
-            ELSE "unknown"
-          END
-      WHEN $track = "battery_stats.phone_state"
-        THEN
-          CASE $value
-            WHEN 0 THEN "in"
-            WHEN 1 THEN "out"
-            WHEN 2 THEN "emergency"
-            WHEN 3 THEN "off"
-            ELSE "unknown"
-          END
-      WHEN ($track = "battery_stats.phone_signal_strength" OR
-            $track = "battery_stats.wifi_signal_strength")
-        THEN
-          CASE $value
-            WHEN 0 THEN "none"
-            WHEN 1 THEN "poor"
-            WHEN 2 THEN "moderate"
-            WHEN 3 THEN "good"
-            WHEN 4 THEN "great"
-            ELSE "unknown"
-          END
-      WHEN $track = "battery_stats.wifi_suppl"
-        THEN
-          CASE $value
-            WHEN 0 THEN "invalid"
-            WHEN 1 THEN "disconnected"
-            WHEN 2 THEN "disabled"
-            WHEN 3 THEN "inactive"
-            WHEN 4 THEN "scanning"
-            WHEN 5 THEN "authenticating"
-            WHEN 6 THEN "associating"
-            WHEN 7 THEN "associated"
-            WHEN 8 THEN "4-way-handshake"
-            WHEN 9 THEN "group-handshake"
-            WHEN 10 THEN "completed"
-            WHEN 11 THEN "dormant"
-            WHEN 12 THEN "uninitialized"
-            ELSE "unknown"
-          END
-      WHEN $track = "battery_stats.data_conn"
-        THEN
-          CASE $value
-            WHEN 0 THEN "Out of service"
-            WHEN 1 THEN "2.5G (GPRS)"
-            WHEN 2 THEN "2.7G (EDGE)"
-            WHEN 3 THEN "3G (UMTS)"
-            WHEN 4 THEN "3G (CDMA)"
-            WHEN 5 THEN "3G (EVDO Rel 0)"
-            WHEN 6 THEN "3G (EVDO Rev A)"
-            WHEN 7 THEN "3G (LXRTT)"
-            WHEN 8 THEN "3.5G (HSDPA)"
-            WHEN 9 THEN "3.5G (HSUPA)"
-            WHEN 10 THEN "3.5G (HSPA)"
-            WHEN 11 THEN "2G (IDEN)"
-            WHEN 12 THEN "3G (EVDO Rev B)"
-            WHEN 13 THEN "4G (LTE)"
-            WHEN 14 THEN "3.5G (eHRPD)"
-            WHEN 15 THEN "3.7G (HSPA+)"
-            WHEN 16 THEN "2G (GSM)"
-            WHEN 17 THEN "3G (TD SCDMA)"
-            WHEN 18 THEN "Wifi calling (IWLAN)"
-            WHEN 19 THEN "4.5G (LTE CA)"
-            WHEN 20 THEN "5G (NR)"
-            WHEN 21 THEN "Emergency calls only"
-            WHEN 22 THEN "Other"
-            ELSE "unknown"
-          END
-      ELSE CAST($value AS text)
-    END
-  '
-);
-
+CREATE PERFETTO FUNCTION ANDROID_BATTERY_STATS_COUNTER_TO_STRING(track STRING, value FLOAT)
+RETURNS STRING AS
+SELECT
+  CASE
+    WHEN ($track = "battery_stats.wifi_scan" OR
+          $track = "battery_stats.wifi_radio" OR
+          $track = "battery_stats.mobile_radio" OR
+          $track = "battery_stats.audio" OR
+          $track = "battery_stats.video" OR
+          $track = "battery_stats.camera" OR
+          $track = "battery_stats.power_save" OR
+          $track = "battery_stats.phone_in_call")
+      THEN
+        CASE $value
+          WHEN 0 THEN "inactive"
+          WHEN 1 THEN "active"
+          ELSE "unknown"
+        END
+    WHEN $track = "battery_stats.wifi"
+      THEN
+        CASE $value
+          WHEN 0 THEN "off"
+          WHEN 1 THEN "on"
+          ELSE "unknown"
+        END
+    WHEN $track = "battery_stats.phone_state"
+      THEN
+        CASE $value
+          WHEN 0 THEN "in"
+          WHEN 1 THEN "out"
+          WHEN 2 THEN "emergency"
+          WHEN 3 THEN "off"
+          ELSE "unknown"
+        END
+    WHEN ($track = "battery_stats.phone_signal_strength" OR
+          $track = "battery_stats.wifi_signal_strength")
+      THEN
+        CASE $value
+          WHEN 0 THEN "0/4"
+          WHEN 1 THEN "1/4"
+          WHEN 2 THEN "2/4"
+          WHEN 3 THEN "3/4"
+          WHEN 4 THEN "4/4"
+          ELSE "unknown"
+        END
+    WHEN $track = "battery_stats.wifi_suppl"
+      THEN
+        CASE $value
+          WHEN 0 THEN "invalid"
+          WHEN 1 THEN "disconnected"
+          WHEN 2 THEN "disabled"
+          WHEN 3 THEN "inactive"
+          WHEN 4 THEN "scanning"
+          WHEN 5 THEN "authenticating"
+          WHEN 6 THEN "associating"
+          WHEN 7 THEN "associated"
+          WHEN 8 THEN "4-way-handshake"
+          WHEN 9 THEN "group-handshake"
+          WHEN 10 THEN "completed"
+          WHEN 11 THEN "dormant"
+          WHEN 12 THEN "uninitialized"
+          ELSE "unknown"
+        END
+    WHEN $track = "battery_stats.data_conn"
+      THEN
+        CASE $value
+          WHEN 0 THEN "Out of service"
+          WHEN 1 THEN "2.5G (GPRS)"
+          WHEN 2 THEN "2.7G (EDGE)"
+          WHEN 3 THEN "3G (UMTS)"
+          WHEN 4 THEN "3G (CDMA)"
+          WHEN 5 THEN "3G (EVDO Rel 0)"
+          WHEN 6 THEN "3G (EVDO Rev A)"
+          WHEN 7 THEN "3G (LXRTT)"
+          WHEN 8 THEN "3.5G (HSDPA)"
+          WHEN 9 THEN "3.5G (HSUPA)"
+          WHEN 10 THEN "3.5G (HSPA)"
+          WHEN 11 THEN "2G (IDEN)"
+          WHEN 12 THEN "3G (EVDO Rev B)"
+          WHEN 13 THEN "4G (LTE)"
+          WHEN 14 THEN "3.5G (eHRPD)"
+          WHEN 15 THEN "3.7G (HSPA+)"
+          WHEN 16 THEN "2G (GSM)"
+          WHEN 17 THEN "3G (TD SCDMA)"
+          WHEN 18 THEN "Wifi calling (IWLAN)"
+          WHEN 19 THEN "4.5G (LTE CA)"
+          WHEN 20 THEN "5G (NR)"
+          WHEN 21 THEN "Emergency calls only"
+          WHEN 22 THEN "Other"
+          ELSE "unknown"
+        END
+    ELSE CAST($value AS text)
+  END;
 
 -- View of human readable battery stats counter-based states. These are recorded
 -- by BatteryStats as a bitmap where each 'category' has a unique value at any
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
index 9fae0c7..284d391 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
@@ -18,42 +18,33 @@
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_THREAD(slice_name STRING)',
-    'STRING',
-    '
-    SELECT STR_SPLIT(STR_SPLIT($slice_name, "with owner ", 1), " (", 0)
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_THREAD(
+  slice_name STRING
+)
+RETURNS STRING AS
+SELECT STR_SPLIT(STR_SPLIT($slice_name, "with owner ", 1), " (", 0);
 
 -- Extracts the blocking thread tid from a slice name
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret INT                 Blocking thread tid
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_TID(slice_name STRING)',
-    'INT',
-    '
-    SELECT CAST(STR_SPLIT(STR_SPLIT($slice_name, " (", 1), ")", 0) AS INT)
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_TID(
+  slice_name STRING
+)
+RETURNS INT AS
+SELECT CAST(STR_SPLIT(STR_SPLIT($slice_name, " (", 1), ")", 0) AS INT);
 
 -- Extracts the blocking method from a slice name
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_METHOD(slice_name STRING)',
-    'STRING',
-    '
-    SELECT STR_SPLIT(STR_SPLIT($slice_name, ") at ", 1), "(", 0)
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_METHOD(
+  slice_name STRING
+)
+RETURNS STRING AS
+SELECT STR_SPLIT(STR_SPLIT($slice_name, ") at ", 1), "(", 0)
     || "("
-    || STR_SPLIT(STR_SPLIT($slice_name, ") at ", 1), "(", 1)
-  '
-);
+    || STR_SPLIT(STR_SPLIT($slice_name, ") at ", 1), "(", 1);
 
 -- Extracts a shortened form of the blocking method name from a slice name.
 -- The shortened form discards the parameter and return
@@ -61,30 +52,24 @@
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_SHORT_BLOCKING_METHOD(
+  slice_name STRING
+)
+RETURNS STRING AS
 SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_SHORT_BLOCKING_METHOD(slice_name STRING)',
-    'STRING',
-    '
-    SELECT
-    STR_SPLIT(STR_SPLIT(ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_METHOD($slice_name), " ", 1), "(", 0)
-  '
-);
+    STR_SPLIT(STR_SPLIT(ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_METHOD($slice_name), " ", 1), "(", 0);
 
 -- Extracts the monitor contention blocked method from a slice name
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKED_METHOD(slice_name STRING)',
-    'STRING',
-    '
-    SELECT STR_SPLIT(STR_SPLIT($slice_name, "blocking from ", 1), "(", 0)
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKED_METHOD(
+  slice_name STRING
+)
+RETURNS STRING AS
+SELECT STR_SPLIT(STR_SPLIT($slice_name, "blocking from ", 1), "(", 0)
     || "("
-    || STR_SPLIT(STR_SPLIT($slice_name, "blocking from ", 1), "(", 1)
-  '
-);
+    || STR_SPLIT(STR_SPLIT($slice_name, "blocking from ", 1), "(", 1);
 
 -- Extracts a shortened form of the monitor contention blocked method name
 -- from a slice name. The shortened form discards the parameter and return
@@ -92,57 +77,44 @@
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_SHORT_BLOCKED_METHOD(
+  slice_name STRING
+)
+RETURNS STRING AS
 SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_SHORT_BLOCKED_METHOD(slice_name STRING)',
-    'STRING',
-    '
-    SELECT
-    STR_SPLIT(STR_SPLIT(ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKED_METHOD($slice_name), " ", 1), "(", 0)
-  '
-);
+    STR_SPLIT(STR_SPLIT(ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKED_METHOD($slice_name), " ", 1), "(", 0);
 
 -- Extracts the number of waiters on the monitor from a slice name
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret INT                 Count of waiters on the lock
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_WAITER_COUNT(slice_name STRING)',
-    'INT',
-    '
-    SELECT CAST(STR_SPLIT(STR_SPLIT($slice_name, "waiters=", 1), " ", 0) AS INT)
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_WAITER_COUNT(
+  slice_name STRING
+)
+RETURNS INT AS
+SELECT CAST(STR_SPLIT(STR_SPLIT($slice_name, "waiters=", 1), " ", 0) AS INT);
 
 -- Extracts the monitor contention blocking source location from a slice name
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_SRC(slice_name STRING)',
-    'STRING',
-    '
-    SELECT STR_SPLIT(STR_SPLIT($slice_name, ")(", 1), ")", 0)
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKING_SRC(
+  slice_name STRING
+)
+RETURNS STRING AS
+SELECT STR_SPLIT(STR_SPLIT($slice_name, ")(", 1), ")", 0);
 
 -- Extracts the monitor contention blocked source location from a slice name
 --
 -- @arg slice_name STRING   Name of slice
 -- @ret STRING              Blocking thread
-SELECT
-  CREATE_FUNCTION(
-    'ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKED_SRC(slice_name STRING)',
-    'STRING',
-    '
-    SELECT STR_SPLIT(STR_SPLIT($slice_name, ")(", 2), ")", 0)
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_EXTRACT_ANDROID_MONITOR_CONTENTION_BLOCKED_SRC(
+  slice_name STRING
+)
+RETURNS STRING AS
+SELECT STR_SPLIT(STR_SPLIT($slice_name, ")(", 2), ")", 0);
 
-CREATE TABLE internal_broken_android_monitor_contention
-AS
+CREATE TABLE internal_broken_android_monitor_contention AS
 SELECT ancestor.parent_id AS id FROM slice
     JOIN slice ancestor ON ancestor.id = slice.parent_id
     WHERE ancestor.name GLOB 'Lock contention on a monitor lock*'
@@ -247,6 +219,9 @@
 LEFT JOIN android_monitor_contention parent ON child.blocked_utid = parent.blocking_utid
     AND parent.ts BETWEEN child.ts AND child.ts + child.dur;
 
+CREATE INDEX internal_android_monitor_contention_chain_idx
+  ON android_monitor_contention_chain (blocking_method, blocking_utid, ts);
+
 -- First blocked node on a lock, i.e nodes with |waiter_count| = 0. The |dur| here is adjusted
 -- to only account for the time between the first thread waiting and the first thread to acquire
 -- the lock. That way, the thread state span joins below only compute the thread states where
@@ -256,7 +231,7 @@
   AS
 SELECT start.id, start.blocking_utid, start.ts, MIN(end.ts + end.dur) - start.ts AS dur
 FROM android_monitor_contention_chain start
-JOIN android_monitor_contention_chain END
+JOIN android_monitor_contention_chain end
   ON
     start.blocking_utid = end.blocking_utid
     AND start.blocking_method = end.blocking_method
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/slices.sql b/src/trace_processor/perfetto_sql/stdlib/android/slices.sql
index 7cd80e7..f0a23bf 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/slices.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/slices.sql
@@ -23,23 +23,20 @@
 --
 -- @arg name STRING   Raw name of the slice
 -- @ret STRING        Simplified name.
-SELECT CREATE_FUNCTION(
-    'ANDROID_STANDARDIZE_SLICE_NAME(name STRING)',
-    'STRING',
-    '
-    SELECT
-        CASE
-        WHEN $name GLOB "Lock contention on*" THEN "Lock contention on <...>"
-        WHEN $name GLOB "monitor contention with*" THEN "monitor contention with <...>"
-        WHEN $name GLOB "SuspendThreadByThreadId*" THEN "SuspendThreadByThreadId <...>"
-        WHEN $name GLOB "LoadApkAssetsFd*" THEN "LoadApkAssetsFd <...>"
-        WHEN $name GLOB "relayoutWindow*" THEN "relayoutWindow <...>"
-        WHEN $name GLOB "*CancellableContinuationImpl*" THEN "CoroutineContinuation"
-        WHEN $name GLOB "Choreographer#doFrame*" THEN "Choreographer#doFrame"
-        WHEN $name GLOB "DrawFrames*" THEN "DrawFrames"
-        WHEN $name GLOB "/data/app*.apk" THEN "APK load"
-        WHEN $name GLOB "OpenDexFilesFromOat*" THEN "OpenDexFilesFromOat"
-        WHEN $name GLOB "Open oat file*" THEN "Open oat file"
-        ELSE $name
-        END
-');
\ No newline at end of file
+CREATE PERFETTO FUNCTION ANDROID_STANDARDIZE_SLICE_NAME(name STRING)
+RETURNS STRING AS
+SELECT
+  CASE
+    WHEN $name GLOB "Lock contention on*" THEN "Lock contention on <...>"
+    WHEN $name GLOB "monitor contention with*" THEN "monitor contention with <...>"
+    WHEN $name GLOB "SuspendThreadByThreadId*" THEN "SuspendThreadByThreadId <...>"
+    WHEN $name GLOB "LoadApkAssetsFd*" THEN "LoadApkAssetsFd <...>"
+    WHEN $name GLOB "relayoutWindow*" THEN "relayoutWindow <...>"
+    WHEN $name GLOB "*CancellableContinuationImpl*" THEN "CoroutineContinuation"
+    WHEN $name GLOB "Choreographer#doFrame*" THEN "Choreographer#doFrame"
+    WHEN $name GLOB "DrawFrames*" THEN "DrawFrames"
+    WHEN $name GLOB "/data/app*.apk" THEN "APK load"
+    WHEN $name GLOB "OpenDexFilesFromOat*" THEN "OpenDexFilesFromOat"
+    WHEN $name GLOB "Open oat file*" THEN "Open oat file"
+    ELSE $name
+  END;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
index 644d122..73ed192 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
@@ -75,20 +75,19 @@
 FROM slice
 WHERE name IN ('bindApplication', 'activityStart', 'activityResume');
 
-SELECT CREATE_FUNCTION(
-  'INTERNAL_STARTUP_INDICATOR_SLICE_COUNT(start_ts LONG, end_ts LONG, utid INT, name STRING)',
-  'INT',
-  '
-    SELECT COUNT(1)
-    FROM thread_track t
-    JOIN internal_startup_indicator_slices s ON s.track_id = t.id
-    WHERE
-      t.utid = $utid AND
-      s.ts >= $start_ts AND
-      s.ts < $end_ts AND
-      s.name = $name
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_STARTUP_INDICATOR_SLICE_COUNT(start_ts LONG,
+                                                                end_ts LONG,
+                                                                utid INT,
+                                                                name STRING)
+RETURNS INT AS
+SELECT COUNT(1)
+FROM thread_track t
+JOIN internal_startup_indicator_slices s ON s.track_id = t.id
+WHERE
+  t.utid = $utid AND
+  s.ts >= $start_ts AND
+  s.ts < $end_ts AND
+  s.name = $name;
 
 -- Maps a startup to the set of processes that handled the activity start.
 --
@@ -250,15 +249,11 @@
 -- @arg startup_id LONG   Startup id.
 -- @arg slice_name STRING Slice name.
 -- @ret INT               Sum of duration.
-SELECT CREATE_FUNCTION(
-  'ANDROID_SUM_DUR_FOR_STARTUP_AND_SLICE(startup_id LONG, slice_name STRING)',
-  'INT',
-  '
-    SELECT SUM(slice_dur)
-    FROM android_thread_slices_for_all_startups
-    WHERE startup_id = $startup_id AND slice_name GLOB $slice_name
-  '
-);
+CREATE PERFETTO FUNCTION ANDROID_SUM_DUR_FOR_STARTUP_AND_SLICE(startup_id LONG, slice_name STRING)
+RETURNS INT AS
+SELECT SUM(slice_dur)
+FROM android_thread_slices_for_all_startups
+WHERE startup_id = $startup_id AND slice_name GLOB $slice_name;
 
 -- Returns duration of startup for slice name on main thread.
 --
@@ -267,12 +262,8 @@
 -- @arg startup_id LONG   Startup id.
 -- @arg slice_name STRING Slice name.
 -- @ret INT               Sum of duration.
-SELECT CREATE_FUNCTION(
-  'ANDROID_SUM_DUR_ON_MAIN_THREAD_FOR_STARTUP_AND_SLICE(startup_id LONG, slice_name STRING)',
-  'INT',
-  '
-    SELECT SUM(slice_dur)
-    FROM android_thread_slices_for_all_startups
-    WHERE startup_id = $startup_id AND slice_name GLOB $slice_name AND is_main_thread
-  '
-);
\ No newline at end of file
+CREATE PERFETTO FUNCTION ANDROID_SUM_DUR_ON_MAIN_THREAD_FOR_STARTUP_AND_SLICE(startup_id LONG, slice_name STRING)
+RETURNS INT AS
+SELECT SUM(slice_dur)
+FROM android_thread_slices_for_all_startups
+WHERE startup_id = $startup_id AND slice_name GLOB $slice_name AND is_main_thread;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql
index a4c6376..f0090fd 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql
@@ -15,12 +15,9 @@
 -- Returns hardware class of the device, often use to find device brand
 -- and model.
 -- @ret STRING Hardware class name.
-SELECT CREATE_FUNCTION(
-  'CHROME_HARDWARE_CLASS()',
-  'STRING',
-  'SELECT
-    str_value
-   FROM metadata
-  WHERE name = "cr-hardware-class"
-  '
-);
\ No newline at end of file
+CREATE PERFETTO FUNCTION CHROME_HARDWARE_CLASS()
+RETURNS STRING AS
+SELECT
+  str_value
+  FROM metadata
+WHERE name = "cr-hardware-class";
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
index 159d6be..272d6d7 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
@@ -20,80 +20,59 @@
 -- argument of descendant ScopedSetIpcHash slice.
 -- This is relevant only for the older Chrome traces, where mojo IPC
 -- hash was reported in a separate ScopedSetIpcHash slice.
-SELECT CREATE_FUNCTION(
-  'INTERNAL_EXTRACT_MOJO_IPC_HASH(slice_id INT)',
-  'INT',
-  '
-    SELECT EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash")
-    FROM descendant_slice($slice_id)
-    WHERE name="ScopedSetIpcHash"
-    ORDER BY id
-    LIMIT 1
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_EXTRACT_MOJO_IPC_HASH(slice_id INT)
+RETURNS INT AS
+SELECT EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash")
+FROM descendant_slice($slice_id)
+WHERE name="ScopedSetIpcHash"
+ORDER BY id
+LIMIT 1;
 
 -- Returns the frame type (main frame vs subframe) for key navigation tasks
 -- which capture the associated RenderFrameHost in an argument.
-SELECT CREATE_FUNCTION(
-  'INTERNAL_EXTRACT_FRAME_TYPE(slice_id INT)',
-  'INT',
-  '
-    SELECT EXTRACT_ARG(arg_set_id, "render_frame_host.frame_type")
-    FROM descendant_slice($slice_id)
-    WHERE name IN ("RenderFrameHostImpl::BeginNavigation",
-        "RenderFrameHostImpl::DidCommitProvisionalLoad",
-        "RenderFrameHostImpl::DidCommitSameDocumentNavigation",
-        "RenderFrameHostImpl::DidStopLoading")
-    LIMIT 1
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_EXTRACT_FRAME_TYPE(slice_id INT)
+RETURNS INT AS
+SELECT EXTRACT_ARG(arg_set_id, "render_frame_host.frame_type")
+FROM descendant_slice($slice_id)
+WHERE name IN (
+  "RenderFrameHostImpl::BeginNavigation",
+  "RenderFrameHostImpl::DidCommitProvisionalLoad",
+  "RenderFrameHostImpl::DidCommitSameDocumentNavigation",
+  "RenderFrameHostImpl::DidStopLoading"
+)
+LIMIT 1;
 
 -- Human-readable aliases for a few key navigation tasks.
-SELECT CREATE_FUNCTION(
-  'INTERNAL_HUMAN_READABLE_NAVIGATION_TASK_NAME(task_name STRING)',
-  'STRING',
-  'SELECT
-    CASE
-      WHEN $task_name = "content.mojom.FrameHost message (hash=2168461044)"
-        THEN "FrameHost::BeginNavigation"
-      WHEN $task_name = "content.mojom.FrameHost message (hash=3561497419)"
-        THEN "FrameHost::DidCommitProvisionalLoad"
-      WHEN $task_name = "content.mojom.FrameHost message (hash=1421450774)"
-        THEN "FrameHost::DidCommitSameDocumentNavigation"
-      WHEN $task_name = "content.mojom.FrameHost message (hash=368650583)"
-        THEN "FrameHost::DidStopLoading"
-    END
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_HUMAN_READABLE_NAVIGATION_TASK_NAME(task_name STRING)
+RETURNS STRING AS
+SELECT
+  CASE
+    WHEN $task_name = "content.mojom.FrameHost message (hash=2168461044)"
+      THEN "FrameHost::BeginNavigation"
+    WHEN $task_name = "content.mojom.FrameHost message (hash=3561497419)"
+      THEN "FrameHost::DidCommitProvisionalLoad"
+    WHEN $task_name = "content.mojom.FrameHost message (hash=1421450774)"
+      THEN "FrameHost::DidCommitSameDocumentNavigation"
+    WHEN $task_name = "content.mojom.FrameHost message (hash=368650583)"
+      THEN "FrameHost::DidStopLoading"
+  END;
 
 -- Takes a task name and formats it correctly for scheduler tasks.
-SELECT CREATE_FUNCTION(
-  'INTERNAL_FORMAT_SCHEDULER_TASK_NAME(task_name STRING)',
-  'STRING',
-  'SELECT
-    printf("RunTask(posted_from=%s)", $task_name)
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_FORMAT_SCHEDULER_TASK_NAME(task_name STRING)
+RETURNS STRING AS
+SELECT printf("RunTask(posted_from=%s)", $task_name);
 
 -- Takes the category and determines whether it is "Java" only, as opposed to
 -- "toplevel,Java".
-SELECT CREATE_FUNCTION(
-  'INTERNAL_JAVA_NOT_TOP_LEVEL_CATEGORY(category STRING)',
-  'BOOL',
-  'SELECT
-    $category GLOB "*Java*" AND $category not GLOB "*toplevel*"
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_JAVA_NOT_TOP_LEVEL_CATEGORY(category STRING)
+RETURNS BOOL AS
+SELECT $category GLOB "*Java*" AND $category not GLOB "*toplevel*";
 
 -- Takes the category and determines whether is any valid
 -- toplevel category or combination of categories.
-SELECT CREATE_FUNCTION(
-  'INTERNAL_ANY_TOP_LEVEL_CATEGORY(category STRING)',
-  'BOOL',
-  'SELECT
-    $category IN ("toplevel", "toplevel,viz", "toplevel,Java")
-  '
-);
+CREATE PERFETTO FUNCTION INTERNAL_ANY_TOP_LEVEL_CATEGORY(category STRING)
+RETURNS BOOL AS
+SELECT $category IN ("toplevel", "toplevel,viz", "toplevel,Java");
 
 -- TODO(altimin): the situations with kinds in this file is a bit of a mess.
 -- The idea is that it should work as `type` in the `slice` table, pointing to
@@ -103,16 +82,13 @@
 -- `create perfetto table`.
 
 -- Get task type for a given task kind.
-SELECT CREATE_FUNCTION(
-  'INTERNAL_GET_JAVA_VIEWS_TASK_TYPE(kind STRING)',
-  'STRING',
-    'SELECT
-      (CASE $kind
-        WHEN "Choreographer" THEN "choreographer"
-        WHEN "SingleThreadProxy::BeginMainFrame" THEN "ui_thread_begin_main_frame"
-      END)
-    '
-);
+CREATE PERFETTO FUNCTION INTERNAL_GET_JAVA_VIEWS_TASK_TYPE(kind STRING)
+RETURNS STRING AS
+SELECT
+  CASE $kind
+    WHEN "Choreographer" THEN "choreographer"
+    WHEN "SingleThreadProxy::BeginMainFrame" THEN "ui_thread_begin_main_frame"
+  END;
 
 -- All slices corresponding to receiving mojo messages.
 -- On the newer Chrome versions, it's just "Receive mojo message" and
@@ -309,21 +285,18 @@
   dur,
   name
 FROM slice
-WHERE
-  name GLOB "Looper.dispatch: android.view.Choreographer$FrameHandler*";
+WHERE name GLOB "Looper.dispatch: android.view.Choreographer$FrameHandler*";
 
 -- Extract task's posted_from information from task's arguments.
-SELECT CREATE_FUNCTION('INTERNAL_GET_POSTED_FROM(arg_set_id INT)', 'STRING',
-  '
-  WITH posted_from as (
-    SELECT
-      EXTRACT_ARG($arg_set_id, "task.posted_from.file_name") AS file_name,
-      EXTRACT_ARG($arg_set_id, "task.posted_from.function_name") AS function_name
-  )
+CREATE PERFETTO FUNCTION INTERNAL_GET_POSTED_FROM(arg_set_id INT)
+RETURNS STRING AS
+WITH posted_from as (
   SELECT
-    file_name || ":" || function_name as posted_from
-  FROM posted_from;
-  ');
+    EXTRACT_ARG($arg_set_id, "task.posted_from.file_name") AS file_name,
+    EXTRACT_ARG($arg_set_id, "task.posted_from.function_name") AS function_name
+)
+SELECT file_name || ":" || function_name as posted_from
+FROM posted_from;
 
 -- Selects the BeginMainFrame slices (which as posted from ScheduledActionSendBeginMainFrame),
 -- used for root-level processing. In top-level/Java based slices, these will correspond to the
@@ -441,26 +414,27 @@
 
 -- Select the slice that might be the descendant mojo slice for the given task
 -- slice if it exists.
-SELECT CREATE_FUNCTION('INTERNAL_GET_DESCENDANT_MOJO_SLICE_CANDIDATE(slice_id INT)', 'INT',
-  '
-    SELECT
-      id
-    FROM descendant_slice($slice_id)
-    WHERE
-      -- The tricky case here is dealing with sync mojo IPCs: we do not want to
-      -- pick up sync IPCs when we are in a non-IPC task.
-      -- So we look at all toplevel events and pick up the first one:
-      -- for sync mojo messages, it will be "Send mojo message", which then
-      -- will fail.
-      -- Some events are excluded as they can legimately appear under "RunTask"
-      -- before "Receive mojo message".
-      category GLOB "*toplevel*" AND
-      name NOT IN (
-        "SimpleWatcher::OnHandleReady",
-        "MessagePipe peer closed")
-    ORDER by depth, ts
-    LIMIT 1
-  ');
+CREATE PERFETTO FUNCTION INTERNAL_GET_DESCENDANT_MOJO_SLICE_CANDIDATE(
+  slice_id INT
+)
+RETURNS INT AS
+SELECT
+  id
+FROM descendant_slice($slice_id)
+WHERE
+  -- The tricky case here is dealing with sync mojo IPCs: we do not want to
+  -- pick up sync IPCs when we are in a non-IPC task.
+  -- So we look at all toplevel events and pick up the first one:
+  -- for sync mojo messages, it will be "Send mojo message", which then
+  -- will fail.
+  -- Some events are excluded as they can legimately appear under "RunTask"
+  -- before "Receive mojo message".
+  category GLOB "*toplevel*" AND
+  name NOT IN (
+    "SimpleWatcher::OnHandleReady",
+    "MessagePipe peer closed")
+ORDER by depth, ts
+LIMIT 1;
 
 SELECT CREATE_VIEW_FUNCTION('INTERNAL_DESCENDANT_MOJO_SLICE(slice_id INT)',
   'task_name STRING',
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/counters.sql b/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
index 22fb72d..cfef984 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/counters.sql
@@ -18,13 +18,10 @@
 -- Timestamp of first counter value in a counter.
 --
 -- @arg counter_track_id  INT Id of a counter track with a counter.
--- @ret LONG                  Timestamp of first counter value. Null if doesn't exist. 
-SELECT CREATE_FUNCTION(
-  'EARLIEST_TIMESTAMP_FOR_COUNTER_TRACK(counter_track_id INT)',
-  'LONG',
-  'SELECT MIN(ts) FROM counter WHERE counter.track_id = $counter_track_id;'
-);
-
+-- @ret LONG                  Timestamp of first counter value. Null if doesn't exist.
+CREATE PERFETTO FUNCTION EARLIEST_TIMESTAMP_FOR_COUNTER_TRACK(counter_track_id INT)
+RETURNS LONG AS
+SELECT MIN(ts) FROM counter WHERE counter.track_id = $counter_track_id;
 
 -- Counter values with details of counter track with calculated duration of each counter value.
 -- Duration is calculated as time from counter to the next counter.
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/cpus.sql b/src/trace_processor/perfetto_sql/stdlib/common/cpus.sql
index 460c80c..6273116 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/cpus.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/cpus.sql
@@ -47,14 +47,10 @@
 --
 -- @arg cpu_index INT   Index of the CPU whose size we will guess.
 -- @ret STRING          A descriptive size ('little', 'mid', 'big', etc) or NULL if we have insufficient information.
-SELECT CREATE_FUNCTION(
-  'GUESS_CPU_SIZE(cpu_index INT)',
-  'STRING',
-  '
-  SELECT
-    IIF((SELECT COUNT(DISTINCT n) FROM internal_ranked_cpus) >= 2, size, null) as size
-  FROM internal_ranked_cpus
-  LEFT JOIN internal_cpu_sizes USING(n)
-  WHERE cpu = $cpu_index;
-  '
-);
+CREATE PERFETTO FUNCTION GUESS_CPU_SIZE(cpu_index INT)
+RETURNS STRING AS
+SELECT
+  IIF((SELECT COUNT(DISTINCT n) FROM internal_ranked_cpus) >= 2, size, null) as size
+FROM internal_ranked_cpus
+LEFT JOIN internal_cpu_sizes USING(n)
+WHERE cpu = $cpu_index;
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql b/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql
index 117af1e..b43b85b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/metadata.sql
@@ -17,9 +17,6 @@
 --
 -- @arg name STRING The name of the metadata entry.
 -- @ret LONG int_value for the given name. NULL if there's no such entry.
-SELECT
-    CREATE_FUNCTION(
-        'EXTRACT_INT_METADATA(name STRING)',
-        'LONG',
-        'SELECT int_value FROM metadata WHERE name = ($name)'
-    );
\ No newline at end of file
+CREATE PERFETTO FUNCTION EXTRACT_INT_METADATA(name STRING)
+RETURNS LONG AS
+SELECT int_value FROM metadata WHERE name = ($name);
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
index 74bd271..07e8ce4 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
@@ -92,22 +92,24 @@
 -- @arg start_ts LONG        Timestamp of start of time range.
 -- @arg end_ts LONG          Timestamp of end of time range.
 -- @ret DOUBLE               Value for the percentile.
-SELECT CREATE_FUNCTION(
-    'COUNTER_TRACK_PERCENTILE_FOR_TIME(counter_track_id INT, percentile INT, start_ts LONG, end_ts LONG)',
-    'DOUBLE',
-    'SELECT value
-    FROM COUNTER_PERCENTILES_FOR_TIME_RANGE($counter_track_id, $start_ts, $end_ts)
-    WHERE percentile = $percentile;'
-);
+CREATE PERFETTO FUNCTION COUNTER_TRACK_PERCENTILE_FOR_TIME(counter_track_id INT,
+                                                          percentile INT,
+                                                          start_ts LONG,
+                                                          end_ts LONG)
+RETURNS DOUBLE AS
+SELECT value
+FROM COUNTER_PERCENTILES_FOR_TIME_RANGE($counter_track_id, $start_ts, $end_ts)
+WHERE percentile = $percentile;
 
 -- Value for specific percentile (range 1-100) for counter track ID.
 --
 -- @arg counter_track_id INT Id of the counter track.
 -- @arg percentile INT       Any of the numbers from 1 to 100.
 -- @ret DOUBLE               Value for the percentile.
-SELECT CREATE_FUNCTION(
-    'COUNTER_TRACK_PERCENTILE(counter_track_id INT, percentile INT)',
-    'DOUBLE',
-    'SELECT COUNTER_TRACK_PERCENTILE_FOR_TIME($counter_track_id, $percentile, TRACE_START(), TRACE_END());'
-);
-
+CREATE PERFETTO FUNCTION COUNTER_TRACK_PERCENTILE(counter_track_id INT,
+                                                  percentile INT)
+RETURNS DOUBLE AS
+SELECT COUNTER_TRACK_PERCENTILE_FOR_TIME($counter_track_id,
+                                         $percentile,
+                                         TRACE_START(),
+                                         TRACE_END());
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/slices.sql b/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
index dbeb23d..4bcab3c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
@@ -106,48 +106,40 @@
 -- @arg id INT              Id of the slice to check parents of.
 -- @arg parent_name STRING  Name of potential ancestor slice.
 -- @ret BOOL                Whether `parent_name` is a name of an ancestor slice.
-SELECT
-  CREATE_FUNCTION(
-    'HAS_PARENT_SLICE_WITH_NAME(id INT, parent_name STRING)',
-    'BOOL',
-    '
-    SELECT EXISTS(
-      SELECT 1
-      FROM ancestor_slice($id)
-      WHERE name = $parent_name
-      LIMIT 1
-    );
-  '
+CREATE PERFETTO FUNCTION HAS_PARENT_SLICE_WITH_NAME(id INT, parent_name STRING)
+RETURNS BOOL AS
+SELECT EXISTS(
+  SELECT 1
+  FROM ancestor_slice($id)
+  WHERE name = $parent_name
+  LIMIT 1
 );
 
 -- Checks if slice has a descendant with provided name.
+--
 -- @arg id INT                  Id of the slice to check descendants of.
 -- @arg descendant_name STRING  Name of potential descendant slice.
--- @ret BOOL                    Whether `descendant_name` is a name of an descendant slice.
-SELECT
-  CREATE_FUNCTION(
-    'HAS_DESCENDANT_SLICE_WITH_NAME(id INT, descendant_name STRING)',
-    'BOOL',
-    '
-    SELECT EXISTS(
-      SELECT 1
-      FROM descendant_slice($id)
-      WHERE name = $descendant_name
-      LIMIT 1
-    );
-  '
+-- @ret BOOL                    Whether `descendant_name` is a name of an
+--                              descendant slice.
+CREATE PERFETTO FUNCTION HAS_DESCENDANT_SLICE_WITH_NAME(
+  id INT,
+  descendant_name STRING
+)
+RETURNS BOOL AS
+SELECT EXISTS(
+  SELECT 1
+  FROM descendant_slice($id)
+  WHERE name = $descendant_name
+  LIMIT 1
 );
 
 -- Count slices with specified name.
 --
 -- @arg slice_glob STRING Name of the slices to counted.
 -- @ret INT               Number of slices with the name.
-SELECT CREATE_FUNCTION(
-  'SLICE_COUNT(slice_glob STRING)',
-  'INT',
-  'SELECT COUNT(1) FROM slice WHERE name GLOB $slice_glob;'
-);
-
+CREATE PERFETTO FUNCTION SLICE_COUNT(slice_glob STRING)
+RETURNS INT AS
+SELECT COUNT(1) FROM slice WHERE name GLOB $slice_glob;;
 
 -- Finds the end timestamp for a given slice's descendant with a given name.
 -- If there are multiple descendants with a given name, the function will return the
@@ -156,16 +148,16 @@
 -- @arg parent_id INT Id of the parent slice.
 -- @arg child_name STRING name of the child with the desired end TS.
 -- @ret INT end timestamp of the child or NULL if it doesn't exist.
-SELECT CREATE_FUNCTION(
-  'DESCENDANT_SLICE_END(parent_id INT, child_name STRING)',
-  'INT',
-  'SELECT
-    CASE WHEN s.dur
-      IS NOT -1 THEN s.ts + s.dur
-      ELSE NULL
-    END
-   FROM descendant_slice($parent_id) s
-   WHERE s.name = $child_name
-   LIMIT 1
-  '
-);
+CREATE PERFETTO FUNCTION DESCENDANT_SLICE_END(
+  parent_id INT,
+  child_name STRING
+)
+RETURNS INT AS
+SELECT
+  CASE WHEN s.dur
+    IS NOT -1 THEN s.ts + s.dur
+    ELSE NULL
+  END
+FROM descendant_slice($parent_id) s
+WHERE s.name = $child_name
+LIMIT 1;
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql b/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
index e3958ed..0a5e48c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/timestamps.sql
@@ -19,27 +19,21 @@
 
 -- Fetch start of the trace.
 -- @ret LONG  Start of the trace in nanoseconds.
-SELECT CREATE_FUNCTION(
-    'TRACE_START()',
-    'LONG',
-    'SELECT start_ts FROM trace_bounds;'
-);
+CREATE PERFETTO FUNCTION TRACE_START()
+RETURNS LONG AS
+SELECT start_ts FROM trace_bounds;
 
 -- Fetch end of the trace.
 -- @ret LONG  End of the trace in nanoseconds.
-SELECT CREATE_FUNCTION(
-    'TRACE_END()',
-    'LONG',
-    'SELECT end_ts FROM trace_bounds;'
-);
+CREATE PERFETTO FUNCTION TRACE_END()
+RETURNS LONG AS
+SELECT end_ts FROM trace_bounds;
 
 -- Fetch duration of the trace.
 -- @ret LONG  Duration of the trace in nanoseconds.
-SELECT CREATE_FUNCTION(
-    'TRACE_DUR()',
-    'LONG',
-    'SELECT TRACE_END() - TRACE_START();'
-);
+CREATE PERFETTO FUNCTION TRACE_DUR()
+RETURNS LONG AS
+SELECT TRACE_END() - TRACE_START();
 
 -- Checks whether two spans are overlapping.
 --
@@ -48,14 +42,10 @@
 -- @arg ts2 LONG      Start of second span.
 -- @arg ts_end2 LONG  End of second span.
 -- @ret BOOL          Whether two spans are overlapping.
-SELECT CREATE_FUNCTION(
-  'IS_SPANS_OVERLAPPING(ts1 LONG, ts_end1 LONG, ts2 LONG, ts_end2 LONG)',
-  'BOOL',
-  '
-    SELECT (IIF($ts1 < $ts2, $ts2, $ts1)
-      < IIF($ts_end1 < $ts_end2, $ts_end1, $ts_end2))
-  '
-);
+CREATE PERFETTO FUNCTION IS_SPANS_OVERLAPPING(ts1 LONG, ts_end1 LONG, ts2 LONG, ts_end2 LONG)
+RETURNS BOOL AS
+SELECT (IIF($ts1 < $ts2, $ts2, $ts1)
+      < IIF($ts_end1 < $ts_end2, $ts_end1, $ts_end2));
 
 --Return the overlapping duration between two spans.
 --If either duration is less than 0 or there's no intersection, 0 is returned
@@ -65,11 +55,9 @@
 -- @arg ts2 LONG Timestamp of second slice start.
 -- @arg dur2 LONG Duration of second slice.
 -- @ret INT               Overlapping duration
-SELECT CREATE_FUNCTION(
-  'SPANS_OVERLAPPING_DUR(ts1 LONG, dur1 LONG, ts2 LONG, dur2 LONG)',
-  'INT',
-  '
-  SELECT
+CREATE PERFETTO FUNCTION SPANS_OVERLAPPING_DUR(ts1 LONG, dur1 LONG, ts2 LONG, dur2 LONG)
+RETURNS INT AS
+SELECT
   CASE
     WHEN $dur1 = -1 OR $dur2 = -1 THEN 0
     WHEN $ts1 + $dur1 < $ts2 OR $ts2 + $dur2 < $ts1 THEN 0
@@ -77,6 +65,4 @@
     WHEN ($ts1 < $ts2) AND ($ts1 + $dur1 < $ts2 + $dur2) THEN $ts1 + $dur1 - $ts2
     WHEN ($ts1 > $ts2) AND ($ts1 + $dur1 > $ts2 + $dur2) THEN $ts2 + $dur2 - $ts1
     ELSE $dur2
-  END
-  '
-);
+  END;
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql
index 2bd8eae..e1cd5fe 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql
@@ -16,12 +16,10 @@
 -- Creates a Stack consisting of one frame for a path in the
 -- EXPERIMENTAL_PROTO_PATH table.
 --
--- @arg path_id  INT Id of the path in EXPERIMENTAL_PROTO_PATH
+-- @arg path_id  INT LONG of the path in EXPERIMENTAL_PROTO_PATH
 -- @ret BYTES    Stack with one frame
-SELECT CREATE_FUNCTION(
-"EXPERIMENTAL_PROTO_PATH_TO_FRAME(path_id LONG)",
-"BYTES",
-"
+CREATE PERFETTO FUNCTON EXPERIMENTAL_PROTO_PATH_TO_FRAME(path_id LONG)
+RETURNS BYTES AS
 SELECT
   CAT_STACKS(
     'event.name:' || EXTRACT_ARG(arg_set_id, 'event.name'),
@@ -29,19 +27,16 @@
     field_name,
     field_type)
 FROM EXPERIMENTAL_PROTO_PATH
-WHERE id = $path_id
-");
+WHERE id = $path_id;
 
 -- Creates a Stack following the parent relations in EXPERIMENTAL_PROTO_PATH
 -- table starting at the given path_id.
 --
--- @arg path_id  INT Id of the path in EXPERIMENTAL_PROTO_PATH that will be
+-- @arg path_id  LONG Id of the path in EXPERIMENTAL_PROTO_PATH that will be
 -- the leaf in the returned stack
 -- @ret BYTES    Stack
-SELECT CREATE_FUNCTION(
-"EXPERIMENTAL_PROTO_PATH_TO_STACK(path_id LONG)",
-"BYTES",
-"
+CREATE PERFETTO FUNCTION EXPERIMENTAL_PROTO_PATH_TO_STACK(path_id LONG)
+RETURNS BYTES AS
 WITH
   R AS (
     -- Starting at the given path_id generate a stack
@@ -67,5 +62,4 @@
 SELECT stack
 FROM R
 WHERE
-  parent_id IS NULL
-");
\ No newline at end of file
+  parent_id IS NULL;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
index be94e25..b778c64 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
@@ -497,22 +497,18 @@
 --
 -- @arg thread_state_id INT   Id of the thread_state to get the thread_executing_span id for
 -- @ret INT                   thread_executing_span id
-SELECT
-  CREATE_FUNCTION(
-'EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(thread_state_id INT)',
-'INT',
-'
-WITH
-  t AS (
+CREATE PERFETTO FUNCTION
+EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(thread_state_id INT)
+RETURNS INT AS
+WITH t AS (
   SELECT
     ts,
     utid
   FROM thread_state
   WHERE
     id = $thread_state_id
-  )
-  SELECT
-    MAX(start_id) AS thread_executing_span_id
-  FROM internal_wakeup w, t
-  WHERE t.utid = w.utid AND t.ts >= w.start_ts AND t.ts < w.end_ts;
-');
+)
+SELECT
+  MAX(start_id) AS thread_executing_span_id
+FROM internal_wakeup w, t
+WHERE t.utid = w.utid AND t.ts >= w.start_ts AND t.ts < w.end_ts;
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index bba6153..e953823 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -1295,9 +1295,9 @@
                      DataSourceInstanceID>::value,
         "data_source_instance_id type mismatch");
     internal_state->muxer_id_for_testing = muxer_id_for_testing_;
+    RegisteredProducerBackend& backend = *FindProducerBackendById(backend_id);
 
     if (startup_session_id) {
-      RegisteredProducerBackend& backend = *FindProducerBackendById(backend_id);
       uint16_t& last_reservation =
           backend.producer->last_startup_target_buffer_reservation_;
       if (last_reservation == std::numeric_limits<uint16_t>::max()) {
@@ -1349,6 +1349,7 @@
 
     DataSourceBase::SetupArgs setup_args;
     setup_args.config = &cfg;
+    setup_args.backend_type = backend.type;
     setup_args.internal_instance_index = i;
 
     if (!rds.requires_callbacks_under_lock)
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index ccd1745..6d58f58 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -39,7 +39,7 @@
   ],
   test_suites: [
     "cts",
-    "mts",
+    "mts-art",
     "vts10",
     "general-tests",
   ],
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_v3.out b/test/trace_processor/diff_tests/chrome/scroll_jank_v3.out
index 5f1c097..e539208 100644
--- a/test/trace_processor/diff_tests/chrome/scroll_jank_v3.out
+++ b/test/trace_processor/diff_tests/chrome/scroll_jank_v3.out
@@ -1,3 +1,4 @@
 "cause_of_jank","sub_cause_of_jank","delay_since_last_frame","vsync_interval"
+"[NULL]","[NULL]",33.462000,16.368000
 "RendererCompositorFinishedToBeginImplFrame","[NULL]",100.274000,16.368000
 "RendererCompositorQueueingDelay","[NULL]",33.404000,16.368000
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_v3_percentage.out b/test/trace_processor/diff_tests/chrome/scroll_jank_v3_percentage.out
index d3d7bfb..2081e06 100644
--- a/test/trace_processor/diff_tests/chrome/scroll_jank_v3_percentage.out
+++ b/test/trace_processor/diff_tests/chrome/scroll_jank_v3_percentage.out
@@ -1,2 +1,2 @@
 "delayed_frame_percentage"
-0.687285
+1.030928
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index b45edf8..f888d41 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -61,6 +61,17 @@
       sys.stderr.write('Offending file: %s\n' % path)
       errors += 1
 
+  # Ban the use of CREATE_FUNCTION.
+  for line in lines:
+    if line.startswith('--'):
+      continue
+
+    if 'create_function' in line.casefold():
+      sys.stderr.write('CREATE_FUNCTION is deprecated in trace processor. '
+                       'Prefer CREATE PERFETTO FUNCTION instead.\n')
+      sys.stderr.write('Offending file: %s\n' % path)
+      errors += 1
+
   return errors
 
 
diff --git a/tools/check_sql_modules.py b/tools/check_sql_modules.py
index f50fdbd..4fb349e 100755
--- a/tools/check_sql_modules.py
+++ b/tools/check_sql_modules.py
@@ -43,6 +43,28 @@
 
       res = parse_file_to_dict(path, sql)
       errors += res if isinstance(res, list) else []
+
+      # Ban the use of LIKE in non-comment lines.
+      lines = [l.strip() for l in sql.split('\n')]
+      for line in lines:
+        if line.startswith('--'):
+          continue
+
+        if 'like' in line.casefold():
+          errors.append('LIKE is banned in trace processor metrics. '
+                        'Prefer GLOB instead.')
+          errors.append('Offending file: %s' % path)
+
+      # Ban the use of CREATE_FUNCTION.
+      for line in lines:
+        if line.startswith('--'):
+          continue
+
+        if 'create_function' in line.casefold():
+          errors.append('CREATE_FUNCTION is deprecated in trace processor. '
+                        'Prefer CREATE PERFETTO FUNCTION instead.')
+          errors.append('Offending file: %s' % path)
+
   sys.stderr.write("\n".join(errors))
   sys.stderr.write("\n")
   return 0 if not errors else 1
diff --git a/tools/gen_ui_imports b/tools/gen_ui_imports
index b644dfe..36b3825 100755
--- a/tools/gen_ui_imports
+++ b/tools/gen_ui_imports
@@ -36,8 +36,7 @@
 
 import os
 import argparse
-import subprocess
-import sys
+import re
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
 UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
@@ -45,7 +44,8 @@
 
 
 def to_camel_case(s):
-  first, *rest = s.split('_')
+  # Split string on periods and underscores
+  first, *rest = re.split(r'\.|\_', s)
   return first + ''.join(x.title() for x in rest)
 
 
@@ -70,7 +70,7 @@
   import_text = '\n'.join(imports)
   registration_text = '\n'.join(registrations)
 
-  expected = f"{header}\n\n{import_text}\n\n{registration_text}"
+  expected = f"{header}\n\n{import_text}\n\n{registration_text}\n"
 
   with open(output_path, 'w') as f:
     f.write(expected)
diff --git a/ui/build.js b/ui/build.js
index 92f793b..f8e2d90 100644
--- a/ui/build.js
+++ b/ui/build.js
@@ -237,6 +237,7 @@
     scanDir('buildtools/typefaces');
     scanDir('buildtools/catapult_trace_viewer');
     generateImports('ui/src/tracks', 'all_tracks.ts');
+    generateImports('ui/src/plugins', 'all_plugins.ts');
     compileProtos();
     genVersion();
     transpileTsProject('ui');
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index af1785f..6044708 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -173,5 +173,8 @@
       textEntry: '',
       hideNonMatching: true,
     },
+
+    // Somewhere to store plugins' persistent state.
+    plugins: {},
   };
 }
diff --git a/ui/src/common/plugin_api.ts b/ui/src/common/plugin_api.ts
index 9ba3a99..53e09a2 100644
--- a/ui/src/common/plugin_api.ts
+++ b/ui/src/common/plugin_api.ts
@@ -16,6 +16,8 @@
 import {TrackControllerFactory} from '../controller/track_controller';
 import {TrackCreator} from '../frontend/track';
 
+import {TracePluginFactory} from './plugins';
+
 export {EngineProxy} from '../common/engine';
 export {
   LONG,
@@ -68,6 +70,10 @@
   // could be registered in dev.perfetto.CounterTrack - a whole
   // different plugin.
   registerTrack(track: TrackCreator): void;
+
+  // Register a new plugin factory for a plugin whose lifecycle in linked to
+  // that of the trace.
+  registerTracePluginFactory<T>(pluginFactory: TracePluginFactory<T>): void;
 }
 
 export interface PluginInfo {
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index aa414bc..5a362c7 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -12,21 +12,57 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {Engine} from '../common/engine';
+import {Disposable} from 'src/base/disposable';
+
 import {
   TrackControllerFactory,
   trackControllerRegistry,
 } from '../controller/track_controller';
+import {Store} from '../frontend/store';
 import {TrackCreator} from '../frontend/track';
 import {trackRegistry} from '../frontend/track_registry';
 
+import {Engine} from './engine';
 import {
+  EngineProxy,
   PluginContext,
   PluginInfo,
   TrackInfo,
   TrackProvider,
 } from './plugin_api';
 import {Registry} from './registry';
+import {State} from './state';
+
+// All trace plugins must implement this interface.
+export interface TracePlugin extends Disposable {
+  // This is where we would add potential extension points that plugins can
+  // override.
+  // E.g. commands(): Command[];
+  // For now, plugins don't do anything so this interface is empty.
+}
+
+// This interface defines what a plugin factory should look like.
+// This can be defined in the plugin class definition by defining a constructor
+// and the relevant static methods:
+// E.g.
+// class MyPlugin implements TracePlugin<MyState> {
+//   static migrate(initialState: unknown): MyState {...}
+//   constructor(store: Store<MyState>, engine: EngineProxy) {...}
+//   ... methods from the TracePlugin interface go here ...
+// }
+// ... which can then be passed around by class i.e. MyPlugin
+export interface TracePluginFactory<StateT> {
+  // Function to migrate the persistent state. Called before new().
+  migrate(initialState: unknown): StateT;
+
+  // Instantiate the plugin.
+  new(store: Store<StateT>, engine: EngineProxy): TracePlugin;
+}
+
+interface TracePluginContext {
+  plugin: TracePlugin;
+  store: Store<unknown>;
+}
 
 // Every plugin gets its own PluginContext. This is how we keep track
 // what each plugin is doing and how we can blame issues on particular
@@ -34,6 +70,8 @@
 export class PluginContextImpl implements PluginContext {
   readonly pluginId: string;
   private trackProviders: TrackProvider[];
+  private tracePluginFactory?: TracePluginFactory<any>;
+  private _tracePluginCtx?: TracePluginContext;
 
   constructor(pluginId: string) {
     this.pluginId = pluginId;
@@ -53,6 +91,10 @@
   registerTrackProvider(provider: TrackProvider) {
     this.trackProviders.push(provider);
   }
+
+  registerTracePluginFactory<T>(pluginFactory: TracePluginFactory<T>): void {
+    this.tracePluginFactory = pluginFactory;
+  }
   // ==================================================================
 
   // ==================================================================
@@ -62,11 +104,50 @@
     return this.trackProviders.map((f) => f(proxy));
   }
 
+  onTraceLoad(store: Store<State>, engine: Engine): void {
+    const TracePluginClass = this.tracePluginFactory;
+    if (TracePluginClass) {
+      // Make an engine proxy for this plugin.
+      const engineProxy = engine.getProxy(this.pluginId);
+
+      // Extract the initial state and pass to the plugin factory for migration.
+      const initialState = store.state.plugins[this.pluginId];
+      const migratedState = TracePluginClass.migrate(initialState);
+
+      // Store the initial state in our root store.
+      store.edit((draft) => {
+        draft.plugins[this.pluginId] = migratedState;
+      });
+
+      // Create a proxy store for our plugin to use.
+      const storeProxy = store.createProxy<unknown>(['plugins', this.pluginId]);
+
+      // Instantiate the plugin.
+      this._tracePluginCtx = {
+        plugin: new TracePluginClass(storeProxy, engineProxy),
+        store: storeProxy,
+      };
+    }
+  }
+
+  onTraceClosed() {
+    if (this._tracePluginCtx) {
+      this._tracePluginCtx.plugin.dispose();
+      this._tracePluginCtx.store.dispose();
+      this._tracePluginCtx = undefined;
+    }
+  }
+
+  get tracePlugin(): TracePlugin|undefined {
+    return this._tracePluginCtx?.plugin;
+  }
+
   // Unload the plugin. Ideally no plugin code runs after this point.
   // PluginContext should unregister everything.
   revoke() {
     // TODO(hjd): Remove from trackControllerRegistry, trackRegistry,
     // etc.
+    // TODO(stevegolton): Close the trace plugin.
   }
   // ==================================================================
 }
@@ -123,6 +204,18 @@
     }
     return promises;
   }
+
+  onTraceLoad(store: Store<State>, engine: Engine): void {
+    for (const context of this.contexts.values()) {
+      context.onTraceLoad(store, engine);
+    }
+  }
+
+  onTraceClose() {
+    for (const context of this.contexts.values()) {
+      context.onTraceClosed();
+    }
+  }
 }
 
 // TODO(hjd): Sort out the story for global singletons like these:
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index dba1158..5f5e1a4 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -624,6 +624,9 @@
   // Pending deeplink which will happen when we first finish opening a
   // trace.
   pendingDeeplink?: PendingDeeplinkState;
+
+  // Individual plugin states
+  plugins: {[key: string]: any};
 }
 
 export const defaultTraceTime = {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 047f005..d46a6c0 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -30,6 +30,7 @@
   getEnabledMetatracingCategories,
   isMetatracingEnabled,
 } from '../common/metatracing';
+import {pluginManager} from '../common/plugins';
 import {
   LONG,
   NUM,
@@ -45,11 +46,7 @@
   PendingDeeplinkState,
   ProfileType,
 } from '../common/state';
-import {Span} from '../common/time';
-import {
-  TPTime,
-  TPTimeSpan,
-} from '../common/time';
+import {Span, TPTime, TPTimeSpan} from '../common/time';
 import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
 import {BottomTabList} from '../frontend/bottom_tab';
 import {
@@ -342,6 +339,7 @@
   }
 
   onDestroy() {
+    pluginManager.onTraceClose();
     globals.engines.delete(this.engineId);
   }
 
@@ -556,6 +554,8 @@
       }
     }
 
+    pluginManager.onTraceLoad(globals.store, engine);
+
     return engineMode;
   }
 
diff --git a/ui/src/frontend/debug.ts b/ui/src/frontend/debug.ts
index 7e9c9e5..64de18a 100644
--- a/ui/src/frontend/debug.ts
+++ b/ui/src/frontend/debug.ts
@@ -16,6 +16,7 @@
 import m from 'mithril';
 
 import {Actions} from '../common/actions';
+import {pluginManager} from '../common/plugins';
 import {getSchema} from '../common/schema';
 
 import {globals} from './globals';
@@ -28,6 +29,7 @@
     globals: typeof globals;
     Actions: typeof Actions;
     produce: typeof produce;
+    pluginManager: typeof pluginManager
   }
 }
 
@@ -37,4 +39,5 @@
   window.globals = globals;
   window.Actions = Actions;
   window.produce = produce;
+  window.pluginManager = pluginManager;
 }
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 9a8d3a4..22a5122 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -14,6 +14,7 @@
 
 // Keep this import first.
 import '../core/static_initializers';
+import '../gen/all_plugins';
 
 import {Draft} from 'immer';
 import m from 'mithril';
diff --git a/ui/src/frontend/store.ts b/ui/src/frontend/store.ts
index 170bb97..1f67eed 100644
--- a/ui/src/frontend/store.ts
+++ b/ui/src/frontend/store.ts
@@ -139,16 +139,19 @@
 export class ProxyStoreImpl<RootT, T> implements Store<T> {
   private subscriptions = new Set<SubscriptionCallback<T>>();
   private rootSubscription;
+  private rootStore?: Store<RootT>;
 
   constructor(
-      private rootStore: Store<RootT>,
+      rootStore: Store<RootT>,
       private path: Path,
   ) {
+    this.rootStore = rootStore;
     this.rootSubscription = rootStore.subscribe(this.rootUpdateHandler);
   }
 
   dispose() {
     this.rootSubscription.dispose();
+    this.rootStore = undefined;
   }
 
   private rootUpdateHandler = (newState: RootT, oldState: RootT) => {
@@ -163,10 +166,15 @@
   };
 
   get state(): T {
+    if (!this.rootStore) {
+      throw new StoreError('Proxy store is no longer useable');
+    }
+
     const state = lookupPath<T, RootT>(this.rootStore.state, this.path);
     if (state === undefined) {
       throw new StoreError(`No such subtree: ${this.path}`);
     }
+
     return state;
   }
 
@@ -179,6 +187,10 @@
   }
 
   private applyEdits(edits: Edit<T>[]): void {
+    if (!this.rootStore) {
+      throw new StoreError('Proxy store is no longer useable');
+    }
+
     // Transform edits to work on the root store.
     const rootEdits = edits.map(
         (edit) => (state: Draft<RootT>) => {
@@ -197,6 +209,10 @@
   }
 
   createProxy<NewSubStateT>(path: Path): Store<NewSubStateT> {
+    if (!this.rootStore) {
+      throw new StoreError('Proxy store is no longer useable');
+    }
+
     const fullPath = [...this.path, ...path];
     return new ProxyStoreImpl<RootT, NewSubStateT>(this.rootStore, fullPath);
   }
diff --git a/ui/src/frontend/store_unittest.ts b/ui/src/frontend/store_unittest.ts
index 13395a1..01d39d1 100644
--- a/ui/src/frontend/store_unittest.ts
+++ b/ui/src/frontend/store_unittest.ts
@@ -282,4 +282,104 @@
     // Ensure proxy callback hasn't been called
     expect(callback).not.toHaveBeenCalled();
   });
+
+  it('notifies subscribers', () => {
+    const store = createStore(initialState);
+    const fooState = store.createProxy<Foo>(['foo']);
+    const callback = jest.fn();
+
+    fooState.subscribe(callback);
+
+    store.edit((draft) => {
+      draft.foo.counter += 1;
+    });
+
+    expect(callback).toHaveBeenCalledTimes(1);
+    expect(callback).toHaveBeenCalledWith(
+        {
+          ...initialState.foo,
+          counter: 1,
+        },
+        initialState.foo);
+  });
+
+  it('does not notify unsubscribed subscribers', () => {
+    const store = createStore(initialState);
+    const fooState = store.createProxy<Foo>(['foo']);
+    const callback = jest.fn();
+
+    // Subscribe then immediately unsubscribe
+    fooState.subscribe(callback).dispose();
+
+    // Make an arbitrary edit
+    fooState.edit((draft) => {
+      draft.counter += 1;
+    });
+
+    expect(callback).not.toHaveBeenCalled();
+  });
+
+  it('throws on state access when path doesn\'t exist', () => {
+    const store = createStore(initialState);
+
+    // This path is incorrect - baz doesn't exist in State
+    const fooStore = store.createProxy<Foo>(['baz']);
+
+    expect(() => {
+      fooStore.state;
+    }).toThrow(StoreError);
+  });
+
+  it('throws on edit when path doesn\'t exist', () => {
+    const store = createStore(initialState);
+
+    // This path is incorrect - baz doesn't exist in State
+    const fooState = store.createProxy<Foo>(['baz']);
+
+    expect(() => {
+      fooState.edit((draft) => {
+        draft.counter += 1;
+      });
+    }).toThrow(StoreError);
+  });
+
+  it('notifies when relevant edits are made from root store', () => {
+    const store = createStore(initialState);
+    const fooState = store.createProxy<Foo>(['foo']);
+    const callback = jest.fn();
+
+    // Subscribe on the proxy store
+    fooState.subscribe(callback);
+
+    // Edit the root store
+    store.edit((draft) => {
+      draft.foo.counter++;
+    });
+
+    // Expect proxy callback called with correct subtree
+    expect(callback).toHaveBeenCalled();
+    expect(callback).toHaveBeenCalledWith(
+        {
+          ...initialState.foo,
+          counter: 1,
+        },
+        initialState.foo);
+  });
+
+  it('ignores irrrelevant edits from the root store', () => {
+    const store = createStore(initialState);
+    const nestedStore = store.createProxy<NestedState>(['foo', 'nested']);
+    const callback = jest.fn();
+
+    // Subscribe on the proxy store
+    nestedStore.subscribe(callback);
+
+    // Edit an irrelevant subtree on the root store
+    store.edit((draft) => {
+      draft.foo.counter++;
+    });
+
+    // Ensure proxy callback hasn't been called
+    expect(callback).not.toHaveBeenCalled();
+  });
 });
diff --git a/ui/src/plugins/dev.perfetto.ExamplePlugin/index.ts b/ui/src/plugins/dev.perfetto.ExamplePlugin/index.ts
new file mode 100644
index 0000000..3305a90
--- /dev/null
+++ b/ui/src/plugins/dev.perfetto.ExamplePlugin/index.ts
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 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 {EngineProxy, PluginContext} from '../../common/plugin_api';
+import {TracePlugin} from '../../common/plugins';
+import {Store} from '../../frontend/store';
+
+interface ExampleState {
+  counter: number;
+}
+
+// This is just an example plugin, used to prove that the plugin system works.
+class ExamplePlugin implements TracePlugin {
+  static migrate(_initialState: unknown): ExampleState {
+    return {counter: 0};
+  }
+
+  constructor(_store: Store<ExampleState>, _engine: EngineProxy) {
+    // No-op
+  }
+
+  dispose(): void {
+    // No-op
+  }
+}
+
+function activate(ctx: PluginContext) {
+  ctx.registerTracePluginFactory(ExamplePlugin);
+}
+
+export const plugin = {
+  pluginId: 'dev.perfetto.ExamplePlugin',
+  activate,
+};
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 8807b52..d09a552 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -221,6 +221,10 @@
         tEnd = visibleWindowTime.end.toTPTime('ceil');
       }
 
+      if (!visibleTimeSpan.intersects(tStart, tEnd)) {
+        continue;
+      }
+
       const rect = this.getSliceRect(
           visibleTimeScale, visibleTimeSpan, windowSpan, tStart, tEnd, depth);
       if (!rect || !rect.visible) {