• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {
16  NUM,
17  NUM_NULL,
18  Plugin,
19  PluginContextTrace,
20  PluginDescriptor,
21  STR,
22} from '../../public';
23import {addDebugSliceTrack} from '../../public';
24
25class AndroidClientServer implements Plugin {
26  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
27    ctx.registerCommand({
28      id: 'dev.perfetto.AndroidClientServer#ThreadRuntimeIPC',
29      name: 'Show dependencies in client server model',
30      callback: async (sliceId) => {
31        if (sliceId === undefined) {
32          sliceId = prompt('Enter a slice id', '');
33          if (sliceId === null) return;
34        }
35        await ctx.engine.query(`
36          include perfetto module android.binder;
37          include perfetto module graphs.search;
38
39          create or replace perfetto table __binder_for_slice_${sliceId} as
40          with s as materialized (
41            select slice.id, ts, ts + dur as ts_end, dur, upid
42            from thread_slice slice
43            where slice.id = ${sliceId}
44          ),
45          child_binder_txns_for_slice as materialized (
46            select
47              (select id from s) as source_node_id,
48              binder_txn_id as dest_node_id
49            from descendant_slice((select id from s)) as desc
50            join android_binder_txns txns on desc.id = txns.binder_txn_id
51          ),
52          binder_txns_in_slice_intervals as materialized (
53            select
54              binder_txn_id as source_node_id,
55              binder_reply_id as dest_node_id
56            from android_binder_txns
57            where client_ts > (select ts from s)
58              and client_ts < (select ts + dur from s)
59          ),
60          nested_binder_txns_in_slice_interval as materialized (
61            select
62              parent.binder_reply_id as source_node_id,
63              child.binder_txn_id as dest_node_id
64            from android_binder_txns parent
65            join descendant_slice(parent.binder_reply_id) desc
66            join android_binder_txns child on desc.id = child.binder_txn_id
67            where parent.server_ts > (select ts from s)
68              and parent.server_ts < (select ts + dur from s)
69          ),
70          all_binder_txns_considered as materialized (
71            select * from child_binder_txns_for_slice
72            union
73            select * from binder_txns_in_slice_intervals
74            union
75            select * from nested_binder_txns_in_slice_interval
76          )
77          select
78            slice.id,
79            slice.ts,
80            slice.dur,
81            coalesce(req.aidl_name, rep.aidl_name, slice.name) name,
82            tt.utid,
83            thread.upid resolved_upid,
84            case
85              when req.binder_txn_id is not null then 'request'
86              when rep.binder_reply_id is not null then 'response'
87              else 'slice'
88            end as slice_type,
89            coalesce(req.is_sync, rep.is_sync, true) as is_sync
90          from graph_reachable_dfs!(
91            all_binder_txns_considered,
92            (select id from s)
93          ) dfs
94          join slice on dfs.node_id = slice.id
95          join thread_track tt on slice.track_id = tt.id
96          join thread using (utid)
97          -- TODO(lalitm): investigate whether it is worth improve this.
98          left join android_binder_txns req on slice.id = req.binder_txn_id
99          left join android_binder_txns rep on slice.id = rep.binder_reply_id
100          where resolved_upid is not null;
101        `);
102        await ctx.engine.query(`
103          create or replace perfetto table __thread_state_for_${sliceId} as
104          with foo as (
105            select
106              ii.ts,
107              ii.dur,
108              tstate.utid,
109              thread.upid,
110              tstate.state,
111              tstate.io_wait,
112              (
113                select name
114                from thread_slice tslice
115                where tslice.utid = tstate.utid and tslice.ts < ii.ts
116                order by ts desc
117                limit 1
118              ) as enclosing_slice_name
119            from interval_intersect!(
120              (
121                select id, ts, dur
122                from __binder_for_slice_${sliceId}
123                where slice_type IN ('slice', 'response') and is_sync
124              ),
125              (
126                select id, ts, dur
127                from thread_state tstate
128                where tstate.utid in (
129                  select distinct utid
130                  from __binder_for_slice_${sliceId}
131                  where slice_type IN ('slice', 'response') and is_sync
132                )
133              )
134            ) ii
135            join __binder_for_slice_${sliceId} bfs on ii.left_id = bfs.id
136            join thread_state tstate on ii.right_id = tstate.id
137            join thread using (utid)
138            where bfs.utid = tstate.utid
139          )
140          select *,
141            case
142              when state = 'S' and enclosing_slice_name = 'binder transaction' then 'Binder'
143              when state = 'S' and enclosing_slice_name GLOB 'Lock*' then 'Lock contention'
144              when state = 'S' and enclosing_slice_name GLOB 'Monitor*' then 'Lock contention'
145              when state = 'S' then 'Sleeping'
146              when state = 'R' then 'Runnable'
147              when state = 'Running' then 'Running'
148              when state GLOB 'R*' then 'Runnable'
149              when state GLOB 'D*' and io_wait then 'IO'
150              when state GLOB 'D*' and not io_wait then 'Unint-sleep'
151            end as name
152          from foo;
153        `);
154
155        const res = await ctx.engine.query(`
156          select
157            process.upid,
158            ifnull(process.name, 'Unknown Process') as process_name,
159            tstate.upid as tstate_upid
160          from (
161            select distinct resolved_upid from __binder_for_slice_${sliceId}
162          ) binder_for_slice
163          join process on binder_for_slice.resolved_upid = process.upid
164          left join (
165            select distinct upid from __thread_state_for_${sliceId}
166          ) tstate using (upid);
167        `);
168        const it = res.iter({
169          upid: NUM,
170          process_name: STR,
171          tstate_upid: NUM_NULL,
172        });
173        for (; it.valid(); it.next()) {
174          if (it.tstate_upid !== null) {
175            await addDebugSliceTrack(
176              ctx,
177              {
178                sqlSource: `
179                  SELECT ts, dur, name
180                  FROM __thread_state_for_${sliceId}
181                  WHERE upid = ${it.upid}
182                `,
183              },
184              it.process_name,
185              {ts: 'ts', dur: 'dur', name: 'name'},
186              [],
187            );
188          }
189          await addDebugSliceTrack(
190            ctx,
191            {
192              sqlSource: `
193                SELECT ts, dur, name
194                FROM __binder_for_slice_${sliceId}
195                WHERE resolved_upid = ${it.upid}
196              `,
197            },
198            it.process_name,
199            {ts: 'ts', dur: 'dur', name: 'name'},
200            [],
201          );
202        }
203      },
204    });
205  }
206}
207
208export const plugin: PluginDescriptor = {
209  pluginId: 'dev.perfetto.AndroidClientServer',
210  plugin: AndroidClientServer,
211};
212