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