blob: d60fdb765968d3069d95bcf640ffab463e09c5b9 [file] [log] [blame]
// 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,
};