1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import { 16 NUM, 17 NUM_NULL, 18 Plugin, 19 PluginContextTrace, 20 PluginDescriptor, 21 STR, 22} from '../../public'; 23import {addDebugSliceTrack} from '../../public'; 24 25class AndroidClientServer implements Plugin { 26 async onTraceLoad(ctx: PluginContextTrace): Promise<void> { 27 ctx.registerCommand({ 28 id: 'dev.perfetto.AndroidClientServer#ThreadRuntimeIPC', 29 name: 'Show dependencies in client server model', 30 callback: async (sliceId) => { 31 if (sliceId === undefined) { 32 sliceId = prompt('Enter a slice id', ''); 33 if (sliceId === null) return; 34 } 35 await ctx.engine.query(` 36 include perfetto module android.binder; 37 include perfetto module graphs.search; 38 39 create or replace perfetto table __binder_for_slice_${sliceId} as 40 with s as materialized ( 41 select slice.id, ts, ts + dur as ts_end, dur, upid 42 from thread_slice slice 43 where slice.id = ${sliceId} 44 ), 45 child_binder_txns_for_slice as materialized ( 46 select 47 (select id from s) as source_node_id, 48 binder_txn_id as dest_node_id 49 from descendant_slice((select id from s)) as desc 50 join android_binder_txns txns on desc.id = txns.binder_txn_id 51 ), 52 binder_txns_in_slice_intervals as materialized ( 53 select 54 binder_txn_id as source_node_id, 55 binder_reply_id as dest_node_id 56 from android_binder_txns 57 where client_ts > (select ts from s) 58 and client_ts < (select ts + dur from s) 59 ), 60 nested_binder_txns_in_slice_interval as materialized ( 61 select 62 parent.binder_reply_id as source_node_id, 63 child.binder_txn_id as dest_node_id 64 from android_binder_txns parent 65 join descendant_slice(parent.binder_reply_id) desc 66 join android_binder_txns child on desc.id = child.binder_txn_id 67 where parent.server_ts > (select ts from s) 68 and parent.server_ts < (select ts + dur from s) 69 ), 70 all_binder_txns_considered as materialized ( 71 select * from child_binder_txns_for_slice 72 union 73 select * from binder_txns_in_slice_intervals 74 union 75 select * from nested_binder_txns_in_slice_interval 76 ) 77 select 78 slice.id, 79 slice.ts, 80 slice.dur, 81 coalesce(req.aidl_name, rep.aidl_name, slice.name) name, 82 tt.utid, 83 thread.upid resolved_upid, 84 case 85 when req.binder_txn_id is not null then 'request' 86 when rep.binder_reply_id is not null then 'response' 87 else 'slice' 88 end as slice_type, 89 coalesce(req.is_sync, rep.is_sync, true) as is_sync 90 from graph_reachable_dfs!( 91 all_binder_txns_considered, 92 (select id from s) 93 ) dfs 94 join slice on dfs.node_id = slice.id 95 join thread_track tt on slice.track_id = tt.id 96 join thread using (utid) 97 -- TODO(lalitm): investigate whether it is worth improve this. 98 left join android_binder_txns req on slice.id = req.binder_txn_id 99 left join android_binder_txns rep on slice.id = rep.binder_reply_id 100 where resolved_upid is not null; 101 `); 102 await ctx.engine.query(` 103 create or replace perfetto table __thread_state_for_${sliceId} as 104 with foo as ( 105 select 106 ii.ts, 107 ii.dur, 108 tstate.utid, 109 thread.upid, 110 tstate.state, 111 tstate.io_wait, 112 ( 113 select name 114 from thread_slice tslice 115 where tslice.utid = tstate.utid and tslice.ts < ii.ts 116 order by ts desc 117 limit 1 118 ) as enclosing_slice_name 119 from interval_intersect!( 120 ( 121 select id, ts, dur 122 from __binder_for_slice_${sliceId} 123 where slice_type IN ('slice', 'response') and is_sync 124 ), 125 ( 126 select id, ts, dur 127 from thread_state tstate 128 where tstate.utid in ( 129 select distinct utid 130 from __binder_for_slice_${sliceId} 131 where slice_type IN ('slice', 'response') and is_sync 132 ) 133 ) 134 ) ii 135 join __binder_for_slice_${sliceId} bfs on ii.left_id = bfs.id 136 join thread_state tstate on ii.right_id = tstate.id 137 join thread using (utid) 138 where bfs.utid = tstate.utid 139 ) 140 select *, 141 case 142 when state = 'S' and enclosing_slice_name = 'binder transaction' then 'Binder' 143 when state = 'S' and enclosing_slice_name GLOB 'Lock*' then 'Lock contention' 144 when state = 'S' and enclosing_slice_name GLOB 'Monitor*' then 'Lock contention' 145 when state = 'S' then 'Sleeping' 146 when state = 'R' then 'Runnable' 147 when state = 'Running' then 'Running' 148 when state GLOB 'R*' then 'Runnable' 149 when state GLOB 'D*' and io_wait then 'IO' 150 when state GLOB 'D*' and not io_wait then 'Unint-sleep' 151 end as name 152 from foo; 153 `); 154 155 const res = await ctx.engine.query(` 156 select 157 process.upid, 158 ifnull(process.name, 'Unknown Process') as process_name, 159 tstate.upid as tstate_upid 160 from ( 161 select distinct resolved_upid from __binder_for_slice_${sliceId} 162 ) binder_for_slice 163 join process on binder_for_slice.resolved_upid = process.upid 164 left join ( 165 select distinct upid from __thread_state_for_${sliceId} 166 ) tstate using (upid); 167 `); 168 const it = res.iter({ 169 upid: NUM, 170 process_name: STR, 171 tstate_upid: NUM_NULL, 172 }); 173 for (; it.valid(); it.next()) { 174 if (it.tstate_upid !== null) { 175 await addDebugSliceTrack( 176 ctx, 177 { 178 sqlSource: ` 179 SELECT ts, dur, name 180 FROM __thread_state_for_${sliceId} 181 WHERE upid = ${it.upid} 182 `, 183 }, 184 it.process_name, 185 {ts: 'ts', dur: 'dur', name: 'name'}, 186 [], 187 ); 188 } 189 await addDebugSliceTrack( 190 ctx, 191 { 192 sqlSource: ` 193 SELECT ts, dur, name 194 FROM __binder_for_slice_${sliceId} 195 WHERE resolved_upid = ${it.upid} 196 `, 197 }, 198 it.process_name, 199 {ts: 'ts', dur: 'dur', name: 'name'}, 200 [], 201 ); 202 } 203 }, 204 }); 205 } 206} 207 208export const plugin: PluginDescriptor = { 209 pluginId: 'dev.perfetto.AndroidClientServer', 210 plugin: AndroidClientServer, 211}; 212