| // 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 type {Trace} from '../../public/trace'; | |
| import type {PerfettoPlugin} from '../../public/plugin'; | |
| import {addDebugSliceTrack} from '../../components/tracks/debug_tracks'; | |
| export default class implements PerfettoPlugin { | |
| static readonly id = 'com.android.AndroidClientServer'; | |
| async onTraceLoad(ctx: Trace): Promise<void> { | |
| ctx.commands.registerCommand({ | |
| id: 'com.android.ShowClientServerDependencies', | |
| name: 'Show dependencies in client server model', | |
| callback: async (sliceId) => { | |
| if (sliceId === undefined) { | |
| sliceId = await ctx.omnibox.prompt('Enter a slice id'); | |
| if (sliceId === undefined) 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, | |
| }); | |
| } | |
| }, | |
| }); | |
| } | |
| } |