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