blob: 405ad8c87ea05ba59d77769aab5b77298e4edc1a [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, STR} from '../../trace_processor/query_result';
import {Trace} from '../../public/trace';
import {PerfettoPlugin} from '../../public/plugin';
import {addDebugSliceTrack} from '../../public/debug_tracks';
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.AndroidClientServer';
async onTraceLoad(ctx: Trace): Promise<void> {
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(
ctx,
{
sqlSource: `
SELECT ts, dur, name
FROM __enhanced_binder_for_slice_${sliceId}
WHERE binder_id = ${it.id}
`,
},
it.name,
{ts: 'ts', dur: 'dur', name: 'name'},
[],
);
}
},
});
}
}