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 {produce} from 'immer'; 16import * as m from 'mithril'; 17 18import {Actions} from '../common/actions'; 19import {MeminfoCounters, VmstatCounters} from '../common/protos'; 20import {RecordMode} from '../common/state'; 21 22import {globals} from './globals'; 23import {createPage} from './pages'; 24import { 25 CodeSnippet, 26 Dropdown, 27 DropdownAttrs, 28 Probe, 29 ProbeAttrs, 30 Slider, 31 SliderAttrs, 32 Textarea, 33 TextareaAttrs 34} from './record_widgets'; 35import {Router} from './router'; 36 37 38const POLL_RATE_MS = [250, 500, 1000, 2500, 5000, 30000, 60000]; 39 40const ATRACE_CATEGORIES = new Map<string, string>(); 41ATRACE_CATEGORIES.set('gfx', 'Graphics'); 42ATRACE_CATEGORIES.set('input', 'Input'); 43ATRACE_CATEGORIES.set('view', 'View System'); 44ATRACE_CATEGORIES.set('webview', 'WebView'); 45ATRACE_CATEGORIES.set('wm', 'Window Manager'); 46ATRACE_CATEGORIES.set('am', 'Activity Manager'); 47ATRACE_CATEGORIES.set('sm', 'Sync Manager'); 48ATRACE_CATEGORIES.set('audio', 'Audio'); 49ATRACE_CATEGORIES.set('video', 'Video'); 50ATRACE_CATEGORIES.set('camera', 'Camera'); 51ATRACE_CATEGORIES.set('hal', 'Hardware Modules'); 52ATRACE_CATEGORIES.set('res', 'Resource Loading'); 53ATRACE_CATEGORIES.set('dalvik', 'ART & Dalvik'); 54ATRACE_CATEGORIES.set('rs', 'RenderScript'); 55ATRACE_CATEGORIES.set('bionic', 'Bionic C library'); 56ATRACE_CATEGORIES.set('gfx', 'Graphics'); 57ATRACE_CATEGORIES.set('power', 'Power Management'); 58ATRACE_CATEGORIES.set('pm', 'Package Manager'); 59ATRACE_CATEGORIES.set('ss', 'System Server'); 60ATRACE_CATEGORIES.set('database', 'Database'); 61ATRACE_CATEGORIES.set('network', 'Network'); 62ATRACE_CATEGORIES.set('adb', 'ADB'); 63ATRACE_CATEGORIES.set('vibrator', 'Vibrator'); 64ATRACE_CATEGORIES.set('aidl', 'AIDL calls'); 65ATRACE_CATEGORIES.set('nnapi', 'Neural Network API'); 66ATRACE_CATEGORIES.set('rro', 'Resource Overlay'); 67 68const LOG_BUFFERS = new Map<string, string>(); 69LOG_BUFFERS.set('LID_RADIO', 'Radio'); 70LOG_BUFFERS.set('LID_EVENTS', 'Binary events'); 71LOG_BUFFERS.set('LID_SYSTEM', 'System'); 72LOG_BUFFERS.set('LID_CRASH', 'Crash'); 73LOG_BUFFERS.set('LID_SECURITY', 'Security'); 74LOG_BUFFERS.set('LID_KERNEL', 'Kernel'); 75 76const FTRACE_CATEGORIES = new Map<string, string>(); 77FTRACE_CATEGORIES.set('binder/*', 'binder'); 78FTRACE_CATEGORIES.set('block/*', 'block'); 79FTRACE_CATEGORIES.set('clk/*', 'clk'); 80FTRACE_CATEGORIES.set('ext4/*', 'ext4'); 81FTRACE_CATEGORIES.set('f2fs/*', 'f2fs'); 82FTRACE_CATEGORIES.set('i2c/*', 'i2c'); 83FTRACE_CATEGORIES.set('irq/*', 'irq'); 84FTRACE_CATEGORIES.set('kmem/*', 'kmem'); 85FTRACE_CATEGORIES.set('memory_bus/*', 'memory_bus'); 86FTRACE_CATEGORIES.set('mmc/*', 'mmc'); 87FTRACE_CATEGORIES.set('oom/*', 'oom'); 88FTRACE_CATEGORIES.set('power/*', 'power'); 89FTRACE_CATEGORIES.set('regulator/*', 'regulator'); 90FTRACE_CATEGORIES.set('sched/*', 'sched'); 91FTRACE_CATEGORIES.set('sync/*', 'sync'); 92FTRACE_CATEGORIES.set('task/*', 'task'); 93FTRACE_CATEGORIES.set('task/*', 'task'); 94FTRACE_CATEGORIES.set('vmscan/*', 'vmscan'); 95 96function RecSettings(cssClass: string) { 97 const S = (x: number) => x * 1000; 98 const M = (x: number) => x * 1000 * 60; 99 const H = (x: number) => x * 1000 * 60 * 60; 100 101 const cfg = globals.state.recordConfig; 102 103 const recButton = (mode: RecordMode, title: string, img: string) => { 104 const checkboxArgs = { 105 checked: cfg.mode === mode, 106 onchange: m.withAttr( 107 'checked', 108 (checked: boolean) => { 109 if (!checked) return; 110 const traceCfg = produce(globals.state.recordConfig, draft => { 111 draft.mode = mode; 112 }); 113 globals.dispatch(Actions.setRecordConfig({config: traceCfg})); 114 }) 115 }; 116 return m( 117 `label${cfg.mode === mode ? '.selected' : ''}`, 118 m(`input[type=radio][name=rec_mode]`, checkboxArgs), 119 m(`img[src=assets/${img}]`), 120 m('span', title)); 121 }; 122 123 return m( 124 `.record-section${cssClass}`, 125 m('header', 'Recording mode'), 126 m('.record-mode', 127 recButton('STOP_WHEN_FULL', 'Stop when full', 'rec_one_shot.png'), 128 recButton('RING_BUFFER', 'Ring buffer', 'rec_ring_buf.png'), 129 recButton('LONG_TRACE', 'Long trace', 'rec_long_trace.png'), ), 130 131 m(Slider, { 132 title: 'In-memory buffer size', 133 icon: '360', 134 values: [4, 8, 16, 32, 64, 128, 256, 512], 135 unit: 'MB', 136 set: (cfg, val) => cfg.bufferSizeMb = val, 137 get: (cfg) => cfg.bufferSizeMb 138 } as SliderAttrs), 139 140 m(Slider, { 141 title: 'Max duration', 142 icon: 'timer', 143 values: [S(10), S(15), S(30), S(60), M(5), M(30), H(1), H(6), H(12)], 144 isTime: true, 145 unit: 'h:m:s', 146 set: (cfg, val) => cfg.durationMs = val, 147 get: (cfg) => cfg.durationMs 148 } as SliderAttrs), 149 m(Slider, { 150 title: 'Max file size', 151 icon: 'save', 152 cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '', 153 values: [5, 25, 50, 100, 500, 1000, 1000 * 5, 1000 * 10], 154 unit: 'MB', 155 set: (cfg, val) => cfg.maxFileSizeMb = val, 156 get: (cfg) => cfg.maxFileSizeMb 157 } as SliderAttrs), 158 m(Slider, { 159 title: 'Flush on disk every', 160 cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '', 161 icon: 'av_timer', 162 values: [100, 250, 500, 1000, 2500, 5000], 163 unit: 'ms', 164 set: (cfg, val) => cfg.fileWritePeriodMs = val, 165 get: (cfg) => cfg.fileWritePeriodMs || 0 166 } as SliderAttrs)); 167} 168 169function PowerSettings(cssClass: string) { 170 return m( 171 `.record-section${cssClass}`, 172 m(Probe, 173 { 174 title: 'Battery drain', 175 img: 'rec_battery_counters.png', 176 descr: `Polls charge counters and instantaneous power draw from 177 the battery power management IC.`, 178 setEnabled: (cfg, val) => cfg.batteryDrain = val, 179 isEnabled: (cfg) => cfg.batteryDrain 180 } as ProbeAttrs, 181 m(Slider, { 182 title: 'Poll rate', 183 cssClass: '.thin', 184 values: POLL_RATE_MS, 185 unit: 'ms', 186 set: (cfg, val) => cfg.batteryDrainPollMs = val, 187 get: (cfg) => cfg.batteryDrainPollMs 188 } as SliderAttrs)), 189 m(Probe, { 190 title: 'Board voltages & frequencies', 191 img: 'rec_board_voltage.png', 192 descr: 'Tracks voltage and frequency changes from board sensors', 193 setEnabled: (cfg, val) => cfg.boardSensors = val, 194 isEnabled: (cfg) => cfg.boardSensors 195 } as ProbeAttrs)); 196} 197 198function CpuSettings(cssClass: string) { 199 return m( 200 `.record-section${cssClass}`, 201 m(Probe, 202 { 203 title: 'Coarse CPU usage counter', 204 img: 'rec_cpu_coarse.png', 205 descr: `Lightweight polling of CPU usage counters via /proc/stat. 206 Allows to periodically monitor CPU usage.`, 207 setEnabled: (cfg, val) => cfg.cpuCoarse = val, 208 isEnabled: (cfg) => cfg.cpuCoarse 209 } as ProbeAttrs, 210 m(Slider, { 211 title: 'Poll rate', 212 cssClass: '.thin', 213 values: POLL_RATE_MS, 214 unit: 'ms', 215 set: (cfg, val) => cfg.cpuCoarsePollMs = val, 216 get: (cfg) => cfg.cpuCoarsePollMs 217 } as SliderAttrs)), 218 m(Probe, { 219 title: 'Scheduling details', 220 img: 'rec_cpu_fine.png', 221 descr: 'Enables high-detailed tracking of scheduling events', 222 setEnabled: (cfg, val) => cfg.cpuSched = val, 223 isEnabled: (cfg) => cfg.cpuSched 224 } as ProbeAttrs), 225 m(Probe, { 226 title: 'CPU frequency and idle states', 227 img: 'rec_cpu_freq.png', 228 descr: 'Records cpu frequency and idle state changes via ftrace', 229 setEnabled: (cfg, val) => cfg.cpuFreq = val, 230 isEnabled: (cfg) => cfg.cpuFreq 231 } as ProbeAttrs), 232 m(Probe, { 233 title: 'Scheduling chains / latency analysis', 234 img: 'rec_cpu_wakeup.png', 235 descr: `Tracks causality of scheduling transitions. When a task 236 X transitions from blocked -> runnable, keeps track of the 237 task Y that X's transition (e.g. posting a semaphore).`, 238 setEnabled: (cfg, val) => cfg.cpuLatency = val, 239 isEnabled: (cfg) => cfg.cpuLatency 240 } as ProbeAttrs)); 241} 242 243function MemorySettings(cssClass: string) { 244 const meminfoOpts = new Map<string, string>(); 245 for (const x in MeminfoCounters) { 246 if (typeof MeminfoCounters[x] === 'number' && 247 !`${x}`.endsWith('_UNSPECIFIED')) { 248 meminfoOpts.set(x, x.replace('MEMINFO_', '').toLowerCase()); 249 } 250 } 251 const vmstatOpts = new Map<string, string>(); 252 for (const x in VmstatCounters) { 253 if (typeof VmstatCounters[x] === 'number' && 254 !`${x}`.endsWith('_UNSPECIFIED')) { 255 vmstatOpts.set(x, x.replace('VMSTAT_', '').toLowerCase()); 256 } 257 } 258 return m( 259 `.record-section${cssClass}`, 260 m(Probe, 261 { 262 title: 'Kernel meminfo', 263 img: 'rec_meminfo.png', 264 descr: 'Polling of /proc/meminfo', 265 setEnabled: (cfg, val) => cfg.meminfo = val, 266 isEnabled: (cfg) => cfg.meminfo 267 } as ProbeAttrs, 268 m(Slider, { 269 title: 'Poll rate', 270 cssClass: '.thin', 271 values: POLL_RATE_MS, 272 unit: 'ms', 273 set: (cfg, val) => cfg.meminfoPeriodMs = val, 274 get: (cfg) => cfg.meminfoPeriodMs 275 } as SliderAttrs), 276 m(Dropdown, { 277 title: 'Select counters', 278 cssClass: '.multicolumn', 279 options: meminfoOpts, 280 set: (cfg, val) => cfg.meminfoCounters = val, 281 get: (cfg) => cfg.meminfoCounters 282 } as DropdownAttrs)), 283 m(Probe, { 284 title: 'High-frequency memory events', 285 img: 'rec_mem_hifreq.png', 286 descr: `Allows to track short memory spikes and transitories through 287 ftrace's mm_event, rss_stat and ion events. Avialable only 288 on recent Android Q+ kernels`, 289 setEnabled: (cfg, val) => cfg.memHiFreq = val, 290 isEnabled: (cfg) => cfg.memHiFreq 291 } as ProbeAttrs), 292 m(Probe, { 293 title: 'Low memory killer', 294 img: 'rec_lmk.png', 295 descr: `Record LMK events. Works both with the old in-kernel LMK 296 and the newer userspace lmkd. It also tracks OOM score 297 adjustments.`, 298 setEnabled: (cfg, val) => cfg.memLmk = val, 299 isEnabled: (cfg) => cfg.memLmk 300 } as ProbeAttrs), 301 m(Probe, 302 { 303 title: 'Per process stats', 304 img: 'rec_ps_stats.png', 305 descr: `Periodically samples all processes in the system tracking: 306 their thread list, memory counters (RSS, swap and other 307 /proc/status counters) and oom_score_adj.`, 308 setEnabled: (cfg, val) => cfg.procStats = val, 309 isEnabled: (cfg) => cfg.procStats 310 } as ProbeAttrs, 311 m(Slider, { 312 title: 'Poll rate', 313 cssClass: '.thin', 314 values: POLL_RATE_MS, 315 unit: 'ms', 316 set: (cfg, val) => cfg.procStatsPeriodMs = val, 317 get: (cfg) => cfg.procStatsPeriodMs 318 } as SliderAttrs)), 319 m(Probe, 320 { 321 title: 'Virtual memory stats', 322 img: 'rec_vmstat.png', 323 descr: `Periodically polls virtual memory stats from /proc/vmstat. 324 Allows to gather statistics about swap, eviction, 325 compression and pagecache efficiency`, 326 setEnabled: (cfg, val) => cfg.vmstat = val, 327 isEnabled: (cfg) => cfg.vmstat 328 } as ProbeAttrs, 329 m(Slider, { 330 title: 'Poll rate', 331 cssClass: '.thin', 332 values: POLL_RATE_MS, 333 unit: 'ms', 334 set: (cfg, val) => cfg.vmstatPeriodMs = val, 335 get: (cfg) => cfg.vmstatPeriodMs 336 } as SliderAttrs), 337 m(Dropdown, { 338 title: 'Select counters', 339 cssClass: '.multicolumn', 340 options: vmstatOpts, 341 set: (cfg, val) => cfg.vmstatCounters = val, 342 get: (cfg) => cfg.vmstatCounters 343 } as DropdownAttrs))); 344} 345 346 347function AndroidSettings(cssClass: string) { 348 return m( 349 `.record-section${cssClass}`, 350 m(Probe, 351 { 352 title: 'Atrace userspace annotations', 353 img: 'rec_atrace.png', 354 descr: `Enables C++ / Java codebase annotations (ATRACE_BEGIN() / 355 os.Trace())`, 356 setEnabled: (cfg, val) => cfg.atrace = val, 357 isEnabled: (cfg) => cfg.atrace 358 } as ProbeAttrs, 359 m(Dropdown, { 360 title: 'Categories', 361 cssClass: '.multicolumn.atrace-categories', 362 options: ATRACE_CATEGORIES, 363 set: (cfg, val) => cfg.atraceCats = val, 364 get: (cfg) => cfg.atraceCats 365 } as DropdownAttrs), 366 m(Textarea, { 367 placeholder: 'Extra apps to profile, one per line, e.g.:\n' + 368 'com.android.phone\n' + 369 'com.android.nfc', 370 set: (cfg, val) => cfg.atraceApps = val, 371 get: (cfg) => cfg.atraceApps 372 } as TextareaAttrs)), 373 m(Probe, 374 { 375 title: 'Event log (logcat)', 376 img: 'rec_logcat.png', 377 descr: `Streams the event log into the trace. If no buffer filter is 378 specified, all buffers are selected.`, 379 setEnabled: (cfg, val) => cfg.androidLogs = val, 380 isEnabled: (cfg) => cfg.androidLogs 381 } as ProbeAttrs, 382 m(Dropdown, { 383 title: 'Buffers', 384 options: LOG_BUFFERS, 385 set: (cfg, val) => cfg.androidLogBuffers = val, 386 get: (cfg) => cfg.androidLogBuffers 387 } as DropdownAttrs), )); 388} 389 390 391function AdvancedSettings(cssClass: string) { 392 return m( 393 `.record-section${cssClass}`, 394 m(Probe, 395 { 396 title: 'Advanced ftrace config', 397 img: 'rec_ftrace.png', 398 descr: `Tunes the kernel-tracing (ftrace) module and allows to 399 enable extra events. The events enabled here are on top 400 of the ones derived when enabling the other probes.`, 401 setEnabled: (cfg, val) => cfg.ftrace = val, 402 isEnabled: (cfg) => cfg.ftrace 403 } as ProbeAttrs, 404 m(Slider, { 405 title: 'Buf size', 406 cssClass: '.thin', 407 values: [512, 1024, 2 * 1024, 4 * 1024, 16 * 1024, 32 * 1024], 408 unit: 'KB', 409 set: (cfg, val) => cfg.ftraceBufferSizeKb = val, 410 get: (cfg) => cfg.ftraceBufferSizeKb 411 } as SliderAttrs), 412 m(Slider, { 413 title: 'Drain rate', 414 cssClass: '.thin', 415 values: [100, 250, 500, 1000, 2500, 5000], 416 unit: 'ms', 417 set: (cfg, val) => cfg.ftraceDrainPeriodMs = val, 418 get: (cfg) => cfg.ftraceDrainPeriodMs 419 } as SliderAttrs), 420 m(Dropdown, { 421 title: 'Event groups', 422 cssClass: '.multicolumn.ftrace-events', 423 options: FTRACE_CATEGORIES, 424 set: (cfg, val) => cfg.ftraceEvents = val, 425 get: (cfg) => cfg.ftraceEvents 426 } as DropdownAttrs), 427 m(Textarea, { 428 placeholder: 'Add extra events, one per line, e.g.:\n' + 429 'sched/sched_switch\n' + 430 'kmem/*', 431 set: (cfg, val) => cfg.ftraceExtraEvents = val, 432 get: (cfg) => cfg.ftraceExtraEvents 433 } as TextareaAttrs))); 434} 435 436function Instructions(cssClass: string) { 437 const data = globals.trackDataStore.get('config') as { 438 commandline: string, 439 pbtxt: string, 440 } | null; 441 442 const pbtx = data ? data.pbtxt : ''; 443 let cmd = ''; 444 cmd += 'adb shell perfetto \\\n'; 445 cmd += ' -c - --txt \\\n'; 446 cmd += ' -o /data/misc/perfetto-traces/trace \\\n'; 447 cmd += '<<EOF\n\n'; 448 cmd += pbtx; 449 cmd += '\nEOF\n'; 450 const docUrl = '//docs.perfetto.dev/#/build-instructions?id=get-the-code'; 451 452 453 const notes: m.Children = []; 454 const doc = 455 m('span', 'Follow the ', m('a', {href: docUrl}, 'instructions here')); 456 457 const msgFeatNotSupported = 458 m('div', `Some of the probes are only supported in the 459 last version of perfetto running on Android Q+`); 460 461 const msgPerfettoNotSupported = 462 m('div', `Perfetto is not supported natively before Android P.`); 463 464 const msgSideload = 465 m('div', 466 `If you have a rooted device you can sideload the latest version of 467 perfetto. `, 468 doc); 469 470 const msgLinux = 471 m('div', `In order to use perfetto on Linux you need to 472 compile it and run from the standalone build. `, doc); 473 474 switch (globals.state.recordConfig.targetOS) { 475 case 'Q': 476 break; 477 case 'P': 478 notes.push(msgFeatNotSupported); 479 notes.push(msgSideload); 480 break; 481 case 'O': 482 notes.push(msgPerfettoNotSupported); 483 notes.push(msgSideload); 484 break; 485 case 'L': 486 notes.push(msgLinux); 487 break; 488 default: 489 } 490 491 const onOsChange = (os: string) => { 492 const traceCfg = produce(globals.state.recordConfig, draft => { 493 draft.targetOS = os; 494 }); 495 globals.dispatch(Actions.setRecordConfig({config: traceCfg})); 496 }; 497 498 return m( 499 `.record-section.instructions${cssClass}`, 500 m('header', 'Instructions'), 501 m('label', 502 'Select target platform', 503 m('select', 504 {onchange: m.withAttr('value', onOsChange)}, 505 m('option', {value: 'Q'}, 'Android Q+'), 506 m('option', {value: 'P'}, 'Android P'), 507 m('option', {value: 'O'}, 'Android O-'), 508 m('option', {value: 'L'}, 'Linux desktop'))), 509 notes.length > 0 ? m('.note', notes) : [], 510 m(CodeSnippet, {text: cmd, hardWhitespace: true}), ); 511} 512 513export const RecordPage = createPage({ 514 view() { 515 const SECTIONS: {[property: string]: (cssClass: string) => m.Child} = { 516 buffers: RecSettings, 517 instructions: Instructions, 518 cpu: CpuSettings, 519 power: PowerSettings, 520 memory: MemorySettings, 521 android: AndroidSettings, 522 advanced: AdvancedSettings, 523 }; 524 525 const pages: m.Children = []; 526 let routePage = Router.param('p'); 527 if (!Object.keys(SECTIONS).includes(routePage)) { 528 routePage = 'buffers'; 529 } 530 for (const key of Object.keys(SECTIONS)) { 531 const cssClass = routePage === key ? '.active' : ''; 532 pages.push(SECTIONS[key](cssClass)); 533 } 534 535 return m( 536 '.record-page', 537 m('.record-container', 538 m('.record-menu', 539 m('header', 'Trace config'), 540 m('ul', 541 m('a[href="#!/record?p=buffers"]', 542 m(`li${routePage === 'buffers' ? '.active' : ''}`, 543 m('i.material-icons', 'tune'), 544 m('.title', 'Recording settings'), 545 m('.sub', 'Buffer mode, size and duration'))), 546 m('a[href="#!/record?p=instructions"]', 547 m(`li${routePage === 'instructions' ? '.active' : ''}`, 548 m('i.material-icons.rec', 'fiber_manual_record'), 549 m('.title', 'Start recording'), 550 m('.sub', 'Generate config and instructions'))), ), 551 m('header', 'Probes'), 552 m('ul', 553 m('a[href="#!/record?p=cpu"]', 554 m(`li${routePage === 'cpu' ? '.active' : ''}`, 555 m('i.material-icons', 'subtitles'), 556 m('.title', 'CPU'), 557 m('.sub', 'CPU usage, scheduling, wakeups'))), 558 m('a[href="#!/record?p=power"]', 559 m(`li${routePage === 'power' ? '.active' : ''}`, 560 m('i.material-icons', 'battery_charging_full'), 561 m('.title', 'Power'), 562 m('.sub', 'Battery and other energy counters'))), 563 m('a[href="#!/record?p=memory"]', 564 m(`li${routePage === 'memory' ? '.active' : ''}`, 565 m('i.material-icons', 'memory'), 566 m('.title', 'Memory'), 567 m('.sub', 'Physical mem, VM, LMK'))), 568 m('a[href="#!/record?p=android"]', 569 m(`li${routePage === 'android' ? '.active' : ''}`, 570 m('i.material-icons', 'android'), 571 m('.title', 'Android apps & svcs'), 572 m('.sub', 'atrace and logcat'))), 573 m('a[href="#!/record?p=advanced"]', 574 m(`li${routePage === 'advanced' ? '.active' : ''}`, 575 m('i.material-icons', 'settings'), 576 m('.title', 'Advanced settings'), 577 m('.sub', 'Complicated stuff for wizards'))), )), 578 pages)); 579 } 580}); 581