Merge "tp: add metrics for lock contention on main thread during app startup"
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index aef7c10..a52e36c 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -45,7 +45,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 31.
+  // Next id: 33.
   message ToFirstFrame {
     // The duration between the intent received and first frame.
     optional int64 dur_ns = 1;
@@ -104,7 +104,18 @@
     // Time spent on garbage collection.
     optional Slice time_gc_total = 29;
     optional Slice time_gc_on_cpu = 30;
-    // Deprecated was other_process_to_activity_cpu_ratio
+
+    // Time spent in lock contention on the main thread of the process being
+    // started up. This includes *all* types of lock contention not just monitor
+    // contention.
+    optional Slice time_lock_contention_thread_main = 31;
+
+    // Time spent in monitor lock contention on the main thread of the
+    // process being started up. This will be a subset of the time counted by
+    // |time_lock_contention_thread_main|.
+    optional Slice time_monitor_contention_thread_main = 32;
+
+    // Removed: was other_process_to_activity_cpu_ratio.
     reserved 12;
 
     // Removed: was uint32 versions of to_post_fork, to_activity_thread_main and
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 6425b3d..58571a9 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -1245,7 +1245,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 31.
+  // Next id: 33.
   message ToFirstFrame {
     // The duration between the intent received and first frame.
     optional int64 dur_ns = 1;
@@ -1304,7 +1304,18 @@
     // Time spent on garbage collection.
     optional Slice time_gc_total = 29;
     optional Slice time_gc_on_cpu = 30;
-    // Deprecated was other_process_to_activity_cpu_ratio
+
+    // Time spent in lock contention on the main thread of the process being
+    // started up. This includes *all* types of lock contention not just monitor
+    // contention.
+    optional Slice time_lock_contention_thread_main = 31;
+
+    // Time spent in monitor lock contention on the main thread of the
+    // process being started up. This will be a subset of the time counted by
+    // |time_lock_contention_thread_main|.
+    optional Slice time_monitor_contention_thread_main = 32;
+
+    // Removed: was other_process_to_activity_cpu_ratio.
     reserved 12;
 
     // Removed: was uint32 versions of to_post_fork, to_activity_thread_main and
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 77c159a..85358ba 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index e6556fb..eb2dd7a 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -164,12 +164,12 @@
           MAIN_THREAD_TIME_FOR_LAUNCH_AND_STATE(launches.id, 'S'), 0
         )
       ),
-      'mcycles_by_core_type', AndroidStartupMetric_McyclesByCoreType(
+      'mcycles_by_core_type', NULL_IF_EMPTY(AndroidStartupMetric_McyclesByCoreType(
         'little', MCYCLES_FOR_LAUNCH_AND_CORE_TYPE(launches.id, 'little'),
         'big', MCYCLES_FOR_LAUNCH_AND_CORE_TYPE(launches.id, 'big'),
         'bigger', MCYCLES_FOR_LAUNCH_AND_CORE_TYPE(launches.id, 'bigger'),
         'unknown', MCYCLES_FOR_LAUNCH_AND_CORE_TYPE(launches.id, 'unknown')
-      ),
+      )),
       'to_post_fork',
         LAUNCH_TO_MAIN_THREAD_SLICE_PROTO(launches.id, 'PostFork'),
       'to_activity_thread_main',
@@ -214,6 +214,16 @@
             slice_name GLOB "*concurrent copying GC"
           )
       ),
