[metrics] Add thread creation spam stats to android_task_names
First added an ANDROID_THREAD_CREATION_SPAM stdlib function to
find stats of rapidly created threads.
To support finding thread names where only some suffix changes,
e.g Thread-123, we aggreate the stats per process per thread prefix.
We currently use a STR_SPLIT workaround to find prefixes by splitting
thread names by some common delimiters. When we have perfetto REGEXP
support, we can replace this with a more robust solution.
Test: tools/diff_test_trace_processor.py out/android/trace_processor_shell
Change-Id: I3862462b17cc8f494a6f2577f05b58d7289e1044
diff --git a/Android.bp b/Android.bp
index 63c30e1..a7bf077 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10508,6 +10508,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql",
"src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql",
"src/trace_processor/perfetto_sql/stdlib/android/statsd.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/thread.sql",
"src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scroll_janks.sql",
"src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql",
"src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql",
diff --git a/BUILD b/BUILD
index a7e0557..6596748 100644
--- a/BUILD
+++ b/BUILD
@@ -2223,6 +2223,7 @@
"src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql",
"src/trace_processor/perfetto_sql/stdlib/android/slices.sql",
"src/trace_processor/perfetto_sql/stdlib/android/statsd.sql",
+ "src/trace_processor/perfetto_sql/stdlib/android/thread.sql",
],
)
diff --git a/protos/perfetto/metrics/android/task_names.proto b/protos/perfetto/metrics/android/task_names.proto
index 3aed87f..dbf985a 100644
--- a/protos/perfetto/metrics/android/task_names.proto
+++ b/protos/perfetto/metrics/android/task_names.proto
@@ -18,6 +18,12 @@
message AndroidTaskNames {
message Process {
+ // Identifies processes creating and destroying threads very quickly.
+ message TaskCreationSpam {
+ optional string thread_name_prefix = 1;
+ optional uint32 max_count_per_sec = 2;
+ }
+
optional int64 pid = 1;
// Process name.
@@ -31,6 +37,12 @@
// Packages matching the process uid.
repeated string uid_package_name = 5;
+
+ // Tasks created and destroyed within a second.
+ optional TaskCreationSpam short_lived_tasks = 6;
+
+ // Tasks created and not necessarily destroyed.
+ optional TaskCreationSpam long_lived_tasks = 7;
}
repeated Process process = 1;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 23e9fde..37fd6a5 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -1990,6 +1990,12 @@
message AndroidTaskNames {
message Process {
+ // Identifies processes creating and destroying threads very quickly.
+ message TaskCreationSpam {
+ optional string thread_name_prefix = 1;
+ optional uint32 max_count_per_sec = 2;
+ }
+
optional int64 pid = 1;
// Process name.
@@ -2003,6 +2009,12 @@
// Packages matching the process uid.
repeated string uid_package_name = 5;
+
+ // Tasks created and destroyed within a second.
+ optional TaskCreationSpam short_lived_tasks = 6;
+
+ // Tasks created and not necessarily destroyed.
+ optional TaskCreationSpam long_lived_tasks = 7;
}
repeated Process process = 1;
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 36ec809..8ed392d 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_task_names.sql b/src/trace_processor/metrics/sql/android/android_task_names.sql
index b86186b..82f4e11 100644
--- a/src/trace_processor/metrics/sql/android/android_task_names.sql
+++ b/src/trace_processor/metrics/sql/android/android_task_names.sql
@@ -15,6 +15,7 @@
--
SELECT RUN_METRIC('android/process_metadata.sql');
+SELECT IMPORT('android.thread');
DROP VIEW IF EXISTS android_task_names_output;
CREATE VIEW android_task_names_output AS
@@ -43,7 +44,19 @@
'process_name', p.name,
'thread_name', threads_by_upid.thread_names,
'uid', p.uid,
- 'uid_package_name', upid_packages.packages
+ 'uid_package_name', upid_packages.packages,
+ 'short_lived_tasks', (
+ SELECT AndroidTaskNames_Process_TaskCreationSpam(
+ 'thread_name_prefix', s.thread_name_prefix,
+ 'max_count_per_sec', s.max_count_per_sec
+ ) FROM ANDROID_THREAD_CREATION_SPAM(1e9, 1e9) s WHERE s.pid = p.pid
+ ),
+ 'long_lived_tasks', (
+ SELECT AndroidTaskNames_Process_TaskCreationSpam(
+ 'thread_name_prefix', s.thread_name_prefix,
+ 'max_count_per_sec', s.max_count_per_sec
+ ) FROM ANDROID_THREAD_CREATION_SPAM(NULL, 1e9) s WHERE s.pid = p.pid
+ )
)
)
)
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
index 24187af..2cb7026 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn
@@ -25,5 +25,6 @@
"process_metadata.sql",
"slices.sql",
"statsd.sql",
+ "thread.sql",
]
}
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/thread.sql b/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
new file mode 100644
index 0000000..ced6b3c
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/thread.sql
@@ -0,0 +1,64 @@
+--
+-- Copyright 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
+--
+-- 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.
+
+SELECT CREATE_FUNCTION(
+ 'INTERNAL_THREAD_PREFIX(thread_name STRING)',
+ 'STRING',
+ '
+ SELECT STR_SPLIT(STR_SPLIT(STR_SPLIT(STR_SPLIT($thread_name, "-", 0), "[", 0), ":", 0), " ", 0);
+ '
+);
+
+-- Per process stats of threads created in a process
+--
+-- @arg min_thread_dur FLOAT Minimum duration between creating and destroying a thread before
+-- their the thread creation event is considered. If NULL, considers all thread creations.
+-- @arg sliding_window_dur FLOAT Sliding window duration for counting the thread creations. Each
+-- window starts at the first thread creation per <process, thread_name_prefix>.
+--
+-- @column process_name Process name creating threads.
+-- @column thread_name_prefix String prefix of thread names created.
+-- @column max_count_per_sec Max number of threads created within a time window.
+SELECT CREATE_VIEW_FUNCTION(
+ 'ANDROID_THREAD_CREATION_SPAM(min_thread_dur FLOAT, window_dur FLOAT)',
+ 'process_name STRING, pid INT, thread_name_prefix STRING, max_count_per_sec INT',
+ '
+ WITH
+ x AS (
+ SELECT
+ pid,
+ upid,
+ INTERNAL_THREAD_PREFIX(thread.name) AS thread_name_prefix,
+ process.name AS process_name,
+ COUNT(thread.start_ts)
+ OVER (
+ PARTITION BY upid, thread.name
+ ORDER BY thread.start_ts
+ RANGE BETWEEN CURRENT ROW AND CAST($window_dur AS INT64) FOLLOWING
+ ) AS count
+ FROM thread
+ JOIN process
+ USING (upid)
+ WHERE
+ ($min_thread_dur AND (thread.end_ts - thread.start_ts) <= $min_thread_dur)
+ OR $min_thread_dur IS NULL
+ )
+ SELECT process_name, pid, thread_name_prefix, MAX(count) AS max_count_per_sec
+ FROM x
+ GROUP BY upid, thread_name_prefix
+ HAVING max_count_per_sec > 0
+ ORDER BY count DESC;
+ '
+);
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index ba2d198..181d97b 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -477,3 +477,38 @@
trace=DataPath('android_monitor_contention_trace.atr'),
query=Metric('android_monitor_contention'),
out=Path('android_monitor_contention.out'))
+
+ def test_thread_creation_spam(self):
+ return DiffTestBlueprint(
+ trace=DataPath('android_monitor_contention_trace.atr'),
+ query="""
+ SELECT IMPORT('android.thread');
+ SELECT * FROM ANDROID_THREAD_CREATION_SPAM(1e9, 1e9);
+ """,
+ out=Csv("""
+ "process_name","pid","thread_name_prefix","max_count_per_sec"
+ "com.android.providers.media.module",3487,"SharedPreferenc",3
+ "com.android.providers.media.module",3487,"MediaCodec",2
+ "/apex/com.android.adbd/bin/adbd",527,"shell",1
+ "media.swcodec",563,"id.hevc.decoder",1
+ "system_server",642,"Thread",1
+ "sh",3474,"sh",1
+ "sh",3476,"sh",1
+ "sh",3478,"sh",1
+ "am",3480,"am",1
+ "cmd",3482,"binder",1
+ "cmd",3482,"cmd",1
+ "com.android.providers.media.module",3487,"CodecLooper",1
+ "sh",3517,"sh",1
+ "sgdisk",3521,"sgdisk",1
+ "blkid",3523,"blkid",1
+ "binder:243_4",3524,"binder",1
+ "fsck_msdos",3525,"fsck",1
+ "binder:243_4",3526,"binder",1
+ "sh",3532,"sh",1
+ "cut",3534,"cut",1
+ "sh",3536,"sh",1
+ "sh",3544,"sh",1
+ "sh",3546,"sh",1
+ "sh",3564,"sh",1
+ """))
diff --git a/test/trace_processor/diff_tests/tables/tests.py b/test/trace_processor/diff_tests/tables/tests.py
index 82fe9c3..b4b441c 100644
--- a/test/trace_processor/diff_tests/tables/tests.py
+++ b/test/trace_processor/diff_tests/tables/tests.py
@@ -175,6 +175,40 @@
}
}
}
+ packet {
+ ftrace_events {
+ cpu: 0
+ event {
+ timestamp: 21963005381
+ pid: 524
+ task_newtask {
+ pid: 1576
+ comm: "adbd"
+ clone_flags: 18874368
+ oom_score_adj: -1000
+ }
+ }
+ event {
+ timestamp: 21973005381
+ pid: 523
+ task_newtask {
+ pid: 1574
+ comm: "sh"
+ clone_flags: 18874368
+ oom_score_adj: -1000
+ }
+ }
+ event {
+ timestamp: 21963008381
+ pid: 1572
+ sched_process_free {
+ comm: "adbd"
+ pid: 1576
+ prio: 120
+ }
+ }
+ }
+ }
"""),
query=Metric('android_task_names'),
out=TextProto(r"""
@@ -190,6 +224,28 @@
uid: 10001
uid_package_name: "com.google.android.gm"
}
+ process {
+ pid: 1576
+ process_name: "adbd"
+ thread_name: "adbd"
+ short_lived_tasks {
+ thread_name_prefix: "adbd"
+ max_count_per_sec: 1
+ }
+ long_lived_tasks {
+ thread_name_prefix: "adbd"
+ max_count_per_sec: 1
+ }
+ }
+ process {
+ pid: 1574
+ process_name: "sh"
+ thread_name: "sh"
+ long_lived_tasks {
+ thread_name_prefix: "sh"
+ max_count_per_sec: 1
+ }
+ }
}
"""))