// 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, STR} from '../../trace_processor/query_result'; import {Trace} from '../../public/trace'; import {PerfettoPlugin} from '../../public/plugin'; import {addDebugSliceTrack} from '../../components/tracks/debug_tracks'; export default class implements PerfettoPlugin { static readonly id = 'dev.perfetto.AndroidClientServer'; async onTraceLoad(ctx: Trace): Promise { ctx.commands.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 dfs.node_id as id, coalesce(client.client_ts, server.client_ts, slice.ts) as ts, coalesce(client.client_dur, server.client_dur, slice.dur) as dur, coalesce( client.aidl_name, server.aidl_name, iif( server.binder_reply_id is not null, coalesce( server.server_process, server.server_thread, 'Unknown server' ), slice.name ) ) name, coalesce( client.client_utid, server.server_utid, thread_track.utid ) as utid, case when client.binder_txn_id is not null then 'client' when server.binder_reply_id is not null then 'server' else 'slice' end as slice_type, coalesce(client.is_sync, server.is_sync, true) as is_sync from graph_reachable_dfs!( all_binder_txns_considered, (select id as node_id from s) ) dfs join slice on dfs.node_id = slice.id join thread_track on slice.track_id = thread_track.id left join android_binder_txns client on dfs.node_id = client.binder_txn_id left join android_binder_txns server on dfs.node_id = server.binder_reply_id order by ts; `); await ctx.engine.query(` include perfetto module intervals.intersect; create or replace perfetto table __enhanced_binder_for_slice_${sliceId} as with foo as ( select bfs.id as binder_id, bfs.name as binder_name, ii.ts, ii.dur, tstate.utid, thread.upid, tstate.cpu, 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', 'server') and is_sync and dur > 0 ), ( 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', 'server') and is_sync and dur > 0 ) and dur > 0 ), () ) ii join __binder_for_slice_${sliceId} bfs on ii.id_0 = bfs.id join thread_state tstate on ii.id_1 = tstate.id join thread using (utid) where bfs.utid = tstate.utid ) select *, case when state = 'S' and enclosing_slice_name = 'binder transaction' then 'Waiting for server' when state = 'S' and enclosing_slice_name GLOB 'Lock*' then 'Waiting for lock' when state = 'S' and enclosing_slice_name GLOB 'Monitor*' then 'Waiting for contention' when state = 'S' then 'Sleeping' when state = 'R' then 'Waiting for CPU' when state = 'Running' then 'Running on CPU ' || foo.cpu 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 order by binder_id; `); const res = await ctx.engine.query(` select id, name from __binder_for_slice_${sliceId} bfs where slice_type IN ('slice', 'server') and dur > 0 order by ts `); const it = res.iter({ id: NUM, name: STR, }); for (; it.valid(); it.next()) { await addDebugSliceTrack({ trace: ctx, data: { sqlSource: ` SELECT ts, dur, name FROM __enhanced_binder_for_slice_${sliceId} WHERE binder_id = ${it.id} `, }, title: it.name, }); } }, }); } }