Show UI warnings for common patterns leading to jank

Ports some of the alerts from Catapult (legacy UI).
Using stricter thresholds for now, as we do not yet have a good way to
detect missed frames. Also limiting the warnings to a few selected
processes until derived events can be generated per-process.

https://screenshot.googleplex.com/G3UGeqzPcSFNHPP

Bug: 161781469
Change-Id: I40e260a4031254c2f7fc65d818cd98382b9b937f
diff --git a/Android.bp b/Android.bp
index be63166..06f1636 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3562,6 +3562,7 @@
     "protos/perfetto/metrics/android/hwcomposer.proto",
     "protos/perfetto/metrics/android/hwui_metric.proto",
     "protos/perfetto/metrics/android/ion_metric.proto",
+    "protos/perfetto/metrics/android/jank_metric.proto",
     "protos/perfetto/metrics/android/java_heap_histogram.proto",
     "protos/perfetto/metrics/android/java_heap_stats.proto",
     "protos/perfetto/metrics/android/lmk_metric.proto",
@@ -3603,6 +3604,7 @@
     "protos/perfetto/metrics/android/hwcomposer.proto",
     "protos/perfetto/metrics/android/hwui_metric.proto",
     "protos/perfetto/metrics/android/ion_metric.proto",
+    "protos/perfetto/metrics/android/jank_metric.proto",
     "protos/perfetto/metrics/android/java_heap_histogram.proto",
     "protos/perfetto/metrics/android/java_heap_stats.proto",
     "protos/perfetto/metrics/android/lmk_metric.proto",
@@ -7535,7 +7537,9 @@
     "src/trace_processor/metrics/android/android_gpu.sql",
     "src/trace_processor/metrics/android/android_hwcomposer.sql",
     "src/trace_processor/metrics/android/android_hwui_metric.sql",
+    "src/trace_processor/metrics/android/android_hwui_threads.sql",
     "src/trace_processor/metrics/android/android_ion.sql",
+    "src/trace_processor/metrics/android/android_jank.sql",
     "src/trace_processor/metrics/android/android_lmk.sql",
     "src/trace_processor/metrics/android/android_lmk_reason.sql",
     "src/trace_processor/metrics/android/android_mem.sql",
diff --git a/BUILD b/BUILD
index 385f3bc..5f7c9d3 100644
--- a/BUILD
+++ b/BUILD
@@ -887,7 +887,9 @@
         "src/trace_processor/metrics/android/android_gpu.sql",
         "src/trace_processor/metrics/android/android_hwcomposer.sql",
         "src/trace_processor/metrics/android/android_hwui_metric.sql",
+        "src/trace_processor/metrics/android/android_hwui_threads.sql",
         "src/trace_processor/metrics/android/android_ion.sql",
+        "src/trace_processor/metrics/android/android_jank.sql",
         "src/trace_processor/metrics/android/android_lmk.sql",
         "src/trace_processor/metrics/android/android_lmk_reason.sql",
         "src/trace_processor/metrics/android/android_mem.sql",
@@ -2288,6 +2290,7 @@
         "protos/perfetto/metrics/android/hwcomposer.proto",
         "protos/perfetto/metrics/android/hwui_metric.proto",
         "protos/perfetto/metrics/android/ion_metric.proto",
+        "protos/perfetto/metrics/android/jank_metric.proto",
         "protos/perfetto/metrics/android/java_heap_histogram.proto",
         "protos/perfetto/metrics/android/java_heap_stats.proto",
         "protos/perfetto/metrics/android/lmk_metric.proto",
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index de8f70b..ce5a28a 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -28,6 +28,7 @@
     "hwcomposer.proto",
     "hwui_metric.proto",
     "ion_metric.proto",
+    "jank_metric.proto",
     "java_heap_histogram.proto",
     "java_heap_stats.proto",
     "lmk_metric.proto",
diff --git a/protos/perfetto/metrics/android/jank_metric.proto b/protos/perfetto/metrics/android/jank_metric.proto
new file mode 100644
index 0000000..9708adf
--- /dev/null
+++ b/protos/perfetto/metrics/android/jank_metric.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+message AndroidJankMetrics {
+  repeated Warning warnings = 1;
+
+  message Warning {
+    optional int64 ts = 1;
+    optional int64 dur = 2;
+
+    optional string process_name = 3;
+    optional string warning_text = 4;
+  }
+}
\ No newline at end of file
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 8986b58..a1b6e19 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -26,6 +26,7 @@
 import "protos/perfetto/metrics/android/hwcomposer.proto";
 import "protos/perfetto/metrics/android/hwui_metric.proto";
 import "protos/perfetto/metrics/android/ion_metric.proto";
+import "protos/perfetto/metrics/android/jank_metric.proto";
 import "protos/perfetto/metrics/android/java_heap_histogram.proto";
 import "protos/perfetto/metrics/android/java_heap_stats.proto";
 import "protos/perfetto/metrics/android/lmk_metric.proto";
@@ -128,6 +129,9 @@
   // Metric associated with hwcomposer.
   optional AndroidHwcomposerMetrics android_hwcomposer = 28;
 
+  // Detects common bad patterns that might lead to jank.
+  optional AndroidJankMetrics android_jank = 29;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 2589b62..8292dd3 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -377,6 +377,21 @@
 
 // End of protos/perfetto/metrics/android/ion_metric.proto
 
