blob: 74629218a5e8cabce530d5b4d7db8e297c2119e4 [file] [log] [blame]
// 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 {copyToClipboard} from './clipboard';
import {globals} from './globals';
import {createPage} from './pages';
const CONFIG_PROTO_URL =
`https://android.googlesource.com/platform/external/perfetto/+/master/protos/perfetto/config/perfetto_config.proto`;
const FTRACE_EVENTS = [
'print',
'sched_switch',
'cpufreq_interactive_already',
'cpufreq_interactive_boost',
'cpufreq_interactive_notyet',
'cpufreq_interactive_setspeed',
'cpufreq_interactive_target',
'cpufreq_interactive_unboost',
'cpu_frequency',
'cpu_frequency_limits',
'cpu_idle',
'clock_enable',
'clock_disable',
'clock_set_rate',
'sched_wakeup',
'sched_blocked_reason',
'sched_cpu_hotplug',
'sched_waking',
'ipi_entry',
'ipi_exit',
'ipi_raise',
'softirq_entry',
'softirq_exit',
'softirq_raise',
'i2c_read',
'i2c_write',
'i2c_result',
'i2c_reply',
'smbus_read',
'smbus_write',
'smbus_result',
'smbus_reply',
'lowmemory_kill',
'irq_handler_entry',
'irq_handler_exit',
'sync_pt',
'sync_timeline',
'sync_wait',
'ext4_da_write_begin',
'ext4_da_write_end',
'ext4_sync_file_enter',
'ext4_sync_file_exit',
'block_rq_issue',
'mm_vmscan_direct_reclaim_begin',
'mm_vmscan_direct_reclaim_end',
'mm_vmscan_kswapd_wake',
'mm_vmscan_kswapd_sleep',
'binder_transaction',
'binder_transaction_received',
'binder_set_priority',
'binder_lock',
'binder_locked',
'binder_unlock',
'workqueue_activate_work',
'workqueue_execute_end',
'workqueue_execute_start',
'workqueue_queue_work',
'regulator_disable',
'regulator_disable_complete',
'regulator_enable',
'regulator_enable_complete',
'regulator_enable_delay',
'regulator_set_voltage',
'regulator_set_voltage_complete',
'cgroup_attach_task',
'cgroup_mkdir',
'cgroup_remount',
'cgroup_rmdir',
'cgroup_transfer_tasks',
'cgroup_destroy_root',
'cgroup_release',
'cgroup_rename',
'cgroup_setup_root',
'mdp_cmd_kickoff',
'mdp_commit',
'mdp_perf_set_ot',
'mdp_sspp_change',
'tracing_mark_write',
'mdp_cmd_pingpong_done',
'mdp_compare_bw',
'mdp_perf_set_panic_luts',
'mdp_sspp_set',
'mdp_cmd_readptr_done',
'mdp_misr_crc',
'mdp_perf_set_qos_luts',
'mdp_trace_counter',
'mdp_cmd_release_bw',
'mdp_mixer_update',
'mdp_perf_set_wm_levels',
'mdp_video_underrun_done',
'mdp_cmd_wait_pingpong',
'mdp_perf_prefill_calc',
'mdp_perf_update_bus',
'rotator_bw_ao_as_context',
'mm_filemap_add_to_page_cache',
'mm_filemap_delete_from_page_cache',
'mm_compaction_begin',
'mm_compaction_defer_compaction',
'mm_compaction_deferred',
'mm_compaction_defer_reset',
'mm_compaction_end',
'mm_compaction_finished',
'mm_compaction_isolate_freepages',
'mm_compaction_isolate_migratepages',
'mm_compaction_kcompactd_sleep',
'mm_compaction_kcompactd_wake',
'mm_compaction_migratepages',
'mm_compaction_suitable',
'mm_compaction_try_to_compact_pages',
'mm_compaction_wakeup_kcompactd',
'suspend_resume',
'sched_wakeup_new',
'block_bio_backmerge',
'block_bio_bounce',
'block_bio_complete',
'block_bio_frontmerge',
'block_bio_queue',
'block_bio_remap',
'block_dirty_buffer',
'block_getrq',
'block_plug',
'block_rq_abort',
'block_rq_complete',
'block_rq_insert',
' removed',
'block_rq_remap',
'block_rq_requeue',
'block_sleeprq',
'block_split',
'block_touch_buffer',
'block_unplug',
'ext4_alloc_da_blocks',
'ext4_allocate_blocks',
'ext4_allocate_inode',
'ext4_begin_ordered_truncate',
'ext4_collapse_range',
'ext4_da_release_space',
'ext4_da_reserve_space',
'ext4_da_update_reserve_space',
'ext4_da_write_pages',
'ext4_da_write_pages_extent',
'ext4_direct_IO_enter',
'ext4_direct_IO_exit',
'ext4_discard_blocks',
'ext4_discard_preallocations',
'ext4_drop_inode',
'ext4_es_cache_extent',
'ext4_es_find_delayed_extent_range_enter',
'ext4_es_find_delayed_extent_range_exit',
'ext4_es_insert_extent',
'ext4_es_lookup_extent_enter',
'ext4_es_lookup_extent_exit',
'ext4_es_remove_extent',
'ext4_es_shrink',
'ext4_es_shrink_count',
'ext4_es_shrink_scan_enter',
'ext4_es_shrink_scan_exit',
'ext4_evict_inode',
'ext4_ext_convert_to_initialized_enter',
'ext4_ext_convert_to_initialized_fastpath',
'ext4_ext_handle_unwritten_extents',
'ext4_ext_in_cache',
'ext4_ext_load_extent',
'ext4_ext_map_blocks_enter',
'ext4_ext_map_blocks_exit',
'ext4_ext_put_in_cache',
'ext4_ext_remove_space',
'ext4_ext_remove_space_done',
'ext4_ext_rm_idx',
'ext4_ext_rm_leaf',
'ext4_ext_show_extent',
'ext4_fallocate_enter',
'ext4_fallocate_exit',
'ext4_find_delalloc_range',
'ext4_forget',
'ext4_free_blocks',
'ext4_free_inode',
'ext4_get_implied_cluster_alloc_exit',
'ext4_get_reserved_cluster_alloc',
'ext4_ind_map_blocks_enter',
'ext4_ind_map_blocks_exit',
'ext4_insert_range',
'ext4_invalidatepage',
'ext4_journal_start',
'ext4_journal_start_reserved',
'ext4_journalled_invalidatepage',
'ext4_journalled_write_end',
'ext4_load_inode',
'ext4_load_inode_bitmap',
'ext4_mark_inode_dirty',
'ext4_mb_bitmap_load',
'ext4_mb_buddy_bitmap_load',
'ext4_mb_discard_preallocations',
'ext4_mb_new_group_pa',
'ext4_mb_new_inode_pa',
'ext4_mb_release_group_pa',
'ext4_mb_release_inode_pa',
'ext4_mballoc_alloc',
'ext4_mballoc_discard',
'ext4_mballoc_free',
'ext4_mballoc_prealloc',
'ext4_other_inode_update_time',
'ext4_punch_hole',
'ext4_read_block_bitmap_load',
'ext4_readpage',
'ext4_releasepage',
'ext4_remove_blocks',
'ext4_request_blocks',
'ext4_request_inode',
'ext4_sync_fs',
'ext4_trim_all_free',
'ext4_trim_extent',
'ext4_truncate_enter',
'ext4_truncate_exit',
'ext4_unlink_enter',
'ext4_unlink_exit',
'ext4_write_begin',
'ext4_write_end',
'ext4_writepage',
'ext4_writepages',
'ext4_writepages_result',
'ext4_zero_range',
'task_newtask',
'task_rename',
'sched_process_exec',
'sched_process_exit',
'sched_process_fork',
'sched_process_free',
'sched_process_hang',
'sched_process_wait',
'f2fs_do_submit_bio',
'f2fs_evict_inode',
'f2fs_fallocate',
'f2fs_get_data_block',
'f2fs_get_victim',
'f2fs_iget',
'f2fs_iget_exit',
'f2fs_new_inode',
'f2fs_readpage',
'f2fs_reserve_new_block',
'f2fs_set_page_dirty',
'f2fs_submit_write_page',
'f2fs_sync_file_enter',
'f2fs_sync_file_exit',
'f2fs_sync_fs',
'f2fs_truncate',
'f2fs_truncate_blocks_enter',
'f2fs_truncate_blocks_exit',
'f2fs_truncate_data_blocks_range',
'f2fs_truncate_inode_blocks_enter',
'f2fs_truncate_inode_blocks_exit',
'f2fs_truncate_node',
'f2fs_truncate_nodes_enter',
'f2fs_truncate_nodes_exit',
'f2fs_truncate_partial_nodes',
'f2fs_unlink_enter',
'f2fs_unlink_exit',
'f2fs_vm_page_mkwrite',
'f2fs_write_begin',
'f2fs_write_checkpoint',
'f2fs_write_end',
];
const ATRACE_CATERGORIES = [
'gfx', 'input', 'view', 'webview', 'wm',
'am', 'sm', 'audio', 'video', 'camera',
'hal', 'res', 'dalvik', 'rs', 'bionic',
'power', 'pm', 'ss', 'database', 'network',
'adb', 'vibrator', 'aidl', 'nnapi', 'sched',
'irq', 'irqoff', 'preemptoff', 'i2c', 'freq',
'membus', 'idle', 'disk', 'mmc', 'load',
'sync', 'workq', 'memreclaim', 'regulators', 'binder_driver',
'binder_lock', 'pagecache',
];
const ATRACE_APPS = [
'com.android.chrome',
'com.android.bluetooth',
'com.android.chrome',
'com.android.nfc',
'com.android.phone',
'com.android.settings',
'com.android.systemui',
'com.android.vending',
'com.google.android.apps.messaging',
'com.google.android.apps.nexuslauncher',
'com.google.android.connectivitymonitor',
'com.google.android.contacts',
'com.google.android.gms',
'com.google.android.gms.learning',
'com.google.android.gms.persistent',
'com.google.android.gms.unstable',
'com.google.android.googlequicksearchbox',
'com.google.android.setupwizard',
'com.google.android.volta',
];
const DURATION_HELP = `Duration to trace for.`;
const BUFFER_SIZE_HELP = `Size of the ring buffer which stores the trace.`;
const PROCESS_METADATA_HELP =
`Record process names and parent child relationships.`;
const SCAN_ALL_PROCESSES_ON_START_HELP =
`When tracing begins read metadata for all processes.`;
function toId(label: string): string {
return label.toLowerCase().replace(' ', '-');
}
interface CodeSampleAttrs {
text: string;
hardWhitespace?: boolean;
}
class CodeSample implements m.ClassComponent<CodeSampleAttrs> {
view({attrs}: m.CVnode<CodeSampleAttrs>) {
return m(
'.example-code',
m('code',
{
style: {
'white-space': attrs.hardWhitespace ? 'pre' : null,
},
},
attrs.text),
m('button',
{
onclick: () => copyToClipboard(attrs.text),
},
'Copy to clipboard'));
}
}
interface ToggleAttrs {
label: string;
value: boolean;
help: string;
enabled: boolean;
onchange: (v: boolean) => void;
}
class Toggle implements m.ClassComponent<ToggleAttrs> {
view({attrs}: m.CVnode<ToggleAttrs>) {
return m(
'label.checkbox',
{
title: attrs.help,
class: attrs.enabled ? '' : 'disabled',
},
attrs.label,
m('input[type="checkbox"]', {
onchange: m.withAttr('checked', attrs.onchange),
disabled: !attrs.enabled,
checked: attrs.value,
}));
}
}
interface MultiSelectAttrs {
enabled: boolean;
label: string;
selected: string[];
options: string[];
onadd: (value: string) => void;
onsubtract: (value: string) => void;
}
class MultiSelect implements m.ClassComponent<MultiSelectAttrs> {
view({attrs}: m.CVnode<MultiSelectAttrs>) {
return m(
'label.multiselect',
{class: attrs.enabled ? '' : 'disabled'},
attrs.label,
m('input', {
list: toId(attrs.label),
disabled: !attrs.enabled,
onchange: (e: Event) => {
const elem = e.target as HTMLInputElement;
attrs.onadd(elem.value);
elem.value = '';
},
}),
m('datalist',
{
id: toId(attrs.label),
},
attrs.options.filter(option => !attrs.selected.includes(option))
.map(value => m('option', {value}))),
m('.multiselect-selected',
attrs.selected.map(
selected =>
m('button.multiselect-selected',
{
onclick: (_: Event) => attrs.onsubtract(selected),
},
selected))), );
}
}
interface Preset {
label: string;
value: number;
}
interface NumericAttrs {
label: string;
sublabel: string;
help: string;
value: number;
onchange: (value: number) => void;
presets: Preset[];
}
class Numeric implements m.ClassComponent<NumericAttrs> {
view({attrs}: m.CVnode<NumericAttrs>) {
return m(
'label.range',
{
'for': `range-${attrs.label}`,
'title': attrs.help,
},
attrs.label,
m('.range-control',
attrs.presets.map(
p =>
m('button',
{
class: attrs.value === p.value ? 'selected' : '',
onclick: () => attrs.onchange(p.value),
},
p.label)),
m('input[type=number][min=0]', {
id: `range-${attrs.label}`,
value: attrs.value,
onchange: m.withAttr('value', attrs.onchange),
})),
m('small', attrs.sublabel), );
}
}
export const RecordPage = createPage({
view() {
const state = globals.state.recordConfig;
const data = globals.trackDataStore.get('config') as {
commandline: string,
pbtxt: string,
} | null;
return m(
'.record-page',
m('.text-column', ),
m('.text-column', `To collect a ${state.durationSeconds}
second Perfetto trace from an Android phone run this command:`),
m('.text-column',
`A Perfetto config controls what and how much information is
collected. It is encoded as a `,
m('a',
{
href: CONFIG_PROTO_URL,
},
'proto'),
'.'),
m('.text-column',
m(Numeric, {
label: 'Duration',
sublabel: 's',
value: state.durationSeconds,
help: DURATION_HELP,
onchange: (value: number) => {
globals.dispatch(
Actions.setConfigControl({name: 'durationSeconds', value}));
},
presets: [
{label: '10s', value: 10},
{label: '1m', value: 60},
]
}),
m(Numeric, {
label: 'Buffer size',
sublabel: 'mb',
help: BUFFER_SIZE_HELP,
value: state.bufferSizeMb,
onchange: (value: number) => {
globals.dispatch(
Actions.setConfigControl({name: 'bufferSizeMb', value}));
},
presets: [
{label: '1mb', value: 1},
{label: '10mb', value: 10},
{label: '20mb', value: 20},
]
}),
m(Toggle, {
label: 'Process Metadata',
help: PROCESS_METADATA_HELP,
value: state.processMetadata,
enabled: true,
onchange: (value: boolean) => {
globals.dispatch(
Actions.setConfigControl({name: 'processMetadata', value}));
},
}),
m('.control-group', m(Toggle, {
label: 'Scan all processes on start',
value: state.scanAllProcessesOnStart,
help: SCAN_ALL_PROCESSES_ON_START_HELP,
enabled: state.processMetadata,
onchange: (value: boolean) => {
globals.dispatch(Actions.setConfigControl(
{name: 'scanAllProcessesOnStart', value}));
},
}), ),
m(Toggle, {
label: 'Ftrace & Atrace',
value: state.ftrace,
enabled: true,
help: SCAN_ALL_PROCESSES_ON_START_HELP,
onchange: (value: boolean) => {
globals.dispatch(
Actions.setConfigControl({name: 'ftrace', value}));
},
}),
m('.control-group',
m(MultiSelect, {
label: 'Ftrace Events',
enabled: state.ftrace,
selected: state.ftraceEvents,
options: FTRACE_EVENTS,
onadd: (option: string) => {
globals.dispatch(
Actions.addConfigControl({name: 'ftraceEvents', option}));
},
onsubtract: (option: string) => {
globals.dispatch(Actions.removeConfigControl(
{name: 'ftraceEvents', option}));
},
}),
m(MultiSelect, {
label: 'Atrace Categories',
enabled: state.ftrace,
selected: state.atraceCategories,
options: ATRACE_CATERGORIES,
onadd: (option: string) => {
globals.dispatch(Actions.addConfigControl(
{name: 'atraceCategories', option}));
},
onsubtract: (option: string) => {
globals.dispatch(Actions.removeConfigControl(
{name: 'atraceCategories', option}));
},
}),
m(MultiSelect, {
label: 'Atrace Apps',
enabled: state.ftrace,
selected: state.atraceApps,
options: ATRACE_APPS,
onadd: (option: string) => {
globals.dispatch(
Actions.addConfigControl({name: 'atraceApps', option}));
},
onsubtract: (option: string) => {
globals.dispatch(
Actions.removeConfigControl({name: 'atraceApps', option}));
},
}), ),
),
data ?
[
m('.text-column',
m(CodeSample, {text: data.commandline}),
'Then click "Open trace file" in the menu to the left and select',
' "/tmp/trace".', ),
m('.text-column',
m(CodeSample, {text: data.pbtxt, hardWhitespace: true}), ),
] :
null);
}
});