• 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 {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