+// Begin of protos/perfetto/metrics/android/jank_metric.proto
+
+message AndroidJankMetrics {
+  repeated Warning warnings = 1;
+
+  message Warning {
+    optional int64 ts = 1;
+    optional int64 dur = 2;
+
+    optional string process_name = 3;
+    optional string warning_text = 4;
+  }
+}
+// End of protos/perfetto/metrics/android/jank_metric.proto
+
 // Begin of protos/perfetto/metrics/android/java_heap_histogram.proto
 
 message JavaHeapHistogram {
@@ -903,6 +918,9 @@
   // Metric associated with hwcomposer.
   optional AndroidHwcomposerMetrics android_hwcomposer = 28;
 
+  // Detects common bad patterns that might lead to jank.
+  optional AndroidJankMetrics android_jank = 29;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/src/trace_processor/metrics/BUILD.gn b/src/trace_processor/metrics/BUILD.gn
index b7bef7b..7b3f630 100644
--- a/src/trace_processor/metrics/BUILD.gn
+++ b/src/trace_processor/metrics/BUILD.gn
@@ -23,11 +23,13 @@
   "android/android_cpu_agg.sql",
   "android/android_cpu_raw_metrics_per_core.sql",
   "android/android_gpu.sql",
+  "android/android_hwui_threads.sql",
   "android/android_mem.sql",
   "android/android_mem_unagg.sql",
   "android/android_ion.sql",
   "android/composition_layers.sql",
   "android/frame_missed.sql",
+  "android/android_jank.sql",
   "android/android_lmk_reason.sql",
   "android/android_lmk.sql",
   "android/android_powrails.sql",
diff --git a/src/trace_processor/metrics/android/android_hwui_threads.sql b/src/trace_processor/metrics/android/android_hwui_threads.sql
new file mode 100644
index 0000000..d563057
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_hwui_threads.sql
@@ -0,0 +1,106 @@
+--
+-- Copyright 2019 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+
+DROP VIEW IF EXISTS {{table_name_prefix}}_main_thread;
+CREATE VIEW {{table_name_prefix}}_main_thread AS
+  SELECT
+    process.name as process_name,
+    thread.utid
+  FROM thread
+  JOIN {{process_allowlist_table}} process USING (upid)
+  WHERE thread.is_main_thread;
+
+DROP VIEW IF EXISTS {{table_name_prefix}}_render_thread;
+CREATE VIEW {{table_name_prefix}}_render_thread AS
+  SELECT
+    process.name as process_name,
+    thread.utid
+  FROM thread
+  JOIN {{process_allowlist_table}} process USING (upid)
+  WHERE thread.name = 'RenderThread';
+
+DROP VIEW IF EXISTS {{table_name_prefix}}_gpu_completion_thread;
+CREATE VIEW {{table_name_prefix}}_gpu_completion_thread AS
+  SELECT
+    process.name as process_name,
+    thread.utid
+  FROM thread
+  JOIN {{process_allowlist_table}} process USING (upid)
+  WHERE thread.name = 'GPU completion';
+
+DROP VIEW IF EXISTS {{table_name_prefix}}_hwc_release_thread;
+CREATE VIEW {{table_name_prefix}}_hwc_release_thread AS
+  SELECT
+    process.name as process_name,
+    thread.utid
+  FROM thread
+  JOIN {{process_allowlist_table}} process USING (upid)
+  WHERE thread.name = 'HWC release';
+
+DROP TABLE IF EXISTS {{table_name_prefix}}_main_thread_slices;
+CREATE TABLE {{table_name_prefix}}_main_thread_slices AS
+  SELECT
+    process_name,
+    thread.utid,
+    slice.*,
+    ts + dur AS ts_end
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN {{table_name_prefix}}_main_thread thread USING (utid)
+  WHERE dur > 0;
+
+DROP TABLE IF EXISTS {{table_name_prefix}}_render_thread_slices;
+CREATE TABLE {{table_name_prefix}}_render_thread_slices AS
+  SELECT
+    process_name,
+    thread.utid,
+    slice.*,
+    ts + dur AS ts_end
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN {{table_name_prefix}}_render_thread thread USING (utid)
+  WHERE dur > 0;
+
+DROP VIEW IF EXISTS {{table_name_prefix}}_gpu_completion_slices;
+CREATE VIEW {{table_name_prefix}}_gpu_completion_slices AS
+  SELECT
+    process_name,
+    thread.utid,
+    slice.*,
+    ts + dur AS ts_end,
+    -- Extracts 1234 from 'waiting for GPU completion 1234'
+    CAST(STR_SPLIT(slice.name, ' ', 4) AS INTEGER) as idx
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN {{table_name_prefix}}_gpu_completion_thread thread USING (utid)
+  WHERE slice.name LIKE 'waiting for GPU completion %'
+  AND dur > 0;
+
+DROP VIEW IF EXISTS {{table_name_prefix}}_hwc_release_slices;
+CREATE VIEW {{table_name_prefix}}_hwc_release_slices AS
+  SELECT
+    process_name,
+    thread.utid,
+    slice.*,
+    ts + dur as ts_end,
+    -- Extracts 1234 from 'waiting for HWC release 1234'
+    CAST(STR_SPLIT(slice.name, ' ', 4) AS INTEGER) as idx
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN {{table_name_prefix}}_hwc_release_thread thread USING (utid)
+  WHERE slice.name LIKE 'waiting for HWC release %'
+  AND dur > 0;
diff --git a/src/trace_processor/metrics/android/android_jank.sql b/src/trace_processor/metrics/android/android_jank.sql
new file mode 100644
index 0000000..b58c062
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_jank.sql
@@ -0,0 +1,283 @@
+--
+-- Copyright 2020 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+
+DROP TABLE IF EXISTS android_jank_process_allowlist;
+CREATE TABLE android_jank_process_allowlist AS
+SELECT process.name, process.upid
+FROM process
+WHERE process.name IN (
+  'com.android.systemui',
+  'com.google.android.apps.nexuslauncher',
+  'com.google.android.inputmethod.latin'
+);
+
+SELECT RUN_METRIC(
+  'android/android_hwui_threads.sql',
+  'table_name_prefix', 'android_jank',
+  'process_allowlist_table', 'android_jank_process_allowlist');
+
+DROP TABLE IF EXISTS android_jank_thread_state_running;
+CREATE TABLE android_jank_thread_state_running AS
+SELECT utid, ts, dur, state
+FROM thread_state
+WHERE utid IN (SELECT utid FROM android_jank_main_thread_slices)
+AND state = 'Running'
+AND dur > 0;
+
+DROP TABLE IF EXISTS android_jank_thread_state_scheduled;
+CREATE TABLE android_jank_thread_state_scheduled AS
+SELECT utid, ts, dur, state
+FROM thread_state
+WHERE utid IN (SELECT utid FROM android_jank_main_thread_slices)
+AND (state = 'R' OR state = 'R+')
+AND dur > 0;
+
+DROP TABLE IF EXISTS android_jank_thread_state_io_wait;
+CREATE TABLE android_jank_thread_state_io_wait AS
+SELECT utid, ts, dur, state
+FROM thread_state
+WHERE utid IN (SELECT utid FROM android_jank_main_thread_slices)
+AND (((state = 'D' OR state = 'DK') AND io_wait) OR (state = 'DK' AND io_wait IS NULL))
+AND dur > 0;
+
+--
+-- Main Thread alerts
+--
+
+-- Expensive measure/layout
+
+DROP TABLE IF EXISTS android_jank_measure_layout_slices;
+CREATE TABLE android_jank_measure_layout_slices AS
+SELECT
+  process_name,
+  utid,
+  id,
+  ts,
+  dur
+FROM android_jank_main_thread_slices
+WHERE name in ('measure', 'layout')
+AND dur >= 3000000;
+
+CREATE VIRTUAL TABLE IF NOT EXISTS android_jank_measure_layout_slices_state
+USING span_join(android_jank_measure_layout_slices PARTITIONED utid, android_jank_thread_state_running PARTITIONED utid);
+
+DROP TABLE IF EXISTS android_jank_measure_layout_slices_high_cpu;
+CREATE TABLE android_jank_measure_layout_slices_high_cpu AS
+SELECT id FROM android_jank_measure_layout_slices_state
+GROUP BY id
+HAVING SUM(dur) > 3000000;
+
+DROP TABLE IF EXISTS android_jank_measure_layout_alerts;
+CREATE TABLE android_jank_measure_layout_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Expensive measure/layout pass' as alert_name,
+  id
+FROM android_jank_measure_layout_slices
+JOIN android_jank_measure_layout_slices_high_cpu USING (id);
+
+-- Inflation during ListView recycling
+-- as additional alerts for expensive layout slices
+
+DROP TABLE IF EXISTS android_jank_listview_inflation_alerts;
+CREATE TABLE android_jank_listview_inflation_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Inflation during ListView recycling' as alert_name
+FROM android_jank_main_thread_slices
+WHERE name IN ('obtainView', 'setupListItem')
+AND EXISTS (
+  SELECT 1
+  FROM descendant_slice(android_jank_main_thread_slices.id)
+  WHERE name = 'inflate')
+AND EXISTS(
+  SELECT 1
+  FROM android_jank_measure_layout_alerts
+  JOIN ancestor_slice(android_jank_main_thread_slices.id) USING (id)
+);
+
+-- Long View#draw()
+
+DROP TABLE IF EXISTS android_jank_view_draw_slices;
+CREATE TABLE android_jank_view_draw_slices AS
+SELECT
+  process_name,
+  utid,
+  id,
+  ts,
+  dur
+FROM android_jank_main_thread_slices
+WHERE name in ('getDisplayList', 'Record View#draw()')
+AND dur >= 3000000;
+
+CREATE VIRTUAL TABLE IF NOT EXISTS android_jank_view_draw_slices_state
+USING span_join(android_jank_view_draw_slices PARTITIONED utid, android_jank_thread_state_running PARTITIONED utid);
+
+DROP TABLE IF EXISTS android_jank_view_draw_slices_high_cpu;
+CREATE TABLE android_jank_view_draw_slices_high_cpu AS
+SELECT id FROM android_jank_view_draw_slices_state
+GROUP BY id
+HAVING SUM(dur) > 3000000;
+
+DROP TABLE IF EXISTS android_jank_view_draw_alerts;
+CREATE TABLE android_jank_view_draw_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Long View#draw()' as alert_name
+FROM android_jank_main_thread_slices
+JOIN android_jank_view_draw_slices_high_cpu USING (id);
+
+-- Scheduling delay and Blocking I/O delay
+
+DROP TABLE IF EXISTS android_jank_do_frame_slices;
+CREATE TABLE android_jank_do_frame_slices AS
+SELECT
+  process_name,
+  utid,
+  id,
+  ts,
+  dur
+FROM android_jank_main_thread_slices
+WHERE name = 'Choreographer#doFrame'
+AND dur >= 5000000;
+
+CREATE VIRTUAL TABLE IF NOT EXISTS android_jank_do_frame_slices_state_scheduled
+USING span_join(android_jank_do_frame_slices PARTITIONED utid, android_jank_thread_state_scheduled PARTITIONED utid);
+
+
+DROP TABLE IF EXISTS android_jank_do_frame_slices_long_scheduled;
+CREATE TABLE android_jank_do_frame_slices_long_scheduled AS
+SELECT id FROM android_jank_do_frame_slices_state_scheduled
+GROUP BY id
+HAVING SUM(dur) > 5000000;
+
+DROP TABLE IF EXISTS android_jank_scheduling_delay_alerts;
+CREATE TABLE android_jank_scheduling_delay_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Scheduling delay' as alert_name
+FROM android_jank_do_frame_slices
+JOIN android_jank_do_frame_slices_long_scheduled USING (id);
+
+CREATE VIRTUAL TABLE IF NOT EXISTS android_jank_do_frame_slices_state_io_wait
+USING span_join(android_jank_do_frame_slices PARTITIONED utid, android_jank_thread_state_io_wait PARTITIONED utid);
+
+DROP TABLE IF EXISTS android_jank_do_frame_slices_long_io_wait;
+CREATE TABLE android_jank_do_frame_slices_long_io_wait AS
+SELECT id FROM android_jank_do_frame_slices_state_io_wait
+GROUP BY id
+HAVING SUM(dur) > 5000000;
+
+DROP TABLE IF EXISTS android_jank_blocking_delay_alerts;
+CREATE TABLE android_jank_blocking_delay_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Blocking I/O delay' as alert_name
+FROM android_jank_do_frame_slices
+JOIN android_jank_do_frame_slices_long_io_wait USING (id);
+
+--
+-- Render Thread alerts
+--
+
+-- Expensive Canvas#saveLayer()
+
+DROP TABLE IF EXISTS android_jank_save_layer_alerts;
+CREATE TABLE android_jank_save_layer_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Expensive rendering with Canvas#saveLayer()' as alert_name
+FROM android_jank_render_thread_slices
+WHERE name LIKE '%alpha caused %saveLayer %'
+AND dur >= 1000000;
+
+-- Path texture churn
+
+DROP TABLE IF EXISTS android_jank_generate_path_alerts;
+CREATE TABLE android_jank_generate_path_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Path texture churn' as alert_name
+FROM android_jank_render_thread_slices
+WHERE name = 'Generate Path Texture'
+AND dur >= 3000000;
+
+-- Expensive Bitmap uploads
+
+DROP TABLE IF EXISTS android_jank_upload_texture_alerts;
+CREATE TABLE android_jank_upload_texture_alerts AS
+SELECT
+  process_name,
+  ts,
+  dur,
+  'Expensive Bitmap uploads' as alert_name
+FROM android_jank_render_thread_slices
+WHERE name LIKE 'Upload %x% Texture'
+AND dur >= 3000000;
+
+-- Merge all alerts tables into one table
+DROP TABLE IF EXISTS android_jank_alerts;
+CREATE TABLE android_jank_alerts AS
+SELECT process_name, ts, dur, alert_name FROM android_jank_measure_layout_alerts
+UNION ALL
+SELECT process_name, ts, dur, alert_name FROM android_jank_listview_inflation_alerts
+UNION ALL
+SELECT process_name, ts, dur, alert_name FROM android_jank_scheduling_delay_alerts
+UNION ALL
+SELECT process_name, ts, dur, alert_name FROM android_jank_blocking_delay_alerts
+UNION ALL
+SELECT process_name, ts, dur, alert_name FROM android_jank_save_layer_alerts
+UNION ALL
+SELECT process_name, ts, dur, alert_name FROM android_jank_generate_path_alerts
+UNION ALL
+SELECT process_name, ts, dur, alert_name FROM android_jank_upload_texture_alerts;
+
+DROP VIEW IF EXISTS android_jank_event;
+CREATE VIEW android_jank_event AS
+SELECT
+  'slice' as track_type,
+  process_name || ' warnings' as track_name,
+  ts,
+  dur,
+  alert_name as slice_name
+FROM android_jank_alerts;
+
+DROP VIEW IF EXISTS android_jank_output;
+CREATE VIEW android_jank_output AS
+SELECT AndroidJankMetrics(
+  'warnings', (
+    SELECT RepeatedField(
+      AndroidJankMetrics_Warning(
+       'ts', ts,
+       'dur', dur,
+       'process_name', process_name,
+       'warning_text', alert_name))
+    FROM android_jank_alerts
+    ORDER BY process_name, ts, dur));
diff --git a/src/trace_processor/metrics/android/android_sysui_cuj.sql b/src/trace_processor/metrics/android/android_sysui_cuj.sql
index 9b3cdf2..f872e87 100644
--- a/src/trace_processor/metrics/android/android_sysui_cuj.sql
+++ b/src/trace_processor/metrics/android/android_sysui_cuj.sql
@@ -16,19 +16,14 @@
 DROP TABLE IF EXISTS android_sysui_cuj_last_cuj;
 CREATE TABLE android_sysui_cuj_last_cuj AS
   SELECT
-    process.name AS process_name,
+    process.name AS name,
     process.upid AS upid,
-    main_thread.utid AS main_thread_utid,
-    main_thread.name AS main_thread_name,
-    thread_track.id AS main_thread_track_id,
     SUBSTR(slice.name, 3, LENGTH(slice.name) - 3) AS cuj_name,
     ts AS ts_start,
     ts + dur AS ts_end
   FROM slice
   JOIN process_track ON slice.track_id = process_track.id
   JOIN process USING (upid)
-  JOIN thread AS main_thread ON main_thread.upid = process.upid AND main_thread.is_main_thread
-  JOIN thread_track USING (utid)
   WHERE
     slice.name LIKE 'J<%>'
     AND slice.dur > 0
@@ -38,70 +33,34 @@
   ORDER BY ts desc
   LIMIT 1;
 
-DROP VIEW IF EXISTS android_sysui_cuj_render_thread;
-CREATE VIEW android_sysui_cuj_render_thread AS
-  SELECT thread.*, last_cuj.ts_start as ts_cuj_start, last_cuj.ts_end as ts_cuj_end
-  FROM thread
-  JOIN android_sysui_cuj_last_cuj last_cuj USING (upid)
-  WHERE thread.name = 'RenderThread';
+SELECT RUN_METRIC(
+  'android/android_hwui_threads.sql',
+  'table_name_prefix', 'android_sysui_cuj',
+  'process_allowlist_table', 'android_sysui_cuj_last_cuj');
 
-DROP VIEW IF EXISTS android_sysui_cuj_gpu_completion_thread;
-CREATE VIEW android_sysui_cuj_gpu_completion_thread AS
-  SELECT thread.*, last_cuj.ts_start as ts_cuj_start, last_cuj.ts_end as ts_cuj_end
-  FROM thread
-  JOIN android_sysui_cuj_last_cuj last_cuj USING (upid)
-  WHERE thread.name = 'GPU completion';
+DROP TABLE IF EXISTS android_sysui_cuj_main_thread_slices_in_cuj;
+CREATE TABLE android_sysui_cuj_main_thread_slices_in_cuj AS
+SELECT slices.* FROM android_sysui_cuj_main_thread_slices slices
+JOIN android_sysui_cuj_last_cuj last_cuj
+ON ts >= last_cuj.ts_start AND ts <= last_cuj.ts_end;
 
-DROP VIEW IF EXISTS android_sysui_cuj_hwc_release_thread;
-CREATE VIEW android_sysui_cuj_hwc_release_thread AS
-  SELECT thread.*, last_cuj.ts_start as ts_cuj_start, last_cuj.ts_end as ts_cuj_end
-  FROM thread
-  JOIN android_sysui_cuj_last_cuj last_cuj USING (upid)
-  WHERE thread.name = 'HWC release';
+DROP TABLE IF EXISTS android_sysui_cuj_render_thread_slices_in_cuj;
+CREATE TABLE android_sysui_cuj_render_thread_slices_in_cuj AS
+SELECT slices.* FROM android_sysui_cuj_render_thread_slices slices
+JOIN android_sysui_cuj_last_cuj last_cuj
+ON ts >= last_cuj.ts_start AND ts <= last_cuj.ts_end;
 
-DROP TABLE IF EXISTS android_sysui_cuj_main_thread_slices;
-CREATE TABLE android_sysui_cuj_main_thread_slices AS
-  SELECT slice.*, ts + dur AS ts_end
-  FROM slice
-  JOIN android_sysui_cuj_last_cuj last_cuj
-    ON slice.track_id = last_cuj.main_thread_track_id
-  WHERE ts >= last_cuj.ts_start AND ts <= last_cuj.ts_end;
+DROP TABLE IF EXISTS android_sysui_cuj_hwc_release_slices_in_cuj;
+CREATE TABLE android_sysui_cuj_hwc_release_slices_in_cuj AS
+SELECT slices.* FROM android_sysui_cuj_hwc_release_slices slices
+JOIN android_sysui_cuj_last_cuj last_cuj
+ON ts >= last_cuj.ts_start AND ts <= last_cuj.ts_end;
 
-DROP TABLE IF EXISTS android_sysui_cuj_render_thread_slices;
-CREATE TABLE android_sysui_cuj_render_thread_slices AS
-  SELECT slice.*, ts + dur AS ts_end
-  FROM slice
-  JOIN thread_track ON slice.track_id = thread_track.id
-  JOIN android_sysui_cuj_render_thread USING (utid)
-  WHERE ts >= ts_cuj_start AND ts <= ts_cuj_end;
-
-DROP TABLE IF EXISTS android_sysui_cuj_gpu_completion_slices;
-CREATE TABLE android_sysui_cuj_gpu_completion_slices AS
-  SELECT
-    slice.*,
-    ts + dur AS ts_end,
-    -- Extracts 1234 from 'waiting for GPU completion 1234'
-    CAST(STR_SPLIT(slice.name, ' ', 4) AS INTEGER) as idx
-  FROM slice
-  JOIN thread_track ON slice.track_id = thread_track.id
-  JOIN android_sysui_cuj_gpu_completion_thread USING (utid)
-  WHERE
-    slice.name LIKE 'waiting for GPU completion %'
-    AND ts >= ts_cuj_start AND ts <= ts_cuj_end;
-
-DROP TABLE IF EXISTS android_sysui_cuj_hwc_release_slices;
-CREATE TABLE android_sysui_cuj_hwc_release_slices AS
-  SELECT
-    slice.*,
-    ts + dur as ts_end,
-    -- Extracts 1234 from 'waiting for HWC release 1234'
-    CAST(STR_SPLIT(slice.name, ' ', 4) AS INTEGER) as idx
-  FROM slice
-  JOIN thread_track ON slice.track_id = thread_track.id
-  JOIN android_sysui_cuj_hwc_release_thread USING (utid)
-  WHERE
-    slice.name LIKE 'waiting for HWC release %'
-    AND ts >= ts_cuj_start AND ts <= ts_cuj_end;
+DROP TABLE IF EXISTS android_sysui_cuj_gpu_completion_slices_in_cuj;
+CREATE TABLE android_sysui_cuj_gpu_completion_slices_in_cuj AS
+SELECT slices.* FROM android_sysui_cuj_gpu_completion_slices slices
+JOIN android_sysui_cuj_last_cuj last_cuj
+ON ts >= last_cuj.ts_start AND ts <= last_cuj.ts_end;
 
 DROP TABLE IF EXISTS android_sysui_cuj_frames;
 CREATE TABLE android_sysui_cuj_frames AS
@@ -113,8 +72,8 @@
       gcs.dur as gcs_dur,
       gcs.idx as idx,
       MAX(rts.ts) as rts_ts
-    FROM android_sysui_cuj_gpu_completion_slices gcs
-    JOIN android_sysui_cuj_render_thread_slices rts ON rts.ts < gcs.ts
+    FROM android_sysui_cuj_gpu_completion_slices_in_cuj gcs
+    JOIN android_sysui_cuj_render_thread_slices_in_cuj rts ON rts.ts < gcs.ts
     -- dispatchFrameCallbacks might be seen in case of
     -- drawing that happens on RT only (e.g. ripple effect)
     WHERE (rts.name = 'DrawFrame' OR rts.name = 'dispatchFrameCallbacks')
@@ -128,8 +87,8 @@
       mts.dur as mts_dur,
       MAX(gcs_rt.gcs_ts) as gcs_ts_start,
       MAX(gcs_rt.gcs_ts_end) as gcs_ts_end
-    FROM android_sysui_cuj_main_thread_slices mts
-    JOIN android_sysui_cuj_render_thread_slices rts
+    FROM android_sysui_cuj_main_thread_slices_in_cuj mts
+    JOIN android_sysui_cuj_render_thread_slices_in_cuj rts
       ON mts.ts < rts.ts AND mts.ts_end >= rts.ts
     LEFT JOIN gcs_to_rt_match gcs_rt ON gcs_rt.rts_ts = rts.ts
     WHERE mts.name = 'Choreographer#doFrame' AND rts.name = 'DrawFrame'
@@ -149,11 +108,11 @@
     COUNT(DISTINCT(rts.ts)) as draw_frames,
     COUNT(DISTINCT(gcs_rt.gcs_ts)) as gpu_completions
   FROM frame_boundaries f
-  JOIN android_sysui_cuj_render_thread_slices rts
+  JOIN android_sysui_cuj_render_thread_slices_in_cuj rts
     ON f.mts_ts < rts.ts AND f.mts_ts_end >= rts.ts
   LEFT JOIN gcs_to_rt_match gcs_rt
     ON rts.ts = gcs_rt.rts_ts
-  LEFT JOIN android_sysui_cuj_hwc_release_slices hwc USING (idx)
+  LEFT JOIN android_sysui_cuj_hwc_release_slices_in_cuj hwc USING (idx)
   WHERE rts.name = 'DrawFrame'
   GROUP BY f.mts_ts
   HAVING gpu_completions >= 1;
@@ -166,7 +125,7 @@
 DROP VIEW IF EXISTS android_sysui_cuj_main_thread_state_data;
 CREATE VIEW android_sysui_cuj_main_thread_state_data AS
 SELECT * FROM thread_state
-WHERE utid = (SELECT main_thread_utid FROM android_sysui_cuj_last_cuj);
+WHERE utid = (SELECT utid FROM android_sysui_cuj_main_thread);
 
 DROP TABLE IF EXISTS android_sysui_cuj_main_thread_state_vt;
 CREATE VIRTUAL TABLE android_sysui_cuj_main_thread_state_vt
@@ -215,7 +174,7 @@
     SUM(mts.dur) AS dur,
     COUNT(*) AS call_count
   FROM android_sysui_cuj_frames f
-  JOIN android_sysui_cuj_main_thread_slices mts
+  JOIN android_sysui_cuj_main_thread_slices_in_cuj mts
     ON mts.ts >= f.ts_main_thread_start AND mts.ts < f.ts_main_thread_end
   WHERE mts.name = 'binder transaction'
   GROUP BY f.frame_number;
@@ -226,7 +185,7 @@
   frame_number,
   'RenderThread - long shader_compile' AS jank_cause
   FROM android_sysui_cuj_frames f
-  JOIN android_sysui_cuj_render_thread_slices rts
+  JOIN android_sysui_cuj_render_thread_slices_in_cuj rts
     ON rts.ts >= f.ts_render_thread_start AND rts.ts < f.ts_render_thread_end
   WHERE rts.name = 'shader_compile'
   AND rts.dur > 8000000
@@ -236,7 +195,7 @@
   frame_number,
   'RenderThread - long flush layers' AS jank_cause
   FROM android_sysui_cuj_frames f
-  JOIN android_sysui_cuj_render_thread_slices rts
+  JOIN android_sysui_cuj_render_thread_slices_in_cuj rts
     ON rts.ts >= f.ts_render_thread_start AND rts.ts < f.ts_render_thread_end
   WHERE rts.name = 'flush layers'
   AND rts.dur > 8000000
diff --git a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
index 12c6f2c..1f3d692 100644
--- a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
+++ b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1 b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
index dd4358f..58aa1c0 100644
--- a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
+++ b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
@@ -2,4 +2,4 @@
 // SHA1(tools/gen_binary_descriptors)
 // 70978e4b6e0d773dd222715b1c7e74c25d344da0
 // SHA1(protos/perfetto/metrics/metrics.proto)
-// e0033c91fc2ae9cd9730bbf5684f5661c16e4d5c
+// dc5f60a304848e44572924b88f4b1493c8e99588
diff --git a/test/trace_processor/graphics/android_jank.out b/test/trace_processor/graphics/android_jank.out
new file mode 100644
index 0000000..0bed674
--- /dev/null
+++ b/test/trace_processor/graphics/android_jank.out
@@ -0,0 +1,38 @@
+android_jank: {
+  warnings {
+    ts: 4000500
+    dur: 4999500
+    process_name: "com.android.systemui"
+    warning_text: "Expensive measure/layout pass"
+  }
+  warnings {
+    ts: 4001000
+    dur: 3499500
+    process_name: "com.android.systemui"
+    warning_text: "Inflation during ListView recycling"
+  }
+  warnings {
+    ts: 8000000
+    dur: 900000
+    process_name: "com.android.systemui"
+    warning_text: "Inflation during ListView recycling"
+  }
+  warnings {
+    ts: 1000000
+    dur: 19000000
+    process_name: "com.android.systemui"
+    warning_text: "Scheduling delay"
+  }
+  warnings {
+    ts: 116000000
+    dur: 1300000
+    process_name: "com.google.android.inputmethod.latin"
+    warning_text: "Expensive rendering with Canvas#saveLayer()"
+  }
+  warnings {
+    ts: 108000000
+    dur: 4000000
+    process_name: "com.google.android.inputmethod.latin"
+    warning_text: "Expensive Bitmap uploads"
+  }
+}
diff --git a/test/trace_processor/graphics/android_jank.py b/test/trace_processor/graphics/android_jank.py
new file mode 100644
index 0000000..d2195a3
--- /dev/null
+++ b/test/trace_processor/graphics/android_jank.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from os import sys, path
+
+import synth_common
+
+
+def add_main_thread_atrace(trace, ts, ts_end, buf):
+  trace.add_atrace_begin(ts=ts, tid=PID, pid=PID, buf=buf)
+  trace.add_atrace_end(ts=ts_end, tid=PID, pid=PID)
+
+
+def add_render_thread_atrace(trace, ts, ts_end, buf):
+  trace.add_atrace_begin(ts=ts, tid=RTID, pid=PID, buf=buf)
+  trace.add_atrace_end(ts=ts_end, tid=RTID, pid=PID)
+
+
+trace = synth_common.create_trace()
+
+trace.add_packet()
+trace.add_package_list(
+    ts=0, name="com.android.systemui", uid=10001, version_code=1)
+trace.add_package_list(
+    ts=0,
+    name="com.google.android.inputmethod.latin",
+    uid=10002,
+    version_code=1)
+
+trace.add_process(pid=1000, ppid=1, cmdline="com.android.systemui", uid=10001)
+trace.add_thread(
+    tid=1001, tgid=1000, cmdline="RenderThread", name="RenderThread")
+trace.add_process(
+    pid=2000, ppid=1, cmdline="com.google.android.inputmethod.latin", uid=10002)
+trace.add_thread(
+    tid=2001, tgid=2000, cmdline="RenderThread", name="RenderThread")
+
+trace.add_ftrace_packet(cpu=0)
+
+# com.android.systemui
+
+trace.add_atrace_begin(
+    ts=1_000_000, tid=1000, pid=1000, buf='Choreographer#doFrame')
+trace.add_atrace_begin(ts=1_000_100, tid=1000, pid=1000, buf='traversal')
+trace.add_atrace_begin(ts=1_000_500, tid=1000, pid=1000, buf='measure')
+trace.add_atrace_end(ts=4_000_000, tid=1000, pid=1000)
+trace.add_atrace_begin(ts=4_000_500, tid=1000, pid=1000, buf='layout')
+trace.add_atrace_begin(ts=4_001_000, tid=1000, pid=1000, buf='setupListItem')
+trace.add_atrace_begin(ts=4_500_000, tid=1000, pid=1000, buf='inflate')
+trace.add_atrace_end(ts=5_500_000, tid=1000, pid=1000)
+trace.add_atrace_begin(ts=6_500_000, tid=1000, pid=1000, buf='inflate')
+trace.add_atrace_end(ts=7_500_000, tid=1000, pid=1000)
+trace.add_atrace_end(ts=7_500_500, tid=1000, pid=1000)
+trace.add_atrace_begin(ts=8_000_000, tid=1000, pid=1000, buf='obtainView')
+trace.add_atrace_begin(ts=8_000_100, tid=1000, pid=1000, buf='inflate')
+trace.add_atrace_end(ts=8_500_000, tid=1000, pid=1000)
+trace.add_atrace_end(ts=8_900_000, tid=1000, pid=1000)
+trace.add_atrace_end(ts=9_000_000, tid=1000, pid=1000)
+trace.add_atrace_end(ts=9_000_000, tid=1000, pid=1000)
+trace.add_atrace_end(ts=20_000_000, tid=1000, pid=1000)
+
+trace.add_sched(ts=1_000_000, prev_pid=0, next_pid=1000)
+trace.add_sched(ts=10_000_000, prev_pid=1000, next_pid=0, prev_state='R')
+trace.add_sched(ts=10_500_000, prev_pid=0, next_pid=0)
+trace.add_sched(ts=19_500_000, prev_pid=0, next_pid=1000)
+trace.add_sched(ts=20_500_000, prev_pid=1000, next_pid=0, prev_state='R')
+
+# com.google.android.inputmethod.latin
+
+trace.add_atrace_begin(
+    ts=101_000_000, tid=2000, pid=2000, buf='Choreographer#doFrame')
+trace.add_atrace_begin(ts=101_000_100, tid=2000, pid=2000, buf='traversal')
+trace.add_atrace_begin(ts=101_000_500, tid=2000, pid=2000, buf='measure')
+trace.add_atrace_end(ts=104_000_000, tid=2000, pid=2000)
+trace.add_atrace_begin(ts=104_000_500, tid=2000, pid=2000, buf='layout')
+trace.add_atrace_end(ts=105_000_000, tid=2000, pid=2000)
+trace.add_atrace_end(ts=105_000_000, tid=2000, pid=2000)
+trace.add_atrace_begin(ts=105_000_000, tid=2000, pid=2000, buf='draw')
+trace.add_atrace_end(ts=119_000_000, tid=2000, pid=2000)
+trace.add_atrace_end(ts=120_000_000, tid=2000, pid=2000)
+
+trace.add_atrace_begin(ts=105_000_000, tid=2001, pid=2000, buf='DrawFrame')
+trace.add_atrace_begin(
+    ts=108_000_000, tid=2001, pid=2000, buf='Upload 300x300 Texture')
+trace.add_atrace_end(ts=112_000_000, tid=2001, pid=2000)
+trace.add_atrace_begin(
+    ts=116_000_000,
+    tid=2001,
+    pid=2000,
+    buf='alpha caused unclipped saveLayer 201x319')
+trace.add_atrace_end(ts=117_300_000, tid=2001, pid=2000)
+trace.add_atrace_end(ts=118_000_000, tid=2001, pid=2000)
+
+trace.add_sched(ts=101_000_000, prev_pid=0, next_pid=2000)
+trace.add_sched(ts=120_000_000, prev_pid=2000, next_pid=0, prev_state='R')
+trace.add_sched(ts=120_500_000, prev_pid=0, next_pid=0)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/graphics/android_sysui_cuj.py b/test/trace_processor/graphics/android_sysui_cuj.py
index b5d56c6..2b2724b 100644
--- a/test/trace_processor/graphics/android_sysui_cuj.py
+++ b/test/trace_processor/graphics/android_sysui_cuj.py
@@ -195,4 +195,14 @@
 add_render_thread_atrace(
     trace, ts=305_000_000, ts_end=308_000_000, buf="dispatchFrameCallbacks")
 
+# One more frame after the CUJ is finished
+add_frame(
+    trace,
+    ts_do_frame=1_100_000_000,
+    ts_end_do_frame=1_200_000_000,
+    ts_draw_frame=1_150_000_000,
+    ts_end_draw_frame=1_300_000_000,
+    ts_gpu=1_400_000_000,
+    ts_end_gpu=1_500_000_000)
+
 sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/graphics/index b/test/trace_processor/graphics/index
index 256e72d..61cd5c2 100644
--- a/test/trace_processor/graphics/index
+++ b/test/trace_processor/graphics/index
@@ -32,3 +32,6 @@
 
 # Composition layer
 composition_layer.py composition_layer_count.sql composition_layer_count.out
+
+# Android Jank metrics
+android_jank.py android_jank android_jank.out
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index ab061c7..89fc3b1 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -493,7 +493,8 @@
                  'android_thread_time_in_state',
                  'android_surfaceflinger',
                  'android_batt',
-                 'android_sysui_cuj']) {
+                 'android_sysui_cuj',
+                 'android_jank']) {
       this.updateStatus(`Computing ${metric} metric`);
       try {
         // We don't care about the actual result of metric here as we are just