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