1#!/usr/bin/env python3 2# Copyright (C) 2021 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16from perfetto.trace_processor.api import TraceProcessor 17from perfetto.trace_processor.api import TraceProcessorException 18 19 20def compute_breakdown(tp: TraceProcessor, 21 start_ts=None, 22 end_ts=None, 23 process_name=None, 24 main_thread: bool = False): 25 """For each userspace slice in the trace processor instance |tp|, computes 26 the self-time of that slice grouping by process name, thread name 27 and thread state. 28 29 Args: 30 tp: the trace processor instance to query. 31 start_ts: optional bound to only consider slices after this ts 32 end_ts: optional bound to only consider slices until this ts 33 process_name: optional process name to filter for slices; specifying 34 this argument can make computing the breakdown a lot faster. 35 main_thread: whether to only consider slices on the main thread of each 36 process. 37 38 Returns: 39 A Pandas dataframe containing the total self time taken by a slice stack 40 broken down by process name, thread name and thread state. 41 """ 42 bounds = tp.query('SELECT * FROM trace_bounds').as_pandas_dataframe() 43 start_ts = start_ts if start_ts else bounds['start_ts'][0] 44 end_ts = end_ts if end_ts else bounds['end_ts'][0] 45 46 tp.query(""" 47 DROP VIEW IF EXISTS modded_names 48 """) 49 50 tp.query(""" 51 INCLUDE PERFETTO MODULE android.slices; 52 CREATE VIEW modded_names AS 53 SELECT 54 slice.id, 55 slice.depth, 56 slice.stack_id, 57 ANDROID_STANDARDIZE_SLICE_NAME(slice.name) AS modded_name 58 FROM slice 59 """) 60 61 tp.query(""" 62 DROP VIEW IF EXISTS thread_slice_stack 63 """) 64 65 tp.query(f""" 66 CREATE VIEW thread_slice_stack AS 67 SELECT 68 efs.ts, 69 efs.dur, 70 IFNULL(n.stack_id, -1) AS stack_id, 71 t.utid, 72 IIF(efs.source_id IS NULL, '[No slice]', IFNULL( 73 ( 74 SELECT GROUP_CONCAT(modded_name, ' > ') 75 FROM ( 76 SELECT p.modded_name 77 FROM ancestor_slice(efs.source_id) a 78 JOIN modded_names p ON a.id = p.id 79 ORDER BY p.depth 80 ) 81 ) || ' > ' || n.modded_name, 82 n.modded_name 83 )) AS stack_name 84 FROM experimental_flat_slice({start_ts}, {end_ts}) efs 85 LEFT JOIN modded_names n ON efs.source_id = n.id 86 JOIN thread_track t ON t.id = efs.track_id 87 """) 88 89 tp.query(""" 90 DROP TABLE IF EXISTS thread_slice_stack_with_state 91 """) 92 93 tp.query(""" 94 CREATE VIRTUAL TABLE thread_slice_stack_with_state 95 USING SPAN_JOIN( 96 thread_slice_stack PARTITIONED utid, 97 thread_state PARTITIONED utid 98 ) 99 """) 100 101 if process_name: 102 where_process = f"AND process.name GLOB '{process_name}'" 103 else: 104 where_process = '' 105 106 if main_thread: 107 where_main_thread = "AND thread.is_main_thread" 108 else: 109 where_main_thread = "" 110 111 breakdown = tp.query(f""" 112 SELECT 113 process.name AS process_name, 114 thread.name AS thread_name, 115 CASE 116 WHEN slice.state = 'D' and slice.io_wait 117 THEN 'Uninterruptible sleep (IO)' 118 WHEN slice.state = 'DK' and slice.io_wait 119 THEN 'Uninterruptible sleep + Wake-kill (IO)' 120 WHEN slice.state = 'D' and not slice.io_wait 121 THEN 'Uninterruptible sleep (non-IO)' 122 WHEN slice.state = 'DK' and not slice.io_wait 123 THEN 'Uninterruptible sleep + Wake-kill (non-IO)' 124 WHEN slice.state = 'D' 125 THEN 'Uninterruptible sleep' 126 WHEN slice.state = 'DK' 127 THEN 'Uninterruptible sleep + Wake-kill' 128 WHEN slice.state = 'S' THEN 'Interruptible sleep' 129 WHEN slice.state = 'R' THEN 'Runnable' 130 WHEN slice.state = 'R+' THEN 'Runnable (Preempted)' 131 ELSE slice.state 132 END AS state, 133 slice.stack_name, 134 SUM(slice.dur)/1e6 AS dur_sum, 135 MIN(slice.dur/1e6) AS dur_min, 136 MAX(slice.dur/1e6) AS dur_max, 137 AVG(slice.dur/1e6) AS dur_mean, 138 PERCENTILE(slice.dur/1e6, 50) AS dur_median, 139 PERCENTILE(slice.dur/1e6, 25) AS dur_25_percentile, 140 PERCENTILE(slice.dur/1e6, 75) AS dur_75_percentile, 141 PERCENTILE(slice.dur/1e6, 95) AS dur_95_percentile, 142 PERCENTILE(slice.dur/1e6, 99) AS dur_99_percentile, 143 COUNT(1) as count 144 FROM process 145 JOIN thread USING (upid) 146 JOIN thread_slice_stack_with_state slice USING (utid) 147 WHERE dur != -1 148 {where_main_thread} 149 {where_process} 150 GROUP BY thread.name, stack_id, state 151 ORDER BY dur_sum DESC 152 """).as_pandas_dataframe() 153 154 return breakdown 155 156 157def compute_breakdown_for_startup(tp: TraceProcessor, 158 package_name=None, 159 process_name=None, 160 main_thread: bool = False): 161 """Computes the slice breakdown (like |compute_breakdown|) but only 162 considering slices which happened during an app startup 163 164 Args: 165 tp: the trace processor instance to query. 166 package_name: optional package name to filter for startups. Only a single 167 startup matching this package name should be present. If not specified, 168 only a single startup of any app should be in the trace. 169 process_name: optional process name to filter for slices; specifying 170 this argument can make computing the breakdown a lot faster. 171 main_thread: whether to only consider slices on the main thread of each 172 process. 173 174 Returns: 175 The same as |compute_breakdown| but only containing slices which happened 176 during app startup. 177 """ 178 179 # Verify there was only one startup in the trace matching the package 180 # name. 181 filter = "WHERE package = '{}'".format(package_name) if package_name else '' 182 launches = tp.query(f''' 183 INCLUDE PERFETTO MODULE android.startup.startups; 184 185 SELECT ts, ts_end, dur 186 FROM android_startups 187 {filter} 188 ''').as_pandas_dataframe() 189 if len(launches) == 0: 190 raise TraceProcessorException("Didn't find startup in trace") 191 if len(launches) > 1: 192 raise TraceProcessorException("Found multiple startups in trace") 193 194 start = launches['ts'][0] 195 end = launches['ts_end'][0] 196 197 return compute_breakdown(tp, start, end, process_name, main_thread) 198