| // Copyright (C) 2022 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 {base64Encode} from '../../base/string_utils'; |
| import {RecordConfig} from '../../controller/record_config_types'; |
| import { |
| AndroidLogConfig, |
| AndroidLogId, |
| AndroidPowerConfig, |
| BufferConfig, |
| ChromeConfig, |
| DataSourceConfig, |
| FtraceConfig, |
| HeapprofdConfig, |
| JavaContinuousDumpConfig, |
| JavaHprofConfig, |
| MeminfoCounters, |
| NativeContinuousDumpConfig, |
| NetworkPacketTraceConfig, |
| ProcessStatsConfig, |
| SysStatsConfig, |
| TraceConfig, |
| TrackEventConfig, |
| VmstatCounters, |
| } from '../protos'; |
| |
| import {TargetInfo} from './recording_interfaces_v2'; |
| |
| export interface ConfigProtoEncoded { |
| configProtoText?: string; |
| configProtoBase64?: string; |
| hasDataSources: boolean; |
| } |
| |
| export class RecordingConfigUtils { |
| private lastConfig?: RecordConfig; |
| private lastTargetInfo?: TargetInfo; |
| private configProtoText?: string; |
| private configProtoBase64?: string; |
| private hasDataSources: boolean = false; |
| |
| fetchLatestRecordCommand(recordConfig: RecordConfig, targetInfo: TargetInfo): |
| ConfigProtoEncoded { |
| if (recordConfig === this.lastConfig && |
| targetInfo === this.lastTargetInfo) { |
| return { |
| configProtoText: this.configProtoText, |
| configProtoBase64: this.configProtoBase64, |
| hasDataSources: this.hasDataSources, |
| }; |
| } |
| this.lastConfig = recordConfig; |
| this.lastTargetInfo = targetInfo; |
| |
| const traceConfig = genTraceConfig(recordConfig, targetInfo); |
| const configProto = TraceConfig.encode(traceConfig).finish(); |
| this.configProtoText = toPbtxt(configProto); |
| this.configProtoBase64 = base64Encode(configProto); |
| this.hasDataSources = traceConfig.dataSources.length > 0; |
| return { |
| configProtoText: this.configProtoText, |
| configProtoBase64: this.configProtoBase64, |
| hasDataSources: this.hasDataSources, |
| }; |
| } |
| } |
| |
| export function genTraceConfig( |
| uiCfg: RecordConfig, targetInfo: TargetInfo): TraceConfig { |
| const androidApiLevel = (targetInfo.targetType === 'ANDROID') ? |
| targetInfo.androidApiLevel : |
| undefined; |
| const protoCfg = new TraceConfig(); |
| protoCfg.durationMs = uiCfg.durationMs; |
| |
| // Auxiliary buffer for slow-rate events. |
| // Set to 1/8th of the main buffer size, with reasonable limits. |
| let slowBufSizeKb = uiCfg.bufferSizeMb * (1024 / 8); |
| slowBufSizeKb = Math.min(slowBufSizeKb, 2 * 1024); |
| slowBufSizeKb = Math.max(slowBufSizeKb, 256); |
| |
| // Main buffer for ftrace and other high-freq events. |
| const fastBufSizeKb = uiCfg.bufferSizeMb * 1024 - slowBufSizeKb; |
| |
| protoCfg.buffers.push(new BufferConfig()); |
| protoCfg.buffers.push(new BufferConfig()); |
| protoCfg.buffers[1].sizeKb = slowBufSizeKb; |
| protoCfg.buffers[0].sizeKb = fastBufSizeKb; |
| |
| if (uiCfg.mode === 'STOP_WHEN_FULL') { |
| protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.DISCARD; |
| protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.DISCARD; |
| } else { |
| protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; |
| protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; |
| protoCfg.flushPeriodMs = 30000; |
| if (uiCfg.mode === 'LONG_TRACE') { |
| protoCfg.writeIntoFile = true; |
| protoCfg.fileWritePeriodMs = uiCfg.fileWritePeriodMs; |
| protoCfg.maxFileSizeBytes = uiCfg.maxFileSizeMb * 1e6; |
| } |
| |
| // Clear incremental state every 5 seconds when tracing into a ring |
| // buffer. |
| const incStateConfig = new TraceConfig.IncrementalStateConfig(); |
| incStateConfig.clearPeriodMs = 5000; |
| protoCfg.incrementalStateConfig = incStateConfig; |
| } |
| |
| const ftraceEvents = new Set<string>(uiCfg.ftrace ? uiCfg.ftraceEvents : []); |
| const atraceCats = new Set<string>(uiCfg.atrace ? uiCfg.atraceCats : []); |
| const atraceApps = new Set<string>(); |
| const chromeCategories = new Set<string>(); |
| uiCfg.chromeCategoriesSelected.forEach((it) => chromeCategories.add(it)); |
| uiCfg.chromeHighOverheadCategoriesSelected.forEach( |
| (it) => chromeCategories.add(it)); |
| |
| let procThreadAssociationPolling = false; |
| let procThreadAssociationFtrace = false; |
| let trackInitialOomScore = false; |
| |
| if (uiCfg.cpuSched) { |
| procThreadAssociationPolling = true; |
| procThreadAssociationFtrace = true; |
| uiCfg.ftrace = true; |
| if (androidApiLevel && androidApiLevel >= 31) { |
| uiCfg.symbolizeKsyms = true; |
| } |
| ftraceEvents.add('sched/sched_switch'); |
| ftraceEvents.add('power/suspend_resume'); |
| ftraceEvents.add('sched/sched_wakeup'); |
| ftraceEvents.add('sched/sched_wakeup_new'); |
| ftraceEvents.add('sched/sched_waking'); |
| ftraceEvents.add('power/suspend_resume'); |
| } |
| |
| let sysStatsCfg: SysStatsConfig|undefined = undefined; |
| |
| if (uiCfg.cpuFreq) { |
| ftraceEvents.add('power/cpu_frequency'); |
| ftraceEvents.add('power/cpu_idle'); |
| ftraceEvents.add('power/suspend_resume'); |
| |
| sysStatsCfg = new SysStatsConfig(); |
| sysStatsCfg.cpufreqPeriodMs = uiCfg.cpuFreqPollMs; |
| } |
| |
| if (uiCfg.gpuFreq) { |
| ftraceEvents.add('power/gpu_frequency'); |
| } |
| |
| if (uiCfg.gpuMemTotal) { |
| ftraceEvents.add('gpu_mem/gpu_mem_total'); |
| |
| if (targetInfo.targetType !== 'CHROME') { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.name = 'android.gpu.memory'; |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| if (uiCfg.cpuSyscall) { |
| ftraceEvents.add('raw_syscalls/sys_enter'); |
| ftraceEvents.add('raw_syscalls/sys_exit'); |
| } |
| |
| if (uiCfg.batteryDrain) { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| if (targetInfo.targetType === 'CHROME_OS' || |
| targetInfo.targetType === 'LINUX') { |
| ds.config.name = 'linux.sysfs_power'; |
| } else { |
| ds.config.name = 'android.power'; |
| ds.config.androidPowerConfig = new AndroidPowerConfig(); |
| ds.config.androidPowerConfig.batteryPollMs = uiCfg.batteryDrainPollMs; |
| ds.config.androidPowerConfig.batteryCounters = [ |
| AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT, |
| AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE, |
| AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT, |
| ]; |
| ds.config.androidPowerConfig.collectPowerRails = true; |
| } |
| if (targetInfo.targetType !== 'CHROME') { |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| if (uiCfg.boardSensors) { |
| ftraceEvents.add('regulator/regulator_set_voltage'); |
| ftraceEvents.add('regulator/regulator_set_voltage_complete'); |
| ftraceEvents.add('power/clock_enable'); |
| ftraceEvents.add('power/clock_disable'); |
| ftraceEvents.add('power/clock_set_rate'); |
| ftraceEvents.add('power/suspend_resume'); |
| } |
| |
| if (uiCfg.cpuCoarse) { |
| if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); |
| sysStatsCfg.statPeriodMs = uiCfg.cpuCoarsePollMs; |
| sysStatsCfg.statCounters = [ |
| SysStatsConfig.StatCounters.STAT_CPU_TIMES, |
| SysStatsConfig.StatCounters.STAT_FORK_COUNT, |
| ]; |
| } |
| |
| if (uiCfg.memHiFreq) { |
| procThreadAssociationPolling = true; |
| procThreadAssociationFtrace = true; |
| ftraceEvents.add('mm_event/mm_event_record'); |
| ftraceEvents.add('kmem/rss_stat'); |
| ftraceEvents.add('ion/ion_stat'); |
| ftraceEvents.add('dmabuf_heap/dma_heap_stat'); |
| ftraceEvents.add('kmem/ion_heap_grow'); |
| ftraceEvents.add('kmem/ion_heap_shrink'); |
| } |
| |
| if (procThreadAssociationFtrace) { |
| ftraceEvents.add('sched/sched_process_exit'); |
| ftraceEvents.add('sched/sched_process_free'); |
| ftraceEvents.add('task/task_newtask'); |
| ftraceEvents.add('task/task_rename'); |
| } |
| |
| if (uiCfg.meminfo) { |
| if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); |
| sysStatsCfg.meminfoPeriodMs = uiCfg.meminfoPeriodMs; |
| sysStatsCfg.meminfoCounters = uiCfg.meminfoCounters.map((name) => { |
| return MeminfoCounters[name as any as number] as any as number; |
| }); |
| } |
| |
| if (uiCfg.vmstat) { |
| if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); |
| sysStatsCfg.vmstatPeriodMs = uiCfg.vmstatPeriodMs; |
| sysStatsCfg.vmstatCounters = uiCfg.vmstatCounters.map((name) => { |
| return VmstatCounters[name as any as number] as any as number; |
| }); |
| } |
| |
| if (uiCfg.memLmk) { |
| // For in-kernel LMK (roughly older devices until Go and Pixel 3). |
| ftraceEvents.add('lowmemorykiller/lowmemory_kill'); |
| |
| // For userspace LMKd (newer devices). |
| // 'lmkd' is not really required because the code in lmkd.c emits events |
| // with ATRACE_TAG_ALWAYS. We need something just to ensure that the final |
| // config will enable atrace userspace events. |
| atraceApps.add('lmkd'); |
| |
| ftraceEvents.add('oom/oom_score_adj_update'); |
| procThreadAssociationPolling = true; |
| trackInitialOomScore = true; |
| } |
| |
| let heapprofd: HeapprofdConfig|undefined = undefined; |
| if (uiCfg.heapProfiling) { |
| // TODO(hjd): Check or inform user if buffer size are too small. |
| const cfg = new HeapprofdConfig(); |
| cfg.samplingIntervalBytes = uiCfg.hpSamplingIntervalBytes; |
| if (uiCfg.hpSharedMemoryBuffer >= 8192 && |
| uiCfg.hpSharedMemoryBuffer % 4096 === 0) { |
| cfg.shmemSizeBytes = uiCfg.hpSharedMemoryBuffer; |
| } |
| for (const value of uiCfg.hpProcesses.split('\n')) { |
| if (value === '') { |
| // Ignore empty lines |
| } else if (isNaN(+value)) { |
| cfg.processCmdline.push(value); |
| } else { |
| cfg.pid.push(+value); |
| } |
| } |
| if (uiCfg.hpContinuousDumpsInterval > 0) { |
| const cdc = cfg.continuousDumpConfig = new NativeContinuousDumpConfig(); |
| cdc.dumpIntervalMs = uiCfg.hpContinuousDumpsInterval; |
| if (uiCfg.hpContinuousDumpsPhase > 0) { |
| cdc.dumpPhaseMs = uiCfg.hpContinuousDumpsPhase; |
| } |
| } |
| cfg.blockClient = uiCfg.hpBlockClient; |
| if (uiCfg.hpAllHeaps) { |
| cfg.allHeaps = true; |
| } |
| heapprofd = cfg; |
| } |
| |
| let javaHprof: JavaHprofConfig|undefined = undefined; |
| if (uiCfg.javaHeapDump) { |
| const cfg = new JavaHprofConfig(); |
| for (const value of uiCfg.jpProcesses.split('\n')) { |
| if (value === '') { |
| // Ignore empty lines |
| } else if (isNaN(+value)) { |
| cfg.processCmdline.push(value); |
| } else { |
| cfg.pid.push(+value); |
| } |
| } |
| if (uiCfg.jpContinuousDumpsInterval > 0) { |
| const cdc = cfg.continuousDumpConfig = new JavaContinuousDumpConfig(); |
| cdc.dumpIntervalMs = uiCfg.jpContinuousDumpsInterval; |
| if (uiCfg.hpContinuousDumpsPhase > 0) { |
| cdc.dumpPhaseMs = uiCfg.jpContinuousDumpsPhase; |
| } |
| } |
| javaHprof = cfg; |
| } |
| |
| if (uiCfg.procStats || procThreadAssociationPolling || trackInitialOomScore) { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.targetBuffer = 1; // Aux |
| ds.config.name = 'linux.process_stats'; |
| ds.config.processStatsConfig = new ProcessStatsConfig(); |
| if (uiCfg.procStats) { |
| ds.config.processStatsConfig.procStatsPollMs = uiCfg.procStatsPeriodMs; |
| } |
| if (procThreadAssociationPolling || trackInitialOomScore) { |
| ds.config.processStatsConfig.scanAllProcessesOnStart = true; |
| } |
| if (targetInfo.targetType !== 'CHROME') { |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| if (uiCfg.androidLogs) { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.name = 'android.log'; |
| ds.config.androidLogConfig = new AndroidLogConfig(); |
| ds.config.androidLogConfig.logIds = uiCfg.androidLogBuffers.map((name) => { |
| return AndroidLogId[name as any as number] as any as number; |
| }); |
| |
| if (targetInfo.targetType !== 'CHROME') { |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| if (uiCfg.androidFrameTimeline) { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.name = 'android.surfaceflinger.frametimeline'; |
| if (targetInfo.targetType !== 'CHROME') { |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| if (uiCfg.androidGameInterventionList) { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.name = 'android.game_interventions'; |
| if (targetInfo.targetType !== 'CHROME') { |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| if (uiCfg.androidNetworkTracing) { |
| if (targetInfo.targetType !== 'CHROME') { |
| const net = new TraceConfig.DataSource(); |
| net.config = new DataSourceConfig(); |
| net.config.name = 'android.network_packets'; |
| net.config.networkPacketTraceConfig = new NetworkPacketTraceConfig(); |
| net.config.networkPacketTraceConfig.pollMs = |
| uiCfg.androidNetworkTracingPollMs; |
| protoCfg.dataSources.push(net); |
| |
| // Record package info so that Perfetto can display the package name for |
| // network packet events based on the event uid. |
| const pkg = new TraceConfig.DataSource(); |
| pkg.config = new DataSourceConfig(); |
| pkg.config.name = 'android.packages_list'; |
| protoCfg.dataSources.push(pkg); |
| } |
| } |
| |
| if (uiCfg.chromeLogs) { |
| chromeCategories.add('log'); |
| } |
| |
| if (uiCfg.taskScheduling) { |
| chromeCategories.add('toplevel'); |
| chromeCategories.add('toplevel.flow'); |
| chromeCategories.add('scheduler'); |
| chromeCategories.add('sequence_manager'); |
| chromeCategories.add('disabled-by-default-toplevel.flow'); |
| } |
| |
| if (uiCfg.ipcFlows) { |
| chromeCategories.add('toplevel'); |
| chromeCategories.add('toplevel.flow'); |
| chromeCategories.add('disabled-by-default-ipc.flow'); |
| chromeCategories.add('mojom'); |
| } |
| |
| if (uiCfg.jsExecution) { |
| chromeCategories.add('toplevel'); |
| chromeCategories.add('v8'); |
| } |
| |
| if (uiCfg.webContentRendering) { |
| chromeCategories.add('toplevel'); |
| chromeCategories.add('blink'); |
| chromeCategories.add('cc'); |
| chromeCategories.add('gpu'); |
| } |
| |
| if (uiCfg.uiRendering) { |
| chromeCategories.add('toplevel'); |
| chromeCategories.add('cc'); |
| chromeCategories.add('gpu'); |
| chromeCategories.add('viz'); |
| chromeCategories.add('ui'); |
| chromeCategories.add('views'); |
| } |
| |
| if (uiCfg.inputEvents) { |
| chromeCategories.add('toplevel'); |
| chromeCategories.add('benchmark'); |
| chromeCategories.add('evdev'); |
| chromeCategories.add('input'); |
| chromeCategories.add('disabled-by-default-toplevel.flow'); |
| } |
| |
| if (uiCfg.navigationAndLoading) { |
| chromeCategories.add('loading'); |
| chromeCategories.add('net'); |
| chromeCategories.add('netlog'); |
| chromeCategories.add('navigation'); |
| chromeCategories.add('browser'); |
| } |
| |
| if (chromeCategories.size !== 0) { |
| let chromeRecordMode; |
| if (uiCfg.mode === 'STOP_WHEN_FULL') { |
| chromeRecordMode = 'record-until-full'; |
| } else { |
| chromeRecordMode = 'record-continuously'; |
| } |
| const configStruct = { |
| record_mode: chromeRecordMode, |
| included_categories: [...chromeCategories.values()], |
| // Only include explicitly selected categories |
| excluded_categories: ['*'], |
| memory_dump_config: {}, |
| }; |
| if (chromeCategories.has('disabled-by-default-memory-infra')) { |
| configStruct.memory_dump_config = { |
| allowed_dump_modes: ['background', 'light', 'detailed'], |
| triggers: [{ |
| min_time_between_dumps_ms: 10000, |
| mode: 'detailed', |
| type: 'periodic_interval', |
| }], |
| }; |
| } |
| const chromeConfig = new ChromeConfig(); |
| chromeConfig.clientPriority = ChromeConfig.ClientPriority.USER_INITIATED; |
| chromeConfig.privacyFilteringEnabled = uiCfg.chromePrivacyFiltering; |
| chromeConfig.traceConfig = JSON.stringify(configStruct); |
| |
| const traceDs = new TraceConfig.DataSource(); |
| traceDs.config = new DataSourceConfig(); |
| traceDs.config.name = 'org.chromium.trace_event'; |
| traceDs.config.chromeConfig = chromeConfig; |
| protoCfg.dataSources.push(traceDs); |
| |
| // Configure "track_event" datasource for the Chrome SDK build. |
| const trackEventDs = new TraceConfig.DataSource(); |
| trackEventDs.config = new DataSourceConfig(); |
| trackEventDs.config.name = 'track_event'; |
| trackEventDs.config.chromeConfig = chromeConfig; |
| trackEventDs.config.trackEventConfig = new TrackEventConfig(); |
| trackEventDs.config.trackEventConfig.disabledCategories = ['*']; |
| trackEventDs.config.trackEventConfig.enabledCategories = |
| [...chromeCategories.values(), '__metadata']; |
| trackEventDs.config.trackEventConfig.enableThreadTimeSampling = true; |
| trackEventDs.config.trackEventConfig.timestampUnitMultiplier = 1000; |
| trackEventDs.config.trackEventConfig.filterDynamicEventNames = |
| uiCfg.chromePrivacyFiltering; |
| trackEventDs.config.trackEventConfig.filterDebugAnnotations = |
| uiCfg.chromePrivacyFiltering; |
| protoCfg.dataSources.push(trackEventDs); |
| |
| const metadataDs = new TraceConfig.DataSource(); |
| metadataDs.config = new DataSourceConfig(); |
| metadataDs.config.name = 'org.chromium.trace_metadata'; |
| metadataDs.config.chromeConfig = chromeConfig; |
| protoCfg.dataSources.push(metadataDs); |
| |
| if (chromeCategories.has('disabled-by-default-memory-infra')) { |
| const memoryDs = new TraceConfig.DataSource(); |
| memoryDs.config = new DataSourceConfig(); |
| memoryDs.config.name = 'org.chromium.memory_instrumentation'; |
| memoryDs.config.chromeConfig = chromeConfig; |
| protoCfg.dataSources.push(memoryDs); |
| |
| const HeapProfDs = new TraceConfig.DataSource(); |
| HeapProfDs.config = new DataSourceConfig(); |
| HeapProfDs.config.name = 'org.chromium.native_heap_profiler'; |
| HeapProfDs.config.chromeConfig = chromeConfig; |
| protoCfg.dataSources.push(HeapProfDs); |
| } |
| |
| if (chromeCategories.has('disabled-by-default-cpu_profiler') || |
| chromeCategories.has('disabled-by-default-cpu_profiler.debug')) { |
| const dataSource = new TraceConfig.DataSource(); |
| dataSource.config = new DataSourceConfig(); |
| dataSource.config.name = 'org.chromium.sampler_profiler'; |
| dataSource.config.chromeConfig = chromeConfig; |
| protoCfg.dataSources.push(dataSource); |
| } |
| } |
| |
| // Keep these last. The stages above can enrich them. |
| |
| if (sysStatsCfg !== undefined && targetInfo.targetType !== 'CHROME') { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.name = 'linux.sys_stats'; |
| ds.config.sysStatsConfig = sysStatsCfg; |
| protoCfg.dataSources.push(ds); |
| } |
| |
| if (heapprofd !== undefined && targetInfo.targetType !== 'CHROME') { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.targetBuffer = 0; |
| ds.config.name = 'android.heapprofd'; |
| ds.config.heapprofdConfig = heapprofd; |
| protoCfg.dataSources.push(ds); |
| } |
| |
| if (javaHprof !== undefined && targetInfo.targetType !== 'CHROME') { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.targetBuffer = 0; |
| ds.config.name = 'android.java_hprof'; |
| ds.config.javaHprofConfig = javaHprof; |
| protoCfg.dataSources.push(ds); |
| } |
| |
| if (uiCfg.ftrace || uiCfg.atrace || ftraceEvents.size > 0 || |
| atraceCats.size > 0 || atraceApps.size > 0) { |
| const ds = new TraceConfig.DataSource(); |
| ds.config = new DataSourceConfig(); |
| ds.config.name = 'linux.ftrace'; |
| ds.config.ftraceConfig = new FtraceConfig(); |
| // Override the advanced ftrace parameters only if the user has ticked the |
| // "Advanced ftrace config" tab. |
| if (uiCfg.ftrace) { |
| if (uiCfg.ftraceBufferSizeKb) { |
| ds.config.ftraceConfig.bufferSizeKb = uiCfg.ftraceBufferSizeKb; |
| } |
| if (uiCfg.ftraceDrainPeriodMs) { |
| ds.config.ftraceConfig.drainPeriodMs = uiCfg.ftraceDrainPeriodMs; |
| } |
| if (uiCfg.symbolizeKsyms) { |
| ds.config.ftraceConfig.symbolizeKsyms = true; |
| ftraceEvents.add('sched/sched_blocked_reason'); |
| } |
| for (const line of uiCfg.ftraceExtraEvents.split('\n')) { |
| if (line.trim().length > 0) ftraceEvents.add(line.trim()); |
| } |
| } |
| |
| if (uiCfg.atrace) { |
| if (uiCfg.allAtraceApps) { |
| atraceApps.clear(); |
| atraceApps.add('*'); |
| } else { |
| for (const line of uiCfg.atraceApps.split('\n')) { |
| if (line.trim().length > 0) atraceApps.add(line.trim()); |
| } |
| } |
| } |
| |
| if (atraceCats.size > 0 || atraceApps.size > 0) { |
| ftraceEvents.add('ftrace/print'); |
| } |
| |
| let ftraceEventsArray: string[] = []; |
| if (androidApiLevel && androidApiLevel === 28) { |
| for (const ftraceEvent of ftraceEvents) { |
| // On P, we don't support groups so strip all group names from ftrace |
| // events. |
| const groupAndName = ftraceEvent.split('/'); |
| if (groupAndName.length !== 2) { |
| ftraceEventsArray.push(ftraceEvent); |
| continue; |
| } |
| // Filter out any wildcard event groups which was not supported |
| // before Q. |
| if (groupAndName[1] === '*') { |
| continue; |
| } |
| ftraceEventsArray.push(groupAndName[1]); |
| } |
| } else { |
| ftraceEventsArray = Array.from(ftraceEvents); |
| } |
| |
| ds.config.ftraceConfig.ftraceEvents = ftraceEventsArray; |
| ds.config.ftraceConfig.atraceCategories = Array.from(atraceCats); |
| ds.config.ftraceConfig.atraceApps = Array.from(atraceApps); |
| |
| if (androidApiLevel && androidApiLevel >= 31) { |
| const compact = new FtraceConfig.CompactSchedConfig(); |
| compact.enabled = true; |
| ds.config.ftraceConfig.compactSched = compact; |
| } |
| |
| if (targetInfo.targetType !== 'CHROME') { |
| protoCfg.dataSources.push(ds); |
| } |
| } |
| |
| return protoCfg; |
| } |
| |
| function toPbtxt(configBuffer: Uint8Array): string { |
| const msg = TraceConfig.decode(configBuffer); |
| const json = msg.toJSON(); |
| function snakeCase(s: string): string { |
| return s.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase()); |
| } |
| // With the ahead of time compiled protos we can't seem to tell which |
| // fields are enums. |
| function isEnum(value: string): boolean { |
| return value.startsWith('MEMINFO_') || value.startsWith('VMSTAT_') || |
| value.startsWith('STAT_') || value.startsWith('LID_') || |
| value.startsWith('BATTERY_COUNTER_') || value === 'DISCARD' || |
| value === 'RING_BUFFER'; |
| } |
| // Since javascript doesn't have 64 bit numbers when converting protos to |
| // json the proto library encodes them as strings. This is lossy since |
| // we can't tell which strings that look like numbers are actually strings |
| // and which are actually numbers. Ideally we would reflect on the proto |
| // definition somehow but for now we just hard code keys which have this |
| // problem in the config. |
| function is64BitNumber(key: string): boolean { |
| return [ |
| 'maxFileSizeBytes', |
| 'samplingIntervalBytes', |
| 'shmemSizeBytes', |
| 'pid', |
| ].includes(key); |
| } |
| function* message(msg: {}, indent: number): IterableIterator<string> { |
| for (const [key, value] of Object.entries(msg)) { |
| const isRepeated = Array.isArray(value); |
| const isNested = typeof value === 'object' && !isRepeated; |
| for (const entry of (isRepeated ? value as Array<{}>: [value])) { |
| yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `; |
| if (typeof entry === 'string') { |
| if (isEnum(entry) || is64BitNumber(key)) { |
| yield entry; |
| } else { |
| yield `"${entry.replace(new RegExp('"', 'g'), '\\"')}"`; |
| } |
| } else if (typeof entry === 'number') { |
| yield entry.toString(); |
| } else if (typeof entry === 'boolean') { |
| yield entry.toString(); |
| } else if (typeof entry === 'object' && entry !== null) { |
| yield '{\n'; |
| yield* message(entry, indent + 4); |
| yield ' '.repeat(indent) + '}'; |
| } else { |
| throw new Error(`Record proto entry "${entry}" with unexpected type ${ |
| typeof entry}`); |
| } |
| yield '\n'; |
| } |
| } |
| } |
| return [...message(json, 0)].join(''); |
| } |