xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.AndroidPerf/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 {Trace} from '../../public/trace';
16import {PerfettoPlugin} from '../../public/plugin';
17import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
18import {addQueryResultsTab} from '../../components/query_table/query_result_tab';
19import {
20  addDebugCounterTrack,
21  addDebugSliceTrack,
22  addPivotedTracks,
23} from '../../components/tracks/debug_tracks';
24
25export default class implements PerfettoPlugin {
26  static readonly id = 'dev.perfetto.AndroidPerf';
27  async addAppProcessStartsDebugTrack(
28    ctx: Trace,
29    reason: string,
30    sliceName: string,
31  ): Promise<void> {
32    const sliceColumns = [
33      'id',
34      'ts',
35      'dur',
36      'reason',
37      'process_name',
38      'intent',
39      'table_name',
40    ];
41    await addDebugSliceTrack({
42      trace: ctx,
43      data: {
44        sqlSource: `
45                    SELECT
46                      start_id AS id,
47                      proc_start_ts AS ts,
48                      total_dur AS dur,
49                      reason,
50                      process_name,
51                      intent,
52                      'slice' AS table_name
53                    FROM android_app_process_starts
54                    WHERE reason = '${reason}'
55                 `,
56        columns: sliceColumns,
57      },
58      title: 'app_' + sliceName + '_start reason: ' + reason,
59      argColumns: sliceColumns,
60    });
61  }
62
63  async onTraceLoad(ctx: Trace): Promise<void> {
64    ctx.commands.registerCommand({
65      id: 'dev.perfetto.AndroidPerf#BinderSystemServerIncoming',
66      name: 'Run query: system_server incoming binder graph',
67      callback: () =>
68        addQueryResultsTab(ctx, {
69          query: `INCLUDE PERFETTO MODULE android.binder;
70           SELECT * FROM android_binder_incoming_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
71          title: 'system_server incoming binder graph',
72        }),
73    });
74
75    ctx.commands.registerCommand({
76      id: 'dev.perfetto.AndroidPerf#BinderSystemServerOutgoing',
77      name: 'Run query: system_server outgoing binder graph',
78      callback: () =>
79        addQueryResultsTab(ctx, {
80          query: `INCLUDE PERFETTO MODULE android.binder;
81           SELECT * FROM android_binder_outgoing_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
82          title: 'system_server outgoing binder graph',
83        }),
84    });
85
86    ctx.commands.registerCommand({
87      id: 'dev.perfetto.AndroidPerf#MonitorContentionSystemServer',
88      name: 'Run query: system_server monitor_contention graph',
89      callback: () =>
90        addQueryResultsTab(ctx, {
91          query: `INCLUDE PERFETTO MODULE android.monitor_contention;
92           SELECT * FROM android_monitor_contention_graph((SELECT upid FROM process WHERE name = 'system_server'))`,
93          title: 'system_server monitor_contention graph',
94        }),
95    });
96
97    ctx.commands.registerCommand({
98      id: 'dev.perfetto.AndroidPerf#BinderAll',
99      name: 'Run query: all process binder graph',
100      callback: () =>
101        addQueryResultsTab(ctx, {
102          query: `INCLUDE PERFETTO MODULE android.binder;
103           SELECT * FROM android_binder_graph(-1000, 1000, -1000, 1000)`,
104          title: 'all process binder graph',
105        }),
106    });
107
108    ctx.commands.registerCommand({
109      id: 'dev.perfetto.AndroidPerf#ThreadClusterDistribution',
110      name: 'Run query: runtime cluster distribution for a thread',
111      callback: async (tid) => {
112        if (tid === undefined) {
113          tid = prompt('Enter a thread tid', '');
114          if (tid === null) return;
115        }
116        addQueryResultsTab(ctx, {
117          query: `
118          INCLUDE PERFETTO MODULE android.cpu.cluster_type;
119          WITH
120            total_runtime AS (
121              SELECT sum(dur) AS total_runtime
122              FROM sched s
123              LEFT JOIN thread t
124                USING (utid)
125              WHERE t.tid = ${tid}
126            )
127            SELECT
128              c.cluster_type AS cluster, sum(dur)/1e6 AS total_dur_ms,
129              sum(dur) * 1.0 / (SELECT * FROM total_runtime) AS percentage
130            FROM sched s
131            LEFT JOIN thread t
132              USING (utid)
133            LEFT JOIN android_cpu_cluster_mapping c
134              USING (cpu)
135            WHERE t.tid = ${tid}
136            GROUP BY 1`,
137          title: `runtime cluster distrubtion for tid ${tid}`,
138        });
139      },
140    });
141
142    ctx.commands.registerCommand({
143      id: 'dev.perfetto.AndroidPerf#SchedLatency',
144      name: 'Run query: top 50 sched latency for a thread',
145      callback: async (tid) => {
146        if (tid === undefined) {
147          tid = prompt('Enter a thread tid', '');
148          if (tid === null) return;
149        }
150        addQueryResultsTab(ctx, {
151          query: `
152          SELECT ts.*, t.tid, t.name, tt.id AS track_id
153          FROM thread_state ts
154          LEFT JOIN thread_track tt
155           USING (utid)
156          LEFT JOIN thread t
157           USING (utid)
158          WHERE ts.state IN ('R', 'R+') AND tid = ${tid}
159           ORDER BY dur DESC
160          LIMIT 50`,
161          title: `top 50 sched latency slice for tid ${tid}`,
162        });
163      },
164    });
165
166    ctx.commands.registerCommand({
167      id: 'dev.perfetto.AndroidPerf#SchedLatencyInSelectedWindow',
168      name: 'Top 50 sched latency in selected time window',
169      callback: async () => {
170        const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
171        addQueryResultsTab(ctx, {
172          title: 'top 50 sched latency slice in selcted time window',
173          query: `SELECT
174            ts.*,
175            t.tid,
176            t.name AS thread_name,
177            tt.id AS track_id,
178            p.name AS process_name
179          FROM thread_state ts
180          LEFT JOIN thread_track tt
181           USING (utid)
182          LEFT JOIN thread t
183           USING (utid)
184          LEFT JOIN process p
185           USING (upid)
186          WHERE ts.state IN ('R', 'R+')
187           AND ts.ts >= ${window.start} and ts.ts < ${window.end}
188          ORDER BY dur DESC
189          LIMIT 50`,
190        });
191      },
192    });
193
194    ctx.commands.registerCommand({
195      id: 'dev.perfetto.AndroidPerf#AppProcessStarts',
196      name: 'Add tracks: app process starts',
197      callback: async () => {
198        await ctx.engine.query(
199          `INCLUDE PERFETTO MODULE android.app_process_starts;`,
200        );
201
202        const startReason = ['activity', 'service', 'broadcast', 'provider'];
203        for (const reason of startReason) {
204          await this.addAppProcessStartsDebugTrack(ctx, reason, 'process_name');
205        }
206      },
207    });
208
209    ctx.commands.registerCommand({
210      id: 'dev.perfetto.AndroidPerf#AppIntentStarts',
211      name: 'Add tracks: app intent starts',
212      callback: async () => {
213        await ctx.engine.query(
214          `INCLUDE PERFETTO MODULE android.app_process_starts;`,
215        );
216
217        const startReason = ['activity', 'service', 'broadcast'];
218        for (const reason of startReason) {
219          await this.addAppProcessStartsDebugTrack(ctx, reason, 'intent');
220        }
221      },
222    });
223
224    ctx.commands.registerCommand({
225      id: 'dev.perfetto.AndroidPerf#CounterByFtraceEventArgs',
226      name: 'Add counter tracks by ftrace event arguments',
227      callback: async (event, value, filter, filterValue) => {
228        if (event === undefined) {
229          event = prompt('Enter the name of the targeted ftrace event', '');
230          if (event === null) return;
231          const resp = await ctx.engine.query(`
232            SELECT * FROM ftrace_event WHERE name = '${event}' LIMIT 1
233          `);
234          if (resp.numRows() === 0) {
235            alert(`Can not find ${event} ftrace event in this trace`);
236            return;
237          }
238        }
239        if (value === undefined) {
240          value = prompt('Enter the name of arguments as counter value', '');
241          if (value === null) return;
242          const resp = await ctx.engine.query(`
243            SELECT *
244            FROM ftrace_event
245            JOIN args
246              USING (arg_set_id)
247            WHERE name = '${event}' AND key = '${value}'
248            LIMIT 1
249          `);
250          if (resp.numRows() === 0) {
251            alert(`The ${event} ftrace event does not have argument ${value}`);
252            return;
253          }
254        }
255        if (filter === undefined) {
256          filter = prompt('Enter the name of arguments to pivot', '');
257          if (filter === null) return;
258          const resp = await ctx.engine.query(`
259            SELECT *
260            FROM ftrace_event
261            JOIN args
262              USING (arg_set_id)
263            WHERE name = '${event}' AND key = '${filter}'
264            LIMIT 1
265          `);
266          if (resp.numRows() === 0) {
267            alert(`The ${event} ftrace event does not have argument ${filter}`);
268            return;
269          }
270        }
271        if (filterValue === undefined) {
272          filterValue = prompt(
273            'List the target pivot values (separate by comma) to present\n' +
274              'ex1: 123,456 \n' +
275              'ex2: "task_name1","task_name2"\n',
276            '',
277          );
278          if (filterValue === null) return;
279        }
280        await addPivotedTracks(
281          ctx,
282          {
283            sqlSource: `
284              SELECT
285                ts,
286                EXTRACT_ARG(arg_set_id, '${value}') AS value,
287                EXTRACT_ARG(arg_set_id, '${filter}') AS pivot
288              FROM ftrace_event
289                WHERE name = '${event}' AND pivot IN (${filterValue})`,
290          },
291          event + '#' + value + '@' + filter,
292          'pivot',
293          async (ctx, data, trackName) =>
294            addDebugCounterTrack({
295              trace: ctx,
296              data,
297              title: trackName,
298            }),
299        );
300      },
301    });
302  }
303}
304