| // Copyright (C) 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 |
| // |
| // 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. |
| |
| import { |
| NUM, |
| NUM_NULL, |
| Plugin, |
| PluginContextTrace, |
| PluginDescriptor, |
| STR, |
| } from '../../public'; |
| import {addDebugSliceTrack} from '../../public'; |
| |
| class AndroidClientServer implements Plugin { |
| async onTraceLoad(ctx: PluginContextTrace): Promise<void> { |
| ctx.registerCommand({ |
| id: 'dev.perfetto.AndroidClientServer#ThreadRuntimeIPC', |
| name: 'Show dependencies in client server model', |
| callback: async (sliceId) => { |
| if (sliceId === undefined) { |
| sliceId = prompt('Enter a slice id', ''); |
| if (sliceId === null) return; |
| } |
| await ctx.engine.query(` |
| include perfetto module android.binder; |
| include perfetto module graphs.search; |
| |
| create or replace perfetto table __binder_for_slice_${sliceId} as |
| with s as materialized ( |
| select slice.id, ts, ts + dur as ts_end, dur, upid |
| from thread_slice slice |
| where slice.id = ${sliceId} |
| ), |
| child_binder_txns_for_slice as materialized ( |
| select |
| (select id from s) as source_node_id, |
| binder_txn_id as dest_node_id |
| from descendant_slice((select id from s)) as desc |
| join android_binder_txns txns on desc.id = txns.binder_txn_id |
| ), |
| binder_txns_in_slice_intervals as materialized ( |
| select |
| binder_txn_id as source_node_id, |
| binder_reply_id as dest_node_id |
| from android_binder_txns |
| where client_ts > (select ts from s) |
| and client_ts < (select ts + dur from s) |
| ), |
| nested_binder_txns_in_slice_interval as materialized ( |
| select |
| parent.binder_reply_id as source_node_id, |
| child.binder_txn_id as dest_node_id |
| from android_binder_txns parent |
| join descendant_slice(parent.binder_reply_id) desc |
| join android_binder_txns child on desc.id = child.binder_txn_id |
| where parent.server_ts > (select ts from s) |
| and parent.server_ts < (select ts + dur from s) |
| ), |
| all_binder_txns_considered as materialized ( |
| select * from child_binder_txns_for_slice |
| union |
| select * from binder_txns_in_slice_intervals |
| union |
| select * from nested_binder_txns_in_slice_interval |
| ) |
| select |
| slice.id, |
| slice.ts, |
| slice.dur, |
| coalesce(req.aidl_name, rep.aidl_name, slice.name) name, |
| tt.utid, |
| thread.upid resolved_upid, |
| case |
| when req.binder_txn_id is not null then 'request' |
| when rep.binder_reply_id is not null then 'response' |
| else 'slice' |
| end as slice_type, |
| coalesce(req.is_sync, rep.is_sync, true) as is_sync |
| from graph_reachable_dfs!( |
| all_binder_txns_considered, |
| (select id from s) |
| ) dfs |
| join slice on dfs.node_id = slice.id |
| join thread_track tt on slice.track_id = tt.id |
| join thread using (utid) |
| -- TODO(lalitm): investigate whether it is worth improve this. |
| left join android_binder_txns req on slice.id = req.binder_txn_id |
| left join android_binder_txns rep on slice.id = rep.binder_reply_id |
| where resolved_upid is not null; |
| `); |
| await ctx.engine.query(` |
| create or replace perfetto table __thread_state_for_${sliceId} as |
| with foo as ( |
| select |
| ii.ts, |
| ii.dur, |
| tstate.utid, |
| thread.upid, |
| tstate.state, |
| tstate.io_wait, |
| ( |
| select name |
| from thread_slice tslice |
| where tslice.utid = tstate.utid and tslice.ts < ii.ts |
| order by ts desc |
| limit 1 |
| ) as enclosing_slice_name |
| from interval_intersect!( |
| ( |
| select id, ts, dur |
| from __binder_for_slice_${sliceId} |
| where slice_type IN ('slice', 'response') and is_sync |
| ), |
| ( |
| select id, ts, dur |
| from thread_state tstate |
| where tstate.utid in ( |
| select distinct utid |
| from __binder_for_slice_${sliceId} |
| where slice_type IN ('slice', 'response') and is_sync |
| ) |
| ) |
| ) ii |
| join __binder_for_slice_${sliceId} bfs on ii.left_id = bfs.id |
| join thread_state tstate on ii.right_id = tstate.id |
| join thread using (utid) |
| where bfs.utid = tstate.utid |
| ) |
| select *, |
| case |
| when state = 'S' and enclosing_slice_name = 'binder transaction' then 'Binder' |
| when state = 'S' and enclosing_slice_name GLOB 'Lock*' then 'Lock contention' |
| when state = 'S' and enclosing_slice_name GLOB 'Monitor*' then 'Lock contention' |
| when state = 'S' then 'Sleeping' |
| when state = 'R' then 'Runnable' |
| when state = 'Running' then 'Running' |
| when state GLOB 'R*' then 'Runnable' |
| when state GLOB 'D*' and io_wait then 'IO' |
| when state GLOB 'D*' and not io_wait then 'Unint-sleep' |
| end as name |
| from foo; |
| `); |
| |
| const res = await ctx.engine.query(` |
| select |
| process.upid, |
| ifnull(process.name, 'Unknown Process') as process_name, |
| tstate.upid as tstate_upid |
| from ( |
| select distinct resolved_upid from __binder_for_slice_${sliceId} |
| ) binder_for_slice |
| join process on binder_for_slice.resolved_upid = process.upid |
| left join ( |
| select distinct upid from __thread_state_for_${sliceId} |
| ) tstate using (upid); |
| `); |
| const it = res.iter({ |
| upid: NUM, |
| process_name: STR, |
| tstate_upid: NUM_NULL, |
| }); |
| for (; it.valid(); it.next()) { |
| if (it.tstate_upid !== null) { |
| await addDebugSliceTrack( |
| ctx.engine, |
| { |
| sqlSource: ` |
| SELECT ts, dur, name |
| FROM __thread_state_for_${sliceId} |
| WHERE upid = ${it.upid} |
| `, |
| }, |
| it.process_name, |
| {ts: 'ts', dur: 'dur', name: 'name'}, |
| [], |
| ); |
| } |
| await addDebugSliceTrack( |
| ctx.engine, |
| { |
| sqlSource: ` |
| SELECT ts, dur, name |
| FROM __binder_for_slice_${sliceId} |
| WHERE resolved_upid = ${it.upid} |
| `, |
| }, |
| it.process_name, |
| {ts: 'ts', dur: 'dur', name: 'name'}, |
| [], |
| ); |
| } |
| }, |
| }); |
| } |
| } |
| |
| export const plugin: PluginDescriptor = { |
| pluginId: 'dev.perfetto.AndroidClientServer', |
| plugin: AndroidClientServer, |
| }; |