• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2018 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 * as m from 'mithril';
16
17import {Actions} from '../common/actions';
18
19import {globals} from './globals';
20import {
21  isLegacyTrace,
22  openFileWithLegacyTraceViewer,
23} from './legacy_trace_viewer';
24
25const ALL_PROCESSES_QUERY = 'select name, pid from process order by name;';
26
27const CPU_TIME_FOR_PROCESSES = `
28select
29  process.name,
30  tot_proc/1e9 as cpu_sec
31from
32  (select
33    upid,
34    sum(tot_thd) as tot_proc
35  from
36    (select
37      utid,
38      sum(dur) as tot_thd
39    from sched group by utid)
40  join thread using(utid) group by upid)
41join process using(upid)
42order by cpu_sec desc limit 100;`;
43
44const CYCLES_PER_P_STATE_PER_CPU = `
45select
46  cpu,
47  freq,
48  dur,
49  sum(dur * freq)/1e6 as mcycles
50from (
51  select
52    ref as cpu,
53    value as freq,
54    lead(ts) over (partition by ref order by ts) - ts as dur
55  from counters
56  where name = 'cpufreq'
57) group by cpu, freq
58order by mcycles desc limit 32;`;
59
60const CPU_TIME_BY_CLUSTER_BY_PROCESS = `
61select process.name as process, thread, core, cpu_sec from (
62  select thread.name as thread, upid,
63    case when cpug = 0 then 'little' else 'big' end as core,
64    cpu_sec from (select cpu/4 as cpug, utid, sum(dur)/1e9 as cpu_sec
65    from sched group by utid, cpug order by cpu_sec desc
66  ) inner join thread using(utid)
67) inner join process using(upid) limit 30;`;
68
69
70const SQL_STATS = `
71with first as (select started as ts from sqlstats limit 1)
72select query,
73    round((max(ended - started, 0))/1e6) as runtime_ms,
74    round((max(started - queued, 0))/1e6) as latency_ms,
75    round((started - first.ts)/1e6) as t_start_ms
76from sqlstats, first
77order by started desc`;
78
79const TRACE_STATS = 'select * from stats order by severity, source, name, idx';
80
81function createCannedQuery(query: string): (_: Event) => void {
82  return (e: Event) => {
83    e.preventDefault();
84    globals.dispatch(Actions.executeQuery({
85      engineId: '0',
86      queryId: 'command',
87      query,
88    }));
89  };
90}
91
92const EXAMPLE_ANDROID_TRACE_URL =
93    'https://storage.googleapis.com/perfetto-misc/example_android_trace_30s_1';
94
95const EXAMPLE_CHROME_TRACE_URL =
96    'https://storage.googleapis.com/perfetto-misc/example_chrome_trace_4s_1.json';
97
98const SECTIONS = [
99  {
100    title: 'Navigation',
101    summary: 'Open or record a new trace',
102    expanded: true,
103    items: [
104      {t: 'Open trace file', a: popupFileSelectionDialog, i: 'folder_open'},
105      {
106        t: 'Open with legacy UI',
107        a: popupFileSelectionDialogOldUI,
108        i: 'folder_open'
109      },
110      {t: 'Record new trace', a: navigateRecord, i: 'fiber_smart_record'},
111      {t: 'Show timeline', a: navigateViewer, i: 'line_style'},
112      {t: 'Share current trace', a: dispatchCreatePermalink, i: 'share'},
113      {t: 'Download current trace', a: downloadTrace, i: 'file_download'},
114    ],
115  },
116  {
117    title: 'Example Traces',
118    expanded: true,
119    summary: 'Open an example trace',
120    items: [
121      {
122        t: 'Open Android example',
123        a: openTraceUrl(EXAMPLE_ANDROID_TRACE_URL),
124        i: 'description'
125      },
126      {
127        t: 'Open Chrome example',
128        a: openTraceUrl(EXAMPLE_CHROME_TRACE_URL),
129        i: 'description'
130      },
131    ],
132  },
133  {
134    title: 'Metrics and auditors',
135    summary: 'Compute summary statistics',
136    items: [
137      {
138        t: 'All Processes',
139        a: createCannedQuery(ALL_PROCESSES_QUERY),
140        i: 'search',
141      },
142      {
143        t: 'CPU Time by process',
144        a: createCannedQuery(CPU_TIME_FOR_PROCESSES),
145        i: 'search',
146      },
147      {
148        t: 'Cycles by p-state by CPU',
149        a: createCannedQuery(CYCLES_PER_P_STATE_PER_CPU),
150        i: 'search',
151      },
152      {
153        t: 'CPU Time by cluster by process',
154        a: createCannedQuery(CPU_TIME_BY_CLUSTER_BY_PROCESS),
155        i: 'search',
156      },
157      {
158        t: 'Trace stats',
159        a: createCannedQuery(TRACE_STATS),
160        i: 'bug_report',
161      },
162      {
163        t: 'Debug SQL performance',
164        a: createCannedQuery(SQL_STATS),
165        i: 'bug_report',
166      },
167    ],
168  },
169  {
170    title: 'Support',
171    summary: 'Documentation & Bugs',
172    items: [
173      {
174        t: 'Documentation',
175        a: 'https://perfetto.dev',
176        i: 'help',
177      },
178      {
179        t: 'Report a bug',
180        a: 'https://goto.google.com/perfetto-ui-bug',
181        i: 'bug_report',
182      },
183    ],
184  },
185
186];
187
188function getFileElement(): HTMLInputElement {
189  return document.querySelector('input[type=file]')! as HTMLInputElement;
190}
191
192function popupFileSelectionDialog(e: Event) {
193  e.preventDefault();
194  delete getFileElement().dataset['useCatapultLegacyUi'];
195  getFileElement().click();
196}
197
198function popupFileSelectionDialogOldUI(e: Event) {
199  e.preventDefault();
200  getFileElement().dataset['useCatapultLegacyUi'] = '1';
201  getFileElement().click();
202}
203
204function openTraceUrl(url: string): (e: Event) => void {
205  return e => {
206    e.preventDefault();
207    globals.dispatch(Actions.openTraceFromUrl({url}));
208  };
209}
210
211function onInputElementFileSelectionChanged(e: Event) {
212  if (!(e.target instanceof HTMLInputElement)) {
213    throw new Error('Not an input element');
214  }
215  if (!e.target.files) return;
216  const file = e.target.files[0];
217
218  if (e.target.dataset['useCatapultLegacyUi'] === '1') {
219    // Switch back the old catapult UI.
220    if (isLegacyTrace(file.name)) {
221      openFileWithLegacyTraceViewer(file);
222    } else {
223      globals.dispatch(Actions.convertTraceToJson({file}));
224    }
225    return;
226  }
227
228  // Open with the current UI.
229  globals.dispatch(Actions.openTraceFromFile({file}));
230}
231
232function navigateRecord(e: Event) {
233  e.preventDefault();
234  globals.dispatch(Actions.navigate({route: '/record'}));
235}
236
237function navigateViewer(e: Event) {
238  e.preventDefault();
239  globals.dispatch(Actions.navigate({route: '/viewer'}));
240}
241
242function dispatchCreatePermalink(e: Event) {
243  e.preventDefault();
244  globals.dispatch(Actions.createPermalink({}));
245}
246
247function downloadTrace(e: Event) {
248  e.preventDefault();
249  const engine = Object.values(globals.state.engines)[0];
250  if (!engine) return;
251  const src = engine.source;
252  if (typeof src === 'string') {
253    window.open(src);
254  } else {
255    const url = URL.createObjectURL(src);
256    const a = document.createElement('a');
257    a.href = url;
258    a.download = src.name;
259    document.body.appendChild(a);
260    a.click();
261    document.body.removeChild(a);
262    URL.revokeObjectURL(url);
263  }
264}
265
266export class Sidebar implements m.ClassComponent {
267  view() {
268    const vdomSections = [];
269    for (const section of SECTIONS) {
270      const vdomItems = [];
271      for (const item of section.items) {
272        vdomItems.push(
273            m('li',
274              m(`a`,
275                {
276                  onclick: typeof item.a === 'function' ? item.a : null,
277                  href: typeof item.a === 'string' ? item.a : '#',
278                },
279                m('i.material-icons', item.i),
280                item.t)));
281      }
282      vdomSections.push(
283          m(`section${section.expanded ? '.expanded' : ''}`,
284            m('.section-header',
285              {
286                onclick: () => {
287                  section.expanded = !section.expanded;
288                  globals.rafScheduler.scheduleFullRedraw();
289                }
290              },
291              m('h1', section.title),
292              m('h2', section.summary), ),
293            m('.section-content', m('ul', vdomItems))));
294    }
295    return m(
296        'nav.sidebar',
297        m('header', 'Perfetto'),
298        m('input[type=file]', {onchange: onInputElementFileSelectionChanged}),
299        ...vdomSections);
300  }
301}
302