| // 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 {MeminfoCounters, StatCounters, VmstatCounters} from '../common/protos'; |
| |
| import {copyToClipboard} from './clipboard'; |
| import {globals} from './globals'; |
| import {createPage} from './pages'; |
| |
| const COUNTER_PRESETS = [ |
| {label: 'never', value: null}, |
| {label: '10ms', value: 10}, |
| {label: '50ms', value: 50}, |
| {label: '500ms', value: 500}, |
| ]; |
| |
| 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', |
| '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 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 FTRACE_AND_ATRACE_HELP = `Record ftrace & atrace events`; |
| const SYS_STATS_HELP = ``; |
| |
| 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: (values: string[]) => void; |
| onsubtract: (values: string[]) => void; |
| } |
| |
| class MultiSelect implements m.ClassComponent<MultiSelectAttrs> { |
| view({attrs}: m.CVnode<MultiSelectAttrs>) { |
| const unselected = attrs.options.filter(o => !attrs.selected.includes(o)); |
| |
| const helpers: m.Children = []; |
| |
| if (attrs.selected.length > 0) { |
| helpers.push( |
| m('button', |
| { |
| disabled: !attrs.enabled, |
| onclick: () => attrs.onsubtract(attrs.selected), |
| }, |
| 'Remove all')); |
| } else if (attrs.options.length > 0 && attrs.options.length < 100) { |
| helpers.push( |
| m('button', |
| { |
| disabled: !attrs.enabled, |
| onclick: () => attrs.onadd(unselected), |
| }, |
| 'Add all')); |
| } |
| |
| return m( |
| 'label.multiselect', |
| { |
| class: attrs.enabled ? '' : 'disabled', |
| for: `multiselect-${toId(attrs.label)}`, |
| }, |
| attrs.label, |
| m('div', helpers), |
| m('input', { |
| id: `multiselect-${toId(attrs.label)}`, |
| 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', |
| { |
| disabled: !attrs.enabled, |
| onclick: (_: Event) => attrs.onsubtract([selected]), |
| }, |
| selected)))); |
| } |
| } |
| |
| interface Preset { |
| label: string; |
| value: number|null; |
| } |
| |
| interface NumericAttrs { |
| label: string; |
| sublabel: string; |
| enabled: boolean; |
| help: string; |
| placeholder?: string; |
| value: number|null; |
| onchange: (value: null|number) => void; |
| presets: Preset[]; |
| } |
| |
| function toNumber(s: string): number|null { |
| const n = Number(s); |
| return s === '' || isNaN(n) ? null : n; |
| } |
| |
| class Numeric implements m.ClassComponent<NumericAttrs> { |
| view({attrs}: m.CVnode<NumericAttrs>) { |
| return m( |
| 'label.range', |
| { |
| 'for': `range-${toId(attrs.label)}`, |
| 'title': attrs.help, |
| class: attrs.enabled ? '' : 'disabled', |
| }, |
| attrs.label, |
| m('.range-control', |
| attrs.presets.map( |
| p => |
| m('button', |
| { |
| disabled: !attrs.enabled, |
| class: attrs.value === p.value ? 'selected' : '', |
| onclick: () => attrs.onchange(p.value), |
| }, |
| p.label)), |
| m('input[type=number][min=1]', { |
| id: `range-${toId(attrs.label)}`, |
| placeholder: attrs.placeholder, |
| value: attrs.value, |
| disabled: !attrs.enabled, |
| onchange: m.withAttr('value', s => attrs.onchange(toNumber(s))), |
| })), |
| m('small', attrs.sublabel), ); |
| } |
| } |
| |
| function onAdd(name: string) { |
| return (optionsToAdd: string[]) => { |
| globals.dispatch(Actions.addConfigControl({name, optionsToAdd})); |
| }; |
| } |
| |
| function onSubtract(name: string) { |
| return (optionsToRemove: string[]) => { |
| globals.dispatch(Actions.removeConfigControl({name, optionsToRemove})); |
| }; |
| } |
| |
| function onChange<T extends string|number|boolean|null>(name: string) { |
| return (value: T) => { |
| globals.dispatch(Actions.setConfigControl({name, value})); |
| }; |
| } |
| |
| function isFalsy(x: undefined|null|number|'') { |
| return x === undefined || x === null || x === 0 || x === ''; |
| } |
| |
| function isTruthy(x: undefined|null|number|'') { |
| return !isFalsy(x); |
| } |
| |
| 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', |
| {class: state.displayConfigAsPbtxt ? 'three' : 'two' }, |
| m('.config.text-column', |
| `To collect a Perfetto trace, configure the options below then |
| use the command on the right to capture the trace.`, |
| m('.heading', m(Numeric, { |
| enabled: true, |
| label: 'Duration', |
| sublabel: 's', |
| placeholder: '', |
| value: state.durationSeconds, |
| help: DURATION_HELP, |
| onchange: onChange<number|null>('durationSeconds'), |
| presets: [ |
| {label: '10s', value: 10}, |
| {label: '1m', value: 60}, |
| ] |
| })), |
| |
| m(Toggle, { |
| label: 'Long trace mode', |
| help: '', |
| value: state.writeIntoFile, |
| enabled: true, |
| onchange: onChange<boolean>('writeIntoFile'), |
| }), |
| m('.control-group', m(Numeric, { |
| enabled: state.writeIntoFile, |
| label: 'Flush into file every', |
| sublabel: 'ms', |
| placeholder: 'default', |
| value: state.fileWritePeriodMs, |
| help: '', |
| onchange: onChange<number|null>('fileWritePeriodMs'), |
| presets: [ |
| {label: '5000ms', value: 5000}, |
| ] |
| }), ), |
| |
| m(Numeric, { |
| enabled: true, |
| label: 'Buffer size', |
| sublabel: 'mb', |
| help: BUFFER_SIZE_HELP, |
| placeholder: '', |
| value: state.bufferSizeMb, |
| onchange: onChange<number|null>('bufferSizeMb'), |
| presets: [ |
| {label: '1mb', value: 1}, |
| {label: '10mb', value: 10}, |
| {label: '20mb', value: 20}, |
| ] |
| }), |
| |
| m('.heading', m(Toggle, { |
| label: 'Record process/thread associations', |
| help: PROCESS_METADATA_HELP, |
| value: state.processMetadata, |
| enabled: true, |
| onchange: onChange<boolean|null>('processMetadata'), |
| })), |
| |
| // TODO(hjd): Re-add when multi-buffer support comes. |
| //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: onChange<boolean>('scanAllProcessesOnStart'), |
| //})), |
| |
| m('.heading', m(Toggle, { |
| label: 'Ftrace & Atrace', |
| value: state.ftrace, |
| enabled: true, |
| help: FTRACE_AND_ATRACE_HELP, |
| onchange: onChange<boolean>('ftrace'), |
| })), |
| |
| m('.control-group', |
| m(MultiSelect, { |
| label: 'Ftrace Events', |
| enabled: state.ftrace, |
| selected: state.ftraceEvents, |
| options: FTRACE_EVENTS, |
| onadd: onAdd('ftraceEvents'), |
| onsubtract: onSubtract('ftraceEvents'), |
| }), |
| |
| m(MultiSelect, { |
| label: 'Atrace Categories', |
| enabled: state.ftrace, |
| selected: state.atraceCategories, |
| options: ATRACE_CATERGORIES, |
| onadd: onAdd('atraceCategories'), |
| onsubtract: onSubtract('atraceCategories'), |
| }), |
| |
| m(MultiSelect, { |
| label: 'Atrace Apps', |
| enabled: state.ftrace, |
| selected: state.atraceApps, |
| options: [], |
| onadd: onAdd('atraceApps'), |
| onsubtract: onSubtract('atraceApps'), |
| }), |
| |
| m('i', { |
| class: state.ftrace ? '' : 'disabled' |
| }, 'Advanced ftrace configuration'), |
| |
| m(Numeric, { |
| enabled: state.ftrace, |
| label: 'Drain kernel buffer every', |
| sublabel: 'ms', |
| help: '', |
| placeholder: 'default', |
| value: state.ftraceDrainPeriodMs, |
| onchange: onChange<number|null>('ftraceDrainPeriodMs'), |
| presets: [ |
| {label: '100ms', value: 100}, |
| {label: '500ms', value: 500}, |
| {label: '1000ms', value: 1000}, |
| ] |
| }), |
| |
| m(Numeric, { |
| enabled: state.ftrace, |
| label: 'Kernel buffer size (per cpu)', |
| sublabel: 'kb', |
| help: '', |
| placeholder: 'default', |
| value: state.ftraceBufferSizeKb, |
| onchange: onChange<number|null>('ftraceBufferSizeKb'), |
| presets: [ |
| {label: '1mb', value: 1 * 1024}, |
| {label: '4mb', value: 4 * 1024}, |
| {label: '8mb', value: 8 * 1024}, |
| ] |
| }), |
| |
| ), |
| |
| m('.heading', m(Toggle, { |
| label: '/proc poller', |
| value: state.sysStats, |
| enabled: true, |
| help: SYS_STATS_HELP, |
| onchange: onChange<boolean>('sysStats'), |
| })), |
| |
| m('.control-group', |
| m(Numeric, { |
| label: 'Poll /proc/stat every', |
| sublabel: 'ms', |
| enabled: state.sysStats, |
| help: '', |
| placeholder: 'never', |
| value: state.statPeriodMs, |
| onchange: onChange<null|number>('statPeriodMs'), |
| presets: COUNTER_PRESETS, |
| }), |
| |
| m(MultiSelect, { |
| label: 'Stat Counters', |
| enabled: state.sysStats && isTruthy(state.statPeriodMs), |
| selected: state.statCounters, |
| options: Object.keys(StatCounters) |
| .filter(c => c !== 'STAT_UNSPECIFIED'), |
| onadd: onAdd('statCounters'), |
| onsubtract: onSubtract('statCounters'), |
| }), |
| |
| m(Numeric, { |
| label: 'Poll /proc/meminfo every', |
| sublabel: 'ms', |
| enabled: state.sysStats, |
| help: '', |
| placeholder: 'never', |
| value: state.meminfoPeriodMs, |
| onchange: onChange<null|number>('meminfoPeriodMs'), |
| presets: COUNTER_PRESETS, |
| }), |
| |
| m(MultiSelect, { |
| label: 'Meminfo Counters', |
| enabled: state.sysStats && isTruthy(state.meminfoPeriodMs), |
| selected: state.meminfoCounters, |
| options: Object.keys(MeminfoCounters) |
| .filter(c => c !== 'MEMINFO_UNSPECIFIED'), |
| onadd: onAdd('meminfoCounters'), |
| onsubtract: onSubtract('meminfoCounters'), |
| }), |
| |
| m(Numeric, { |
| label: 'Poll /proc/vmstat every', |
| sublabel: 'ms', |
| enabled: state.sysStats, |
| help: '', |
| placeholder: 'never', |
| value: state.vmstatPeriodMs, |
| onchange: onChange<null|number>('vmstatPeriodMs'), |
| presets: COUNTER_PRESETS, |
| }), |
| |
| m(MultiSelect, { |
| label: 'Vmstat Counters', |
| enabled: state.sysStats && isTruthy(state.vmstatPeriodMs), |
| selected: state.vmstatCounters, |
| options: Object.keys(VmstatCounters) |
| .filter(c => c !== 'VMSTAT_UNSPECIFIED'), |
| onadd: onAdd('vmstatCounters'), |
| onsubtract: onSubtract('vmstatCounters'), |
| }), |
| |
| ), |
| |
| m('hr'), |
| |
| m(Toggle, { |
| label: 'Display config as pbtxt', |
| value: state.displayConfigAsPbtxt, |
| enabled: true, |
| help: '', |
| onchange: onChange<boolean>('displayConfigAsPbtxt'), |
| }), |
| |
| |
| ), |
| |
| data ? |
| [ |
| m('.command.text-column', |
| `To collect a ${state.durationSeconds} |
| second Perfetto trace from an Android phone run this command:`, |
| m(CodeSample, {text: data.commandline}), |
| 'Then click "Open trace file" in the menu to the left and select', |
| ' "/tmp/trace".'), |
| |
| state.displayConfigAsPbtxt ? |
| m('.pbtxt.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(CodeSample, {text: data.pbtxt, hardWhitespace: true}) |
| ) : null, |
| ] : null, |
| |
| ); |
| |
| |
| } |
| }); |