1// Copyright (C) 2022 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 {isString} from '../../base/object_utils'; 16import {base64Encode} from '../../base/string_utils'; 17import {exists} from '../../base/utils'; 18import {RecordConfig} from '../../controller/record_config_types'; 19import { 20 AndroidLogConfig, 21 AndroidLogId, 22 AndroidPowerConfig, 23 BufferConfig, 24 ChromeConfig, 25 DataSourceConfig, 26 EtwConfig, 27 FtraceConfig, 28 HeapprofdConfig, 29 JavaContinuousDumpConfig, 30 JavaHprofConfig, 31 MeminfoCounters, 32 NativeContinuousDumpConfig, 33 NetworkPacketTraceConfig, 34 PerfEventConfig, 35 PerfEvents, 36 ProcessStatsConfig, 37 SysStatsConfig, 38 TraceConfig, 39 TrackEventConfig, 40 VmstatCounters, 41} from '../../protos'; 42 43import {TargetInfo} from './recording_interfaces_v2'; 44 45import PerfClock = PerfEvents.PerfClock; 46import Timebase = PerfEvents.Timebase; 47import CallstackSampling = PerfEventConfig.CallstackSampling; 48import Scope = PerfEventConfig.Scope; 49 50export interface ConfigProtoEncoded { 51 configProtoText?: string; 52 configProtoBase64?: string; 53 hasDataSources: boolean; 54} 55 56export class RecordingConfigUtils { 57 private lastConfig?: RecordConfig; 58 private lastTargetInfo?: TargetInfo; 59 private configProtoText?: string; 60 private configProtoBase64?: string; 61 private hasDataSources: boolean = false; 62 63 fetchLatestRecordCommand( 64 recordConfig: RecordConfig, 65 targetInfo: TargetInfo, 66 ): ConfigProtoEncoded { 67 if ( 68 recordConfig === this.lastConfig && 69 targetInfo === this.lastTargetInfo 70 ) { 71 return { 72 configProtoText: this.configProtoText, 73 configProtoBase64: this.configProtoBase64, 74 hasDataSources: this.hasDataSources, 75 }; 76 } 77 this.lastConfig = recordConfig; 78 this.lastTargetInfo = targetInfo; 79 80 const traceConfig = genTraceConfig(recordConfig, targetInfo); 81 const configProto = TraceConfig.encode(traceConfig).finish(); 82 this.configProtoText = toPbtxt(configProto); 83 this.configProtoBase64 = base64Encode(configProto); 84 this.hasDataSources = traceConfig.dataSources.length > 0; 85 return { 86 configProtoText: this.configProtoText, 87 configProtoBase64: this.configProtoBase64, 88 hasDataSources: this.hasDataSources, 89 }; 90 } 91} 92 93function enableSchedBlockedReason(androidApiLevel?: number): boolean { 94 return androidApiLevel !== undefined && androidApiLevel >= 31; 95} 96 97function enableCompactSched(androidApiLevel?: number): boolean { 98 return androidApiLevel !== undefined && androidApiLevel >= 31; 99} 100 101export function genTraceConfig( 102 uiCfg: RecordConfig, 103 targetInfo: TargetInfo, 104): TraceConfig { 105 const isAndroid = targetInfo.targetType === 'ANDROID'; 106 const androidApiLevel = isAndroid ? targetInfo.androidApiLevel : undefined; 107 const protoCfg = new TraceConfig(); 108 protoCfg.durationMs = uiCfg.durationMs; 109 110 // Auxiliary buffer for slow-rate events. 111 // Set to 1/8th of the main buffer size, with reasonable limits. 112 let slowBufSizeKb = uiCfg.bufferSizeMb * (1024 / 8); 113 slowBufSizeKb = Math.min(slowBufSizeKb, 2 * 1024); 114 slowBufSizeKb = Math.max(slowBufSizeKb, 256); 115 116 // Main buffer for ftrace and other high-freq events. 117 const fastBufSizeKb = uiCfg.bufferSizeMb * 1024 - slowBufSizeKb; 118 119 protoCfg.buffers.push(new BufferConfig()); 120 protoCfg.buffers.push(new BufferConfig()); 121 protoCfg.buffers[0].sizeKb = fastBufSizeKb; 122 protoCfg.buffers[1].sizeKb = slowBufSizeKb; 123 124 if (uiCfg.mode === 'STOP_WHEN_FULL') { 125 protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.DISCARD; 126 protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.DISCARD; 127 } else { 128 protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; 129 protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; 130 protoCfg.flushPeriodMs = 30000; 131 if (uiCfg.mode === 'LONG_TRACE') { 132 protoCfg.writeIntoFile = true; 133 protoCfg.fileWritePeriodMs = uiCfg.fileWritePeriodMs; 134 protoCfg.maxFileSizeBytes = uiCfg.maxFileSizeMb * 1e6; 135 } 136 137 // Clear incremental state every 5 seconds when tracing into a ring 138 // buffer. 139 const incStateConfig = new TraceConfig.IncrementalStateConfig(); 140 incStateConfig.clearPeriodMs = 5000; 141 protoCfg.incrementalStateConfig = incStateConfig; 142 } 143 144 const ftraceEvents = new Set<string>(uiCfg.ftrace ? uiCfg.ftraceEvents : []); 145 const atraceCats = new Set<string>(uiCfg.atrace ? uiCfg.atraceCats : []); 146 const atraceApps = new Set<string>(); 147 const chromeCategories = new Set<string>(); 148 uiCfg.chromeCategoriesSelected.forEach((it) => chromeCategories.add(it)); 149 uiCfg.chromeHighOverheadCategoriesSelected.forEach((it) => 150 chromeCategories.add(it), 151 ); 152 153 let procThreadAssociationPolling = false; 154 let procThreadAssociationFtrace = false; 155 let trackInitialOomScore = false; 156 157 if (isAndroid) { 158 const ds = new TraceConfig.DataSource(); 159 ds.config = new DataSourceConfig(); 160 ds.config.targetBuffer = 1; 161 ds.config.name = 'android.packages_list'; 162 protoCfg.dataSources.push(ds); 163 } 164 165 let ftrace = false; 166 let symbolizeKsyms = false; 167 if (uiCfg.cpuSched) { 168 procThreadAssociationPolling = true; 169 procThreadAssociationFtrace = true; 170 ftrace = true; 171 if (enableSchedBlockedReason(androidApiLevel)) { 172 symbolizeKsyms = true; 173 } 174 ftraceEvents.add('sched/sched_switch'); 175 ftraceEvents.add('power/suspend_resume'); 176 ftraceEvents.add('sched/sched_wakeup'); 177 ftraceEvents.add('sched/sched_wakeup_new'); 178 ftraceEvents.add('sched/sched_waking'); 179 ftraceEvents.add('power/suspend_resume'); 180 } 181 182 let sysStatsCfg: SysStatsConfig | undefined = undefined; 183 184 if (uiCfg.cpuFreq) { 185 ftraceEvents.add('power/cpu_frequency'); 186 ftraceEvents.add('power/cpu_idle'); 187 ftraceEvents.add('power/suspend_resume'); 188 189 sysStatsCfg = new SysStatsConfig(); 190 sysStatsCfg.cpufreqPeriodMs = uiCfg.cpuFreqPollMs; 191 } 192 193 if (uiCfg.gpuFreq) { 194 ftraceEvents.add('power/gpu_frequency'); 195 } 196 197 if (uiCfg.gpuMemTotal) { 198 ftraceEvents.add('gpu_mem/gpu_mem_total'); 199 200 if (targetInfo.targetType !== 'CHROME') { 201 const ds = new TraceConfig.DataSource(); 202 ds.config = new DataSourceConfig(); 203 ds.config.name = 'android.gpu.memory'; 204 protoCfg.dataSources.push(ds); 205 } 206 } 207 208 if (uiCfg.gpuWorkPeriod) { 209 ftraceEvents.add('power/gpu_work_period'); 210 } 211 212 if (uiCfg.cpuSyscall) { 213 ftraceEvents.add('raw_syscalls/sys_enter'); 214 ftraceEvents.add('raw_syscalls/sys_exit'); 215 } 216 217 if (uiCfg.batteryDrain) { 218 const ds = new TraceConfig.DataSource(); 219 ds.config = new DataSourceConfig(); 220 if ( 221 targetInfo.targetType === 'CHROME_OS' || 222 targetInfo.targetType === 'LINUX' 223 ) { 224 ds.config.name = 'linux.sysfs_power'; 225 } else { 226 ds.config.name = 'android.power'; 227 ds.config.androidPowerConfig = new AndroidPowerConfig(); 228 ds.config.androidPowerConfig.batteryPollMs = uiCfg.batteryDrainPollMs; 229 ds.config.androidPowerConfig.batteryCounters = [ 230 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT, 231 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE, 232 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT, 233 ]; 234 ds.config.androidPowerConfig.collectPowerRails = true; 235 } 236 if (targetInfo.targetType !== 'CHROME') { 237 protoCfg.dataSources.push(ds); 238 } 239 } 240 241 if (uiCfg.boardSensors) { 242 ftraceEvents.add('regulator/regulator_set_voltage'); 243 ftraceEvents.add('regulator/regulator_set_voltage_complete'); 244 ftraceEvents.add('power/clock_enable'); 245 ftraceEvents.add('power/clock_disable'); 246 ftraceEvents.add('power/clock_set_rate'); 247 ftraceEvents.add('power/suspend_resume'); 248 } 249 250 if (uiCfg.cpuCoarse) { 251 if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); 252 sysStatsCfg.statPeriodMs = uiCfg.cpuCoarsePollMs; 253 sysStatsCfg.statCounters = [ 254 SysStatsConfig.StatCounters.STAT_CPU_TIMES, 255 SysStatsConfig.StatCounters.STAT_FORK_COUNT, 256 ]; 257 } 258 259 if (uiCfg.memHiFreq) { 260 procThreadAssociationPolling = true; 261 procThreadAssociationFtrace = true; 262 ftraceEvents.add('mm_event/mm_event_record'); 263 ftraceEvents.add('kmem/rss_stat'); 264 ftraceEvents.add('ion/ion_stat'); 265 ftraceEvents.add('dmabuf_heap/dma_heap_stat'); 266 ftraceEvents.add('kmem/ion_heap_grow'); 267 ftraceEvents.add('kmem/ion_heap_shrink'); 268 } 269 270 if (procThreadAssociationFtrace) { 271 ftraceEvents.add('sched/sched_process_exit'); 272 ftraceEvents.add('sched/sched_process_free'); 273 ftraceEvents.add('task/task_newtask'); 274 ftraceEvents.add('task/task_rename'); 275 } 276 277 if (uiCfg.linuxDeviceRpm) { 278 ftraceEvents.add('rpm/rpm_status'); 279 } 280 281 if (uiCfg.meminfo) { 282 if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); 283 sysStatsCfg.meminfoPeriodMs = uiCfg.meminfoPeriodMs; 284 sysStatsCfg.meminfoCounters = uiCfg.meminfoCounters.map((name) => { 285 // eslint-disable-next-line @typescript-eslint/no-explicit-any 286 return MeminfoCounters[name as any as number] as any as number; 287 }); 288 } 289 290 if (uiCfg.vmstat) { 291 if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); 292 sysStatsCfg.vmstatPeriodMs = uiCfg.vmstatPeriodMs; 293 sysStatsCfg.vmstatCounters = uiCfg.vmstatCounters.map((name) => { 294 // eslint-disable-next-line @typescript-eslint/no-explicit-any 295 return VmstatCounters[name as any as number] as any as number; 296 }); 297 } 298 299 if (uiCfg.memLmk) { 300 // For in-kernel LMK (roughly older devices until Go and Pixel 3). 301 ftraceEvents.add('lowmemorykiller/lowmemory_kill'); 302 303 // For userspace LMKd (newer devices). 304 // 'lmkd' is not really required because the code in lmkd.c emits events 305 // with ATRACE_TAG_ALWAYS. We need something just to ensure that the final 306 // config will enable atrace userspace events. 307 atraceApps.add('lmkd'); 308 309 ftraceEvents.add('oom/oom_score_adj_update'); 310 procThreadAssociationPolling = true; 311 trackInitialOomScore = true; 312 } 313 314 let heapprofd: HeapprofdConfig | undefined = undefined; 315 if (uiCfg.heapProfiling) { 316 // TODO(hjd): Check or inform user if buffer size are too small. 317 const cfg = new HeapprofdConfig(); 318 cfg.samplingIntervalBytes = uiCfg.hpSamplingIntervalBytes; 319 if ( 320 uiCfg.hpSharedMemoryBuffer >= 8192 && 321 uiCfg.hpSharedMemoryBuffer % 4096 === 0 322 ) { 323 cfg.shmemSizeBytes = uiCfg.hpSharedMemoryBuffer; 324 } 325 for (const value of uiCfg.hpProcesses.split('\n')) { 326 if (value === '') { 327 // Ignore empty lines 328 } else if (isNaN(+value)) { 329 cfg.processCmdline.push(value); 330 } else { 331 cfg.pid.push(+value); 332 } 333 } 334 if (uiCfg.hpContinuousDumpsInterval > 0) { 335 const cdc = (cfg.continuousDumpConfig = new NativeContinuousDumpConfig()); 336 cdc.dumpIntervalMs = uiCfg.hpContinuousDumpsInterval; 337 if (uiCfg.hpContinuousDumpsPhase > 0) { 338 cdc.dumpPhaseMs = uiCfg.hpContinuousDumpsPhase; 339 } 340 } 341 cfg.blockClient = uiCfg.hpBlockClient; 342 if (uiCfg.hpAllHeaps) { 343 cfg.allHeaps = true; 344 } 345 heapprofd = cfg; 346 } 347 348 let javaHprof: JavaHprofConfig | undefined = undefined; 349 if (uiCfg.javaHeapDump) { 350 const cfg = new JavaHprofConfig(); 351 for (const value of uiCfg.jpProcesses.split('\n')) { 352 if (value === '') { 353 // Ignore empty lines 354 } else if (isNaN(+value)) { 355 cfg.processCmdline.push(value); 356 } else { 357 cfg.pid.push(+value); 358 } 359 } 360 if (uiCfg.jpContinuousDumpsInterval > 0) { 361 const cdc = (cfg.continuousDumpConfig = new JavaContinuousDumpConfig()); 362 cdc.dumpIntervalMs = uiCfg.jpContinuousDumpsInterval; 363 if (uiCfg.hpContinuousDumpsPhase > 0) { 364 cdc.dumpPhaseMs = uiCfg.jpContinuousDumpsPhase; 365 } 366 } 367 javaHprof = cfg; 368 } 369 370 if (uiCfg.procStats || procThreadAssociationPolling || trackInitialOomScore) { 371 const ds = new TraceConfig.DataSource(); 372 ds.config = new DataSourceConfig(); 373 ds.config.targetBuffer = 1; // Aux 374 ds.config.name = 'linux.process_stats'; 375 ds.config.processStatsConfig = new ProcessStatsConfig(); 376 if (uiCfg.procStats) { 377 ds.config.processStatsConfig.procStatsPollMs = uiCfg.procStatsPeriodMs; 378 } 379 if (procThreadAssociationPolling || trackInitialOomScore) { 380 ds.config.processStatsConfig.scanAllProcessesOnStart = true; 381 } 382 if (targetInfo.targetType !== 'CHROME') { 383 protoCfg.dataSources.push(ds); 384 } 385 } 386 387 if (uiCfg.androidLogs) { 388 const ds = new TraceConfig.DataSource(); 389 ds.config = new DataSourceConfig(); 390 ds.config.name = 'android.log'; 391 ds.config.androidLogConfig = new AndroidLogConfig(); 392 ds.config.androidLogConfig.logIds = uiCfg.androidLogBuffers.map((name) => { 393 // eslint-disable-next-line @typescript-eslint/no-explicit-any 394 return AndroidLogId[name as any as number] as any as number; 395 }); 396 397 if (targetInfo.targetType !== 'CHROME') { 398 protoCfg.dataSources.push(ds); 399 } 400 } 401 402 if (uiCfg.androidFrameTimeline) { 403 const ds = new TraceConfig.DataSource(); 404 ds.config = new DataSourceConfig(); 405 ds.config.name = 'android.surfaceflinger.frametimeline'; 406 if (targetInfo.targetType !== 'CHROME') { 407 protoCfg.dataSources.push(ds); 408 } 409 } 410 411 if (uiCfg.androidGameInterventionList) { 412 const ds = new TraceConfig.DataSource(); 413 ds.config = new DataSourceConfig(); 414 ds.config.name = 'android.game_interventions'; 415 if (targetInfo.targetType !== 'CHROME') { 416 protoCfg.dataSources.push(ds); 417 } 418 } 419 420 if (uiCfg.androidNetworkTracing) { 421 if (targetInfo.targetType !== 'CHROME') { 422 const net = new TraceConfig.DataSource(); 423 net.config = new DataSourceConfig(); 424 net.config.name = 'android.network_packets'; 425 net.config.networkPacketTraceConfig = new NetworkPacketTraceConfig(); 426 net.config.networkPacketTraceConfig.pollMs = 427 uiCfg.androidNetworkTracingPollMs; 428 protoCfg.dataSources.push(net); 429 430 // Record package info so that Perfetto can display the package name for 431 // network packet events based on the event uid. 432 const pkg = new TraceConfig.DataSource(); 433 pkg.config = new DataSourceConfig(); 434 pkg.config.name = 'android.packages_list'; 435 protoCfg.dataSources.push(pkg); 436 } 437 } 438 439 if (uiCfg.chromeLogs) { 440 chromeCategories.add('log'); 441 } 442 443 if (uiCfg.taskScheduling) { 444 chromeCategories.add('toplevel'); 445 chromeCategories.add('toplevel.flow'); 446 chromeCategories.add('scheduler'); 447 chromeCategories.add('sequence_manager'); 448 chromeCategories.add('disabled-by-default-toplevel.flow'); 449 } 450 451 if (uiCfg.ipcFlows) { 452 chromeCategories.add('toplevel'); 453 chromeCategories.add('toplevel.flow'); 454 chromeCategories.add('disabled-by-default-ipc.flow'); 455 chromeCategories.add('mojom'); 456 } 457 458 if (uiCfg.jsExecution) { 459 chromeCategories.add('toplevel'); 460 chromeCategories.add('v8'); 461 } 462 463 if (uiCfg.webContentRendering) { 464 chromeCategories.add('toplevel'); 465 chromeCategories.add('blink'); 466 chromeCategories.add('cc'); 467 chromeCategories.add('gpu'); 468 } 469 470 if (uiCfg.uiRendering) { 471 chromeCategories.add('toplevel'); 472 chromeCategories.add('cc'); 473 chromeCategories.add('gpu'); 474 chromeCategories.add('viz'); 475 chromeCategories.add('ui'); 476 chromeCategories.add('views'); 477 } 478 479 if (uiCfg.inputEvents) { 480 chromeCategories.add('toplevel'); 481 chromeCategories.add('benchmark'); 482 chromeCategories.add('evdev'); 483 chromeCategories.add('input'); 484 chromeCategories.add('disabled-by-default-toplevel.flow'); 485 } 486 487 if (uiCfg.navigationAndLoading) { 488 chromeCategories.add('loading'); 489 chromeCategories.add('net'); 490 chromeCategories.add('netlog'); 491 chromeCategories.add('navigation'); 492 chromeCategories.add('browser'); 493 } 494 495 if (uiCfg.audio) { 496 function addCategoryAndDisabledByDefault(category: string) { 497 chromeCategories.add(category); 498 chromeCategories.add('disabled-by-default-' + category); 499 } 500 501 addCategoryAndDisabledByDefault('audio'); 502 addCategoryAndDisabledByDefault('webaudio'); 503 addCategoryAndDisabledByDefault('webaudio.audionode'); 504 addCategoryAndDisabledByDefault('webrtc'); 505 addCategoryAndDisabledByDefault('audio-worklet'); 506 addCategoryAndDisabledByDefault('mediastream'); 507 addCategoryAndDisabledByDefault('v8.gc'); 508 addCategoryAndDisabledByDefault('toplevel'); 509 addCategoryAndDisabledByDefault('toplevel.flow'); 510 addCategoryAndDisabledByDefault('wakeup.flow'); 511 addCategoryAndDisabledByDefault('cpu_profiler'); 512 addCategoryAndDisabledByDefault('scheduler'); 513 addCategoryAndDisabledByDefault('p2p'); 514 addCategoryAndDisabledByDefault('net'); 515 chromeCategories.add('base'); 516 } 517 518 if (uiCfg.video) { 519 chromeCategories.add('base'); 520 chromeCategories.add('gpu'); 521 chromeCategories.add('gpu.capture'); 522 chromeCategories.add('media'); 523 chromeCategories.add('toplevel'); 524 chromeCategories.add('toplevel.flow'); 525 chromeCategories.add('scheduler'); 526 chromeCategories.add('wakeup.flow'); 527 chromeCategories.add('webrtc'); 528 chromeCategories.add('disabled-by-default-video_and_image_capture'); 529 chromeCategories.add('disabled-by-default-webrtc'); 530 } 531 532 // linux.perf stack sampling 533 if (uiCfg.tracePerf) { 534 const ds = new TraceConfig.DataSource(); 535 ds.config = new DataSourceConfig(); 536 ds.config.name = 'linux.perf'; 537 538 const perfEventConfig = new PerfEventConfig(); 539 perfEventConfig.timebase = new Timebase(); 540 perfEventConfig.timebase.frequency = uiCfg.timebaseFrequency; 541 // TODO: The timestampClock needs to be changed to MONOTONIC once we start 542 // offering a choice of counter to record on through the recording UI, as 543 // not all clocks are compatible with hardware counters). 544 perfEventConfig.timebase.timestampClock = PerfClock.PERF_CLOCK_BOOTTIME; 545 546 const callstackSampling = new CallstackSampling(); 547 if (uiCfg.targetCmdLine.length > 0) { 548 const scope = new Scope(); 549 for (const cmdLine of uiCfg.targetCmdLine) { 550 if (cmdLine == '') { 551 continue; 552 } 553 scope.targetCmdline?.push(cmdLine.trim()); 554 } 555 callstackSampling.scope = scope; 556 } 557 558 perfEventConfig.callstackSampling = callstackSampling; 559 560 ds.config.perfEventConfig = perfEventConfig; 561 protoCfg.dataSources.push(ds); 562 } 563 564 if (chromeCategories.size !== 0) { 565 let chromeRecordMode; 566 if (uiCfg.mode === 'STOP_WHEN_FULL') { 567 chromeRecordMode = 'record-until-full'; 568 } else { 569 chromeRecordMode = 'record-continuously'; 570 } 571 const configStruct = { 572 record_mode: chromeRecordMode, 573 included_categories: [...chromeCategories.values()], 574 // Only include explicitly selected categories 575 excluded_categories: ['*'], 576 memory_dump_config: {}, 577 }; 578 if (chromeCategories.has('disabled-by-default-memory-infra')) { 579 configStruct.memory_dump_config = { 580 allowed_dump_modes: ['background', 'light', 'detailed'], 581 triggers: [ 582 { 583 min_time_between_dumps_ms: 10000, 584 mode: 'detailed', 585 type: 'periodic_interval', 586 }, 587 ], 588 }; 589 } 590 const chromeConfig = new ChromeConfig(); 591 chromeConfig.clientPriority = ChromeConfig.ClientPriority.USER_INITIATED; 592 chromeConfig.privacyFilteringEnabled = uiCfg.chromePrivacyFiltering; 593 chromeConfig.traceConfig = JSON.stringify(configStruct); 594 595 const traceDs = new TraceConfig.DataSource(); 596 traceDs.config = new DataSourceConfig(); 597 traceDs.config.name = 'org.chromium.trace_event'; 598 traceDs.config.chromeConfig = chromeConfig; 599 protoCfg.dataSources.push(traceDs); 600 601 // Configure "track_event" datasource for the Chrome SDK build. 602 const trackEventDs = new TraceConfig.DataSource(); 603 trackEventDs.config = new DataSourceConfig(); 604 trackEventDs.config.name = 'track_event'; 605 trackEventDs.config.chromeConfig = chromeConfig; 606 trackEventDs.config.trackEventConfig = new TrackEventConfig(); 607 trackEventDs.config.trackEventConfig.disabledCategories = ['*']; 608 trackEventDs.config.trackEventConfig.enabledCategories = [ 609 ...chromeCategories.values(), 610 '__metadata', 611 ]; 612 trackEventDs.config.trackEventConfig.enableThreadTimeSampling = true; 613 trackEventDs.config.trackEventConfig.timestampUnitMultiplier = 1000; 614 trackEventDs.config.trackEventConfig.filterDynamicEventNames = 615 uiCfg.chromePrivacyFiltering; 616 trackEventDs.config.trackEventConfig.filterDebugAnnotations = 617 uiCfg.chromePrivacyFiltering; 618 protoCfg.dataSources.push(trackEventDs); 619 620 const metadataDs = new TraceConfig.DataSource(); 621 metadataDs.config = new DataSourceConfig(); 622 metadataDs.config.name = 'org.chromium.trace_metadata'; 623 metadataDs.config.chromeConfig = chromeConfig; 624 protoCfg.dataSources.push(metadataDs); 625 626 if (chromeCategories.has('disabled-by-default-memory-infra')) { 627 const memoryDs = new TraceConfig.DataSource(); 628 memoryDs.config = new DataSourceConfig(); 629 memoryDs.config.name = 'org.chromium.memory_instrumentation'; 630 memoryDs.config.chromeConfig = chromeConfig; 631 protoCfg.dataSources.push(memoryDs); 632 633 const HeapProfDs = new TraceConfig.DataSource(); 634 HeapProfDs.config = new DataSourceConfig(); 635 HeapProfDs.config.name = 'org.chromium.native_heap_profiler'; 636 HeapProfDs.config.chromeConfig = chromeConfig; 637 protoCfg.dataSources.push(HeapProfDs); 638 } 639 640 if ( 641 chromeCategories.has('disabled-by-default-cpu_profiler') || 642 chromeCategories.has('disabled-by-default-cpu_profiler.debug') 643 ) { 644 const dataSource = new TraceConfig.DataSource(); 645 dataSource.config = new DataSourceConfig(); 646 dataSource.config.name = 'org.chromium.sampler_profiler'; 647 dataSource.config.chromeConfig = chromeConfig; 648 protoCfg.dataSources.push(dataSource); 649 } 650 } 651 652 // Keep these last. The stages above can enrich them. 653 if ( 654 targetInfo.targetType !== 'WINDOWS' && 655 targetInfo.targetType !== 'CHROME' 656 ) { 657 if (sysStatsCfg !== undefined) { 658 const ds = new TraceConfig.DataSource(); 659 ds.config = new DataSourceConfig(); 660 ds.config.name = 'linux.sys_stats'; 661 ds.config.sysStatsConfig = sysStatsCfg; 662 protoCfg.dataSources.push(ds); 663 } 664 665 if (heapprofd !== undefined) { 666 const ds = new TraceConfig.DataSource(); 667 ds.config = new DataSourceConfig(); 668 ds.config.targetBuffer = 0; 669 ds.config.name = 'android.heapprofd'; 670 ds.config.heapprofdConfig = heapprofd; 671 protoCfg.dataSources.push(ds); 672 } 673 674 if (javaHprof !== undefined) { 675 const ds = new TraceConfig.DataSource(); 676 ds.config = new DataSourceConfig(); 677 ds.config.targetBuffer = 0; 678 ds.config.name = 'android.java_hprof'; 679 ds.config.javaHprofConfig = javaHprof; 680 protoCfg.dataSources.push(ds); 681 } 682 } 683 684 if ( 685 uiCfg.ftrace || 686 ftrace || 687 uiCfg.atrace || 688 ftraceEvents.size > 0 || 689 atraceCats.size > 0 || 690 atraceApps.size > 0 691 ) { 692 const ds = new TraceConfig.DataSource(); 693 ds.config = new DataSourceConfig(); 694 ds.config.name = 'linux.ftrace'; 695 ds.config.ftraceConfig = new FtraceConfig(); 696 // Override the advanced ftrace parameters only if the user has ticked the 697 // "Advanced ftrace config" tab. 698 if (uiCfg.ftrace || ftrace) { 699 if (uiCfg.ftraceBufferSizeKb) { 700 ds.config.ftraceConfig.bufferSizeKb = uiCfg.ftraceBufferSizeKb; 701 } 702 if (uiCfg.ftraceDrainPeriodMs) { 703 ds.config.ftraceConfig.drainPeriodMs = uiCfg.ftraceDrainPeriodMs; 704 } 705 if (uiCfg.symbolizeKsyms || symbolizeKsyms) { 706 ds.config.ftraceConfig.symbolizeKsyms = true; 707 ftraceEvents.add('sched/sched_blocked_reason'); 708 } 709 for (const line of uiCfg.ftraceExtraEvents.split('\n')) { 710 if (line.trim().length > 0) ftraceEvents.add(line.trim()); 711 } 712 } 713 714 if (uiCfg.atrace) { 715 if (uiCfg.allAtraceApps) { 716 atraceApps.clear(); 717 atraceApps.add('*'); 718 } else { 719 for (const line of uiCfg.atraceApps.split('\n')) { 720 if (line.trim().length > 0) atraceApps.add(line.trim()); 721 } 722 } 723 } 724 725 if (atraceCats.size > 0 || atraceApps.size > 0) { 726 ftraceEvents.add('ftrace/print'); 727 } 728 729 let ftraceEventsArray: string[] = []; 730 if (exists(androidApiLevel) && androidApiLevel === 28) { 731 for (const ftraceEvent of ftraceEvents) { 732 // On P, we don't support groups so strip all group names from ftrace 733 // events. 734 const groupAndName = ftraceEvent.split('/'); 735 if (groupAndName.length !== 2) { 736 ftraceEventsArray.push(ftraceEvent); 737 continue; 738 } 739 // Filter out any wildcard event groups which was not supported 740 // before Q. 741 if (groupAndName[1] === '*') { 742 continue; 743 } 744 ftraceEventsArray.push(groupAndName[1]); 745 } 746 } else { 747 ftraceEventsArray = Array.from(ftraceEvents); 748 } 749 750 ds.config.ftraceConfig.ftraceEvents = ftraceEventsArray; 751 ds.config.ftraceConfig.atraceCategories = Array.from(atraceCats); 752 ds.config.ftraceConfig.atraceApps = Array.from(atraceApps); 753 754 if (enableCompactSched(androidApiLevel)) { 755 const compact = new FtraceConfig.CompactSchedConfig(); 756 compact.enabled = true; 757 ds.config.ftraceConfig.compactSched = compact; 758 } 759 760 if (targetInfo.targetType !== 'CHROME') { 761 protoCfg.dataSources.push(ds); 762 } 763 } 764 765 if ( 766 targetInfo.targetType === 'WINDOWS' || 767 uiCfg.etwCSwitch || 768 uiCfg.etwThreadState 769 ) { 770 const ds = new TraceConfig.DataSource(); 771 ds.config = new DataSourceConfig(); 772 ds.config.name = 'org.chromium.etw_system'; 773 ds.config.etwConfig = new EtwConfig(); 774 775 const kernelFlags: EtwConfig.KernelFlag[] = []; 776 777 if (uiCfg.etwCSwitch) { 778 kernelFlags.push(EtwConfig.KernelFlag.CSWITCH); 779 } 780 if (uiCfg.etwThreadState) { 781 kernelFlags.push(EtwConfig.KernelFlag.DISPATCHER); 782 } 783 ds.config.etwConfig.kernelFlags = kernelFlags; 784 protoCfg.dataSources.push(ds); 785 } 786 787 return protoCfg; 788} 789 790function toPbtxt(configBuffer: Uint8Array): string { 791 const msg = TraceConfig.decode(configBuffer); 792 const json = msg.toJSON(); 793 function snakeCase(s: string): string { 794 return s.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase()); 795 } 796 // With the ahead of time compiled protos we can't seem to tell which 797 // fields are enums. 798 function isEnum(value: string): boolean { 799 return ( 800 value.startsWith('MEMINFO_') || 801 value.startsWith('VMSTAT_') || 802 value.startsWith('STAT_') || 803 value.startsWith('LID_') || 804 value.startsWith('BATTERY_COUNTER_') || 805 value === 'DISCARD' || 806 value === 'RING_BUFFER' || 807 value.startsWith('PERF_CLOCK_') 808 ); 809 } 810 // Since javascript doesn't have 64 bit numbers when converting protos to 811 // json the proto library encodes them as strings. This is lossy since 812 // we can't tell which strings that look like numbers are actually strings 813 // and which are actually numbers. Ideally we would reflect on the proto 814 // definition somehow but for now we just hard code keys which have this 815 // problem in the config. 816 function is64BitNumber(key: string): boolean { 817 return [ 818 'maxFileSizeBytes', 819 'samplingIntervalBytes', 820 'shmemSizeBytes', 821 'pid', 822 'frequency', 823 ].includes(key); 824 } 825 function* message(msg: {}, indent: number): IterableIterator<string> { 826 for (const [key, value] of Object.entries(msg)) { 827 const isRepeated = Array.isArray(value); 828 const isNested = typeof value === 'object' && !isRepeated; 829 for (const entry of isRepeated ? (value as Array<{}>) : [value]) { 830 yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `; 831 if (isString(entry)) { 832 if (isEnum(entry) || is64BitNumber(key)) { 833 yield entry; 834 } else { 835 yield `"${entry.replace(new RegExp('"', 'g'), '\\"')}"`; 836 } 837 } else if (typeof entry === 'number') { 838 yield entry.toString(); 839 } else if (typeof entry === 'boolean') { 840 yield entry.toString(); 841 } else if (typeof entry === 'object' && entry !== null) { 842 yield '{\n'; 843 yield* message(entry, indent + 4); 844 yield ' '.repeat(indent) + '}'; 845 } else { 846 throw new Error( 847 `Record proto entry "${entry}" with unexpected type ${typeof entry}`, 848 ); 849 } 850 yield '\n'; 851 } 852 } 853 } 854 return [...message(json, 0)].join(''); 855} 856