• 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 {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} from '../../components/tracks/debug_tracks';
23import {STR} from '../../trace_processor/query_result';
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 tracks: counter by ftrace event arguments',
227      callback: async (event, value, filter, filterValue) => {
228        if (event === undefined) {
229          const result = await ctx.engine.query(`
230            SELECT DISTINCT name FROM ftrace_event
231          `);
232          const ftraceEvents: string[] = [];
233          const it = result.iter({name: STR});
234          for (; it.valid(); it.next()) {
235            ftraceEvents.push(it.name);
236          }
237          event = await ctx.omnibox.prompt(
238            'Choose a ftrace event...',
239            ftraceEvents,
240          );
241          if (event === undefined) {
242            return;
243          }
244        }
245        if (value === undefined) {
246          const result = await ctx.engine.query(`
247            SELECT DISTINCT
248              key
249            FROM ftrace_event JOIN args USING(arg_set_id)
250            WHERE name = '${event}'
251          `);
252          const args: string[] = [];
253          const it = result.iter({key: STR});
254          for (; it.valid(); it.next()) {
255            args.push(it.key);
256          }
257          value = await ctx.omnibox.prompt(
258            'Choose a argument as counter value...',
259            args,
260          );
261          if (value === undefined) {
262            return;
263          }
264          filter = await ctx.omnibox.prompt(
265            'Choose a argument as pivot key...',
266            args,
267          );
268          if (filter === undefined) {
269            return;
270          }
271        }
272        if (filterValue === undefined) {
273          filterValue = await ctx.omnibox.prompt(
274            'List the target pivot values (separate by comma) to present\n' +
275              'ex1: 123,456 \n' +
276              'ex2: "task_name1","task_name2"\n',
277          );
278          if (filterValue === null) return;
279        }
280        await addDebugCounterTrack({
281          trace: ctx,
282          data: {
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          title: event + '#' + value + '@' + filter,
292          pivotOn: 'pivot',
293        });
294      },
295    });
296  }
297}
298