aboutsummaryrefslogtreecommitdiff
path: root/python/perfetto/experimental/slice_breakdown/breakdown.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/perfetto/experimental/slice_breakdown/breakdown.py')
-rw-r--r--python/perfetto/experimental/slice_breakdown/breakdown.py194
1 files changed, 194 insertions, 0 deletions
diff --git a/python/perfetto/experimental/slice_breakdown/breakdown.py b/python/perfetto/experimental/slice_breakdown/breakdown.py
new file mode 100644
index 000000000..9eb99defe
--- /dev/null
+++ b/python/perfetto/experimental/slice_breakdown/breakdown.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# Copyright (C) 2021 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 perfetto.trace_processor.api import TraceProcessor
+from perfetto.trace_processor.api import TraceProcessorException
+
+
+def compute_breakdown(tp: TraceProcessor,
+ start_ts=None,
+ end_ts=None,
+ process_name=None):
+ """For each userspace slice in the trace processor instance |tp|, computes
+ the self-time of that slice grouping by process name, thread name
+ and thread state.
+
+ Args:
+ tp: the trace processor instance to query.
+ start_ts: optional bound to only consider slices after this ts
+ end_ts: optional bound to only consider slices until this ts
+ process_name: optional process name to filter for slices; specifying
+ this argument can make computing the breakdown a lot faster.
+
+ Returns:
+ A Pandas dataframe containing the total self time taken by a slice stack
+ broken down by process name, thread name and thread state.
+ """
+ bounds = tp.query('SELECT * FROM trace_bounds').as_pandas_dataframe()
+ start_ts = start_ts if start_ts else bounds['start_ts'][0]
+ end_ts = end_ts if end_ts else bounds['end_ts'][0]
+
+ tp.query("""
+ DROP VIEW IF EXISTS modded_names
+ """)
+
+ tp.query("""
+ CREATE VIEW modded_names AS
+ SELECT
+ slice.id,
+ slice.depth,
+ slice.stack_id,
+ CASE
+ WHEN slice.name LIKE 'Choreographer#doFrame%'
+ THEN 'Choreographer#doFrame'
+ WHEN slice.name LIKE 'DrawFrames%'
+ THEN 'DrawFrames'
+ WHEN slice.name LIKE '/data/app%.apk'
+ THEN 'APK load'
+ WHEN slice.name LIKE 'OpenDexFilesFromOat%'
+ THEN 'OpenDexFilesFromOat'
+ WHEN slice.name LIKE 'Open oat file%'
+ THEN 'Open oat file'
+ ELSE slice.name
+ END AS modded_name
+ FROM slice
+ """)
+
+ tp.query("""
+ DROP VIEW IF EXISTS thread_slice_stack
+ """)
+
+ tp.query("""
+ CREATE VIEW thread_slice_stack AS
+ SELECT
+ efs.ts,
+ efs.dur,
+ IFNULL(n.stack_id, -1) AS stack_id,
+ t.utid,
+ IIF(efs.source_id IS NULL, '[No slice]', IFNULL(
+ (
+ SELECT GROUP_CONCAT(modded_name, ' > ')
+ FROM (
+ SELECT p.modded_name
+ FROM ancestor_slice(efs.source_id) a
+ JOIN modded_names p ON a.id = p.id
+ ORDER BY p.depth
+ )
+ ) || ' > ' || n.modded_name,
+ n.modded_name
+ )) AS stack_name
+ FROM experimental_flat_slice({}, {}) efs
+ LEFT JOIN modded_names n ON efs.source_id = n.id
+ JOIN thread_track t ON t.id = efs.track_id
+ """.format(start_ts, end_ts))
+
+ tp.query("""
+ DROP TABLE IF EXISTS thread_slice_stack_with_state
+ """)
+
+ tp.query("""
+ CREATE VIRTUAL TABLE thread_slice_stack_with_state
+ USING SPAN_JOIN(
+ thread_slice_stack PARTITIONED utid,
+ thread_state PARTITIONED utid
+ )
+ """)
+
+ if process_name:
+ where_process = "AND process.name = '{}'".format(process_name)
+ else:
+ where_process = ''
+
+ breakdown = tp.query("""
+ SELECT
+ process.name AS process_name,
+ thread.name AS thread_name,
+ CASE
+ WHEN slice.state = 'D' and slice.io_wait
+ THEN 'Uninterruptible sleep (IO)'
+ WHEN slice.state = 'DK' and slice.io_wait
+ THEN 'Uninterruptible sleep + Wake-kill (IO)'
+ WHEN slice.state = 'D' and not slice.io_wait
+ THEN 'Uninterruptible sleep (non-IO)'
+ WHEN slice.state = 'DK' and not slice.io_wait
+ THEN 'Uninterruptible sleep + Wake-kill (non-IO)'
+ WHEN slice.state = 'D'
+ THEN 'Uninterruptible sleep'
+ WHEN slice.state = 'DK'
+ THEN 'Uninterruptible sleep + Wake-kill'
+ WHEN slice.state = 'S' THEN 'Interruptible sleep'
+ WHEN slice.state = 'R' THEN 'Runnable'
+ WHEN slice.state = 'R+' THEN 'Runnable (Preempted)'
+ ELSE slice.state
+ END AS state,
+ slice.stack_name,
+ SUM(slice.dur)/1e6 AS dur_sum,
+ MIN(slice.dur/1e6) AS dur_min,
+ MAX(slice.dur/1e6) AS dur_max,
+ AVG(slice.dur/1e6) AS dur_mean,
+ PERCENTILE(slice.dur/1e6, 50) AS dur_median,
+ PERCENTILE(slice.dur/1e6, 25) AS dur_25_percentile,
+ PERCENTILE(slice.dur/1e6, 75) AS dur_75_percentile,
+ PERCENTILE(slice.dur/1e6, 95) AS dur_95_percentile,
+ PERCENTILE(slice.dur/1e6, 99) AS dur_99_percentile,
+ COUNT(1) as count
+ FROM process
+ JOIN thread USING (upid)
+ JOIN thread_slice_stack_with_state slice USING (utid)
+ WHERE dur != -1 {}
+ GROUP BY thread.name, stack_id, state
+ ORDER BY dur_sum DESC
+ """.format(where_process)).as_pandas_dataframe()
+
+ return breakdown
+
+
+def compute_breakdown_for_startup(tp: TraceProcessor,
+ package_name=None,
+ process_name=None):
+ """Computes the slice breakdown (like |compute_breakdown|) but only
+ considering slices which happened during an app startup
+
+ Args:
+ tp: the trace processor instance to query.
+ package_name: optional package name to filter for startups. Only a single
+ startup matching this package name should be present. If not specified,
+ only a single startup of any app should be in the trace.
+ process_name: optional process name to filter for slices; specifying
+ this argument can make computing the breakdown a lot faster.
+
+ Returns:
+ The same as |compute_breakdown| but only containing slices which happened
+ during app startup.
+ """
+ tp.metric(['android_startup'])
+
+ # Verify there was only one startup in the trace matching the package
+ # name.
+ filter = "WHERE package = '{}'".format(package_name) if package_name else ''
+ launches = tp.query('''
+ SELECT ts, ts_end, dur
+ FROM launches
+ {}
+ '''.format(filter)).as_pandas_dataframe()
+ if len(launches) == 0:
+ raise TraceProcessorException("Didn't find startup in trace")
+ if len(launches) > 1:
+ raise TraceProcessorException("Found multiple startups in trace")
+
+ start = launches['ts'][0]
+ end = launches['ts_end'][0]
+
+ return compute_breakdown(tp, start, end, process_name)