| // Copyright (C) 2018 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import * as m from 'mithril'; |
| |
| import {Actions} from '../common/actions'; |
| |
| import {globals} from './globals'; |
| import { |
| isLegacyTrace, |
| openFileWithLegacyTraceViewer, |
| } from './legacy_trace_viewer'; |
| |
| const ALL_PROCESSES_QUERY = 'select name, pid from process order by name;'; |
| |
| const CPU_TIME_FOR_PROCESSES = ` |
| select |
| process.name, |
| tot_proc/1e9 as cpu_sec |
| from |
| (select |
| upid, |
| sum(tot_thd) as tot_proc |
| from |
| (select |
| utid, |
| sum(dur) as tot_thd |
| from sched group by utid) |
| join thread using(utid) group by upid) |
| join process using(upid) |
| order by cpu_sec desc limit 100;`; |
| |
| const CYCLES_PER_P_STATE_PER_CPU = ` |
| select ref as cpu, value as freq, sum(dur * value)/1e6 as mcycles |
| from counters where name = 'cpufreq' group by cpu, freq |
| order by mcycles desc limit 32;`; |
| |
| const CPU_TIME_BY_CLUSTER_BY_PROCESS = ` |
| select process.name as process, thread, core, cpu_sec from ( |
| select thread.name as thread, upid, |
| case when cpug = 0 then 'big' else 'little' end as core, |
| cpu_sec from (select cpu/4 as cpug, utid, sum(dur)/1e9 as cpu_sec |
| from sched group by utid, cpug order by cpu_sec desc |
| ) inner join thread using(utid) |
| ) inner join process using(upid) limit 30;`; |
| |
| |
| const SQL_STATS = ` |
| with first as (select started as ts from sqlstats limit 1) |
| select query, |
| round((max(ended - started, 0))/1e6) as runtime_ms, |
| round((max(started - queued, 0))/1e6) as latency_ms, |
| round((started - first.ts)/1e6) as t_start_ms |
| from sqlstats, first |
| order by started desc`; |
| |
| function createCannedQuery(query: string): (_: Event) => void { |
| return (e: Event) => { |
| e.preventDefault(); |
| globals.dispatch(Actions.executeQuery({ |
| engineId: '0', |
| queryId: 'command', |
| query, |
| })); |
| }; |
| } |
| |
| const EXAMPLE_ANDROID_TRACE_URL = |
| 'https://storage.googleapis.com/perfetto-misc/example_android_trace_30s_1'; |
| |
| const EXAMPLE_CHROME_TRACE_URL = |
| 'https://storage.googleapis.com/perfetto-misc/example_chrome_trace_4s_1.json'; |
| |
| const SECTIONS = [ |
| { |
| title: 'Navigation', |
| summary: 'Open or record a new trace', |
| expanded: true, |
| items: [ |
| {t: 'Open trace file', a: popupFileSelectionDialog, i: 'folder_open'}, |
| { |
| t: 'Open with legacy UI', |
| a: popupFileSelectionDialogOldUI, |
| i: 'folder_open' |
| }, |
| {t: 'Record new trace', a: navigateRecord, i: 'fiber_smart_record'}, |
| {t: 'Show timeline', a: navigateViewer, i: 'line_style'}, |
| {t: 'Share current trace', a: dispatchCreatePermalink, i: 'share'}, |
| ], |
| }, |
| { |
| title: 'Example Traces', |
| expanded: true, |
| summary: 'Open an example trace', |
| items: [ |
| { |
| t: 'Open Android example', |
| a: openTraceUrl(EXAMPLE_ANDROID_TRACE_URL), |
| i: 'description' |
| }, |
| { |
| t: 'Open Chrome example', |
| a: openTraceUrl(EXAMPLE_CHROME_TRACE_URL), |
| i: 'description' |
| }, |
| ], |
| }, |
| { |
| title: 'Metrics and auditors', |
| summary: 'Compute summary statistics', |
| items: [ |
| { |
| t: 'All Processes', |
| a: createCannedQuery(ALL_PROCESSES_QUERY), |
| i: 'search', |
| }, |
| { |
| t: 'CPU Time by process', |
| a: createCannedQuery(CPU_TIME_FOR_PROCESSES), |
| i: 'search', |
| }, |
| { |
| t: 'Cycles by p-state by CPU', |
| a: createCannedQuery(CYCLES_PER_P_STATE_PER_CPU), |
| i: 'search', |
| }, |
| { |
| t: 'CPU Time by cluster by process', |
| a: createCannedQuery(CPU_TIME_BY_CLUSTER_BY_PROCESS), |
| i: 'search', |
| }, |
| { |
| t: 'Debug SQL performance', |
| a: createCannedQuery(SQL_STATS), |
| i: 'bug_report', |
| }, |
| ], |
| }, |
| { |
| title: 'Support', |
| summary: 'Documentation & Bugs', |
| items: [ |
| { |
| t: 'Documentation', |
| a: 'https://perfetto.dev', |
| i: 'help', |
| }, |
| { |
| t: 'Report a bug', |
| a: 'https://goto.google.com/perfetto-ui-bug', |
| i: 'bug_report', |
| }, |
| ], |
| }, |
| |
| ]; |
| |
| function getFileElement(): HTMLInputElement { |
| return document.querySelector('input[type=file]')! as HTMLInputElement; |
| } |
| |
| function popupFileSelectionDialog(e: Event) { |
| e.preventDefault(); |
| delete getFileElement().dataset['useCatapultLegacyUi']; |
| getFileElement().click(); |
| } |
| |
| function popupFileSelectionDialogOldUI(e: Event) { |
| e.preventDefault(); |
| getFileElement().dataset['useCatapultLegacyUi'] = '1'; |
| getFileElement().click(); |
| } |
| |
| function openTraceUrl(url: string): (e: Event) => void { |
| return e => { |
| e.preventDefault(); |
| globals.dispatch(Actions.openTraceFromUrl({url})); |
| }; |
| } |
| function onInputElementFileSelectionChanged(e: Event) { |
| if (!(e.target instanceof HTMLInputElement)) { |
| throw new Error('Not an input element'); |
| } |
| if (!e.target.files) return; |
| const file = e.target.files[0]; |
| |
| if (e.target.dataset['useCatapultLegacyUi'] === '1') { |
| // Switch back the old catapult UI. |
| if (isLegacyTrace(file.name)) { |
| openFileWithLegacyTraceViewer(file); |
| } else { |
| globals.dispatch(Actions.convertTraceToJson({file})); |
| } |
| return; |
| } |
| |
| // Open with the current UI. |
| globals.dispatch(Actions.openTraceFromFile({file})); |
| } |
| |
| function navigateRecord(e: Event) { |
| e.preventDefault(); |
| globals.dispatch(Actions.navigate({route: '/record'})); |
| } |
| |
| function navigateViewer(e: Event) { |
| e.preventDefault(); |
| globals.dispatch(Actions.navigate({route: '/viewer'})); |
| } |
| |
| function dispatchCreatePermalink(e: Event) { |
| e.preventDefault(); |
| globals.dispatch(Actions.createPermalink({})); |
| } |
| |
| export class Sidebar implements m.ClassComponent { |
| view() { |
| const vdomSections = []; |
| for (const section of SECTIONS) { |
| const vdomItems = []; |
| for (const item of section.items) { |
| vdomItems.push( |
| m('li', |
| m(`a`, |
| { |
| onclick: typeof item.a === 'function' ? item.a : null, |
| href: typeof item.a === 'string' ? item.a : '#', |
| }, |
| m('i.material-icons', item.i), |
| item.t))); |
| } |
| vdomSections.push( |
| m(`section${section.expanded ? '.expanded' : ''}`, |
| m('.section-header', |
| { |
| onclick: () => { |
| section.expanded = !section.expanded; |
| globals.rafScheduler.scheduleFullRedraw(); |
| } |
| }, |
| m('h1', section.title), |
| m('h2', section.summary), ), |
| m('.section-content', m('ul', vdomItems)))); |
| } |
| return m( |
| 'nav.sidebar', |
| m('header', 'Perfetto'), |
| m('input[type=file]', {onchange: onInputElementFileSelectionChanged}), |
| ...vdomSections); |
| } |
| } |