+      'time_lock_contention_thread_main',
+        DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(
+          launches.id,
+          'Lock contention on*'
+        ),
+      'time_monitor_contention_thread_main',
+        DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(
+          launches.id,
+          'Lock contention on a monitor*'
+        ),
       'time_before_start_process', (
         SELECT STARTUP_SLICE_PROTO(ts - launches.ts)
         FROM ZYGOTE_FORK_FOR_LAUNCH(launches.id)
diff --git a/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql b/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql
index 25ea8cd..9160bee 100644
--- a/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql
+++ b/src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql
@@ -22,9 +22,9 @@
   SUBSTR(name, 19) id
 FROM slice
 WHERE
-  name GLOB 'launchingActivity#*'
-  AND dur != 0
-  AND INSTR(name, ':') = 0;
+  name GLOB 'launchingActivity#*' AND
+  dur != 0 AND
+  INSTR(name, ':') = 0;
 
 DROP VIEW IF EXISTS launch_complete_events;
 CREATE VIEW launch_complete_events AS
@@ -35,7 +35,9 @@
 FROM (
   SELECT ts, SUBSTR(name, 19) completed
   FROM slice
-  WHERE dur = 0 AND name GLOB 'launchingActivity#*:completed:*'
+  WHERE
+    dur = 0 AND
+    name GLOB 'launchingActivity#*:completed:*'
 )
 GROUP BY 1, 2;
 
@@ -46,4 +48,5 @@
   ts + dur ts_end,
   dur,
   package_name
-FROM launch_async_events JOIN launch_complete_events USING (id);
+FROM launch_async_events
+JOIN launch_complete_events USING (id);
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 571bebd..7ee083c 100644
--- a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
+++ b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
@@ -45,7 +45,8 @@
 -- Given a launch id and GLOB for a slice name, returns columns for matching slices.
 SELECT CREATE_VIEW_FUNCTION(
   'SLICES_FOR_LAUNCH_AND_SLICE_NAME(launch_id INT, slice_name STRING)',
-  'slice_name STRING, slice_ts INT, slice_dur INT, thread_name STRING, arg_set_id INT', '
+  'slice_name STRING, slice_ts INT, slice_dur INT, thread_name STRING, arg_set_id INT',
+  '
     SELECT slice_name, slice_ts, slice_dur, thread_name, arg_set_id
     FROM thread_slices_for_all_launches
     WHERE launch_id = $launch_id AND slice_name GLOB $slice_name
@@ -56,18 +57,32 @@
 -- summing the slice durations across the whole startup.
 SELECT CREATE_FUNCTION(
   'DUR_SUM_SLICE_PROTO_FOR_LAUNCH(launch_id LONG, slice_name STRING)',
-  'PROTO', '
+  'PROTO',
+  '
     SELECT NULL_IF_EMPTY(STARTUP_SLICE_PROTO(SUM(slice_dur)))
     FROM thread_slices_for_all_launches
     WHERE launch_id = $launch_id AND slice_name GLOB $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(launch_id LONG, slice_name STRING)',
+  'PROTO',
+  '
+    SELECT NULL_IF_EMPTY(STARTUP_SLICE_PROTO(SUM(slice_dur)))
+    FROM thread_slices_for_all_launches
+    WHERE launch_id = $launch_id AND slice_name GLOB $slice_name AND is_main_thread
+  '
+);
+
 -- 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.
 SELECT CREATE_FUNCTION(
   'LAUNCH_TO_MAIN_THREAD_SLICE_PROTO(launch_id INT, slice_name STRING)',
-  'PROTO', '
+  'PROTO',
+  '
     SELECT STARTUP_SLICE_PROTO(slice_ts - launch_ts)
     FROM thread_slices_for_all_launches
     WHERE slice_name GLOB $slice_name AND launch_id = $launch_id AND is_main_thread
diff --git a/test/trace_processor/startup/android_startup.out b/test/trace_processor/startup/android_startup.out
index 5d323e4..bbb5d45 100644
--- a/test/trace_processor/startup/android_startup.out
+++ b/test/trace_processor/startup/android_startup.out
@@ -32,8 +32,6 @@
         dur_ns: 8
         dur_ms: 8e-06
       }
-      mcycles_by_core_type {
-      }
       time_activity_start {
         dur_ns: 2
         dur_ms: 2e-06
diff --git a/test/trace_processor/startup/android_startup_attribution.out b/test/trace_processor/startup/android_startup_attribution.out
index 3c74145..8d18b5e 100644
--- a/test/trace_processor/startup/android_startup_attribution.out
+++ b/test/trace_processor/startup/android_startup_attribution.out
@@ -30,8 +30,6 @@
         dur_ns: 40
         dur_ms: 4e-05
       }
-      mcycles_by_core_type {
-      }
       jit_compiled_methods: 2
       time_jit_thread_pool_on_cpu {
         dur_ns: 20
diff --git a/test/trace_processor/startup/android_startup_lock_contention.out b/test/trace_processor/startup/android_startup_lock_contention.out
new file mode 100644
index 0000000..89455a6
--- /dev/null
+++ b/test/trace_processor/startup/android_startup_lock_contention.out
@@ -0,0 +1,54 @@
+android_startup {
+  startup {
+    startup_id: 1
+    package_name: "com.google.android.calendar"
+    process_name: "com.google.android.calendar"
+    zygote_new_process: false
+    to_first_frame {
+      dur_ns: 100
+      main_thread_by_task_state {
+        running_dur_ns: 0
+        runnable_dur_ns: 0
+        uninterruptible_sleep_dur_ns: 0
+        interruptible_sleep_dur_ns: 0
+      }
+      other_processes_spawned_count: 0
+      time_bind_application {
+        dur_ns: 3
+        dur_ms: 3e-06
+      }
+      dur_ms: 0.0001
+      to_bind_application {
+        dur_ns: 2
+        dur_ms: 2e-06
+      }
+      time_lock_contention_thread_main {
+        dur_ns: 20
+        dur_ms: 2e-05
+      }
+      time_monitor_contention_thread_main {
+        dur_ns: 10
+        dur_ms: 1e-05
+      }
+    }
+    activity_hosting_process_count: 1
+    process {
+      name: "com.google.android.calendar"
+      uid: 10001
+      package {
+        package_name: "com.google.android.calendar"
+        apk_version_code: 123
+        debuggable: false
+      }
+      packages_for_uid {
+        package_name: "com.google.android.calendar"
+        apk_version_code: 123
+        debuggable: false
+      }
+    }
+    event_timestamps {
+      intent_received: 110
+      first_frame: 210
+    }
+  }
+}
diff --git a/test/trace_processor/startup/android_startup_lock_contention.py b/test/trace_processor/startup/android_startup_lock_contention.py
new file mode 100644
index 0000000..acc082c
--- /dev/null
+++ b/test/trace_processor/startup/android_startup_lock_contention.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from os import sys
+
+import synth_common
+
+trace = synth_common.create_trace()
+trace.add_packet()
+trace.add_process(1, 0, 'init')
+trace.add_process(2, 1, 'system_server')
+trace.add_process(3, 1, 'com.google.android.calendar', 10001)
+trace.add_thread(4, 3, 'Binder')
+
+trace.add_package_list(
+    ts=1, name='com.google.android.calendar', uid=10001, version_code=123)
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_atrace_async_begin(ts=110, tid=2, pid=2, buf='launchingActivity#1')
+trace.add_atrace_async_end(ts=210, tid=2, pid=2, buf='launchingActivity#1')
+
+# Required so we know this process is the one being started up.
+trace.add_atrace_begin(ts=112, tid=3, pid=3, buf='bindApplication')
+trace.add_atrace_end(ts=115, tid=3, pid=3)
+
+# Add some non-monitor lock contention.
+trace.add_atrace_begin(
+    ts=120,
+    tid=3,
+    pid=3,
+    buf='Lock contention on thread list lock (owner tid: 2)')
+trace.add_atrace_end(ts=130, tid=3, pid=3)
+
+# Add monitor contention
+trace.add_atrace_begin(
+    ts=140,
+    tid=3,
+    pid=3,
+    buf='Lock contention on a monitor lock (owner tid: 2)')
+trace.add_atrace_end(ts=150, tid=3, pid=3)
+
+# Lock contention on non-main thread should not be counted.
+trace.add_atrace_begin(
+    ts=155,
+    tid=4,
+    pid=3,
+    buf='Lock contention on a monitor lock (owner tid: 3)')
+trace.add_atrace_end(ts=160, tid=4, pid=3)
+
+# Lock contention in other process should not be counted.
+trace.add_atrace_begin(
+    ts=175,
+    tid=2,
+    pid=2,
+    buf='Lock contention on a monitor lock (owner tid: 3)')
+trace.add_atrace_end(ts=180, tid=2, pid=2)
+
+trace.add_atrace_instant(
+    ts=211,
+    tid=2,
+    pid=2,
+    buf='launchingActivity#1:completed:com.google.android.calendar')
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/startup/android_startup_minsdk33.out b/test/trace_processor/startup/android_startup_minsdk33.out
index e7022b5..a6b1b06 100644
--- a/test/trace_processor/startup/android_startup_minsdk33.out
+++ b/test/trace_processor/startup/android_startup_minsdk33.out
@@ -13,8 +13,6 @@
       }
       other_processes_spawned_count: 0
       dur_ms: 0.0001
-      mcycles_by_core_type {
-      }
     }
     activity_hosting_process_count: 0
     event_timestamps {
diff --git a/test/trace_processor/startup/android_startup_process_track.out b/test/trace_processor/startup/android_startup_process_track.out
index 7a8ae0f..e0f6011 100644
--- a/test/trace_processor/startup/android_startup_process_track.out
+++ b/test/trace_processor/startup/android_startup_process_track.out
@@ -20,8 +20,6 @@
         dur_ns: 2
         dur_ms: 2e-06
       }
-      mcycles_by_core_type {
-      }
       time_bind_application {
         dur_ns: 1
         dur_ms: 1e-06
@@ -59,8 +57,6 @@
         dur_ns: 2
         dur_ms: 2e-06
       }
-      mcycles_by_core_type {
-      }
       time_bind_application {
         dur_ns: 1
         dur_ms: 1e-06
diff --git a/test/trace_processor/startup/index b/test/trace_processor/startup/index
index ee6003e..afdfdf5 100644
--- a/test/trace_processor/startup/index
+++ b/test/trace_processor/startup/index
@@ -7,6 +7,9 @@
 android_startup_process_track.py android_startup android_startup_process_track.out
 android_startup_attribution.py android_startup android_startup_attribution.out
 
+# Test lock contention is correctly attributed.
+android_startup_lock_contention.py android_startup android_startup_lock_contention.out
+
 # Other metrics associated with startup.
 android_startup_battery.py android_batt android_batt_counters.out
 android_startup_cpu.py android_cpu android_startup_cpu.out
diff --git a/tools/diff_test_trace_processor.py b/tools/diff_test_trace_processor.py
index 2c34ae6..6a0c5f4 100755
--- a/tools/diff_test_trace_processor.py
+++ b/tools/diff_test_trace_processor.py
@@ -264,7 +264,7 @@
           sys.stderr.write(f"Rebasing {expected_path}\n")
           with open(expected_path, 'w') as f:
             f.write(result.actual)
-          rebase += 1
+          rebased += 1
         else:
           sys.stderr.write(
               f"Rebase failed for {expected_path} as query failed\n")
@@ -418,6 +418,7 @@
       f"[==========] {len(tests)} tests ran. ({test_time_ms} ms total)\n")
   sys.stderr.write(f"[  PASSED  ] {len(tests) - test_failures} tests.\n")
   if args.rebase:
+    sys.stderr.write('\n')
     sys.stderr.write(f"{rebased} tests rebased.\n")
 
   if test_failures == 0: