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 {Time, time} from '../../base/time'; 16import {exists} from '../../base/utils'; 17import { 18 Plugin, 19 PluginContext, 20 PluginContextTrace, 21 PluginDescriptor, 22} from '../../public'; 23 24const SQL_STATS = ` 25with first as (select started as ts from sqlstats limit 1) 26select 27 round((max(ended - started, 0))/1e6) as runtime_ms, 28 round((started - first.ts)/1e6) as t_start_ms, 29 query 30from sqlstats, first 31order by started desc`; 32 33const ALL_PROCESSES_QUERY = 'select name, pid from process order by name;'; 34 35const CPU_TIME_FOR_PROCESSES = ` 36select 37 process.name, 38 sum(dur)/1e9 as cpu_sec 39from sched 40join thread using(utid) 41join process using(upid) 42group by upid 43order by cpu_sec desc 44limit 100;`; 45 46const CYCLES_PER_P_STATE_PER_CPU = ` 47select 48 cpu, 49 freq, 50 dur, 51 sum(dur * freq)/1e6 as mcycles 52from ( 53 select 54 cpu, 55 value as freq, 56 lead(ts) over (partition by cpu order by ts) - ts as dur 57 from counter 58 inner join cpu_counter_track on counter.track_id = cpu_counter_track.id 59 where name = 'cpufreq' 60) group by cpu, freq 61order by mcycles desc limit 32;`; 62 63const CPU_TIME_BY_CPU_BY_PROCESS = ` 64select 65 process.name as process, 66 thread.name as thread, 67 cpu, 68 sum(dur) / 1e9 as cpu_sec 69from sched 70inner join thread using(utid) 71inner join process using(upid) 72group by utid, cpu 73order by cpu_sec desc 74limit 30;`; 75 76const HEAP_GRAPH_BYTES_PER_TYPE = ` 77select 78 o.upid, 79 o.graph_sample_ts, 80 c.name, 81 sum(o.self_size) as total_self_size 82from heap_graph_object o join heap_graph_class c on o.type_id = c.id 83group by 84 o.upid, 85 o.graph_sample_ts, 86 c.name 87order by total_self_size desc 88limit 100;`; 89 90class CoreCommandsPlugin implements Plugin { 91 onActivate(ctx: PluginContext) { 92 ctx.registerCommand({ 93 id: 'dev.perfetto.CoreCommands#ToggleLeftSidebar', 94 name: 'Toggle left sidebar', 95 callback: () => { 96 if (ctx.sidebar.isVisible()) { 97 ctx.sidebar.hide(); 98 } else { 99 ctx.sidebar.show(); 100 } 101 }, 102 defaultHotkey: '!Mod+B', 103 }); 104 } 105 106 async onTraceLoad(ctx: PluginContextTrace): Promise<void> { 107 ctx.registerCommand({ 108 id: 'dev.perfetto.CoreCommands#RunQueryAllProcesses', 109 name: 'Run query: All processes', 110 callback: () => { 111 ctx.tabs.openQuery(ALL_PROCESSES_QUERY, 'All Processes'); 112 }, 113 }); 114 115 ctx.registerCommand({ 116 id: 'dev.perfetto.CoreCommands#RunQueryCpuTimeByProcess', 117 name: 'Run query: CPU time by process', 118 callback: () => { 119 ctx.tabs.openQuery(CPU_TIME_FOR_PROCESSES, 'CPU time by process'); 120 }, 121 }); 122 123 ctx.registerCommand({ 124 id: 'dev.perfetto.CoreCommands#RunQueryCyclesByStateByCpu', 125 name: 'Run query: cycles by p-state by CPU', 126 callback: () => { 127 ctx.tabs.openQuery( 128 CYCLES_PER_P_STATE_PER_CPU, 129 'Cycles by p-state by CPU', 130 ); 131 }, 132 }); 133 134 ctx.registerCommand({ 135 id: 'dev.perfetto.CoreCommands#RunQueryCyclesByCpuByProcess', 136 name: 'Run query: CPU Time by CPU by process', 137 callback: () => { 138 ctx.tabs.openQuery( 139 CPU_TIME_BY_CPU_BY_PROCESS, 140 'CPU time by CPU by process', 141 ); 142 }, 143 }); 144 145 ctx.registerCommand({ 146 id: 'dev.perfetto.CoreCommands#RunQueryHeapGraphBytesPerType', 147 name: 'Run query: heap graph bytes per type', 148 callback: () => { 149 ctx.tabs.openQuery( 150 HEAP_GRAPH_BYTES_PER_TYPE, 151 'Heap graph bytes per type', 152 ); 153 }, 154 }); 155 156 ctx.registerCommand({ 157 id: 'dev.perfetto.CoreCommands#DebugSqlPerformance', 158 name: 'Debug SQL performance', 159 callback: () => { 160 ctx.tabs.openQuery(SQL_STATS, 'Recent SQL queries'); 161 }, 162 }); 163 164 ctx.registerCommand({ 165 id: 'dev.perfetto.CoreCommands#UnpinAllTracks', 166 name: 'Unpin all pinned tracks', 167 callback: () => { 168 ctx.timeline.unpinTracksByPredicate((_) => { 169 return true; 170 }); 171 }, 172 }); 173 174 ctx.registerCommand({ 175 id: 'dev.perfetto.CoreCommands#ExpandAllGroups', 176 name: 'Expand all track groups', 177 callback: () => { 178 ctx.timeline.expandGroupsByPredicate((_) => { 179 return true; 180 }); 181 }, 182 }); 183 184 ctx.registerCommand({ 185 id: 'dev.perfetto.CoreCommands#CollapseAllGroups', 186 name: 'Collapse all track groups', 187 callback: () => { 188 ctx.timeline.collapseGroupsByPredicate((_) => { 189 return true; 190 }); 191 }, 192 }); 193 194 ctx.registerCommand({ 195 id: 'dev.perfetto.CoreCommands#PanToTimestamp', 196 name: 'Pan to timestamp', 197 callback: (tsRaw: unknown) => { 198 if (exists(tsRaw)) { 199 if (typeof tsRaw !== 'bigint') { 200 throw Error(`${tsRaw} is not a bigint`); 201 } 202 ctx.timeline.panToTimestamp(Time.fromRaw(tsRaw)); 203 } else { 204 // No args passed, probably run from the command palette. 205 const ts = promptForTimestamp('Enter a timestamp'); 206 if (exists(ts)) { 207 ctx.timeline.panToTimestamp(Time.fromRaw(ts)); 208 } 209 } 210 }, 211 }); 212 213 ctx.registerCommand({ 214 id: 'dev.perfetto.CoreCommands#ShowCurrentSelectionTab', 215 name: 'Show current selection tab', 216 callback: () => { 217 ctx.tabs.showTab('current_selection'); 218 }, 219 }); 220 } 221} 222 223function promptForTimestamp(message: string): time | undefined { 224 const tsStr = window.prompt(message); 225 if (tsStr !== null) { 226 try { 227 return Time.fromRaw(BigInt(tsStr)); 228 } catch { 229 window.alert(`${tsStr} is not an integer`); 230 } 231 } 232 return undefined; 233} 234 235export const plugin: PluginDescriptor = { 236 pluginId: 'dev.perfetto.CoreCommands', 237 plugin: CoreCommandsPlugin, 238}; 239